Repository: owlchester/kanka Branch: develop Commit: 6a00d9571154 Files: 6286 Total size: 28.5 MB Directory structure: gitextract_tqacef9c/ ├── .claude/ │ ├── commands/ │ │ └── translate.md │ └── skills/ │ ├── livewire-development/ │ │ └── SKILL.md │ ├── pest-testing/ │ │ └── SKILL.md │ └── tailwindcss-development/ │ └── SKILL.md ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ ├── claude.yml │ ├── lint.yml │ └── phpstan.yml ├── .gitignore ├── .jshintrc ├── .mariadb/ │ ├── 10-create-logs-database.sh │ └── conf.d/ │ └── kanka.cnf ├── .mcp.json ├── .nginx/ │ └── thumbor.conf ├── .phpactor.json ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── app/ │ ├── Auth/ │ │ └── PassportTokenGuard.php │ ├── Console/ │ │ ├── Commands/ │ │ │ ├── Campaigns/ │ │ │ │ ├── CleanupCommand.php │ │ │ │ ├── DeleteCommand.php │ │ │ │ ├── DummyEntities.php │ │ │ │ ├── ExportCommand.php │ │ │ │ ├── ImportCommand.php │ │ │ │ ├── PermissionsSyncCommand.php │ │ │ │ ├── PopulateCommand.php │ │ │ │ └── VisibileEntityCountCommand.php │ │ │ ├── Cleanup/ │ │ │ │ ├── AnonymiseUserLogs.php │ │ │ │ ├── CleanupEntityLogs.php │ │ │ │ ├── CleanupImages.php │ │ │ │ ├── CleanupTrashed.php │ │ │ │ ├── CleanupTrashedCampaigns.php │ │ │ │ └── CleanupUsers.php │ │ │ ├── Entities/ │ │ │ │ ├── CalendarAdvancer.php │ │ │ │ ├── ChildlessEntities.php │ │ │ │ └── MapChunk.php │ │ │ ├── InstallCommand.php │ │ │ ├── Metrics/ │ │ │ │ └── MetricsGa4.php │ │ │ ├── Migrations/ │ │ │ │ ├── BookmarkEntityType.php │ │ │ │ ├── Cdn.php │ │ │ │ ├── MigrateEntityStatuses.php │ │ │ │ ├── MigrateStatusFilters.php │ │ │ │ ├── MigrateSubMentions.php │ │ │ │ └── NewsletterSubCommand.php │ │ │ ├── Report/ │ │ │ │ ├── Accounts.php │ │ │ │ ├── Churn.php │ │ │ │ ├── Onboarding.php │ │ │ │ └── Weekly.php │ │ │ ├── SetupMeilisearch.php │ │ │ ├── SubscriptionEndPaypalFix.php │ │ │ ├── Subscriptions/ │ │ │ │ ├── EndFreeTrials.php │ │ │ │ ├── EndSubscriptions.php │ │ │ │ ├── ExpiringCardCommand.php │ │ │ │ ├── PaypalExpiringCommand.php │ │ │ │ └── SubCleanupCommand.php │ │ │ ├── Tests/ │ │ │ │ ├── Mailerlite.php │ │ │ │ ├── SendNotification.php │ │ │ │ ├── SignImageCommand.php │ │ │ │ ├── TestEmail.php │ │ │ │ └── TestWhiteboards.php │ │ │ ├── Translations/ │ │ │ │ └── Missing.php │ │ │ ├── Users/ │ │ │ │ ├── OfferFreeTrial.php │ │ │ │ ├── RegenerateDiscordToken.php │ │ │ │ ├── ResetUserPassword.php │ │ │ │ └── SyncUserRoles.php │ │ │ ├── VerifyParentIds.php │ │ │ └── WordCount.php │ │ └── Kernel.php │ ├── Datagrids/ │ │ ├── Actions/ │ │ │ ├── BookmarkDatagridActions.php │ │ │ ├── DatagridActions.php │ │ │ ├── DefaultDatagridActions.php │ │ │ ├── DeprecatedDatagridActions.php │ │ │ ├── HistoryActions.php │ │ │ ├── NoDatagridActions.php │ │ │ └── RelationDatagridActions.php │ │ ├── Bulks/ │ │ │ ├── AbilityBulk.php │ │ │ ├── AttributeTemplateBulk.php │ │ │ ├── BookmarkBulk.php │ │ │ ├── Bulk.php │ │ │ ├── CalendarBulk.php │ │ │ ├── CharacterBulk.php │ │ │ ├── ConversationBulk.php │ │ │ ├── CreatureBulk.php │ │ │ ├── DefaultBulk.php │ │ │ ├── DiceRollBulk.php │ │ │ ├── EntityBulk.php │ │ │ ├── EventBulk.php │ │ │ ├── FamilyBulk.php │ │ │ ├── ItemBulk.php │ │ │ ├── JournalBulk.php │ │ │ ├── LocationBulk.php │ │ │ ├── MapBulk.php │ │ │ ├── NoteBulk.php │ │ │ ├── OrganisationBulk.php │ │ │ ├── QuestBulk.php │ │ │ ├── RaceBulk.php │ │ │ ├── RelationBulk.php │ │ │ ├── TagBulk.php │ │ │ └── TimelineBulk.php │ │ ├── Datagrid.php │ │ ├── Filters/ │ │ │ ├── AbilityFilter.php │ │ │ ├── AttributeTemplateFilter.php │ │ │ ├── CalendarFilter.php │ │ │ ├── CharacterFilter.php │ │ │ ├── ConversationFilter.php │ │ │ ├── CreatureFilter.php │ │ │ ├── CustomEntityFilter.php │ │ │ ├── DatagridFilter.php │ │ │ ├── DiceRollFilter.php │ │ │ ├── EventFilter.php │ │ │ ├── FamilyFilter.php │ │ │ ├── HistoryFilter.php │ │ │ ├── ItemFilter.php │ │ │ ├── JournalFilter.php │ │ │ ├── LocationFilter.php │ │ │ ├── MapFilter.php │ │ │ ├── NoteFilter.php │ │ │ ├── OrganisationFilter.php │ │ │ ├── QuestFilter.php │ │ │ ├── RaceFilter.php │ │ │ ├── RelationFilter.php │ │ │ ├── TagFilter.php │ │ │ ├── TimelineFilter.php │ │ │ └── WhiteboardFilter.php │ │ └── Sorters/ │ │ ├── DatagridSorter.php │ │ └── QuestElementSorter.php │ ├── Definitions/ │ │ ├── CustomCssDefinitions.php │ │ └── CustomDefinitions.php │ ├── Enums/ │ │ ├── AppReleaseCategory.php │ │ ├── ApplicationStatus.php │ │ ├── AttributeType.php │ │ ├── CampaignExportStatus.php │ │ ├── CampaignFilterType.php │ │ ├── CampaignFlags.php │ │ ├── CampaignImportStatus.php │ │ ├── CampaignVisibility.php │ │ ├── CharacterStatus.php │ │ ├── ConversationTarget.php │ │ ├── Descendants.php │ │ ├── EntityAssetType.php │ │ ├── EntityEventTypes.php │ │ ├── FeatureStatus.php │ │ ├── FilterOption.php │ │ ├── MapMarkerShape.php │ │ ├── OrganisationMemberPin.php │ │ ├── OrganisationMemberStatus.php │ │ ├── Permission.php │ │ ├── PricingPeriod.php │ │ ├── QuestStatus.php │ │ ├── ReferralEventType.php │ │ ├── SpotlightContentStatus.php │ │ ├── SpotlightStatus.php │ │ ├── Tier.php │ │ ├── UserAction.php │ │ ├── UserFlags.php │ │ ├── Visibility.php │ │ ├── WebhookAction.php │ │ └── Widget.php │ ├── Events/ │ │ ├── AdminInviteCreated.php │ │ ├── Campaigns/ │ │ │ ├── Applications/ │ │ │ │ ├── Accepted.php │ │ │ │ └── Rejected.php │ │ │ ├── Dashboards/ │ │ │ │ ├── DashboardCreated.php │ │ │ │ ├── DashboardDeleted.php │ │ │ │ └── DashboardUpdated.php │ │ │ ├── Deleted.php │ │ │ ├── EntityTypes/ │ │ │ │ ├── EntityTypeCreated.php │ │ │ │ ├── EntityTypeDeleted.php │ │ │ │ ├── EntityTypeToggled.php │ │ │ │ └── EntityTypeUpdated.php │ │ │ ├── Exports/ │ │ │ │ └── ExportCreated.php │ │ │ ├── Followers/ │ │ │ │ ├── FollowerCreated.php │ │ │ │ └── FollowerRemoved.php │ │ │ ├── Invites/ │ │ │ │ ├── InviteCreated.php │ │ │ │ └── InviteDeleted.php │ │ │ ├── Members/ │ │ │ │ ├── RoleUserAdded.php │ │ │ │ ├── RoleUserRemoved.php │ │ │ │ ├── Switched.php │ │ │ │ ├── UserJoined.php │ │ │ │ └── UserLeft.php │ │ │ ├── Plugins/ │ │ │ │ ├── PluginDeleted.php │ │ │ │ ├── PluginImported.php │ │ │ │ └── PluginUpdated.php │ │ │ ├── Roles/ │ │ │ │ ├── RoleCreated.php │ │ │ │ ├── RoleDeleted.php │ │ │ │ └── RoleUpdated.php │ │ │ ├── Saved.php │ │ │ ├── SettingsSaved.php │ │ │ ├── Sidebar/ │ │ │ │ ├── SidebarReset.php │ │ │ │ └── SidebarSaved.php │ │ │ ├── Styles/ │ │ │ │ ├── StyleCreated.php │ │ │ │ ├── StyleDeleted.php │ │ │ │ └── StyleUpdated.php │ │ │ ├── Thumbnails/ │ │ │ │ ├── ThumbnailCreated.php │ │ │ │ ├── ThumbnailDeleted.php │ │ │ │ └── ThumbnailsDeleted.php │ │ │ ├── Updated.php │ │ │ └── Webhooks/ │ │ │ ├── WebhookCreated.php │ │ │ ├── WebhookDeleted.php │ │ │ ├── WebhookTested.php │ │ │ └── WebhookUpdated.php │ │ ├── Entities/ │ │ │ └── EntityRestored.php │ │ ├── FeatureCreated.php │ │ ├── Posts/ │ │ │ ├── PostCreated.php │ │ │ ├── PostDeleted.php │ │ │ ├── PostRestored.php │ │ │ └── PostUpdated.php │ │ ├── SpotlightSubmitted.php │ │ ├── Subscriptions/ │ │ │ ├── AutoRemove.php │ │ │ ├── Boost.php │ │ │ ├── Disable.php │ │ │ ├── Premium.php │ │ │ ├── SuperBoost.php │ │ │ └── Upgrade.php │ │ ├── Users/ │ │ │ └── EmailChanged.php │ │ ├── WhiteboardUpdated.php │ │ └── Whiteboards/ │ │ └── Updated.php │ ├── Exceptions/ │ │ ├── Campaign/ │ │ │ ├── AlreadyBoostedException.php │ │ │ ├── ExhaustedBoostsException.php │ │ │ ├── ExhaustedSuperboostsException.php │ │ │ └── ImportException.php │ │ ├── Handler.php │ │ ├── OpenAiException.php │ │ ├── RequireLoginException.php │ │ └── TranslatableException.php │ ├── Facades/ │ │ ├── AdCache.php │ │ ├── ApiLog.php │ │ ├── Attributes.php │ │ ├── Avatar.php │ │ ├── BookmarkCache.php │ │ ├── Breadcrumb.php │ │ ├── CampaignCache.php │ │ ├── CampaignLocalization.php │ │ ├── CharacterCache.php │ │ ├── Dashboard.php │ │ ├── DataLayer.php │ │ ├── Datagrid.php │ │ ├── Domain.php │ │ ├── EntityAssetCache.php │ │ ├── EntityCache.php │ │ ├── EntityLogger.php │ │ ├── EntityPermission.php │ │ ├── FormCopy.php │ │ ├── Identity.php │ │ ├── Images.php │ │ ├── Img.php │ │ ├── ImportIdMapper.php │ │ ├── Limit.php │ │ ├── MapMarkerCache.php │ │ ├── MarketplaceCache.php │ │ ├── Mentions.php │ │ ├── Module.php │ │ ├── Permissions.php │ │ ├── QuestCache.php │ │ ├── ReleaseCache.php │ │ ├── RolePermission.php │ │ ├── SingleUserCache.php │ │ ├── TimelineElementCache.php │ │ ├── UserCache.php │ │ └── UserPermission.php │ ├── Http/ │ │ ├── Controllers/ │ │ │ ├── Abilities/ │ │ │ │ ├── AbilityController.php │ │ │ │ └── EntityController.php │ │ │ ├── Account/ │ │ │ │ ├── Billing/ │ │ │ │ │ └── InformationController.php │ │ │ │ ├── DeleteController.php │ │ │ │ ├── EmailController.php │ │ │ │ ├── PasswordController.php │ │ │ │ └── SocialController.php │ │ │ ├── Api/ │ │ │ │ ├── Public/ │ │ │ │ │ ├── CampaignController.php │ │ │ │ │ ├── HallOfFameController.php │ │ │ │ │ ├── KbController.php │ │ │ │ │ ├── ShowcaseController.php │ │ │ │ │ └── VoteController.php │ │ │ │ └── v1/ │ │ │ │ ├── AbilityApiController.php │ │ │ │ ├── ApiController.php │ │ │ │ ├── ApplicationApiController.php │ │ │ │ ├── AttributeTemplateApiController.php │ │ │ │ ├── BookmarkApiController.php │ │ │ │ ├── CalendarApiController.php │ │ │ │ ├── CalendarEventApiController.php │ │ │ │ ├── CalendarWeatherApiController.php │ │ │ │ ├── Calendars/ │ │ │ │ │ └── AdvancerApiController.php │ │ │ │ ├── CampaignApiController.php │ │ │ │ ├── CampaignDashboardWidgetApiController.php │ │ │ │ ├── CampaignImageApiController.php │ │ │ │ ├── CampaignRoleApiController.php │ │ │ │ ├── CampaignStyleApiController.php │ │ │ │ ├── Campaigns/ │ │ │ │ │ ├── CategoryStatusController.php │ │ │ │ │ ├── EntityTypeApiController.php │ │ │ │ │ └── UserApiController.php │ │ │ │ ├── CharacterApiController.php │ │ │ │ ├── ConversationApiController.php │ │ │ │ ├── ConversationMessageApiController.php │ │ │ │ ├── ConversationParticipantApiController.php │ │ │ │ ├── CreatureApiController.php │ │ │ │ ├── DefaultThumbnailApiController.php │ │ │ │ ├── DiceRollApiController.php │ │ │ │ ├── Entities/ │ │ │ │ │ ├── Attributes/ │ │ │ │ │ │ ├── PatchController.php │ │ │ │ │ │ └── PutController.php │ │ │ │ │ └── ReminderApiController.php │ │ │ │ ├── EntityAbilityApiController.php │ │ │ │ ├── EntityApiController.php │ │ │ │ ├── EntityArchiveApiController.php │ │ │ │ ├── EntityAssetApiController.php │ │ │ │ ├── EntityAttributeApiController.php │ │ │ │ ├── EntityImageApiController.php │ │ │ │ ├── EntityInventoryApiController.php │ │ │ │ ├── EntityMentionApiController.php │ │ │ │ ├── EntityMoveApiController.php │ │ │ │ ├── EntityPermissionApiController.php │ │ │ │ ├── EntityRecoveryApiController.php │ │ │ │ ├── EntityRelationApiController.php │ │ │ │ ├── EntityTagApiController.php │ │ │ │ ├── EntityTemplateApiController.php │ │ │ │ ├── EntityTransformApiController.php │ │ │ │ ├── EntityTypeApiController.php │ │ │ │ ├── EventApiController.php │ │ │ │ ├── FamilyApiController.php │ │ │ │ ├── FamilyTreeApiController.php │ │ │ │ ├── FilterApiController.php │ │ │ │ ├── FullTextSearchApiController.php │ │ │ │ ├── HealthController.php │ │ │ │ ├── ItemApiController.php │ │ │ │ ├── JournalApiController.php │ │ │ │ ├── LocationApiController.php │ │ │ │ ├── MapApiController.php │ │ │ │ ├── MapGroupApiController.php │ │ │ │ ├── MapLayerApiController.php │ │ │ │ ├── MapMarkerApiController.php │ │ │ │ ├── MiscApiController.php │ │ │ │ ├── NoteApiController.php │ │ │ │ ├── OrganisationApiController.php │ │ │ │ ├── OrganisationMemberApiController.php │ │ │ │ ├── PostApiController.php │ │ │ │ ├── PostLayoutApiController.php │ │ │ │ ├── PostRecoveryApiController.php │ │ │ │ ├── ProfileApiController.php │ │ │ │ ├── QuestApiController.php │ │ │ │ ├── QuestElementApiController.php │ │ │ │ ├── RaceApiController.php │ │ │ │ ├── RecentEntityApiController.php │ │ │ │ ├── RelationApiController.php │ │ │ │ ├── ReminderApiController.php │ │ │ │ ├── SearchApiController.php │ │ │ │ ├── TagApiController.php │ │ │ │ ├── TimelineApiController.php │ │ │ │ ├── TimelineElementApiController.php │ │ │ │ ├── TimelineEraApiController.php │ │ │ │ └── VisibilityController.php │ │ │ ├── Attributes/ │ │ │ │ └── ApiController.php │ │ │ ├── Auth/ │ │ │ │ ├── AuthController.php │ │ │ │ ├── ConfirmPasswordController.php │ │ │ │ ├── ForgotPasswordController.php │ │ │ │ ├── LoginController.php │ │ │ │ ├── RegisterController.php │ │ │ │ ├── ResetPasswordController.php │ │ │ │ └── VerificationController.php │ │ │ ├── Billing/ │ │ │ │ ├── HistoryController.php │ │ │ │ └── PaymentMethodController.php │ │ │ ├── Bookmarks/ │ │ │ │ ├── RandomController.php │ │ │ │ └── ReorderController.php │ │ │ ├── Bragi/ │ │ │ │ └── BragiController.php │ │ │ ├── BulkController.php │ │ │ ├── Bulks/ │ │ │ │ ├── BatchController.php │ │ │ │ ├── CopyController.php │ │ │ │ ├── DeleteController.php │ │ │ │ ├── DeleteRelationController.php │ │ │ │ ├── PermissionController.php │ │ │ │ ├── PrintController.php │ │ │ │ ├── TemplateController.php │ │ │ │ └── TransformController.php │ │ │ ├── Calendar/ │ │ │ │ └── CalendarWeatherController.php │ │ │ ├── Calendars/ │ │ │ │ ├── Bulks/ │ │ │ │ │ └── EntityEventController.php │ │ │ │ └── EventController.php │ │ │ ├── Campaign/ │ │ │ │ ├── AchievementController.php │ │ │ │ ├── ApplicationController.php │ │ │ │ ├── ApplicationDashboardController.php │ │ │ │ ├── ApplicationSetupController.php │ │ │ │ ├── ApplyController.php │ │ │ │ ├── CreateController.php │ │ │ │ ├── CssController.php │ │ │ │ ├── DashboardController.php │ │ │ │ ├── DashboardHeaderController.php │ │ │ │ ├── DashboardWidgetController.php │ │ │ │ ├── DefaultImageController.php │ │ │ │ ├── DefaultsController.php │ │ │ │ ├── DeleteController.php │ │ │ │ ├── EntityTypeController.php │ │ │ │ ├── ExportController.php │ │ │ │ ├── FollowController.php │ │ │ │ ├── ImageController.php │ │ │ │ ├── ImportController.php │ │ │ │ ├── InviteController.php │ │ │ │ ├── LeaveController.php │ │ │ │ ├── LogController.php │ │ │ │ ├── MemberController.php │ │ │ │ ├── Members/ │ │ │ │ │ └── RoleController.php │ │ │ │ ├── ModuleController.php │ │ │ │ ├── PluginController.php │ │ │ │ ├── Plugins/ │ │ │ │ │ ├── BulkController.php │ │ │ │ │ ├── CssController.php │ │ │ │ │ ├── ImportController.php │ │ │ │ │ ├── ToggleController.php │ │ │ │ │ └── UpdateController.php │ │ │ │ ├── RecoveryController.php │ │ │ │ ├── RoleController.php │ │ │ │ ├── RoleUserController.php │ │ │ │ ├── ShareController.php │ │ │ │ ├── SidebarController.php │ │ │ │ ├── StatController.php │ │ │ │ ├── StyleController.php │ │ │ │ ├── ThemeBuilderController.php │ │ │ │ ├── UserController.php │ │ │ │ ├── VanityController.php │ │ │ │ ├── VisibilityController.php │ │ │ │ ├── WebController.php │ │ │ │ └── WebhookController.php │ │ │ ├── CampaignBoostController.php │ │ │ ├── Characters/ │ │ │ │ ├── Families/ │ │ │ │ │ └── ManagementController.php │ │ │ │ ├── MemberController.php │ │ │ │ ├── MembershipController.php │ │ │ │ └── Races/ │ │ │ │ └── ManagementController.php │ │ │ ├── ConfirmController.php │ │ │ ├── Controller.php │ │ │ ├── ConversationMessageController.php │ │ │ ├── ConversationParticipantController.php │ │ │ ├── CookieConsentController.php │ │ │ ├── Creatures/ │ │ │ │ └── CreatureController.php │ │ │ ├── Crud/ │ │ │ │ ├── AbilityController.php │ │ │ │ ├── AttributeTemplateController.php │ │ │ │ ├── BookmarkController.php │ │ │ │ ├── CalendarController.php │ │ │ │ ├── CampaignController.php │ │ │ │ ├── CharacterController.php │ │ │ │ ├── ConversationController.php │ │ │ │ ├── CreatureController.php │ │ │ │ ├── DiceRollController.php │ │ │ │ ├── EventController.php │ │ │ │ ├── FamilyController.php │ │ │ │ ├── ItemController.php │ │ │ │ ├── JournalController.php │ │ │ │ ├── LocationController.php │ │ │ │ ├── MapController.php │ │ │ │ ├── NoteController.php │ │ │ │ ├── OrganisationController.php │ │ │ │ ├── QuestController.php │ │ │ │ ├── RaceController.php │ │ │ │ ├── TagController.php │ │ │ │ └── TimelineController.php │ │ │ ├── CrudController.php │ │ │ ├── DashboardController.php │ │ │ ├── Dashboards/ │ │ │ │ ├── GettingStartedController.php │ │ │ │ └── SetupController.php │ │ │ ├── Datagrid2/ │ │ │ │ └── BulkControllerTrait.php │ │ │ ├── Datagrids/ │ │ │ │ └── SubscriptionController.php │ │ │ ├── DiceRolls/ │ │ │ │ └── ResultsController.php │ │ │ ├── EditingController.php │ │ │ ├── Entities/ │ │ │ │ ├── Apis/ │ │ │ │ │ └── DocumentController.php │ │ │ │ ├── ChildrenController.php │ │ │ │ ├── CreateController.php │ │ │ │ ├── DeleteController.php │ │ │ │ ├── EditController.php │ │ │ │ ├── IndexController.php │ │ │ │ └── ListingPreferenceController.php │ │ │ ├── Entity/ │ │ │ │ ├── Abilities/ │ │ │ │ │ ├── ApiController.php │ │ │ │ │ ├── ChargeController.php │ │ │ │ │ ├── ImportController.php │ │ │ │ │ └── ReorderController.php │ │ │ │ ├── AbilityController.php │ │ │ │ ├── ArchiveController.php │ │ │ │ ├── AssetController.php │ │ │ │ ├── AttributeController.php │ │ │ │ ├── AttributeTemplateController.php │ │ │ │ ├── Attributes/ │ │ │ │ │ ├── ApiController.php │ │ │ │ │ ├── LiveApiController.php │ │ │ │ │ └── LiveController.php │ │ │ │ ├── Connections/ │ │ │ │ │ ├── MapController.php │ │ │ │ │ └── TableController.php │ │ │ │ ├── CopyInventoryController.php │ │ │ │ ├── EntryController.php │ │ │ │ ├── ExportController.php │ │ │ │ ├── GenerateInventoryController.php │ │ │ │ ├── ImageController.php │ │ │ │ ├── Inventory/ │ │ │ │ │ └── DetailController.php │ │ │ │ ├── InventoryController.php │ │ │ │ ├── InventorySectionController.php │ │ │ │ ├── LogController.php │ │ │ │ ├── MentionController.php │ │ │ │ ├── MoveController.php │ │ │ │ ├── PermissionController.php │ │ │ │ ├── PostController.php │ │ │ │ ├── Posts/ │ │ │ │ │ ├── LayoutController.php │ │ │ │ │ ├── LogController.php │ │ │ │ │ ├── MoveController.php │ │ │ │ │ ├── TemplateController.php │ │ │ │ │ └── VisibilityController.php │ │ │ │ ├── PreviewController.php │ │ │ │ ├── PrivacyController.php │ │ │ │ ├── ProfileController.php │ │ │ │ ├── RelationController.php │ │ │ │ ├── ReminderController.php │ │ │ │ ├── ShareController.php │ │ │ │ ├── ShowController.php │ │ │ │ ├── StoryController.php │ │ │ │ ├── TagController.php │ │ │ │ ├── TemplateController.php │ │ │ │ ├── TooltipController.php │ │ │ │ └── TransformController.php │ │ │ ├── EntityCreatorController.php │ │ │ ├── Events/ │ │ │ │ └── EventController.php │ │ │ ├── Facebook/ │ │ │ │ └── DeletionController.php │ │ │ ├── Families/ │ │ │ │ ├── FamilyController.php │ │ │ │ ├── MemberController.php │ │ │ │ ├── TreeController.php │ │ │ │ └── Trees/ │ │ │ │ └── ApiController.php │ │ │ ├── Filters/ │ │ │ │ ├── FormController.php │ │ │ │ └── SaveController.php │ │ │ ├── Front/ │ │ │ │ └── HelperController.php │ │ │ ├── FrontendPrepareController.php │ │ │ ├── Gallery/ │ │ │ │ ├── BrowseController.php │ │ │ │ ├── CreateController.php │ │ │ │ ├── DeleteController.php │ │ │ │ ├── ImageController.php │ │ │ │ ├── SearchController.php │ │ │ │ ├── SetupController.php │ │ │ │ ├── ShowController.php │ │ │ │ ├── TiptapController.php │ │ │ │ ├── UpdateController.php │ │ │ │ ├── UploadController.php │ │ │ │ └── VisibilityController.php │ │ │ ├── GalleryController.php │ │ │ ├── HealthController.php │ │ │ ├── HistoryController.php │ │ │ ├── HomeController.php │ │ │ ├── InvitationController.php │ │ │ ├── Items/ │ │ │ │ ├── EntityController.php │ │ │ │ └── ItemController.php │ │ │ ├── Journals/ │ │ │ │ └── JournalController.php │ │ │ ├── Layout/ │ │ │ │ └── NavigationController.php │ │ │ ├── Locations/ │ │ │ │ ├── CharacterController.php │ │ │ │ ├── EventController.php │ │ │ │ ├── LocationController.php │ │ │ │ └── QuestController.php │ │ │ ├── Maps/ │ │ │ │ ├── Bulks/ │ │ │ │ │ ├── GroupController.php │ │ │ │ │ ├── LayerController.php │ │ │ │ │ └── MarkerController.php │ │ │ │ ├── ExploreController.php │ │ │ │ ├── GroupController.php │ │ │ │ ├── LayerController.php │ │ │ │ ├── Layers/ │ │ │ │ │ └── MigrateController.php │ │ │ │ ├── MapController.php │ │ │ │ ├── MarkerController.php │ │ │ │ ├── Markers/ │ │ │ │ │ ├── DetailController.php │ │ │ │ │ └── MoveController.php │ │ │ │ ├── PreviewController.php │ │ │ │ └── Reorders/ │ │ │ │ ├── GroupController.php │ │ │ │ └── LayerController.php │ │ │ ├── Notes/ │ │ │ │ └── NoteController.php │ │ │ ├── NotificationController.php │ │ │ ├── Onboarding/ │ │ │ │ └── InitialController.php │ │ │ ├── Organisation/ │ │ │ │ ├── MemberController.php │ │ │ │ └── OrganisationController.php │ │ │ ├── Passport/ │ │ │ │ └── ClientController.php │ │ │ ├── PasswordSecurityController.php │ │ │ ├── PayPalController.php │ │ │ ├── PresetController.php │ │ │ ├── Quests/ │ │ │ │ ├── ElementController.php │ │ │ │ └── QuestController.php │ │ │ ├── Races/ │ │ │ │ ├── MemberController.php │ │ │ │ └── RaceController.php │ │ │ ├── ReferralController.php │ │ │ ├── RelationController.php │ │ │ ├── ReminderUpdateController.php │ │ │ ├── Roadmap/ │ │ │ │ ├── FeatureController.php │ │ │ │ └── RoadmapController.php │ │ │ ├── Search/ │ │ │ │ ├── AttributeController.php │ │ │ │ ├── CalendarController.php │ │ │ │ ├── CampaignController.php │ │ │ │ ├── FullTextController.php │ │ │ │ ├── GameSystemSearchController.php │ │ │ │ ├── ImageController.php │ │ │ │ ├── ListController.php │ │ │ │ ├── LiveController.php │ │ │ │ ├── MapGroupController.php │ │ │ │ ├── MarkerController.php │ │ │ │ ├── MentionController.php │ │ │ │ ├── RecentController.php │ │ │ │ └── TemplateController.php │ │ │ ├── SearchController.php │ │ │ ├── Settings/ │ │ │ │ ├── AccountController.php │ │ │ │ ├── ApiController.php │ │ │ │ ├── AppearanceController.php │ │ │ │ ├── Apps/ │ │ │ │ │ └── DiscordController.php │ │ │ │ ├── AppsController.php │ │ │ │ ├── BoostController.php │ │ │ │ ├── BragiController.php │ │ │ │ ├── ClientController.php │ │ │ │ ├── NewsletterApiController.php │ │ │ │ ├── NewsletterController.php │ │ │ │ ├── PatreonController.php │ │ │ │ ├── PremiumController.php │ │ │ │ ├── ProfileController.php │ │ │ │ ├── ReferralController.php │ │ │ │ ├── ReleaseController.php │ │ │ │ ├── Subscription/ │ │ │ │ │ ├── CancellationController.php │ │ │ │ │ ├── CancelledController.php │ │ │ │ │ ├── FinishController.php │ │ │ │ │ └── FreeTrialController.php │ │ │ │ ├── SubscriptionApiController.php │ │ │ │ ├── SubscriptionController.php │ │ │ │ └── TutorialController.php │ │ │ ├── SetupController.php │ │ │ ├── Spotlights/ │ │ │ │ └── ApplicationController.php │ │ │ ├── Subscription/ │ │ │ │ └── PayPal/ │ │ │ │ └── RenewalController.php │ │ │ ├── Summernote/ │ │ │ │ └── GalleryController.php │ │ │ ├── Tags/ │ │ │ │ ├── ChildController.php │ │ │ │ ├── PostController.php │ │ │ │ ├── TagController.php │ │ │ │ └── TransferController.php │ │ │ ├── Templates/ │ │ │ │ └── LoadController.php │ │ │ ├── Timelines/ │ │ │ │ ├── TimelineController.php │ │ │ │ ├── TimelineElementController.php │ │ │ │ ├── TimelineEraController.php │ │ │ │ └── TimelineReorderController.php │ │ │ ├── TroubleshootingController.php │ │ │ ├── User/ │ │ │ │ ├── EmailValidationController.php │ │ │ │ └── ProfileController.php │ │ │ ├── WebhookController.php │ │ │ ├── Whiteboards/ │ │ │ │ ├── ApiController.php │ │ │ │ ├── CrudController.php │ │ │ │ └── DrawController.php │ │ │ └── Widgets/ │ │ │ └── CalendarWidgetController.php │ │ ├── Kernel.php │ │ ├── Middleware/ │ │ │ ├── Adless.php │ │ │ ├── ApiLogMiddleware.php │ │ │ ├── CachedResponse.php │ │ │ ├── Campaigns/ │ │ │ │ ├── Boosted.php │ │ │ │ ├── Premium.php │ │ │ │ └── Superboosted.php │ │ │ ├── CheckIfUserBanned.php │ │ │ ├── EncryptCookies.php │ │ │ ├── HttpsProtocol.php │ │ │ ├── Identity.php │ │ │ ├── LastCampaign.php │ │ │ ├── LocaleChange.php │ │ │ ├── LocalizeDatetime.php │ │ │ ├── LoginRedirect.php │ │ │ ├── OTP.php │ │ │ ├── PasswordConfirm.php │ │ │ ├── RedirectIfAuthenticated.php │ │ │ ├── ReplicationSwitcher.php │ │ │ ├── Subscriptions.php │ │ │ ├── Tracking.php │ │ │ ├── Translator.php │ │ │ ├── TrimStrings.php │ │ │ ├── TrustProxies.php │ │ │ └── VerifyCsrfToken.php │ │ ├── Requests/ │ │ │ ├── API/ │ │ │ │ ├── PatchEntity.php │ │ │ │ ├── StoreEntities.php │ │ │ │ ├── StoreReminder.php │ │ │ │ ├── UpdateUserRole.php │ │ │ │ └── UploadEntityImage.php │ │ │ ├── AddCalendarEvent.php │ │ │ ├── AddCalendarWeather.php │ │ │ ├── ApplyAttributeTemplate.php │ │ │ ├── BragiRequest.php │ │ │ ├── BulkRequest.php │ │ │ ├── Bulks/ │ │ │ │ ├── Copy.php │ │ │ │ ├── Template.php │ │ │ │ └── Transform.php │ │ │ ├── Campaigns/ │ │ │ │ ├── ApproveApplication.php │ │ │ │ ├── DefaultImageDestroy.php │ │ │ │ ├── DefaultImageStore.php │ │ │ │ ├── DestroyDefaultThumbnail.php │ │ │ │ ├── GalleryImageFolderStore.php │ │ │ │ ├── GalleryImageStore.php │ │ │ │ ├── GalleryImageUpdate.php │ │ │ │ ├── PatchCampaignApplication.php │ │ │ │ ├── RejectApplication.php │ │ │ │ ├── StoreCampaignApplication.php │ │ │ │ ├── StoreCampaignApplicationStatus.php │ │ │ │ ├── StoreCampaignSetup.php │ │ │ │ ├── StoreCampaignVisibility.php │ │ │ │ ├── StoreDefaultThumbnail.php │ │ │ │ ├── StoreDefaults.php │ │ │ │ ├── StoreImage.php │ │ │ │ ├── StoreTheme.php │ │ │ │ └── Vanity.php │ │ │ ├── CopyEntityToCampaignRequest.php │ │ │ ├── CopyInventory.php │ │ │ ├── DeleteCampaign.php │ │ │ ├── DeleteEntityType.php │ │ │ ├── DeleteSettingsAccount.php │ │ │ ├── EditPostVisibility.php │ │ │ ├── Entities/ │ │ │ │ └── UpdateListingPreferenceRequest.php │ │ │ ├── FilterPublicCampaignRequest.php │ │ │ ├── Front/ │ │ │ │ └── StoreCommunityEventEntry.php │ │ │ ├── Gallery/ │ │ │ │ ├── CreateFolder.php │ │ │ │ ├── DeleteImages.php │ │ │ │ ├── MoveFiles.php │ │ │ │ ├── UpdateFile.php │ │ │ │ ├── UpdateFiles.php │ │ │ │ ├── UpdateFocus.php │ │ │ │ ├── UploadFile.php │ │ │ │ ├── UploadFiles.php │ │ │ │ └── UploadUrl.php │ │ │ ├── GalleryBulkDelete.php │ │ │ ├── GenerateInventory.php │ │ │ ├── HistoryRequest.php │ │ │ ├── ManageFamilies.php │ │ │ ├── ManageRaces.php │ │ │ ├── MoveEntity.php │ │ │ ├── MoveEntityRequest.php │ │ │ ├── MovePostRequest.php │ │ │ ├── Onboarding/ │ │ │ │ └── InitialRequest.php │ │ │ ├── PermissionTestRequest.php │ │ │ ├── QuickCreator/ │ │ │ │ ├── StoreEntity.php │ │ │ │ └── StorePost.php │ │ │ ├── ReadBanner.php │ │ │ ├── RecoverEntity.php │ │ │ ├── RecoverPost.php │ │ │ ├── RenameEntityFile.php │ │ │ ├── ReorderAbility.php │ │ │ ├── ReorderBookmarks.php │ │ │ ├── ReorderGroups.php │ │ │ ├── ReorderLayers.php │ │ │ ├── ReorderStories.php │ │ │ ├── ReorderStyles.php │ │ │ ├── ReorderTimeline.php │ │ │ ├── SaveAttributes.php │ │ │ ├── SaveAttributesApi.php │ │ │ ├── SaveUserHelp.php │ │ │ ├── Search/ │ │ │ │ └── MentionRequest.php │ │ │ ├── Settings/ │ │ │ │ ├── NewsletterStore.php │ │ │ │ ├── StoreApiToken.php │ │ │ │ ├── StoreClient.php │ │ │ │ ├── UserAltSubscribeStore.php │ │ │ │ ├── UserBillingStore.php │ │ │ │ └── UserSubscribeStore.php │ │ │ ├── Spotlights/ │ │ │ │ └── ApplyRequest.php │ │ │ ├── StoreAbility.php │ │ │ ├── StoreAbilityEntity.php │ │ │ ├── StoreAttribute.php │ │ │ ├── StoreAttributeTemplate.php │ │ │ ├── StoreBillingSettings.php │ │ │ ├── StoreBookmark.php │ │ │ ├── StoreCalendar.php │ │ │ ├── StoreCampaign.php │ │ │ ├── StoreCampaignDashboard.php │ │ │ ├── StoreCampaignDashboardWidget.php │ │ │ ├── StoreCampaignInvite.php │ │ │ ├── StoreCampaignRole.php │ │ │ ├── StoreCampaignRoleUser.php │ │ │ ├── StoreCampaignStyle.php │ │ │ ├── StoreCampaignTheme.php │ │ │ ├── StoreCampaignUser.php │ │ │ ├── StoreCharacter.php │ │ │ ├── StoreCharacterFamily.php │ │ │ ├── StoreCharacterOrganisation.php │ │ │ ├── StoreCharacterRace.php │ │ │ ├── StoreConversation.php │ │ │ ├── StoreConversationMessage.php │ │ │ ├── StoreConversationParticipant.php │ │ │ ├── StoreCreature.php │ │ │ ├── StoreCustomEntity.php │ │ │ ├── StoreDiceRoll.php │ │ │ ├── StoreEntityAbility.php │ │ │ ├── StoreEntityAlias.php │ │ │ ├── StoreEntityAsset.php │ │ │ ├── StoreEntityAssets.php │ │ │ ├── StoreEntityFile.php │ │ │ ├── StoreEntityLink.php │ │ │ ├── StoreEntityPermission.php │ │ │ ├── StoreEntityTag.php │ │ │ ├── StoreEntityType.php │ │ │ ├── StoreEvent.php │ │ │ ├── StoreFamily.php │ │ │ ├── StoreFamilyTree.php │ │ │ ├── StoreFeature.php │ │ │ ├── StoreImageFocus.php │ │ │ ├── StoreInventory.php │ │ │ ├── StoreItem.php │ │ │ ├── StoreJournal.php │ │ │ ├── StoreLocation.php │ │ │ ├── StoreMap.php │ │ │ ├── StoreMapGroup.php │ │ │ ├── StoreMapLayer.php │ │ │ ├── StoreMapMarker.php │ │ │ ├── StoreMarketplaceProfile.php │ │ │ ├── StoreNote.php │ │ │ ├── StoreOrganisation.php │ │ │ ├── StoreOrganisationMember.php │ │ │ ├── StoreOrganisationMembers.php │ │ │ ├── StorePermission.php │ │ │ ├── StorePost.php │ │ │ ├── StorePreset.php │ │ │ ├── StoreQuest.php │ │ │ ├── StoreQuestElement.php │ │ │ ├── StoreRace.php │ │ │ ├── StoreRelation.php │ │ │ ├── StoreSettingsAccount.php │ │ │ ├── StoreSettingsAccountEmail.php │ │ │ ├── StoreSettingsLayout.php │ │ │ ├── StoreSettingsProfile.php │ │ │ ├── StoreShare.php │ │ │ ├── StoreTag.php │ │ │ ├── StoreTagEntity.php │ │ │ ├── StoreTimeline.php │ │ │ ├── StoreTimelineElement.php │ │ │ ├── StoreTimelineEra.php │ │ │ ├── StoreWebhook.php │ │ │ ├── StoreWhiteboard.php │ │ │ ├── SubscriptionCancel.php │ │ │ ├── TransferTag.php │ │ │ ├── TransformEntity.php │ │ │ ├── TransformEntityRequest.php │ │ │ ├── Translation/ │ │ │ │ └── StoreFaqTranslationRequest.php │ │ │ ├── UpdateAttribute.php │ │ │ ├── UpdateCalendarEvent.php │ │ │ ├── UpdateCampaign.php │ │ │ ├── UpdateCampaignHeader.php │ │ │ ├── UpdateEntityAbility.php │ │ │ ├── UpdateEntityAttribute.php │ │ │ ├── UpdateEntityEntry.php │ │ │ ├── UpdateEntityFile.php │ │ │ ├── UpdateEntityImage.php │ │ │ ├── UpdateEntityTags.php │ │ │ ├── UpdateInventory.php │ │ │ ├── UpdateModuleName.php │ │ │ ├── UpdateRelation.php │ │ │ ├── UpdateUserRoles.php │ │ │ ├── ValidateCoupon.php │ │ │ ├── ValidatePledge.php │ │ │ ├── ValidateReminderLength.php │ │ │ └── Whiteboards/ │ │ │ ├── CreateRequest.php │ │ │ ├── CreateStrokeRequest.php │ │ │ ├── StoreShapeRequest.php │ │ │ ├── UpdateRequest.php │ │ │ └── UpdateShapeRequest.php │ │ ├── Resources/ │ │ │ ├── AbilityResource.php │ │ │ ├── Api/ │ │ │ │ ├── CategoryStatusResource.php │ │ │ │ ├── DefaultEntityTypeResource.php │ │ │ │ ├── EntityImagesResource.php │ │ │ │ └── EntityTagResource.php │ │ │ ├── ApiEntityTagResource.php │ │ │ ├── ApiExclusion.php │ │ │ ├── ApiSync.php │ │ │ ├── ApplicationResource.php │ │ │ ├── AttributeResource.php │ │ │ ├── AttributeTemplateResource.php │ │ │ ├── Attributes/ │ │ │ │ └── LiveAttributeResource.php │ │ │ ├── BookmarkResource.php │ │ │ ├── CalendarResource.php │ │ │ ├── CalendarWeatherResource.php │ │ │ ├── CampaignDashboardWidgetResource.php │ │ │ ├── CampaignResource.php │ │ │ ├── CampaignStyleResource.php │ │ │ ├── CampaignUserResource.php │ │ │ ├── CampaignUserRoleResource.php │ │ │ ├── CategoryStatusResource.php │ │ │ ├── CharacterOrganisationResource.php │ │ │ ├── CharacterResource.php │ │ │ ├── CharacterTraitResource.php │ │ │ ├── Conversation/ │ │ │ │ ├── ConversationMessageResource.php │ │ │ │ └── ConversationResource.php │ │ │ ├── ConversationMessageResource.php │ │ │ ├── ConversationParticipantResource.php │ │ │ ├── ConversationResource.php │ │ │ ├── CreatureResource.php │ │ │ ├── DiceRollResource.php │ │ │ ├── Entities/ │ │ │ │ ├── ExploreResource.php │ │ │ │ └── TemplateResource.php │ │ │ ├── Entity.php │ │ │ ├── EntityAbilityResource.php │ │ │ ├── EntityAssetResource.php │ │ │ ├── EntityChild.php │ │ │ ├── EntityDefaultThumbnailResource.php │ │ │ ├── EntityMentionResource.php │ │ │ ├── EntityPermissionResource.php │ │ │ ├── EntityResource.php │ │ │ ├── EntityTagResource.php │ │ │ ├── EntityTypeResource.php │ │ │ ├── EventResource.php │ │ │ ├── FamilyResource.php │ │ │ ├── FamilyTreeResource.php │ │ │ ├── Gallery/ │ │ │ │ └── Tiptap/ │ │ │ │ └── ImageResource.php │ │ │ ├── GalleryFile.php │ │ │ ├── GalleryFileFull.php │ │ │ ├── ImageResource.php │ │ │ ├── InventoryResource.php │ │ │ ├── ItemResource.php │ │ │ ├── JournalResource.php │ │ │ ├── KankaCollection.php │ │ │ ├── LocationResource.php │ │ │ ├── MapGroupResource.php │ │ │ ├── MapLayerResource.php │ │ │ ├── MapMarkerResource.php │ │ │ ├── MapResource.php │ │ │ ├── ModelResource.php │ │ │ ├── NoteResource.php │ │ │ ├── OrganisationMemberResource.php │ │ │ ├── OrganisationResource.php │ │ │ ├── PostLayoutResource.php │ │ │ ├── PostPermissionResource.php │ │ │ ├── PostResource.php │ │ │ ├── ProfileResource.php │ │ │ ├── Public/ │ │ │ │ └── CampaignResource.php │ │ │ ├── QuestElementResource.php │ │ │ ├── QuestResource.php │ │ │ ├── RaceResource.php │ │ │ ├── RelationResource.php │ │ │ ├── ReminderResource.php │ │ │ ├── TagResource.php │ │ │ ├── TimelineElementResource.php │ │ │ ├── TimelineEraResource.php │ │ │ ├── TimelineResource.php │ │ │ ├── UserResource.php │ │ │ ├── VisibilityResource.php │ │ │ ├── VoteResource.php │ │ │ ├── Web/ │ │ │ │ └── EntityResource.php │ │ │ └── Whiteboards/ │ │ │ ├── EntityResource.php │ │ │ ├── ShapeResource.php │ │ │ └── StrokeResource.php │ │ └── Validators/ │ │ └── HashValidator.php │ ├── Jobs/ │ │ ├── CalendarsClearElapsed.php │ │ ├── CampaignRoleUserJob.php │ │ ├── Campaigns/ │ │ │ ├── Delete.php │ │ │ ├── Export.php │ │ │ ├── Hide.php │ │ │ ├── Import.php │ │ │ ├── ImportCsv.php │ │ │ ├── NotifyAdmins.php │ │ │ └── Populate.php │ │ ├── ChunkMapJob.php │ │ ├── Copyright/ │ │ │ ├── DeleteCampaignImage.php │ │ │ ├── DeleteEntityImage.php │ │ │ └── DeleteUserImage.php │ │ ├── DeletedCampaignCleanupJob.php │ │ ├── Discord/ │ │ │ ├── AdminInviteJob.php │ │ │ ├── ReportJob.php │ │ │ ├── SendNewFeature.php │ │ │ └── UpdateFeatureUpvotes.php │ │ ├── DiscordRoleJob.php │ │ ├── Emails/ │ │ │ ├── EmailChangeJob.php │ │ │ ├── MailSettingsChangeJob.php │ │ │ ├── Purge/ │ │ │ │ ├── FirstWarningJob.php │ │ │ │ └── SecondWarningJob.php │ │ │ ├── SubscriptionCancelEmailJob.php │ │ │ ├── SubscriptionCreatedEmailJob.php │ │ │ ├── SubscriptionDeletedEmailJob.php │ │ │ ├── SubscriptionDowngradedEmailJob.php │ │ │ ├── SubscriptionFailedEmailJob.php │ │ │ ├── Subscriptions/ │ │ │ │ ├── Admin/ │ │ │ │ │ └── PaypalRenewedJob.php │ │ │ │ ├── Converted.php │ │ │ │ ├── EmailValidationJob.php │ │ │ │ ├── ExpiringCardAlert.php │ │ │ │ ├── PaypalExpiringAlert.php │ │ │ │ ├── UpcomingYearlyAlert.php │ │ │ │ └── WelcomeSubscriptionEmailJob.php │ │ │ ├── TrialAcceptedEmailJob.php │ │ │ └── WelcomeEmailJob.php │ │ ├── EntityMappingJob.php │ │ ├── EntityUpdatedJob.php │ │ ├── EntityWebhookJob.php │ │ ├── FileCleanup.php │ │ ├── ResetCssCache.php │ │ ├── Roadmap/ │ │ │ └── DiscordFeatureJob.php │ │ ├── Spotlight/ │ │ │ └── DiscordSpotlightJob.php │ │ ├── SubscriptionEndJob.php │ │ ├── TestWebhookJob.php │ │ └── Users/ │ │ ├── AbandonedCart.php │ │ ├── DeleteUser.php │ │ ├── NewPassword.php │ │ ├── UnsubscribeUser.php │ │ ├── UnsyncDiscord.php │ │ └── UpdateEmail.php │ ├── Listeners/ │ │ ├── Campaigns/ │ │ │ ├── Admins/ │ │ │ │ └── Notify.php │ │ │ ├── Applications/ │ │ │ │ └── LogApplication.php │ │ │ ├── Campaigns/ │ │ │ │ └── LogCampaign.php │ │ │ ├── ClearCampaignCache.php │ │ │ ├── ClearCampaignThemeCache.php │ │ │ ├── ClearCampaignUsersSaved.php │ │ │ ├── Dashboards/ │ │ │ │ └── LogDashboard.php │ │ │ ├── EntityTypes/ │ │ │ │ └── LogEntityType.php │ │ │ ├── Exports/ │ │ │ │ └── LogExport.php │ │ │ ├── Invites/ │ │ │ │ └── LogInvite.php │ │ │ ├── Members/ │ │ │ │ ├── LogMember.php │ │ │ │ ├── LogUserRoleChanged.php │ │ │ │ └── RunRoleUserJob.php │ │ │ ├── Plugins/ │ │ │ │ ├── ClearThemeCache.php │ │ │ │ └── LogPlugin.php │ │ │ ├── Roles/ │ │ │ │ └── LogRole.php │ │ │ ├── Sidebar/ │ │ │ │ └── LogSidebar.php │ │ │ ├── Styles/ │ │ │ │ ├── ClearStylesCache.php │ │ │ │ └── LogStyle.php │ │ │ ├── Thumbnails/ │ │ │ │ ├── LogThumbnail.php │ │ │ │ └── LogThumbnails.php │ │ │ └── Webhooks/ │ │ │ └── LogWebhook.php │ │ ├── Entities/ │ │ │ └── LogEntity.php │ │ ├── Posts/ │ │ │ └── LogPost.php │ │ ├── SendAdminInviteNotification.php │ │ ├── SendFeatureNotification.php │ │ ├── SendSpotlightNotification.php │ │ ├── UserEventSubscriber.php │ │ └── Users/ │ │ ├── ClearUserCache.php │ │ ├── SendEmailUpdate.php │ │ └── Subscriptions/ │ │ └── LogPremium.php │ ├── Livewire/ │ │ ├── Campaigns/ │ │ │ ├── CsvImport.php │ │ │ ├── ExportsTable.php │ │ │ └── Tags.php │ │ ├── EntityListing.php │ │ ├── Roadmap/ │ │ │ ├── Form.php │ │ │ ├── Ideas.php │ │ │ └── Upvote.php │ │ ├── Roadmap.php │ │ ├── Users/ │ │ │ └── Otp.php │ │ └── Widgets/ │ │ ├── GalleryCarousel.php │ │ └── RandomEntity.php │ ├── Mail/ │ │ ├── Admin/ │ │ │ └── Subscriptions/ │ │ │ └── ConvertedMail.php │ │ ├── Purge/ │ │ │ ├── FirstWarning.php │ │ │ └── SecondWarning.php │ │ ├── Subscription/ │ │ │ ├── Admin/ │ │ │ │ ├── CancelledSubscriptionMail.php │ │ │ │ ├── DowngradedSubscriptionMail.php │ │ │ │ ├── NewSubscriptionMail.php │ │ │ │ ├── NewTrialAcceptedMail.php │ │ │ │ └── PaypalRenewedMail.php │ │ │ └── User/ │ │ │ ├── CancelledUserSubscriptionMail.php │ │ │ ├── EmailChangeMail.php │ │ │ ├── ExpiringCardEmail.php │ │ │ ├── FailedUserSubscriptionMail.php │ │ │ ├── NewElementalSubscriptionMail.php │ │ │ ├── NewSubscriberMail.php │ │ │ ├── PaypalExpiringMail.php │ │ │ ├── UpcomingYearlyEmail.php │ │ │ └── ValidationEmail.php │ │ ├── Users/ │ │ │ └── NewPassword.php │ │ └── WelcomeEmail.php │ ├── Models/ │ │ ├── Ability.php │ │ ├── Ad.php │ │ ├── AdminInvite.php │ │ ├── ApiLog.php │ │ ├── AppRelease.php │ │ ├── Application.php │ │ ├── Attribute.php │ │ ├── AttributeTemplate.php │ │ ├── Bookmark.php │ │ ├── BragiLog.php │ │ ├── Calendar.php │ │ ├── CalendarWeather.php │ │ ├── Campaign.php │ │ ├── CampaignBoost.php │ │ ├── CampaignDashboard.php │ │ ├── CampaignDashboardRole.php │ │ ├── CampaignDashboardWidget.php │ │ ├── CampaignDashboardWidgetTag.php │ │ ├── CampaignDescription.php │ │ ├── CampaignEvent.php │ │ ├── CampaignExport.php │ │ ├── CampaignFilter.php │ │ ├── CampaignFlag.php │ │ ├── CampaignFollower.php │ │ ├── CampaignGenre.php │ │ ├── CampaignImport.php │ │ ├── CampaignInvite.php │ │ ├── CampaignPermission.php │ │ ├── CampaignPlugin.php │ │ ├── CampaignRole.php │ │ ├── CampaignRoleUser.php │ │ ├── CampaignSetting.php │ │ ├── CampaignStyle.php │ │ ├── CampaignSystem.php │ │ ├── CampaignUser.php │ │ ├── CategoryStatus.php │ │ ├── Character.php │ │ ├── CharacterFamily.php │ │ ├── CharacterOrganisation.php │ │ ├── CharacterRace.php │ │ ├── CharacterTrait.php │ │ ├── CommunityEvent.php │ │ ├── CommunityEventEntry.php │ │ ├── CommunityVote.php │ │ ├── CommunityVoteBallot.php │ │ ├── Concerns/ │ │ │ ├── Acl.php │ │ │ ├── Blameable.php │ │ │ ├── Boosted.php │ │ │ ├── CampaignLimit.php │ │ │ ├── CompositeKey.php │ │ │ ├── Copiable.php │ │ │ ├── EntityAsset.php │ │ │ ├── EntityLogs.php │ │ │ ├── EntityType.php │ │ │ ├── HasCampaign.php │ │ │ ├── HasEntity.php │ │ │ ├── HasEntry.php │ │ │ ├── HasFilters.php │ │ │ ├── HasImage.php │ │ │ ├── HasLocation.php │ │ │ ├── HasLocations.php │ │ │ ├── HasMentions.php │ │ │ ├── HasReminder.php │ │ │ ├── HasSlug.php │ │ │ ├── HasSuggestions.php │ │ │ ├── HasUser.php │ │ │ ├── HasVisibility.php │ │ │ ├── LastSync.php │ │ │ ├── Orderable.php │ │ │ ├── Paginatable.php │ │ │ ├── Privatable.php │ │ │ ├── Purifiable.php │ │ │ ├── Sanitizable.php │ │ │ ├── Searchable.php │ │ │ ├── SimpleSortableTrait.php │ │ │ ├── Sortable.php │ │ │ ├── SortableTrait.php │ │ │ ├── Taggable.php │ │ │ ├── Templatable.php │ │ │ ├── TouchSilently.php │ │ │ ├── UserBoosters.php │ │ │ └── UserTokens.php │ │ ├── Conversation.php │ │ ├── ConversationMessage.php │ │ ├── ConversationParticipant.php │ │ ├── Creature.php │ │ ├── DiceRoll.php │ │ ├── DiceRollResult.php │ │ ├── Entity.php │ │ ├── EntityAbility.php │ │ ├── EntityAsset.php │ │ ├── EntityEventType.php │ │ ├── EntityListingPreference.php │ │ ├── EntityLocation.php │ │ ├── EntityLog.php │ │ ├── EntityMention.php │ │ ├── EntityTag.php │ │ ├── EntityType.php │ │ ├── EntityUser.php │ │ ├── Event.php │ │ ├── Family.php │ │ ├── FamilyTree.php │ │ ├── Faq.php │ │ ├── FaqCategory.php │ │ ├── FaqTranslation.php │ │ ├── Feature.php │ │ ├── FeatureCategory.php │ │ ├── FeatureFile.php │ │ ├── FeatureStatus.php │ │ ├── FeatureUpvote.php │ │ ├── FeatureVote.php │ │ ├── GameSystem.php │ │ ├── Genre.php │ │ ├── Image.php │ │ ├── ImageMention.php │ │ ├── Inventory.php │ │ ├── Item.php │ │ ├── ItemCreator.php │ │ ├── JobLog.php │ │ ├── Journal.php │ │ ├── Location.php │ │ ├── Map.php │ │ ├── MapGroup.php │ │ ├── MapLayer.php │ │ ├── MapMarker.php │ │ ├── MiscModel.php │ │ ├── Note.php │ │ ├── Notification.php │ │ ├── OTPAuthentication.php │ │ ├── Organisation.php │ │ ├── OrganisationMember.php │ │ ├── PasswordSecurity.php │ │ ├── Playstyle.php │ │ ├── Pledge.php │ │ ├── Plugin.php │ │ ├── PluginType.php │ │ ├── PluginVersion.php │ │ ├── PluginVersionEntity.php │ │ ├── Post.php │ │ ├── PostLayout.php │ │ ├── PostPermission.php │ │ ├── PostTag.php │ │ ├── Preset.php │ │ ├── PresetType.php │ │ ├── Quest.php │ │ ├── QuestElement.php │ │ ├── Race.php │ │ ├── Referral.php │ │ ├── ReferralEvent.php │ │ ├── Relation.php │ │ ├── Relations/ │ │ │ ├── CalendarRelations.php │ │ │ ├── CampaignRelations.php │ │ │ ├── EntityRelations.php │ │ │ └── UserRelations.php │ │ ├── Reminder.php │ │ ├── Role.php │ │ ├── Scopes/ │ │ │ ├── AclScope.php │ │ │ ├── CalendarWeatherScopes.php │ │ │ ├── CampaignScope.php │ │ │ ├── CampaignScopes.php │ │ │ ├── CommunityEventScopes.php │ │ │ ├── CommunityVoteScopes.php │ │ │ ├── EntityAssetScopes.php │ │ │ ├── EntityEventScopes.php │ │ │ ├── EntityScopes.php │ │ │ ├── Pinnable.php │ │ │ ├── PrivateScope.php │ │ │ ├── SubEntityScopes.php │ │ │ ├── TagScopes.php │ │ │ ├── UserScope.php │ │ │ └── VisibilityIDScope.php │ │ ├── Spotlight.php │ │ ├── SpotlightContent.php │ │ ├── SubscriptionCancellation.php │ │ ├── SubscriptionSource.php │ │ ├── Tag.php │ │ ├── Theme.php │ │ ├── Tier.php │ │ ├── TierPrice.php │ │ ├── Timeline.php │ │ ├── TimelineElement.php │ │ ├── TimelineEra.php │ │ ├── User.php │ │ ├── UserApp.php │ │ ├── UserFlag.php │ │ ├── UserLog.php │ │ ├── UserRole.php │ │ ├── UserSetting.php │ │ ├── UserValidation.php │ │ ├── Users/ │ │ │ └── Tutorial.php │ │ ├── Visibility.php │ │ ├── Webhook.php │ │ ├── Whiteboard.php │ │ ├── WhiteboardShape.php │ │ └── WhiteboardStroke.php │ ├── Notifications/ │ │ └── Header.php │ ├── Observers/ │ │ ├── AdminInviteObserver.php │ │ ├── ApplicationObserver.php │ │ ├── BlameableObserver.php │ │ ├── BookmarkObserver.php │ │ ├── CalendarObserver.php │ │ ├── CampaignDashboardObserver.php │ │ ├── CampaignDashboardRoleObserver.php │ │ ├── CampaignDashboardWidgetObserver.php │ │ ├── CampaignDescriptionObserver.php │ │ ├── CampaignExportObserver.php │ │ ├── CampaignFollowerObserver.php │ │ ├── CampaignInviteObserver.php │ │ ├── CampaignObserver.php │ │ ├── CampaignPluginObserver.php │ │ ├── CampaignRoleObserver.php │ │ ├── CampaignRoleUserObserver.php │ │ ├── CampaignSettingObserver.php │ │ ├── CampaignStyleObserver.php │ │ ├── CampaignUserObserver.php │ │ ├── ChildEntityObserver.php │ │ ├── Concerns/ │ │ │ └── HasMany.php │ │ ├── ConversationMessageObserver.php │ │ ├── ConversationObserver.php │ │ ├── DiceRollObserver.php │ │ ├── DiceRollResultObserver.php │ │ ├── EntityAbilityObserver.php │ │ ├── EntityAssetObserver.php │ │ ├── EntityLogObserver.php │ │ ├── EntityObserver.php │ │ ├── EntityTypeObserver.php │ │ ├── EntryObserver.php │ │ ├── FamilyTreeObserver.php │ │ ├── FeatureObserver.php │ │ ├── ImageObserver.php │ │ ├── ImageableObserver.php │ │ ├── InventoryObserver.php │ │ ├── MapGroupObserver.php │ │ ├── MapLayerObserver.php │ │ ├── MapMarkerObserver.php │ │ ├── MapObserver.php │ │ ├── OrganisationMemberObserver.php │ │ ├── PostObserver.php │ │ ├── PresetObserver.php │ │ ├── PurifiableObserver.php │ │ ├── PurifiableTrait.php │ │ ├── QuestElementObserver.php │ │ ├── Remindable.php │ │ ├── ReminderObserver.php │ │ ├── ReorderTrait.php │ │ ├── SanitizedObserver.php │ │ ├── SlugObserver.php │ │ ├── SuggestionObserver.php │ │ ├── TaggableObserver.php │ │ ├── TimelineElementObserver.php │ │ ├── TimelineEraObserver.php │ │ ├── UserLogObserver.php │ │ ├── UserObserver.php │ │ ├── VisibilityObserver.php │ │ ├── WebhookObserver.php │ │ └── WhiteboardShapeObserver.php │ ├── Policies/ │ │ ├── AbilityPolicy.php │ │ ├── AttributeTemplatePolicy.php │ │ ├── BookmarkPolicy.php │ │ ├── CalendarPolicy.php │ │ ├── CampaignBoostPolicy.php │ │ ├── CampaignPluginPolicy.php │ │ ├── CampaignPolicy.php │ │ ├── CampaignRolePolicy.php │ │ ├── CampaignRoleUserPolicy.php │ │ ├── CampaignStylePolicy.php │ │ ├── CampaignUserPolicy.php │ │ ├── CharacterPolicy.php │ │ ├── ClientPolicy.php │ │ ├── CommunityEventEntryPolicy.php │ │ ├── CommunityVotePolicy.php │ │ ├── ConversationMessagePolicy.php │ │ ├── ConversationPolicy.php │ │ ├── CreaturePolicy.php │ │ ├── DiceRollPolicy.php │ │ ├── EntityPolicy.php │ │ ├── EntityTypePolicy.php │ │ ├── EventPolicy.php │ │ ├── FamilyPolicy.php │ │ ├── FeaturePolicy.php │ │ ├── ImagePolicy.php │ │ ├── ItemPolicy.php │ │ ├── JournalPolicy.php │ │ ├── LocationPolicy.php │ │ ├── MapGroupPolicy.php │ │ ├── MapLayerPolicy.php │ │ ├── MapMarkerPolicy.php │ │ ├── MapPolicy.php │ │ ├── MiscPolicy.php │ │ ├── NotePolicy.php │ │ ├── OrganisationMemberPolicy.php │ │ ├── OrganisationPolicy.php │ │ ├── PluginPolicy.php │ │ ├── PostPolicy.php │ │ ├── QuestPolicy.php │ │ ├── RacePolicy.php │ │ ├── RelationPolicy.php │ │ ├── ReminderPolicy.php │ │ ├── TagPolicy.php │ │ ├── TimelineEraPolicy.php │ │ ├── TimelinePolicy.php │ │ ├── TokenPolicy.php │ │ ├── UserPolicy.php │ │ ├── WebhookPolicy.php │ │ └── WhiteboardPolicy.php │ ├── Providers/ │ │ ├── AppServiceProvider.php │ │ ├── AttributesServiceProvider.php │ │ ├── AuthServiceProvider.php │ │ ├── AvatarServiceProvider.php │ │ ├── BreadcrumbServiceProvider.php │ │ ├── BroadcastServiceProvider.php │ │ ├── CacheServiceProvider.php │ │ ├── CampaignLocalizationServiceProvider.php │ │ ├── DashboardServiceProvider.php │ │ ├── DatagridRendererProvider.php │ │ ├── DatalayerServiceProvider.php │ │ ├── DomainServiceProvider.php │ │ ├── EntitySetupServiceProvider.php │ │ ├── EventServiceProvider.php │ │ ├── FormCopyServiceProvider.php │ │ ├── IdentityServiceProvider.php │ │ ├── ImgServiceProvider.php │ │ ├── ImporterServiceProvider.php │ │ ├── LimitServiceProvider.php │ │ ├── Logs/ │ │ │ └── ApiLogServiceProvider.php │ │ ├── MentionsServiceProvider.php │ │ ├── ModuleServiceProvider.php │ │ ├── PermissionsServiceProvider.php │ │ └── RouteServiceProvider.php │ ├── Renderers/ │ │ ├── CalendarRenderer.php │ │ ├── CharacterSheets/ │ │ │ ├── Blade.php │ │ │ ├── Custom.php │ │ │ └── Renderer.php │ │ ├── DatagridRenderer.php │ │ ├── DatagridRenderer2.php │ │ └── Layouts/ │ │ ├── Ability/ │ │ │ ├── Ability.php │ │ │ └── Entity.php │ │ ├── Calendar/ │ │ │ └── Reminder.php │ │ ├── Campaign/ │ │ │ ├── CampaignImport.php │ │ │ ├── CampaignRole.php │ │ │ ├── CampaignUser.php │ │ │ ├── Plugin.php │ │ │ ├── PostRecovery.php │ │ │ ├── Recovery.php │ │ │ ├── Theme.php │ │ │ └── Webhook.php │ │ ├── Character/ │ │ │ └── Organisation.php │ │ ├── Columns/ │ │ │ ├── Action.php │ │ │ ├── Checkbox.php │ │ │ ├── Column.php │ │ │ └── Standard.php │ │ ├── Creature/ │ │ │ └── Creature.php │ │ ├── Entity/ │ │ │ ├── Children.php │ │ │ ├── Relation.php │ │ │ └── Reminder.php │ │ ├── Event/ │ │ │ └── Event.php │ │ ├── Family/ │ │ │ ├── Character.php │ │ │ └── Family.php │ │ ├── Header.php │ │ ├── Item/ │ │ │ ├── Entity.php │ │ │ └── Item.php │ │ ├── Journal/ │ │ │ └── Journal.php │ │ ├── Layout.php │ │ ├── Location/ │ │ │ ├── Character.php │ │ │ ├── Event.php │ │ │ ├── Location.php │ │ │ └── Quest.php │ │ ├── Map/ │ │ │ ├── Group.php │ │ │ ├── Layer.php │ │ │ ├── Map.php │ │ │ └── Marker.php │ │ ├── Mention/ │ │ │ └── Mention.php │ │ ├── Note/ │ │ │ └── Note.php │ │ ├── Organisation/ │ │ │ ├── Member.php │ │ │ └── Organisation.php │ │ ├── Quest/ │ │ │ └── Quest.php │ │ ├── Race/ │ │ │ ├── Character.php │ │ │ └── Race.php │ │ ├── Tag/ │ │ │ ├── Entity.php │ │ │ ├── Post.php │ │ │ └── Tag.php │ │ └── Timeline/ │ │ ├── Era.php │ │ └── Timeline.php │ ├── Rules/ │ │ ├── AccountEmail.php │ │ ├── AccountName.php │ │ ├── ApiUniqueAttributeNames.php │ │ ├── CalendarFormat.php │ │ ├── CalendarMoonOffset.php │ │ ├── CampaignDelete.php │ │ ├── Confirm.php │ │ ├── EntityField.php │ │ ├── EntityFile.php │ │ ├── EntityLink.php │ │ ├── FontAwesomeIcon.php │ │ ├── GallerySize.php │ │ ├── GoodBye.php │ │ ├── Lessless.php │ │ ├── Location.php │ │ ├── Nested.php │ │ ├── Recaptcha.php │ │ ├── SocialLogin.php │ │ ├── UniqueAttributeNames.php │ │ └── Vanity.php │ ├── Sanitizers/ │ │ ├── CalendarSanitizer.php │ │ ├── MiscSanitizer.php │ │ └── SvgAllowedAttributes.php │ ├── Services/ │ │ ├── Abilities/ │ │ │ ├── AbilityService.php │ │ │ ├── BaseAbilityService.php │ │ │ ├── ChargeService.php │ │ │ ├── ImportService.php │ │ │ └── ReorderService.php │ │ ├── Account/ │ │ │ └── DeletionService.php │ │ ├── Api/ │ │ │ ├── ApiPermissionService.php │ │ │ ├── ApiService.php │ │ │ ├── BulkAttributeService.php │ │ │ ├── BulkEntityCreatorService.php │ │ │ ├── CampaignService.php │ │ │ ├── FilterService.php │ │ │ ├── KbService.php │ │ │ ├── ShowcaseService.php │ │ │ └── VoteService.php │ │ ├── AttributeMentionService.php │ │ ├── AttributeService.php │ │ ├── Attributes/ │ │ │ ├── ApiService.php │ │ │ ├── BaseAttributesService.php │ │ │ ├── RandomService.php │ │ │ └── TemplateService.php │ │ ├── Auth/ │ │ │ ├── LoginService.php │ │ │ └── SessionService.php │ │ ├── BookmarkService.php │ │ ├── Bookmarks/ │ │ │ ├── RandomEntityService.php │ │ │ └── RoutingService.php │ │ ├── Bragi/ │ │ │ ├── BragiService.php │ │ │ └── OpenAiService.php │ │ ├── BreadcrumbService.php │ │ ├── BulkService.php │ │ ├── Caches/ │ │ │ ├── AdCacheService.php │ │ │ ├── BaseCache.php │ │ │ ├── BookmarkCacheService.php │ │ │ ├── CampaignCacheService.php │ │ │ ├── CharacterCacheService.php │ │ │ ├── EntityAssetCacheService.php │ │ │ ├── EntityCacheService.php │ │ │ ├── MapMarkerCacheService.php │ │ │ ├── MarketplaceCacheService.php │ │ │ ├── QuestCacheService.php │ │ │ ├── ReleaseCacheService.php │ │ │ ├── SingleUserCacheService.php │ │ │ ├── TimelineElementCacheService.php │ │ │ ├── Traits/ │ │ │ │ ├── Campaign/ │ │ │ │ │ ├── ApplicationCache.php │ │ │ │ │ ├── DashboardCache.php │ │ │ │ │ ├── FlagCache.php │ │ │ │ │ ├── MemberCache.php │ │ │ │ │ ├── RoleCache.php │ │ │ │ │ ├── SettingCache.php │ │ │ │ │ ├── StyleCache.php │ │ │ │ │ ├── ThemeCache.php │ │ │ │ │ └── ThumbnailCache.php │ │ │ │ ├── PrimaryCache.php │ │ │ │ └── User/ │ │ │ │ ├── CampaignCache.php │ │ │ │ ├── RoleCache.php │ │ │ │ ├── TutorialCache.php │ │ │ │ └── UserFlagCache.php │ │ │ └── UserCacheService.php │ │ ├── CalendarService.php │ │ ├── Calendars/ │ │ │ ├── AdvancerService.php │ │ │ ├── DaysService.php │ │ │ ├── MoonService.php │ │ │ ├── ReminderService.php │ │ │ ├── SeasonService.php │ │ │ └── WeatherService.php │ │ ├── Campaign/ │ │ │ ├── AchievementService.php │ │ │ ├── ApplicationService.php │ │ │ ├── BoostService.php │ │ │ ├── Connections/ │ │ │ │ └── WebService.php │ │ │ ├── Counters/ │ │ │ │ └── VisibleEntityCountService.php │ │ │ ├── CreateService.php │ │ │ ├── DefaultImageService.php │ │ │ ├── DeletionService.php │ │ │ ├── ExportService.php │ │ │ ├── Exports/ │ │ │ │ └── QueueService.php │ │ │ ├── FollowService.php │ │ │ ├── Gallery/ │ │ │ │ └── BulkService.php │ │ │ ├── GenreService.php │ │ │ ├── Import/ │ │ │ │ ├── ImportIdMapper.php │ │ │ │ ├── ImportMentions.php │ │ │ │ ├── ImportService.php │ │ │ │ ├── Mappers/ │ │ │ │ │ ├── AbilityMapper.php │ │ │ │ │ ├── BaseEntityMapper.php │ │ │ │ │ ├── CalendarMapper.php │ │ │ │ │ ├── CampaignMapper.php │ │ │ │ │ ├── CharacterMapper.php │ │ │ │ │ ├── CreatureMapper.php │ │ │ │ │ ├── CustomEntityMapper.php │ │ │ │ │ ├── CustomMapper.php │ │ │ │ │ ├── EntityMapper.php │ │ │ │ │ ├── EventMapper.php │ │ │ │ │ ├── FamilyMapper.php │ │ │ │ │ ├── GalleryMapper.php │ │ │ │ │ ├── ImageMapper.php │ │ │ │ │ ├── ImportMapper.php │ │ │ │ │ ├── ItemMapper.php │ │ │ │ │ ├── JournalMapper.php │ │ │ │ │ ├── LocationMapper.php │ │ │ │ │ ├── MapMapper.php │ │ │ │ │ ├── MiscMapper.php │ │ │ │ │ ├── NoteMapper.php │ │ │ │ │ ├── OrganisationMapper.php │ │ │ │ │ ├── QuestMapper.php │ │ │ │ │ ├── RaceMapper.php │ │ │ │ │ ├── TagMapper.php │ │ │ │ │ └── TimelineMapper.php │ │ │ │ └── PrepareService.php │ │ │ ├── LeaveService.php │ │ │ ├── LocalisationService.php │ │ │ ├── MemberService.php │ │ │ ├── ModuleEditService.php │ │ │ ├── ModuleService.php │ │ │ ├── NotificationService.php │ │ │ ├── Notifications/ │ │ │ │ ├── HideService.php │ │ │ │ └── ImageRemoveService.php │ │ │ ├── PluginService.php │ │ │ ├── PurgeService.php │ │ │ ├── SearchCleanupService.php │ │ │ ├── ShareService.php │ │ │ ├── Sidebar/ │ │ │ │ ├── SaveService.php │ │ │ │ └── SetupService.php │ │ │ ├── StatService.php │ │ │ ├── SystemService.php │ │ │ ├── ThemeBuilderService.php │ │ │ └── Webhooks/ │ │ │ └── SaveService.php │ │ ├── ColourService.php │ │ ├── CommunityVoteService.php │ │ ├── CountryService.php │ │ ├── CsvImportService.php │ │ ├── CsvValidatorService.php │ │ ├── DashboardService.php │ │ ├── Dashboards/ │ │ │ └── GettingStartedService.php │ │ ├── DiceRollerService.php │ │ ├── Discord/ │ │ │ └── NotificationService.php │ │ ├── DiscordService.php │ │ ├── DomainService.php │ │ ├── Entity/ │ │ │ ├── AliasService.php │ │ │ ├── ArchiveService.php │ │ │ ├── ColumnDefinitionService.php │ │ │ ├── Connections/ │ │ │ │ ├── MapService.php │ │ │ │ └── RelatedService.php │ │ │ ├── CopyService.php │ │ │ ├── EntitySaveService.php │ │ │ ├── ExportService.php │ │ │ ├── InventoryService.php │ │ │ ├── LoggerService.php │ │ │ ├── MarkdownExportService.php │ │ │ ├── MoveService.php │ │ │ ├── NewService.php │ │ │ ├── PopularService.php │ │ │ ├── PostLoggerService.php │ │ │ ├── PostService.php │ │ │ ├── PreviewService.php │ │ │ ├── PrivacyService.php │ │ │ ├── PurgeService.php │ │ │ ├── RecoveryService.php │ │ │ ├── RecoverySetupService.php │ │ │ ├── RelationService.php │ │ │ ├── Relations/ │ │ │ │ ├── CharacterRelationsService.php │ │ │ │ ├── Concerns/ │ │ │ │ │ ├── SavesLocations.php │ │ │ │ │ └── SupportsPatchMode.php │ │ │ │ ├── CreatureRelationsService.php │ │ │ │ ├── EntityRelationsServiceFactory.php │ │ │ │ ├── EventRelationsService.php │ │ │ │ ├── FamilyRelationsService.php │ │ │ │ ├── ItemRelationsService.php │ │ │ │ ├── LocationRelationsService.php │ │ │ │ ├── OrganisationRelationsService.php │ │ │ │ ├── RaceRelationsService.php │ │ │ │ └── RelationsServiceInterface.php │ │ │ ├── RemindableService.php │ │ │ ├── ShareService.php │ │ │ ├── StoryService.php │ │ │ ├── TagService.php │ │ │ ├── TemplateService.php │ │ │ ├── TooltipService.php │ │ │ └── TransformService.php │ │ ├── EntityFileService.php │ │ ├── EntityMappingService.php │ │ ├── EntityTypeService.php │ │ ├── Families/ │ │ │ └── FamilyTreeService.php │ │ ├── FilterService.php │ │ ├── FormCopyService.php │ │ ├── Gallery/ │ │ │ ├── BrowseService.php │ │ │ ├── CreateService.php │ │ │ ├── DeleteService.php │ │ │ ├── SelectionService.php │ │ │ ├── SetupService.php │ │ │ ├── StorageService.php │ │ │ ├── SummernoteService.php │ │ │ ├── TiptapService.php │ │ │ ├── UpdateService.php │ │ │ └── UploadService.php │ │ ├── GenreService.php │ │ ├── IdentityManager.php │ │ ├── Images/ │ │ │ └── AvatarService.php │ │ ├── ImagesService.php │ │ ├── ImgService.php │ │ ├── InviteService.php │ │ ├── LanguageService.php │ │ ├── Layout/ │ │ │ └── NavigationService.php │ │ ├── LengthValidatorService.php │ │ ├── LimitService.php │ │ ├── Logs/ │ │ │ └── ApiLogService.php │ │ ├── Maps/ │ │ │ ├── ChunkingService.php │ │ │ └── MigrateLayerService.php │ │ ├── MarkdownMentionsService.php │ │ ├── Mentions/ │ │ │ └── SaveService.php │ │ ├── MentionsService.php │ │ ├── MultiEditingService.php │ │ ├── NewsletterService.php │ │ ├── Onboarding/ │ │ │ └── InitialService.php │ │ ├── PaginationService.php │ │ ├── PatreonService.php │ │ ├── PayPalService.php │ │ ├── PermissionService.php │ │ ├── Permissions/ │ │ │ ├── BulkPermissionService.php │ │ │ ├── EntityPermission.php │ │ │ ├── PermissionService.php │ │ │ ├── RolePermission.php │ │ │ └── RolePermissionService.php │ │ ├── Plugins/ │ │ │ └── ImporterService.php │ │ ├── Posts/ │ │ │ ├── Permissions/ │ │ │ │ └── SavePermissionsService.php │ │ │ ├── PurgeService.php │ │ │ └── RecoveryService.php │ │ ├── QuickCreator/ │ │ │ └── ProcessService.php │ │ ├── Referrals/ │ │ │ ├── JoinService.php │ │ │ └── ManagementService.php │ │ ├── Report/ │ │ │ ├── AccountsReportService.php │ │ │ ├── BaseReportService.php │ │ │ ├── ChurnReportService.php │ │ │ ├── OnboardingReportService.php │ │ │ └── WeeklyReportService.php │ │ ├── Search/ │ │ │ ├── AttributeSearchService.php │ │ │ ├── CampaignSearchService.php │ │ │ ├── EntitySearchService.php │ │ │ ├── LiveSearchService.php │ │ │ ├── MapGroupSearchService.php │ │ │ ├── MentionService.php │ │ │ ├── RecentService.php │ │ │ └── TemplateSearchService.php │ │ ├── SearchService.php │ │ ├── Spotlights/ │ │ │ └── ApplyService.php │ │ ├── StarterService.php │ │ ├── Submenus/ │ │ │ ├── AbilitySubmenu.php │ │ │ ├── BaseSubmenu.php │ │ │ ├── CalendarSubmenu.php │ │ │ ├── CharacterSubmenu.php │ │ │ ├── CreatureSubmenu.php │ │ │ ├── CustomSubmenu.php │ │ │ ├── EntitySubmenu.php │ │ │ ├── EventSubmenu.php │ │ │ ├── FamilySubmenu.php │ │ │ ├── ItemSubmenu.php │ │ │ ├── JournalSubmenu.php │ │ │ ├── LocationSubmenu.php │ │ │ ├── MapSubmenu.php │ │ │ ├── NoteSubmenu.php │ │ │ ├── OrganisationSubmenu.php │ │ │ ├── QuestSubmenu.php │ │ │ ├── RaceSubmenu.php │ │ │ ├── SubmenuService.php │ │ │ ├── TagSubmenu.php │ │ │ └── TimelineSubmenu.php │ │ ├── Subscribers/ │ │ │ └── HallOfFameService.php │ │ ├── Subscription/ │ │ │ ├── CancellationService.php │ │ │ ├── CouponService.php │ │ │ ├── FreeTrialEndService.php │ │ │ ├── PayPalRenewalService.php │ │ │ ├── PaymentMethodService.php │ │ │ ├── SubscriptionEndService.php │ │ │ └── TrialService.php │ │ ├── SubscriptionService.php │ │ ├── SubscriptionUpgradeService.php │ │ ├── TOC/ │ │ │ └── TocSlugify.php │ │ ├── TagService.php │ │ ├── TimelineService.php │ │ ├── Tracking/ │ │ │ └── DatalayerService.php │ │ ├── TroubleshootingService.php │ │ ├── TutorialService.php │ │ ├── UserAuthenticatedService.php │ │ ├── Users/ │ │ │ ├── CampaignService.php │ │ │ ├── CleanupService.php │ │ │ ├── CurrencyService.php │ │ │ ├── EmailValidationService.php │ │ │ ├── OfferTrialService.php │ │ │ ├── PurgeService.php │ │ │ └── UserLogService.php │ │ ├── WebhookService.php │ │ └── Whiteboards/ │ │ ├── ApiService.php │ │ └── Shapes/ │ │ └── PersistanceService.php │ ├── Support/ │ │ └── HtmlPurifier/ │ │ └── CalcStyleDefinition.php │ ├── Traits/ │ │ ├── AdminPolicyTrait.php │ │ ├── ApiRequest.php │ │ ├── BulkControllerTrait.php │ │ ├── CalendarAware.php │ │ ├── CampaignAware.php │ │ ├── Controllers/ │ │ │ ├── HasDatagrid.php │ │ │ ├── HasNested.php │ │ │ └── HasSubview.php │ │ ├── CreatesEntityFromName.php │ │ ├── EntityAware.php │ │ ├── EntityTypeAware.php │ │ ├── ExportableTrait.php │ │ ├── GuestAuthTrait.php │ │ ├── HasJobLog.php │ │ ├── MentionTrait.php │ │ ├── OrderableTrait.php │ │ ├── PostAware.php │ │ ├── RequestAware.php │ │ ├── ResolvesNewForeignEntities.php │ │ ├── RoleAware.php │ │ ├── Search/ │ │ │ └── Orderable.php │ │ └── UserAware.php │ └── View/ │ └── Components/ │ ├── Ad.php │ ├── Ads/ │ │ └── Native.php │ ├── Alert.php │ ├── Badge.php │ ├── Box/ │ │ └── Footer.php │ ├── Box.php │ ├── Button/ │ │ └── DeleteConfirm.php │ ├── Buttons/ │ │ ├── Colours.php │ │ ├── Confirm.php │ │ └── ConfirmDelete.php │ ├── Campaigns/ │ │ └── ModuleBox.php │ ├── CharacterSheet.php │ ├── Checkbox.php │ ├── Dashboards/ │ │ └── Widgets/ │ │ └── Selection.php │ ├── Date.php │ ├── Dialog/ │ │ ├── Article.php │ │ ├── Close.php │ │ ├── Footer.php │ │ └── Header.php │ ├── Dialog.php │ ├── Dropdowns/ │ │ ├── Divider.php │ │ ├── Item.php │ │ └── Section.php │ ├── Entities/ │ │ ├── Submenu.php │ │ └── Thumbnail.php │ ├── EntityLink.php │ ├── FaqElement.php │ ├── Form.php │ ├── Forms/ │ │ ├── Field.php │ │ ├── Foreign.php │ │ ├── Select.php │ │ ├── Tags.php │ │ ├── VisibilityPicker.php │ │ └── VisibilityPickerField.php │ ├── Grid.php │ ├── Helper.php │ ├── Helpers/ │ │ └── Tooltip.php │ ├── Hero.php │ ├── Icon.php │ ├── InfoBox.php │ ├── LearnMore.php │ ├── Lists/ │ │ └── EmptyState.php │ ├── Menu/ │ │ └── Element.php │ ├── Menu.php │ ├── Posts/ │ │ └── Tags.php │ ├── PremiumCta.php │ ├── PremiumCtaFooter.php │ ├── PremiumDialog.php │ ├── Profile/ │ │ └── SocialLink.php │ ├── Reorder/ │ │ └── Child.php │ ├── Sidebar/ │ │ ├── Account.php │ │ ├── Bookmark.php │ │ ├── Campaign.php │ │ ├── Element.php │ │ ├── Profile.php │ │ ├── Section.php │ │ └── Settings.php │ ├── Since.php │ ├── Tab/ │ │ ├── Nav.php │ │ └── Tab.php │ ├── Tags/ │ │ └── Bubble.php │ ├── Toggles/ │ │ └── FilterButton.php │ ├── Tutorial.php │ ├── Users/ │ │ ├── Avatar.php │ │ └── Link.php │ ├── Widgets/ │ │ ├── FilteredLink.php │ │ ├── Forms/ │ │ │ └── Advanced.php │ │ └── Previews/ │ │ ├── Body.php │ │ └── Head.php │ └── WordCount.php ├── artisan ├── boost.json ├── bootstrap/ │ ├── app.php │ └── cache/ │ └── .gitignore ├── composer.json ├── config/ │ ├── ads.php │ ├── app.php │ ├── attribute-templates.php │ ├── auth.php │ ├── backup.php │ ├── billing.php │ ├── blameable.php │ ├── bragi.php │ ├── broadcasting.php │ ├── cache.php │ ├── cdn.php │ ├── colours.php │ ├── cors.php │ ├── database.php │ ├── debugbar.php │ ├── discord.php │ ├── domains.php │ ├── dompdf.php │ ├── entities.php │ ├── filesystems.php │ ├── fontawesome.php │ ├── front.php │ ├── google2fa.php │ ├── ignition.php │ ├── image.php │ ├── imports.php │ ├── laravel-translation-manager.php │ ├── laravellocalization.php │ ├── larecipe.php │ ├── limits.php │ ├── livewire.php │ ├── logging.php │ ├── mail.php │ ├── mailerlite.php │ ├── marketplace.php │ ├── openai.php │ ├── passport.php │ ├── paypal.php │ ├── purge.php │ ├── purify.php │ ├── queue.php │ ├── reverb.php │ ├── scout.php │ ├── sentry.php │ ├── services.php │ ├── session.php │ ├── subscription.php │ ├── thumbor.php │ ├── tracking.php │ ├── trustedproxy.php │ └── view.php ├── database/ │ ├── .gitignore │ ├── factories/ │ │ ├── AbilityFactory.php │ │ ├── AttributeFactory.php │ │ ├── BookmarkFactory.php │ │ ├── CalendarFactory.php │ │ ├── CampaignDashboardWidgetFactory.php │ │ ├── CampaignFactory.php │ │ ├── CampaignStyleFactory.php │ │ ├── CharacterFactory.php │ │ ├── ConversationFactory.php │ │ ├── ConversationMessageFactory.php │ │ ├── ConversationParticipantFactory.php │ │ ├── CreatureFactory.php │ │ ├── DiceRollFactory.php │ │ ├── EntityAbilityFactory.php │ │ ├── EntityAssetFactory.php │ │ ├── EntityTagFactory.php │ │ ├── EventFactory.php │ │ ├── FamilyFactory.php │ │ ├── ImageFactory.php │ │ ├── ItemFactory.php │ │ ├── JournalFactory.php │ │ ├── LocationFactory.php │ │ ├── MapFactory.php │ │ ├── MapGroupFactory.php │ │ ├── MapLayerFactory.php │ │ ├── MapMarkerFactory.php │ │ ├── NoteFactory.php │ │ ├── OrganisationFactory.php │ │ ├── PostFactory.php │ │ ├── PostTagFactory.php │ │ ├── QuestElementFactory.php │ │ ├── QuestFactory.php │ │ ├── RaceFactory.php │ │ ├── RelationFactory.php │ │ ├── ReminderFactory.php │ │ ├── TagFactory.php │ │ ├── TimelineElementFactory.php │ │ ├── TimelineEraFactory.php │ │ ├── TimelineFactory.php │ │ └── UserFactory.php │ ├── migrations/ │ │ ├── .gitkeep │ │ ├── 2014_04_02_193005_create_ltm_translations_table.php │ │ ├── 2014_10_12_000000_create_users_table.php │ │ ├── 2014_10_12_100000_create_password_resets_table.php │ │ ├── 2016_01_01_000000_add_voyager_user_fields.php │ │ ├── 2016_04_11_00352701_create_user_locales_table.php │ │ ├── 2016_06_01_000001_create_oauth_auth_codes_table.php │ │ ├── 2016_06_01_000002_create_oauth_access_tokens_table.php │ │ ├── 2016_06_01_000003_create_oauth_refresh_tokens_table.php │ │ ├── 2016_06_01_000004_create_oauth_clients_table.php │ │ ├── 2016_06_01_000005_create_oauth_personal_access_clients_table.php │ │ ├── 2016_10_21_190000_create_roles_table.php │ │ ├── 2017_10_27_090105_create_campaign.php │ │ ├── 2017_10_27_091125_create_characters.php │ │ ├── 2017_10_27_091755_create_families.php │ │ ├── 2017_10_27_102246_create_locations.php │ │ ├── 2017_10_28_091521_update_character_with_location.php │ │ ├── 2017_10_30_010000_create_real_visibilities_table.php │ │ ├── 2017_10_30_010002_create_real_entities_table.php │ │ ├── 2017_10_30_160256_create_journal_table.php │ │ ├── 2017_10_30_160311_create_item_table.php │ │ ├── 2017_10_30_162322_create_family_relations.php │ │ ├── 2017_10_30_164537_add_character_family.php │ │ ├── 2017_10_31_124138_update_user_and_campaign_link.php │ │ ├── 2017_11_01_222903_create_organisation.php │ │ ├── 2017_11_03_181958_create_notes.php │ │ ├── 2017_11_12_195702_create_invite_tokens.php │ │ ├── 2017_11_16_145219_create_events.php │ │ ├── 2017_11_16_222319_create_campaign_settings.php │ │ ├── 2017_11_22_111255_create_quests.php │ │ ├── 2017_11_30_113216_create_relations_table.php │ │ ├── 2018_01_25_112528_create_attributes_table.php │ │ ├── 2018_02_14_102646_create_attribute_template.php │ │ ├── 2018_03_02_163640_create_new_acl.php │ │ ├── 2018_03_14_152413_create_notifications_table.php │ │ ├── 2018_04_09_17054701_add_ui_settings_to_ltm_user_locales.php │ │ ├── 2018_04_16_090159_create_calendar_table.php │ │ ├── 2018_04_24_075544_create_entity_notes_table.php │ │ ├── 2018_04_24_124131_create_character_traits_table.php │ │ ├── 2018_04_24_193803_create_sections_table.php │ │ ├── 2018_04_30_174331_create_entity_events.php │ │ ├── 2018_05_19_091532_add_dice_rolls.php │ │ ├── 2018_05_25_102446_update_campaign_invites.php │ │ ├── 2018_05_25_113848_create_dice_roll_results.php │ │ ├── 2018_05_31_121110_create_custom_menu_table.php │ │ ├── 2018_07_17_131457_create_jobs_table.php │ │ ├── 2018_08_15_083901_create_failed_jobs_table.php │ │ ├── 2018_08_20_174638_create_conversations.php │ │ ├── 2018_09_19_121625_add_calendar_date_to_quest_and_journal.php │ │ ├── 2018_09_25_134530_create_race.php │ │ ├── 2018_10_17_130533_create_entity_categories.php │ │ ├── 2018_10_23_124704_create_entity_files.php │ │ ├── 2018_11_28_125839_add_campaign_dashboard_calendar.php │ │ ├── 2018_12_06_222045_add_campaign_permission_entity_id.php │ │ ├── 2019_01_24_182847_create_entity_mentions.php │ │ ├── 2019_02_22_083247_create_entity_log.php │ │ ├── 2019_04_11_134145_create_entity_inventory.php │ │ ├── 2019_05_03_000001_create_customer_columns.php │ │ ├── 2019_05_03_000002_create_subscriptions_table.php │ │ ├── 2019_05_03_000003_create_subscription_items_table.php │ │ ├── 2019_05_10_072500_create_entities.php │ │ ├── 2019_05_23_130252_update_attribute_templates_add_entity_type_id.php │ │ ├── 2019_08_15_152154_create_campaign_follow.php │ │ ├── 2019_10_02_131224_create_campaign_boosts.php │ │ ├── 2019_10_02_191220_custom_patreon.php │ │ ├── 2020_01_20_084615_create_calendar_weather.php │ │ ├── 2020_03_05_174449_create_abilities_table.php │ │ ├── 2020_03_05_221303_create_entity_abilities.php │ │ ├── 2020_03_25_192753_add_soft_deletes.php │ │ ├── 2020_04_10_121227_create_discord.php │ │ ├── 2020_04_29_175303_subscription_bills.php │ │ ├── 2020_05_08_065550_create_images.php │ │ ├── 2020_05_10_122725_update_dashboard_widgets_add_tags.php │ │ ├── 2020_06_02_103227_create_sessions_table.php │ │ ├── 2020_06_06_084917_create_maps_table.php │ │ ├── 2020_07_24_160310_add_map_groups.php │ │ ├── 2020_08_02_125607_create_app_updates_table.php │ │ ├── 2020_08_03_155441_create_timelines_table.php │ │ ├── 2020_08_04_095517_create_timeline_elements.php │ │ ├── 2020_08_10_172320_update_entity_events_add_type.php │ │ ├── 2020_09_10_140424_add_entity_image_id.php │ │ ├── 2020_09_22_115919_create_referral_table.php │ │ ├── 2020_11_07_083605_create_campaign_dashboards.php │ │ ├── 2020_11_07_095122_update_campaign_widgets_add_dashboard_id.php │ │ ├── 2020_12_12_191426_create_entity_note_permissions.php │ │ ├── 2021_01_03_231138_create_table_entity_links.php │ │ ├── 2021_01_18_184615_create_campaign_gallery_folders.php │ │ ├── 2021_01_29_003755_update_menu_links_dashboard_id.php │ │ ├── 2021_01_30_233554_create_campaign_submissions.php │ │ ├── 2021_04_07_230722_update_entities_add_marketplace_entity_uuid.php │ │ ├── 2021_04_11_202656_update_relations_add_marketplace_uuid.php │ │ ├── 2021_04_20_201015_create_quest_elements_table.php │ │ ├── 2021_05_25_125309_update_maps_center_marker.php │ │ ├── 2021_07_19_095244_update_entity_notes_marketplace_uuid.php │ │ ├── 2021_09_24_175239_create_entity_user_table.php │ │ ├── 2021_09_27_173618_create_campaign_styles_table.php │ │ ├── 2021_10_19_151149_create_menu_link_tags_table.php │ │ ├── 2021_11_19_031558_create_user_role_table.php │ │ ├── 2021_11_19_034012_update_entities_add_type_id.php │ │ ├── 2022_01_02_212212_add_character_race_table.php │ │ ├── 2022_01_23_023952_create_entity_aliases_table.php │ │ ├── 2022_03_07_224142_create_character_families_table.php │ │ ├── 2022_04_27_194610_update_campaign_permissions_perf.php │ │ ├── 2022_05_06_151813_update_users_add_card_expiration.php │ │ ├── 2022_06_07_172445_update_maps_add_clustering_toggle.php │ │ ├── 2022_06_23_154301_create_entity_assets_table.php │ │ ├── 2022_08_23_005328_add_race_location_table.php │ │ ├── 2022_09_02_191341_create_password_securities_table.php │ │ ├── 2022_09_21_231900_refactor_user_patreon.php │ │ ├── 2022_10_20_192238_create_creatures_table.php │ │ ├── 2022_11_01_214611_update_entity_user_add_campaign_id_post_id_timeline_element_id_quest_element_id.php │ │ ├── 2022_11_07_192125_update_entity_mentions_add_quest_element_id_timeline_element_id.php │ │ ├── 2022_11_08_161937_create_presets_table.php │ │ ├── 2022_12_08_155837_rename_entity_note_id_to_post_id.php │ │ ├── 2023_01_04_213154_create_bragi_logs_table.php │ │ ├── 2023_01_16_021352_create_family_tree_table.php │ │ ├── 2023_01_19_154910_cleanup_user_date_format.php │ │ ├── 2023_02_08_003255_update_entity_logs_add_post_id.php │ │ ├── 2023_03_14_152925_update_subscriptions_rename_stripe_plan.php │ │ ├── 2023_03_14_153209_update_subscription_items_rename_stripe_plan.php │ │ ├── 2023_03_14_154115_update_users_rename_card_brand_card_last_four.php │ │ ├── 2023_05_04_185755_update_gallery_woff2.php │ │ ├── 2023_05_17_191929_update_images_add_focus_x_focus_y.php │ │ ├── 2023_06_02_175012_create_image_mentions_table.php │ │ ├── 2023_06_09_215007_update_campaign_style_add_theme.php │ │ ├── 2023_06_10_220139_create_api_logs.php │ │ ├── 2023_06_15_210220_create_user_logs_table.php │ │ ├── 2023_06_21_221002_update_attributes_rename_is_star.php │ │ ├── 2023_06_21_221208_update_relations_rename_is_star.php │ │ ├── 2023_06_27_194149_create_genres_table.php │ │ ├── 2023_06_27_195226_create_campaign_genre_table.php │ │ ├── 2023_06_29_185027_create_user_flags_table.php │ │ ├── 2023_07_13_164526_create_post_layouts_table.php │ │ ├── 2023_07_13_172639_update_entity_notes_add_layout_id.php │ │ ├── 2023_07_28_181821_update_calendars_add_format.php │ │ ├── 2023_08_11_012819_reset_campaign_slug.php │ │ ├── 2023_08_14_225219_add_new_slug.php │ │ ├── 2023_08_16_170220_remove_campaign_export_path.php │ │ ├── 2023_08_21_233952_create_tutorials_table.php │ │ ├── 2023_08_30_170905_migrate_to_bookmarks.php │ │ ├── 2023_09_12_200523_migrate_to_posts.php │ │ ├── 2023_10_15_183420_add_image_to_entities.php │ │ ├── 2023_10_16_202303_update_locations_rename_parent_location_id_location_id.php │ │ ├── 2023_10_21_172645_update_entity_type_bookmark.php │ │ ├── 2023_10_25_173253_create_campaign_exports_table.php │ │ ├── 2023_10_28_210131_add_is_deleted_index.php │ │ ├── 2023_11_05_174423_create_campaign_import_table.php │ │ ├── 2023_11_07_034812_rollback_entities_deleted_index.php │ │ ├── 2023_11_19_121322_update_campaign_exports_add_progress.php │ │ ├── 2023_11_21_215234_update_calendars_add_show_birthdays.php │ │ ├── 2023_11_28_121013_cleanup_entity_images.php │ │ ├── 2023_11_28_215313_update_map_markers_add_is_popupless.php │ │ ├── 2023_12_02_154727_remove_old_trees.php │ │ ├── 2023_12_05_125348_create_tiers_table.php │ │ ├── 2024_01_08_144309_create_features_table.php │ │ ├── 2024_01_10_191244_create_game_systems_table.php │ │ ├── 2024_01_10_191740_create_campaign_system_table.php │ │ ├── 2024_01_11_150113_update_attributes.php │ │ ├── 2024_01_19_024046_cleanup_reminders.php │ │ ├── 2024_01_22_203110_create_user_validations_table.php │ │ ├── 2024_01_25_192113_update_creatures_add_is_extinct.php │ │ ├── 2024_01_26_194006_update_campaigns_add_is_discreet.php │ │ ├── 2024_02_06_162941_create_feature_files.php │ │ ├── 2024_02_27_095646_update_features_add_original.php │ │ ├── 2024_03_07_010249_update_features_add_rejection.php │ │ ├── 2024_03_27_173140_create_webhooks.php │ │ ├── 2024_04_03_224729_create_webhook_tags_table.php │ │ ├── 2024_04_15_220412_create_organisation_location_table.php │ │ ├── 2024_04_23_062512_update_subscriptions_change_name_type.php │ │ ├── 2024_04_24_193659_update_campaign_settings_add_assets.php │ │ ├── 2024_05_02_162659_update_inventories_add_image_uuid.php │ │ ├── 2024_05_28_202511_create_tier_pricing_table.php │ │ ├── 2024_06_01_000001_create_oauth_device_codes_table.php │ │ ├── 2024_06_04_202056_update_posts_add_deleted_at_deleted_by.php │ │ ├── 2024_06_14_201056_update_campaigns_add_deleted_by_deleted_at.php │ │ ├── 2024_06_22_004252_update_character_race_add_is_private.php │ │ ├── 2024_07_02_172619_update_posts_add_is_template.php │ │ ├── 2024_07_08_051702_update_webhooks_add_settings.php │ │ ├── 2024_07_09_062122_update_character_family_add_is_private.php │ │ ├── 2024_07_25_205325_update_map_layers_add_image_uuid.php │ │ ├── 2024_07_26_160208_update_entity_assets_add_image_uuid.php │ │ ├── 2024_07_31_215400_remove_unused_slug.php │ │ ├── 2024_08_12_231117_update_locations_add_is_destroyed.php │ │ ├── 2024_08_13_202759_update_creatures_add_is_dead.php │ │ ├── 2024_08_14_145655_update_races_add_is_extinct.php │ │ ├── 2024_08_14_164041_update_families_add_is_extinct.php │ │ ├── 2024_08_27_175447_update_notifications_migrate_user_model.php │ │ ├── 2025_01_09_180214_update_objects_add_weight.php │ │ ├── 2025_01_10_175606_update_quests_add_location_id.php │ │ ├── 2025_01_11_022946_update_modules_add_campaign_id.php │ │ ├── 2025_01_13_064804_create_post_tag_table.php │ │ ├── 2025_01_13_230814_update_entities_add_boilerplate.php │ │ ├── 2025_01_14_144924_update_bookmarks_add_entity_type_id.php │ │ ├── 2025_01_15_160953_migrate_entry_to_entities.php │ │ ├── 2025_01_16_164318_add_dashboard_entity_type_id.php │ │ ├── 2025_01_16_175851_migrate_widgets_to_entity_types.php │ │ ├── 2025_01_17_161652_add_parent_to_entities.php │ │ ├── 2025_01_27_142523_add_word_count.php │ │ ├── 2025_02_21_204219_update_module_icon.php │ │ ├── 2025_03_18_055651_update_features_add_message_id.php │ │ ├── 2025_03_21_150239_rename_entity_events_to_reminders.php │ │ ├── 2025_03_21_150723_update_entity_events_add_polymorphism.php │ │ ├── 2025_03_21_151008_fill_entity_events.php │ │ ├── 2025_03_21_153447_update_reminders_nullify_entity_id.php │ │ ├── 2025_04_10_064831_update_attribute_templates_add_is_enabled.php │ │ ├── 2025_04_21_170958_update_user_logs_add_campaign_id.php │ │ ├── 2025_05_29_015926_update_bookmarks_add_created_by_updated_by.php │ │ ├── 2025_05_29_161824_update_campaign_dashboard_widgets_add_created_by_updated_by.php │ │ ├── 2025_05_29_162746_update_attributes_add_created_by_updated_by.php │ │ ├── 2025_06_06_053052_update_reminders_remove_entity_id.php │ │ ├── 2025_06_09_161939_cleanup_old_entry_and_type.php │ │ ├── 2025_06_09_162836_cleanup_old_fields.php │ │ ├── 2025_06_09_181415_rename_campaign_submissions_to_applications.php │ │ ├── 2025_06_09_182950_cleanup_old_logs_tables.php │ │ ├── 2025_06_16_145726_add_api_log_duration.php │ │ ├── 2025_06_16_211604_drop_oauth_personal_access_clients.php │ │ ├── 2025_06_24_124233_update_entity_logs_add_polymorphism.php │ │ ├── 2025_06_24_124354_migrate_entity_logs.php │ │ ├── 2025_06_24_124500_cleanup_entity_logs.php │ │ ├── 2025_06_25_161814_create_campaign_flags_table.php │ │ ├── 2025_07_03_215618_update_objects_add_creator_id.php │ │ ├── 2025_07_03_222904_update_objects_migrate_character_id_to_creator_id.php │ │ ├── 2025_08_14_185936_update_posts_remove_is_private.php │ │ ├── 2025_09_04_194330_update_campaign_flags_add_value.php │ │ ├── 2025_09_12_160608_create_whiteboards_table.php │ │ ├── 2025_09_17_034429_update_user_flags_add_amount.php │ │ ├── 2025_09_22_230759_update_campaign_imports_add_logs_errors.php │ │ ├── 2025_09_29_173336_update_map_markers_add_css.php │ │ ├── 2025_09_29_204049_update_entities_add_archived_at.php │ │ ├── 2025_10_01_200028_update_map_groups_add_parent_id.php │ │ ├── 2025_10_02_170514_update_campaign_settings_add_whiteboards.php │ │ ├── 2025_11_04_205450_create_entity_locations2_table.php │ │ ├── 2025_11_06_154720_update_entity_assets_add_updated_by.php │ │ ├── 2025_11_19_155526_migrate_campaigns_discreet.php │ │ ├── 2025_11_19_160105_remove_campaigns_is_discreet.php │ │ ├── 2025_12_15_191556_add_dashboard_widget_index.php │ │ ├── 2025_12_22_212249_migrate_character_locations_to_entity_locations.php │ │ ├── 2026_01_14_161511_cleanup_old_referrals.php │ │ ├── 2026_01_14_170634_create_referral_codes_table.php │ │ ├── 2026_01_14_174144_create_referral_events_table.php │ │ ├── 2026_01_14_174215_update_users_referred_by.php │ │ ├── 2026_01_15_224615_create_whiteboard_shapes_table.php │ │ ├── 2026_01_15_232603_create_whiteboard_strokes_table.php │ │ ├── 2026_01_23_204425_create_spotlights_table.php │ │ ├── 2026_01_23_205038_cleanup_old_spotlights.php │ │ ├── 2026_01_23_211003_create_spotlight_survey_table.php │ │ ├── 2026_01_30_100000_rename_description_to_entry_in_quest_elements_table.php │ │ ├── 2026_02_03_221238_create_playstyles_table.php │ │ ├── 2026_02_04_170050_migrate_organisation_locations_to_entity_locations.php │ │ ├── 2026_02_04_170051_migrate_creature_locations_to_entity_locations.php │ │ ├── 2026_02_04_170052_migrate_race_locations_to_entity_locations.php │ │ ├── 2026_02_04_213616_create_campaign_filters_table.php │ │ ├── 2026_02_05_074106_create_campaign_playstyle_table.php │ │ ├── 2026_02_06_185348_update_entities_force_name.php │ │ ├── 2026_02_06_190751_add_entity_id_index_to_entities.php │ │ ├── 2026_02_06_190754_add_composite_index_to_entity_tags.php │ │ ├── 2026_02_06_190757_add_deleted_at_index_to_soft_delete_tables.php │ │ ├── 2026_02_07_072211_update_applications_add_new_fields.php │ │ ├── 2026_02_11_162437_add_alias_config.php │ │ ├── 2026_02_16_155432_create_campaign_events_table.php │ │ ├── 2026_02_16_215608_update_entities_add_source.php │ │ ├── 2026_02_16_232044_fix_users_referred_by_foreign_key.php │ │ ├── 2026_02_23_193432_add_prioritised_to_campaigns.php │ │ ├── 2026_03_03_225300_update_characters_is_dead_to_tinyinteger.php │ │ ├── 2026_03_04_044125_update_quests_is_completed_to_tinyinteger.php │ │ ├── 2026_03_05_184417_migrate_event_locations_to_entity_locations.php │ │ ├── 2026_03_05_214251_update_characters_pinned_defaults.php │ │ ├── 2026_03_06_140442_rename_characters_is_dead_to_status.php │ │ ├── 2026_03_06_140443_rename_quests_is_completed_to_status.php │ │ ├── 2026_03_09_224121_add_copy_entity_entry_to_quest_elements_table.php │ │ ├── 2026_03_11_224335_add_icon_to_tags_table.php │ │ ├── 2026_03_12_172704_add_title_to_locations_table.php │ │ ├── 2026_03_16_170743_update_cancellations_add_secondary.php │ │ ├── 2026_03_16_211952_create_entity_listing_preferences_table.php │ │ ├── 2026_03_17_000001_migrate_parent_ids_to_entities.php │ │ ├── 2026_03_17_000002_drop_parent_columns_from_child_tables.php │ │ ├── 2026_03_17_092044_create_item_creator_table.php │ │ ├── 2026_03_18_152531_add_per_page_to_entity_listing_preferences_table.php │ │ ├── 2026_03_23_193613_convert_tag_colours_to_hex.php │ │ ├── 2026_03_26_000001_create_category_statuses_table.php │ │ ├── 2026_03_26_000002_add_status_id_to_entities_table.php │ │ ├── 2026_04_03_164253_update_features_add_meta.php │ │ ├── 2026_04_03_215445_create_campaign_descriptions_table.php │ │ ├── 2026_05_08_181031_drop_old_status_columns.php │ │ ├── 2026_05_08_181951_drop_audit_columns_from_creatures_races_whiteboards.php │ │ ├── 2026_05_08_183610_drop_system_and_is_discreet_from_campaigns.php │ │ ├── 2026_05_08_185251_drop_follower_from_campaigns.php │ │ └── 2026_05_08_190847_drop_export_date_from_campaigns.php │ └── seeders/ │ ├── CategoryStatusSeeder.php │ ├── DatabaseSeeder.php │ ├── EntityEventTypeSeeder.php │ ├── EntityTypesTableSeeder.php │ ├── FeatureCategorySeeder.php │ ├── FeatureStatusSeeder.php │ ├── GameSystemSeeder.php │ ├── GenreTableSeeder.php │ ├── PlaystyleSeeder.php │ ├── PostLayoutTableSeeder.php │ ├── PresetTypeTableSeeder.php │ ├── RolesTableSeeder.php │ ├── ThemesTableSeeder.php │ ├── TierPricingSeeder.php │ ├── TierSeeder.php │ └── VisibilitiesTableSeeder.php ├── docker/ │ └── mysql/ │ └── create-testing-database.sh ├── docker-compose.yml ├── docs/ │ ├── api/ │ │ └── readme.md │ ├── assets.md │ ├── contributing.md │ ├── debugging.md │ ├── meilisearch.md │ ├── running.md │ ├── specs/ │ │ └── 2026-05-07-family-tree-cytoscape-redesign.md │ ├── translating.md │ ├── updating.md │ └── websockets.md ├── lang/ │ ├── base/ │ │ ├── attributes/ │ │ │ └── .gitkeep │ │ ├── calendars/ │ │ │ └── .gitkeep │ │ ├── campaigns/ │ │ │ └── .gitkeep │ │ ├── emails/ │ │ │ └── .gitkeep │ │ ├── entities/ │ │ │ └── .gitkeep │ │ ├── front/ │ │ │ └── .gitkeep │ │ ├── maps/ │ │ │ └── .gitkeep │ │ ├── readme.md │ │ ├── settings/ │ │ │ └── .gitkeep │ │ ├── subscriptions/ │ │ │ └── .gitkeep │ │ ├── timelines/ │ │ │ └── .gitkeep │ │ └── users/ │ │ └── .gitkeep │ ├── de/ │ │ ├── abilities.php │ │ ├── account/ │ │ │ └── social.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── banners.php │ │ ├── billing/ │ │ │ ├── information.php │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── builder.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── delete.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── import.php │ │ │ ├── invites.php │ │ │ ├── limits.php │ │ │ ├── members.php │ │ │ ├── modules.php │ │ │ ├── overview.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ ├── styles.php │ │ │ ├── vanity.php │ │ │ └── webhooks.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── confirm/ │ │ │ └── editing.php │ │ ├── confirm.php │ │ ├── conversations.php │ │ ├── cookieconsent.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── dashboards/ │ │ │ ├── setup.php │ │ │ └── widgets/ │ │ │ ├── calendar.php │ │ │ ├── campaign.php │ │ │ ├── header.php │ │ │ ├── help.php │ │ │ ├── preview.php │ │ │ ├── random.php │ │ │ ├── recent.php │ │ │ └── welcome.php │ │ ├── datagrids.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── activity/ │ │ │ │ ├── email.php │ │ │ │ └── password.php │ │ │ ├── purge/ │ │ │ │ ├── first.php │ │ │ │ └── second.php │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ ├── upcoming.php │ │ │ │ └── validation.php │ │ │ ├── validation.php │ │ │ ├── welcome/ │ │ │ │ └── 2024.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── children.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── reminders.php │ │ │ ├── story.php │ │ │ ├── tags.php │ │ │ ├── timelines.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── export.php │ │ ├── families/ │ │ │ └── trees.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── gallery.php │ │ ├── general.php │ │ ├── genres.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── history.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── lists.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── onboarding/ │ │ │ ├── attributes.php │ │ │ ├── characters.php │ │ │ ├── locations.php │ │ │ ├── posts.php │ │ │ ├── reminders.php │ │ │ └── tags.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── permissions.php │ │ ├── pins.php │ │ ├── post_layouts.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search/ │ │ │ └── fulltext.php │ │ ├── search.php │ │ ├── seo.php │ │ ├── settings/ │ │ │ ├── account.php │ │ │ ├── appearance.php │ │ │ ├── boosters.php │ │ │ └── premium.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ ├── promos.php │ │ │ └── renew.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation.php │ │ ├── visibilities.php │ │ ├── whiteboards/ │ │ │ └── draw.php │ │ └── whiteboards.php │ ├── en/ │ │ ├── abilities.php │ │ ├── account/ │ │ │ ├── email.php │ │ │ ├── password.php │ │ │ └── social.php │ │ ├── articles.php │ │ ├── assistance.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── banners.php │ │ ├── billing/ │ │ │ ├── information.php │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi/ │ │ │ └── backstory.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── builder.php │ │ │ ├── categories.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── defaults.php │ │ │ ├── delete.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── import.php │ │ │ ├── invites.php │ │ │ ├── limits.php │ │ │ ├── logs.php │ │ │ ├── members.php │ │ │ ├── modules.php │ │ │ ├── overview.php │ │ │ ├── permissions.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── share.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ ├── styles.php │ │ │ ├── vanity.php │ │ │ ├── visibilities.php │ │ │ └── webhooks.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── confirm/ │ │ │ └── editing.php │ │ ├── confirm.php │ │ ├── connections/ │ │ │ └── web.php │ │ ├── conversations.php │ │ ├── cookieconsent.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── dashboards/ │ │ │ ├── onboarding.php │ │ │ ├── premium.php │ │ │ ├── setup.php │ │ │ └── widgets/ │ │ │ ├── calendar.php │ │ │ ├── campaign.php │ │ │ ├── gallery.php │ │ │ ├── header.php │ │ │ ├── help.php │ │ │ ├── join.php │ │ │ ├── onboarding.php │ │ │ ├── preview.php │ │ │ ├── random.php │ │ │ ├── recent.php │ │ │ └── welcome.php │ │ ├── datagrids.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── activity/ │ │ │ │ ├── email.php │ │ │ │ └── password.php │ │ │ ├── purge/ │ │ │ │ ├── first.php │ │ │ │ └── second.php │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ ├── paypal-expiring.php │ │ │ │ ├── upcoming.php │ │ │ │ └── validation.php │ │ │ ├── validation.php │ │ │ ├── welcome/ │ │ │ │ └── 2024.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── children.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── reminders.php │ │ │ ├── share.php │ │ │ ├── statuses.php │ │ │ ├── story.php │ │ │ ├── tags.php │ │ │ ├── timelines.php │ │ │ ├── tooltips.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── entries/ │ │ │ ├── archetypes.php │ │ │ ├── archive.php │ │ │ ├── bulk.php │ │ │ ├── fields.php │ │ │ └── tabs.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── export.php │ │ ├── families/ │ │ │ └── trees.php │ │ ├── families.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── gallery.php │ │ ├── general.php │ │ ├── genres.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── history.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── lists.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── onboarding/ │ │ │ ├── attributes.php │ │ │ ├── characters.php │ │ │ ├── locations.php │ │ │ ├── posts.php │ │ │ ├── reminders.php │ │ │ └── tags.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── permissions.php │ │ ├── pins.php │ │ ├── playstyles.php │ │ ├── post_layouts.php │ │ ├── posts/ │ │ │ └── templates.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── referrals.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search/ │ │ │ └── fulltext.php │ │ ├── search.php │ │ ├── seo.php │ │ ├── settings/ │ │ │ ├── account.php │ │ │ ├── api.php │ │ │ ├── appearance.php │ │ │ ├── boosters.php │ │ │ └── premium.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── spotlights.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ ├── cancellation.php │ │ │ ├── cancelled.php │ │ │ ├── confirm.php │ │ │ ├── faq.php │ │ │ ├── finish.php │ │ │ ├── free-trial.php │ │ │ ├── paypal-renew.php │ │ │ ├── paypal.php │ │ │ ├── promos.php │ │ │ └── renew.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── tiptap.php │ │ ├── tutorials/ │ │ │ ├── actions.php │ │ │ ├── characters.php │ │ │ └── home.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation.php │ │ ├── visibilities.php │ │ ├── whiteboards/ │ │ │ └── draw.php │ │ └── whiteboards.php │ ├── en-US/ │ │ ├── bookmarks.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── campaigns/ │ │ │ └── builder.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── emails/ │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ └── attributes.php │ │ ├── entities.php │ │ ├── front.php │ │ ├── helpers.php │ │ ├── items.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ └── markers.php │ │ ├── organisations.php │ │ ├── quests.php │ │ └── sidebar.php │ ├── es/ │ │ ├── abilities.php │ │ ├── account/ │ │ │ ├── email.php │ │ │ ├── password.php │ │ │ └── social.php │ │ ├── assistance.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── billing/ │ │ │ ├── information.php │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi/ │ │ │ └── backstory.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── builder.php │ │ │ ├── categories.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── defaults.php │ │ │ ├── delete.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── import.php │ │ │ ├── invites.php │ │ │ ├── limits.php │ │ │ ├── logs.php │ │ │ ├── members.php │ │ │ ├── modules.php │ │ │ ├── overview.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── share.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ ├── styles.php │ │ │ ├── vanity.php │ │ │ ├── visibilities.php │ │ │ └── webhooks.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── confirm/ │ │ │ └── editing.php │ │ ├── confirm.php │ │ ├── connections/ │ │ │ └── web.php │ │ ├── conversations.php │ │ ├── cookieconsent.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── dashboards/ │ │ │ ├── onboarding.php │ │ │ ├── setup.php │ │ │ └── widgets/ │ │ │ ├── calendar.php │ │ │ ├── campaign.php │ │ │ ├── header.php │ │ │ ├── help.php │ │ │ ├── join.php │ │ │ ├── onboarding.php │ │ │ ├── preview.php │ │ │ ├── random.php │ │ │ ├── recent.php │ │ │ └── welcome.php │ │ ├── datagrids.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── activity/ │ │ │ │ ├── email.php │ │ │ │ └── password.php │ │ │ ├── purge/ │ │ │ │ ├── first.php │ │ │ │ └── second.php │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ ├── upcoming.php │ │ │ │ └── validation.php │ │ │ ├── validation.php │ │ │ ├── welcome/ │ │ │ │ └── 2024.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── children.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── reminders.php │ │ │ ├── share.php │ │ │ ├── story.php │ │ │ ├── tags.php │ │ │ ├── timelines.php │ │ │ ├── tooltips.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── entries/ │ │ │ ├── archetypes.php │ │ │ ├── bulk.php │ │ │ ├── fields.php │ │ │ └── tabs.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── export.php │ │ ├── families/ │ │ │ └── trees.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── gallery.php │ │ ├── general.php │ │ ├── genres.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── history.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── lists.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── onboarding/ │ │ │ ├── attributes.php │ │ │ ├── characters.php │ │ │ ├── locations.php │ │ │ ├── posts.php │ │ │ ├── reminders.php │ │ │ └── tags.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── permissions.php │ │ ├── pins.php │ │ ├── playstyles.php │ │ ├── post_layouts.php │ │ ├── posts/ │ │ │ └── templates.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── referrals.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search/ │ │ │ └── fulltext.php │ │ ├── search.php │ │ ├── seo.php │ │ ├── settings/ │ │ │ ├── account.php │ │ │ ├── api.php │ │ │ ├── appearance.php │ │ │ ├── boosters.php │ │ │ └── premium.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── spotlights.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ ├── cancellation.php │ │ │ ├── cancelled.php │ │ │ ├── confirm.php │ │ │ ├── faq.php │ │ │ ├── finish.php │ │ │ ├── free-trial.php │ │ │ ├── paypal.php │ │ │ ├── promos.php │ │ │ └── renew.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── tiptap.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation.php │ │ ├── visibilities.php │ │ ├── whiteboards/ │ │ │ └── draw.php │ │ └── whiteboards.php │ ├── fr/ │ │ ├── abilities.php │ │ ├── account/ │ │ │ ├── email.php │ │ │ ├── password.php │ │ │ └── social.php │ │ ├── articles.php │ │ ├── assistance.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── billing/ │ │ │ ├── information.php │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi/ │ │ │ └── backstory.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── builder.php │ │ │ ├── categories.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── defaults.php │ │ │ ├── delete.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── import.php │ │ │ ├── invites.php │ │ │ ├── limits.php │ │ │ ├── logs.php │ │ │ ├── members.php │ │ │ ├── modules.php │ │ │ ├── overview.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── share.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ ├── styles.php │ │ │ ├── vanity.php │ │ │ ├── visibilities.php │ │ │ └── webhooks.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── confirm/ │ │ │ └── editing.php │ │ ├── confirm.php │ │ ├── connections/ │ │ │ └── web.php │ │ ├── conversations.php │ │ ├── cookieconsent.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── dashboards/ │ │ │ ├── onboarding.php │ │ │ ├── premium.php │ │ │ ├── setup.php │ │ │ └── widgets/ │ │ │ ├── calendar.php │ │ │ ├── campaign.php │ │ │ ├── gallery.php │ │ │ ├── header.php │ │ │ ├── help.php │ │ │ ├── join.php │ │ │ ├── onboarding.php │ │ │ ├── preview.php │ │ │ ├── random.php │ │ │ ├── recent.php │ │ │ └── welcome.php │ │ ├── datagrids.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── activity/ │ │ │ │ ├── email.php │ │ │ │ └── password.php │ │ │ ├── purge/ │ │ │ │ ├── first.php │ │ │ │ └── second.php │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ ├── paypal-expiring.php │ │ │ │ ├── upcoming.php │ │ │ │ └── validation.php │ │ │ ├── validation.php │ │ │ ├── welcome/ │ │ │ │ └── 2024.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── children.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── reminders.php │ │ │ ├── share.php │ │ │ ├── statuses.php │ │ │ ├── story.php │ │ │ ├── tags.php │ │ │ ├── timelines.php │ │ │ ├── tooltips.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── entries/ │ │ │ ├── archetypes.php │ │ │ ├── archive.php │ │ │ ├── bulk.php │ │ │ ├── fields.php │ │ │ └── tabs.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── export.php │ │ ├── families/ │ │ │ └── trees.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── gallery.php │ │ ├── general.php │ │ ├── genres.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── history.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── lists.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── onboarding/ │ │ │ ├── attributes.php │ │ │ ├── characters.php │ │ │ ├── locations.php │ │ │ ├── posts.php │ │ │ ├── reminders.php │ │ │ └── tags.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── permissions.php │ │ ├── pins.php │ │ ├── playstyles.php │ │ ├── post_layouts.php │ │ ├── posts/ │ │ │ └── templates.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── referrals.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search/ │ │ │ └── fulltext.php │ │ ├── search.php │ │ ├── seo.php │ │ ├── settings/ │ │ │ ├── account.php │ │ │ ├── api.php │ │ │ ├── appearance.php │ │ │ ├── boosters.php │ │ │ └── premium.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── spotlights.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ ├── cancellation.php │ │ │ ├── cancelled.php │ │ │ ├── confirm.php │ │ │ ├── faq.php │ │ │ ├── finish.php │ │ │ ├── free-trial.php │ │ │ ├── paypal-renew.php │ │ │ ├── paypal.php │ │ │ ├── promos.php │ │ │ └── renew.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── tiptap.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation.php │ │ ├── visibilities.php │ │ ├── whiteboards/ │ │ │ └── draw.php │ │ └── whiteboards.php │ ├── hr/ │ │ ├── abilities.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── bookmarks.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── campaigns/ │ │ │ ├── applications.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── gallery.php │ │ │ ├── plugins.php │ │ │ ├── recovery.php │ │ │ └── stats.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── conversations.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── story.php │ │ │ ├── timelines.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── starter.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ └── validation.php │ ├── it/ │ │ ├── abilities.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── billing/ │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── builder.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── import.php │ │ │ ├── limits.php │ │ │ ├── modules.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ ├── styles.php │ │ │ ├── vanity.php │ │ │ └── webhooks.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── confirm/ │ │ │ └── editing.php │ │ ├── conversations.php │ │ ├── cookieconsent.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── dashboards/ │ │ │ └── widgets/ │ │ │ └── welcome.php │ │ ├── datagrids.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── purge/ │ │ │ │ ├── first.php │ │ │ │ └── second.php │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ ├── upcoming.php │ │ │ │ └── validation.php │ │ │ ├── validation.php │ │ │ ├── welcome/ │ │ │ │ └── 2024.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── story.php │ │ │ ├── timelines.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── families/ │ │ │ └── trees.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── gallery.php │ │ ├── general.php │ │ ├── genres.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── history.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── pins.php │ │ ├── post_layouts.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search/ │ │ │ └── fulltext.php │ │ ├── search.php │ │ ├── settings/ │ │ │ ├── appearance.php │ │ │ ├── boosters.php │ │ │ └── premium.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ └── promos.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation.php │ │ └── visibilities.php │ ├── nl/ │ │ ├── abilities.php │ │ ├── attribute_templates.php │ │ ├── auth.php │ │ ├── bookmarks.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── campaigns/ │ │ │ ├── applications.php │ │ │ ├── default-images.php │ │ │ ├── gallery.php │ │ │ ├── plugins.php │ │ │ ├── recovery.php │ │ │ └── stats.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── conversations.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── attributes.php │ │ │ ├── events.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── notes.php │ │ │ ├── relations.php │ │ │ └── timelines.php │ │ ├── entities.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── starter.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ └── validation.php │ ├── pl/ │ │ ├── abilities.php │ │ ├── account/ │ │ │ ├── email.php │ │ │ ├── password.php │ │ │ └── social.php │ │ ├── assistance.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── banners.php │ │ ├── billing/ │ │ │ ├── information.php │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi/ │ │ │ └── backstory.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ ├── .gitkeep │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── .gitkeep │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── builder.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── defaults.php │ │ │ ├── delete.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── import.php │ │ │ ├── invites.php │ │ │ ├── limits.php │ │ │ ├── logs.php │ │ │ ├── members.php │ │ │ ├── modules.php │ │ │ ├── overview.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ ├── styles.php │ │ │ ├── vanity.php │ │ │ ├── visibilities.php │ │ │ └── webhooks.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── confirm/ │ │ │ └── editing.php │ │ ├── confirm.php │ │ ├── connections/ │ │ │ └── web.php │ │ ├── conversations.php │ │ ├── cookieconsent.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── dashboards/ │ │ │ ├── onboarding.php │ │ │ ├── setup.php │ │ │ └── widgets/ │ │ │ ├── calendar.php │ │ │ ├── campaign.php │ │ │ ├── header.php │ │ │ ├── help.php │ │ │ ├── onboarding.php │ │ │ ├── preview.php │ │ │ ├── random.php │ │ │ ├── recent.php │ │ │ └── welcome.php │ │ ├── datagrids.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── activity/ │ │ │ │ ├── email.php │ │ │ │ └── password.php │ │ │ ├── purge/ │ │ │ │ ├── first.php │ │ │ │ └── second.php │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ ├── upcoming.php │ │ │ │ └── validation.php │ │ │ ├── validation.php │ │ │ ├── welcome/ │ │ │ │ └── 2024.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── .gitkeep │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── children.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── reminders.php │ │ │ ├── story.php │ │ │ ├── tags.php │ │ │ ├── timelines.php │ │ │ ├── tooltips.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── export.php │ │ ├── families/ │ │ │ └── trees.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── gallery.php │ │ ├── general.php │ │ ├── genres.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── history.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── lists.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── .gitkeep │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── onboarding/ │ │ │ ├── attributes.php │ │ │ ├── characters.php │ │ │ ├── locations.php │ │ │ ├── posts.php │ │ │ ├── reminders.php │ │ │ └── tags.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── permissions.php │ │ ├── pins.php │ │ ├── post_layouts.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── referrals.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search/ │ │ │ └── fulltext.php │ │ ├── search.php │ │ ├── seo.php │ │ ├── settings/ │ │ │ ├── account.php │ │ │ ├── api.php │ │ │ ├── appearance.php │ │ │ ├── boosters.php │ │ │ └── premium.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── spotlights.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ ├── cancellation.php │ │ │ ├── cancelled.php │ │ │ ├── confirm.php │ │ │ ├── faq.php │ │ │ ├── finish.php │ │ │ ├── free-trial.php │ │ │ ├── paypal.php │ │ │ ├── promos.php │ │ │ └── renew.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── .gitkeep │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation-inline.php │ │ ├── validation.php │ │ ├── visibilities.php │ │ ├── whiteboards/ │ │ │ └── draw.php │ │ └── whiteboards.php │ ├── pt-BR/ │ │ ├── abilities.php │ │ ├── account/ │ │ │ ├── email.php │ │ │ ├── password.php │ │ │ └── social.php │ │ ├── articles.php │ │ ├── assistance.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── banners.php │ │ ├── billing/ │ │ │ ├── information.php │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi/ │ │ │ └── backstory.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── builder.php │ │ │ ├── categories.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── delete.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── import.php │ │ │ ├── invites.php │ │ │ ├── limits.php │ │ │ ├── logs.php │ │ │ ├── members.php │ │ │ ├── modules.php │ │ │ ├── overview.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ ├── styles.php │ │ │ ├── vanity.php │ │ │ └── webhooks.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── confirm/ │ │ │ └── editing.php │ │ ├── confirm.php │ │ ├── conversations.php │ │ ├── cookieconsent.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── dashboards/ │ │ │ ├── premium.php │ │ │ └── widgets/ │ │ │ └── welcome.php │ │ ├── datagrids.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── activity/ │ │ │ │ ├── email.php │ │ │ │ └── password.php │ │ │ ├── purge/ │ │ │ │ ├── first.php │ │ │ │ └── second.php │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ ├── upcoming.php │ │ │ │ └── validation.php │ │ │ ├── validation.php │ │ │ ├── welcome/ │ │ │ │ └── 2024.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── children.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── reminders.php │ │ │ ├── story.php │ │ │ ├── tags.php │ │ │ ├── timelines.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── entries/ │ │ │ └── tabs.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── families/ │ │ │ └── trees.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── gallery.php │ │ ├── general.php │ │ ├── genres.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── history.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── onboarding/ │ │ │ └── characters.php │ │ ├── organisations.php │ │ ├── partials.php │ │ ├── patreon.php │ │ ├── permissions.php │ │ ├── pins.php │ │ ├── playstyles.php │ │ ├── post_layouts.php │ │ ├── posts/ │ │ │ └── templates.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search/ │ │ │ └── fulltext.php │ │ ├── search.php │ │ ├── seo.php │ │ ├── settings/ │ │ │ ├── account.php │ │ │ ├── appearance.php │ │ │ ├── boosters.php │ │ │ └── premium.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ ├── cancelled.php │ │ │ ├── confirm.php │ │ │ ├── faq.php │ │ │ ├── finish.php │ │ │ ├── free-trial.php │ │ │ ├── promos.php │ │ │ └── renew.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── tiptap.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation.php │ │ ├── visibilities.php │ │ └── whiteboards.php │ ├── ru/ │ │ ├── abilities.php │ │ ├── account/ │ │ │ ├── email.php │ │ │ ├── password.php │ │ │ └── social.php │ │ ├── assistance.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── banners.php │ │ ├── billing/ │ │ │ ├── information.php │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── limits.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ └── styles.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── conversations.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ └── upcoming.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── story.php │ │ │ ├── timelines.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── general.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── onboarding/ │ │ │ └── tags.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search.php │ │ ├── settings/ │ │ │ └── boosters.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ └── promos.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation.php │ │ ├── visibilities.php │ │ ├── whiteboards/ │ │ │ └── draw.php │ │ └── whiteboards.php │ ├── sk/ │ │ ├── abilities.php │ │ ├── articles.php │ │ ├── attribute_templates.php │ │ ├── attributes/ │ │ │ └── templates.php │ │ ├── auth.php │ │ ├── banners.php │ │ ├── billing/ │ │ │ ├── invoices.php │ │ │ ├── menu.php │ │ │ └── payment_methods.php │ │ ├── bookmarks.php │ │ ├── bragi.php │ │ ├── calendars/ │ │ │ └── weather.php │ │ ├── calendars.php │ │ ├── callouts.php │ │ ├── campaigns/ │ │ │ ├── achievements.php │ │ │ ├── applications.php │ │ │ ├── builder.php │ │ │ ├── categories.php │ │ │ ├── dashboard-header.php │ │ │ ├── default-images.php │ │ │ ├── delete.php │ │ │ ├── export.php │ │ │ ├── gallery.php │ │ │ ├── import.php │ │ │ ├── invites.php │ │ │ ├── limits.php │ │ │ ├── members.php │ │ │ ├── modules.php │ │ │ ├── overview.php │ │ │ ├── plugins.php │ │ │ ├── public.php │ │ │ ├── recovery.php │ │ │ ├── roles.php │ │ │ ├── sidebar.php │ │ │ ├── stats.php │ │ │ ├── styles.php │ │ │ ├── vanity.php │ │ │ └── webhooks.php │ │ ├── campaigns.php │ │ ├── characters.php │ │ ├── colours.php │ │ ├── concept.php │ │ ├── confirm/ │ │ │ └── editing.php │ │ ├── conversations.php │ │ ├── cookieconsent.php │ │ ├── creatures.php │ │ ├── crud.php │ │ ├── dashboard.php │ │ ├── dashboards/ │ │ │ └── widgets/ │ │ │ └── welcome.php │ │ ├── datagrids.php │ │ ├── datetime.php │ │ ├── default.php │ │ ├── dice_roll_results.php │ │ ├── dice_rolls.php │ │ ├── emails/ │ │ │ ├── activity/ │ │ │ │ └── password.php │ │ │ ├── purge/ │ │ │ │ ├── first.php │ │ │ │ └── second.php │ │ │ ├── subscriptions/ │ │ │ │ ├── expiring.php │ │ │ │ ├── upcoming.php │ │ │ │ └── validation.php │ │ │ ├── validation.php │ │ │ ├── welcome/ │ │ │ │ └── 2024.php │ │ │ └── welcome.php │ │ ├── entities/ │ │ │ ├── abilities.php │ │ │ ├── actions.php │ │ │ ├── aliases.php │ │ │ ├── assets.php │ │ │ ├── attributes.php │ │ │ ├── children.php │ │ │ ├── events.php │ │ │ ├── files.php │ │ │ ├── image.php │ │ │ ├── inventories.php │ │ │ ├── links.php │ │ │ ├── logs.php │ │ │ ├── map-points.php │ │ │ ├── mentions.php │ │ │ ├── move.php │ │ │ ├── notes.php │ │ │ ├── permissions.php │ │ │ ├── pins.php │ │ │ ├── profile.php │ │ │ ├── quests.php │ │ │ ├── relations.php │ │ │ ├── story.php │ │ │ ├── timelines.php │ │ │ └── transform.php │ │ ├── entities.php │ │ ├── errors.php │ │ ├── events.php │ │ ├── export.php │ │ ├── families/ │ │ │ └── trees.php │ │ ├── families.php │ │ ├── faq.php │ │ ├── fields.php │ │ ├── filters.php │ │ ├── footer.php │ │ ├── front/ │ │ │ ├── community-votes.php │ │ │ ├── hall-of-fame.php │ │ │ ├── kb.php │ │ │ └── newsletter.php │ │ ├── front.php │ │ ├── gallery.php │ │ ├── general.php │ │ ├── genres.php │ │ ├── header.php │ │ ├── helpers.php │ │ ├── history.php │ │ ├── items.php │ │ ├── journals.php │ │ ├── languages.php │ │ ├── locations.php │ │ ├── maps/ │ │ │ ├── explore.php │ │ │ ├── groups.php │ │ │ ├── layers.php │ │ │ └── markers.php │ │ ├── maps.php │ │ ├── misc.php │ │ ├── notes.php │ │ ├── notifications.php │ │ ├── onboarding/ │ │ │ └── tags.php │ │ ├── organisations.php │ │ ├── pagination.php │ │ ├── partials.php │ │ ├── passwords.php │ │ ├── patreon.php │ │ ├── permissions.php │ │ ├── pins.php │ │ ├── playstyles.php │ │ ├── post_layouts.php │ │ ├── posts.php │ │ ├── presets.php │ │ ├── profiles.php │ │ ├── quests.php │ │ ├── races.php │ │ ├── redirects.php │ │ ├── releases.php │ │ ├── rpg_systems.php │ │ ├── search/ │ │ │ └── fulltext.php │ │ ├── search.php │ │ ├── seo.php │ │ ├── settings/ │ │ │ ├── account.php │ │ │ ├── appearance.php │ │ │ ├── boosters.php │ │ │ └── premium.php │ │ ├── settings.php │ │ ├── sidebar.php │ │ ├── starter.php │ │ ├── subscription.php │ │ ├── subscriptions/ │ │ │ ├── promos.php │ │ │ └── renew.php │ │ ├── subscriptions.php │ │ ├── tags.php │ │ ├── teams.php │ │ ├── tiers.php │ │ ├── timelines/ │ │ │ ├── elements.php │ │ │ └── eras.php │ │ ├── timelines.php │ │ ├── tiptap.php │ │ ├── users/ │ │ │ └── profile.php │ │ ├── validation.php │ │ └── visibilities.php │ └── vendor/ │ ├── backup/ │ │ ├── ar/ │ │ │ └── notifications.php │ │ ├── ca/ │ │ │ └── notifications.php │ │ ├── cs/ │ │ │ └── notifications.php │ │ ├── da/ │ │ │ └── notifications.php │ │ ├── de/ │ │ │ └── notifications.php │ │ ├── en/ │ │ │ └── notifications.php │ │ ├── es/ │ │ │ └── notifications.php │ │ ├── fa/ │ │ │ └── notifications.php │ │ ├── fi/ │ │ │ └── notifications.php │ │ ├── fr/ │ │ │ └── notifications.php │ │ ├── gl/ │ │ │ └── notifications.php │ │ ├── hi/ │ │ │ └── notifications.php │ │ ├── hr/ │ │ │ └── notifications.php │ │ ├── id/ │ │ │ └── notifications.php │ │ ├── it/ │ │ │ └── notifications.php │ │ ├── ja/ │ │ │ └── notifications.php │ │ ├── nl/ │ │ │ └── notifications.php │ │ ├── no/ │ │ │ └── notifications.php │ │ ├── pl/ │ │ │ └── notifications.php │ │ ├── pt/ │ │ │ └── notifications.php │ │ ├── pt-BR/ │ │ │ └── notifications.php │ │ ├── ro/ │ │ │ └── notifications.php │ │ ├── ru/ │ │ │ └── notifications.php │ │ ├── sk/ │ │ │ └── notifications.php │ │ ├── tr/ │ │ │ └── notifications.php │ │ ├── uk/ │ │ │ └── notifications.php │ │ ├── zh-CN/ │ │ │ └── notifications.php │ │ └── zh-TW/ │ │ └── notifications.php │ └── dnd5emonster/ │ ├── ca/ │ │ └── template.php │ ├── cs/ │ │ └── template.php │ ├── de/ │ │ └── template.php │ ├── en/ │ │ └── template.php │ ├── es/ │ │ └── template.php │ ├── fr/ │ │ └── template.php │ ├── gl/ │ │ └── template.php │ ├── hu/ │ │ └── template.php │ ├── it/ │ │ └── template.php │ ├── pl/ │ │ └── template.php │ ├── pt-BR/ │ │ └── template.php │ ├── ru/ │ │ └── template.php │ └── sk/ │ └── template.php ├── larastan.php ├── package.json ├── phpcs.xml ├── phpmd_ruleset.xml ├── phpstan.neon ├── phpunit.xml ├── pint.json ├── public/ │ ├── .well-known/ │ │ └── security.txt │ ├── build/ │ │ ├── assets/ │ │ │ ├── Browser.vue_vue_type_script_setup_true_lang-DjY0tfEc.js │ │ │ ├── GalleryDialog-B3Id-RLo.js │ │ │ ├── GalleryDialog-SGgOgWve.css │ │ │ ├── SourceEditor-BKF0zshA.css │ │ │ ├── SourceEditor-CaEGRaAo.js │ │ │ ├── Tiptap-B5LGKvdR.css │ │ │ ├── Tiptap-Bd5ZGSft.js │ │ │ ├── _commonjsHelpers-Cpj98o6Y.js │ │ │ ├── _plugin-vue_export-helper-DlAUqK2U.js │ │ │ ├── abilities-_1tR-hmN.js │ │ │ ├── app-7Sr4fLAQ.css │ │ │ ├── app-B8BXQiap.js │ │ │ ├── app-CcekkIPy.css │ │ │ ├── attributes-BgyqIZFe.js │ │ │ ├── attributes-manager-Of4dtl9o.js │ │ │ ├── auth-Bh9mDens.css │ │ │ ├── auth-DX8SCiWG.js │ │ │ ├── billing-CAEv3gKN.js │ │ │ ├── calendar-9Uf0XYJF.js │ │ │ ├── character-BYzNtBCs.js │ │ │ ├── coloris-DsKOFKq5.js │ │ │ ├── colours-Dh8441-n.js │ │ │ ├── conversation-hoDojmhJ.js │ │ │ ├── cookieconsent-DM0O5r9k.js │ │ │ ├── cytoscape-cose-bilkent-DRrwnnKV.js │ │ │ ├── cytoscape-panzoom-Del_Rz3u.js │ │ │ ├── cytoscape.esm-BJ4qIETX.js │ │ │ ├── dark-Pb_2oqsz.css │ │ │ ├── dashboard-CsClHCyP.css │ │ │ ├── dashboard-DzoSMhdl.js │ │ │ ├── dialog-DkrH_pRQ.js │ │ │ ├── explore-PT_Dl9lc.js │ │ │ ├── family-tree-vue-BhAkMdmq.js │ │ │ ├── front-BDaha8uH.js │ │ │ ├── front-CZW4xdyL.css │ │ │ ├── gallery-DqcxhMJm.js │ │ │ ├── history-Bum1jYKg.js │ │ │ ├── html2canvas.esm-DXEQVQnt.js │ │ │ ├── import-CRZICNET.js │ │ │ ├── index-Bajpoqb9.js │ │ │ ├── index-D5GkNzM3.js │ │ │ ├── index.es-MeewMCO3.js │ │ │ ├── index.esm-DvuRQLEU.js │ │ │ ├── jspdf.es.min-DI22nqbU.js │ │ │ ├── map-v3-_JE1ac-e.js │ │ │ ├── maps-7UR0tBUB.css │ │ │ ├── midnight-DjmKO7Sj.css │ │ │ ├── preload-helper-I4rgV-VL.js │ │ │ ├── print-B43HD77n.css │ │ │ ├── profile-D4XIj8wj.js │ │ │ ├── purify.es-21m173o_.js │ │ │ ├── recovery-DDbs8NlV.js │ │ │ ├── relations-BhqWrOkS.css │ │ │ ├── relations-DFy0TfK7.js │ │ │ ├── settings-HvUxJh5V.js │ │ │ ├── sortable.esm-DdTU3J9A.js │ │ │ ├── story-CrmLJDK_.js │ │ │ ├── subscription-BGjwpB-C.css │ │ │ ├── subscription-CbUaAMac.js │ │ │ ├── summernote-DpnuH_QU.js │ │ │ ├── theme-builder-Cvbh2cpO.js │ │ │ ├── tinymce-C7wRrakS.css │ │ │ ├── tippy.esm-CnBRltuW.js │ │ │ ├── tree-Dna4NG6v.css │ │ │ ├── v-click-outside.umd-Cl-Y_A58.js │ │ │ ├── vendor-DEuctmxo.css │ │ │ ├── vendor-final--o6gRmZ3.js │ │ │ ├── vendor-tiptap-D5xFoo7B.js │ │ │ ├── vue-tippy.esm-browser-B_r0Ygiv.js │ │ │ ├── web-C9wt4HvN.css │ │ │ ├── web-CwwBh20P.js │ │ │ └── whiteboards-CYwbHh4e.js │ │ └── manifest.json │ ├── css/ │ │ ├── bootstrap-summernote.css │ │ ├── bootstrap.css │ │ ├── bootstrap.css.br │ │ └── bootstrap.min.css.br │ ├── fonts/ │ │ └── roboto/ │ │ ├── OFL.txt │ │ └── README.txt │ ├── images/ │ │ └── favicon/ │ │ └── site.webmanifest │ ├── index.php │ ├── js/ │ │ ├── vendor.js │ │ ├── vendor.js.LICENSE.txt │ │ └── vendor.js.br │ ├── main.js │ ├── mix-manifest.json │ ├── privacypolicy.html │ └── vendor/ │ ├── binarytorch/ │ │ └── larecipe/ │ │ └── assets/ │ │ ├── css/ │ │ │ ├── app.css │ │ │ ├── font-awesome-v4-shims.css │ │ │ └── font-awesome.css │ │ └── js/ │ │ └── app.js │ ├── bootstrap/ │ │ ├── css/ │ │ │ ├── bootstrap-grid.css │ │ │ ├── bootstrap-reboot.css │ │ │ └── bootstrap.css │ │ └── js/ │ │ ├── bootstrap.bundle.js │ │ └── bootstrap.js │ ├── bootstrap-switch/ │ │ ├── css/ │ │ │ ├── bootstrap2/ │ │ │ │ └── bootstrap-switch.css │ │ │ └── bootstrap3/ │ │ │ └── bootstrap-switch.css │ │ └── js/ │ │ └── bootstrap-switch.js │ ├── codemirror/ │ │ ├── addon/ │ │ │ ├── comment/ │ │ │ │ ├── comment.js │ │ │ │ └── continuecomment.js │ │ │ ├── dialog/ │ │ │ │ ├── dialog.css │ │ │ │ └── dialog.js │ │ │ ├── display/ │ │ │ │ ├── autorefresh.js │ │ │ │ ├── fullscreen.css │ │ │ │ ├── fullscreen.js │ │ │ │ ├── panel.js │ │ │ │ ├── placeholder.js │ │ │ │ └── rulers.js │ │ │ ├── edit/ │ │ │ │ ├── closebrackets.js │ │ │ │ ├── closetag.js │ │ │ │ ├── continuelist.js │ │ │ │ ├── matchbrackets.js │ │ │ │ ├── matchtags.js │ │ │ │ └── trailingspace.js │ │ │ ├── fold/ │ │ │ │ ├── brace-fold.js │ │ │ │ ├── comment-fold.js │ │ │ │ ├── foldcode.js │ │ │ │ ├── foldgutter.css │ │ │ │ ├── foldgutter.js │ │ │ │ ├── indent-fold.js │ │ │ │ ├── markdown-fold.js │ │ │ │ └── xml-fold.js │ │ │ ├── hint/ │ │ │ │ ├── anyword-hint.js │ │ │ │ ├── css-hint.js │ │ │ │ ├── html-hint.js │ │ │ │ ├── javascript-hint.js │ │ │ │ ├── show-hint.css │ │ │ │ ├── show-hint.js │ │ │ │ ├── sql-hint.js │ │ │ │ └── xml-hint.js │ │ │ ├── lint/ │ │ │ │ ├── coffeescript-lint.js │ │ │ │ ├── css-lint.js │ │ │ │ ├── html-lint.js │ │ │ │ ├── javascript-lint.js │ │ │ │ ├── json-lint.js │ │ │ │ ├── lint.css │ │ │ │ ├── lint.js │ │ │ │ └── yaml-lint.js │ │ │ ├── merge/ │ │ │ │ ├── merge.css │ │ │ │ └── merge.js │ │ │ ├── mode/ │ │ │ │ ├── loadmode.js │ │ │ │ ├── multiplex.js │ │ │ │ ├── multiplex_test.js │ │ │ │ ├── overlay.js │ │ │ │ └── simple.js │ │ │ ├── runmode/ │ │ │ │ ├── colorize.js │ │ │ │ ├── runmode-standalone.js │ │ │ │ ├── runmode.js │ │ │ │ └── runmode.node.js │ │ │ ├── scroll/ │ │ │ │ ├── annotatescrollbar.js │ │ │ │ ├── scrollpastend.js │ │ │ │ ├── simplescrollbars.css │ │ │ │ └── simplescrollbars.js │ │ │ ├── search/ │ │ │ │ ├── jump-to-line.js │ │ │ │ ├── match-highlighter.js │ │ │ │ ├── matchesonscrollbar.css │ │ │ │ ├── matchesonscrollbar.js │ │ │ │ ├── search.js │ │ │ │ └── searchcursor.js │ │ │ ├── selection/ │ │ │ │ ├── active-line.js │ │ │ │ ├── mark-selection.js │ │ │ │ └── selection-pointer.js │ │ │ ├── tern/ │ │ │ │ ├── tern.css │ │ │ │ ├── tern.js │ │ │ │ └── worker.js │ │ │ └── wrap/ │ │ │ └── hardwrap.js │ │ ├── lib/ │ │ │ ├── codemirror.css │ │ │ └── codemirror.js │ │ ├── mode/ │ │ │ ├── css/ │ │ │ │ ├── css.js │ │ │ │ ├── gss.html │ │ │ │ ├── gss_test.js │ │ │ │ ├── index.html │ │ │ │ ├── less.html │ │ │ │ ├── less_test.js │ │ │ │ ├── scss.html │ │ │ │ ├── scss_test.js │ │ │ │ └── test.js │ │ │ ├── htmlmixed/ │ │ │ │ ├── htmlmixed.js │ │ │ │ └── index.html │ │ │ └── xml/ │ │ │ ├── index.html │ │ │ ├── test.js │ │ │ └── xml.js │ │ └── theme/ │ │ ├── 3024-day.css │ │ ├── 3024-night.css │ │ ├── abbott.css │ │ ├── abcdef.css │ │ ├── ambiance-mobile.css │ │ ├── ambiance.css │ │ ├── ayu-dark.css │ │ ├── ayu-mirage.css │ │ ├── base16-dark.css │ │ ├── base16-light.css │ │ ├── bespin.css │ │ ├── blackboard.css │ │ ├── cobalt.css │ │ ├── colorforth.css │ │ ├── darcula.css │ │ ├── dracula.css │ │ ├── duotone-dark.css │ │ ├── duotone-light.css │ │ ├── eclipse.css │ │ ├── elegant.css │ │ ├── erlang-dark.css │ │ ├── gruvbox-dark.css │ │ ├── hopscotch.css │ │ ├── icecoder.css │ │ ├── idea.css │ │ ├── isotope.css │ │ ├── juejin.css │ │ ├── lesser-dark.css │ │ ├── liquibyte.css │ │ ├── lucario.css │ │ ├── material-darker.css │ │ ├── material-ocean.css │ │ ├── material-palenight.css │ │ ├── material.css │ │ ├── mbo.css │ │ ├── mdn-like.css │ │ ├── midnight.css │ │ ├── monokai.css │ │ ├── moxer.css │ │ ├── neat.css │ │ ├── neo.css │ │ ├── night.css │ │ ├── nord.css │ │ ├── oceanic-next.css │ │ ├── panda-syntax.css │ │ ├── paraiso-dark.css │ │ ├── paraiso-light.css │ │ ├── pastel-on-dark.css │ │ ├── railscasts.css │ │ ├── rubyblue.css │ │ ├── seti.css │ │ ├── shadowfox.css │ │ ├── solarized.css │ │ ├── ssms.css │ │ ├── the-matrix.css │ │ ├── tomorrow-night-bright.css │ │ ├── tomorrow-night-eighties.css │ │ ├── ttcn.css │ │ ├── twilight.css │ │ ├── vibrant-ink.css │ │ ├── xq-dark.css │ │ ├── xq-light.css │ │ ├── yeti.css │ │ ├── yonce.css │ │ └── zenburn.css │ ├── dnd5emonster/ │ │ ├── css/ │ │ │ └── template.css │ │ └── js/ │ │ └── template.js │ ├── fontawesome/ │ │ └── 6.0.0/ │ │ ├── LICENSE.txt │ │ ├── css/ │ │ │ ├── all.css │ │ │ ├── brands.css │ │ │ ├── fontawesome.css │ │ │ ├── regular.css │ │ │ ├── solid.css │ │ │ ├── svg-with-js.css │ │ │ ├── v4-font-face.css │ │ │ ├── v4-shims.css │ │ │ └── v5-font-face.css │ │ ├── js/ │ │ │ ├── all.js │ │ │ ├── brands.js │ │ │ ├── conflict-detection.js │ │ │ ├── fontawesome.js │ │ │ ├── regular.js │ │ │ ├── solid.js │ │ │ └── v4-shims.js │ │ ├── less/ │ │ │ ├── _animated.less │ │ │ ├── _bordered-pulled.less │ │ │ ├── _core.less │ │ │ ├── _fixed-width.less │ │ │ ├── _icons.less │ │ │ ├── _list.less │ │ │ ├── _mixins.less │ │ │ ├── _rotated-flipped.less │ │ │ ├── _screen-reader.less │ │ │ ├── _shims.less │ │ │ ├── _sizing.less │ │ │ ├── _stacked.less │ │ │ ├── _variables.less │ │ │ ├── brands.less │ │ │ ├── fontawesome.less │ │ │ ├── regular.less │ │ │ ├── solid.less │ │ │ └── v4-shims.less │ │ └── scss/ │ │ ├── _animated.scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _functions.scss │ │ ├── _icons.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _rotated-flipped.scss │ │ ├── _screen-reader.scss │ │ ├── _shims.scss │ │ ├── _sizing.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ ├── brands.scss │ │ ├── fontawesome.scss │ │ ├── regular.scss │ │ ├── solid.scss │ │ └── v4-shims.scss │ ├── laravel-translation-manager/ │ │ ├── css/ │ │ │ ├── index.css │ │ │ └── translations.css │ │ └── js/ │ │ ├── index.js │ │ ├── inflection.js │ │ ├── rails.async.js │ │ ├── rails.js │ │ ├── translations.js │ │ ├── translations_page.js │ │ └── xregexp-all.js │ ├── leaflet/ │ │ ├── leaflet-polyline-measure.js │ │ ├── leaflet.editable.js │ │ ├── leaflet.layerstree.css │ │ ├── leaflet.layerstree.js │ │ ├── leaflet.markercluster.js │ │ ├── leaflet.markercluster.layersupport.js │ │ ├── leaflet.path.drag.js │ │ ├── leaflet.ruler-kanka.js │ │ ├── leaflet.ruler.js │ │ ├── leaflet.zoomcss.js │ │ └── leaflet.zoomdisplay.js │ ├── summernote/ │ │ ├── 0.9.1/ │ │ │ ├── font/ │ │ │ │ └── summernote.hash │ │ │ ├── lang/ │ │ │ │ ├── summernote-ar-AR.js │ │ │ │ ├── summernote-az-AZ.js │ │ │ │ ├── summernote-bg-BG.js │ │ │ │ ├── summernote-bn-BD.js │ │ │ │ ├── summernote-ca-ES.js │ │ │ │ ├── summernote-cs-CZ.js │ │ │ │ ├── summernote-da-DK.js │ │ │ │ ├── summernote-de-CH.js │ │ │ │ ├── summernote-de-DE.js │ │ │ │ ├── summernote-el-GR.js │ │ │ │ ├── summernote-en-US.js │ │ │ │ ├── summernote-es-ES.js │ │ │ │ ├── summernote-es-EU.js │ │ │ │ ├── summernote-fa-IR.js │ │ │ │ ├── summernote-fi-FI.js │ │ │ │ ├── summernote-fr-FR.js │ │ │ │ ├── summernote-gl-ES.js │ │ │ │ ├── summernote-he-IL.js │ │ │ │ ├── summernote-hr-HR.js │ │ │ │ ├── summernote-hu-HU.js │ │ │ │ ├── summernote-id-ID.js │ │ │ │ ├── summernote-it-IT.js │ │ │ │ ├── summernote-ja-JP.js │ │ │ │ ├── summernote-ko-KR.js │ │ │ │ ├── summernote-lt-LT.js │ │ │ │ ├── summernote-lt-LV.js │ │ │ │ ├── summernote-mn-MN.js │ │ │ │ ├── summernote-nb-NO.js │ │ │ │ ├── summernote-nl-NL.js │ │ │ │ ├── summernote-pl-PL.js │ │ │ │ ├── summernote-pt-BR.js │ │ │ │ ├── summernote-pt-PT.js │ │ │ │ ├── summernote-ro-RO.js │ │ │ │ ├── summernote-ru-RU.js │ │ │ │ ├── summernote-sk-SK.js │ │ │ │ ├── summernote-sl-SI.js │ │ │ │ ├── summernote-sr-RS-Latin.js │ │ │ │ ├── summernote-sr-RS.js │ │ │ │ ├── summernote-sv-SE.js │ │ │ │ ├── summernote-ta-IN.js │ │ │ │ ├── summernote-th-TH.js │ │ │ │ ├── summernote-tr-TR.js │ │ │ │ ├── summernote-uk-UA.js │ │ │ │ ├── summernote-uz-UZ.js │ │ │ │ ├── summernote-vi-VN.js │ │ │ │ ├── summernote-zh-CN.js │ │ │ │ └── summernote-zh-TW.js │ │ │ ├── plugin/ │ │ │ │ ├── databasic/ │ │ │ │ │ ├── summernote-ext-databasic.css │ │ │ │ │ └── summernote-ext-databasic.js │ │ │ │ ├── hello/ │ │ │ │ │ └── summernote-ext-hello.js │ │ │ │ └── specialchars/ │ │ │ │ └── summernote-ext-specialchars.js │ │ │ ├── summernote-bs4.css │ │ │ ├── summernote-bs4.js │ │ │ ├── summernote-bs5.css │ │ │ ├── summernote-bs5.js │ │ │ ├── summernote-lite.css │ │ │ ├── summernote-lite.js │ │ │ ├── summernote.css │ │ │ └── summernote.js │ │ ├── lang/ │ │ │ ├── summernote-ar-AR.js │ │ │ ├── summernote-ar-AR.min.js.LICENSE.txt │ │ │ ├── summernote-az-AZ.js │ │ │ ├── summernote-az-AZ.min.js.LICENSE.txt │ │ │ ├── summernote-bg-BG.js │ │ │ ├── summernote-bg-BG.min.js.LICENSE.txt │ │ │ ├── summernote-ca-ES.js │ │ │ ├── summernote-ca-ES.min.js.LICENSE.txt │ │ │ ├── summernote-cs-CZ.js │ │ │ ├── summernote-cs-CZ.min.js.LICENSE.txt │ │ │ ├── summernote-da-DK.js │ │ │ ├── summernote-da-DK.min.js.LICENSE.txt │ │ │ ├── summernote-de-DE.js │ │ │ ├── summernote-de-DE.min.js.LICENSE.txt │ │ │ ├── summernote-el-GR.js │ │ │ ├── summernote-el-GR.min.js.LICENSE.txt │ │ │ ├── summernote-es-ES.js │ │ │ ├── summernote-es-ES.min.js.LICENSE.txt │ │ │ ├── summernote-es-EU.js │ │ │ ├── summernote-es-EU.min.js.LICENSE.txt │ │ │ ├── summernote-fa-IR.js │ │ │ ├── summernote-fa-IR.min.js.LICENSE.txt │ │ │ ├── summernote-fi-FI.js │ │ │ ├── summernote-fi-FI.min.js.LICENSE.txt │ │ │ ├── summernote-fr-FR.js │ │ │ ├── summernote-fr-FR.min.js.LICENSE.txt │ │ │ ├── summernote-gl-ES.js │ │ │ ├── summernote-gl-ES.min.js.LICENSE.txt │ │ │ ├── summernote-he-IL.js │ │ │ ├── summernote-he-IL.min.js.LICENSE.txt │ │ │ ├── summernote-hr-HR.js │ │ │ ├── summernote-hr-HR.min.js.LICENSE.txt │ │ │ ├── summernote-hu-HU.js │ │ │ ├── summernote-hu-HU.min.js.LICENSE.txt │ │ │ ├── summernote-id-ID.js │ │ │ ├── summernote-id-ID.min.js.LICENSE.txt │ │ │ ├── summernote-it-IT.js │ │ │ ├── summernote-it-IT.min.js.LICENSE.txt │ │ │ ├── summernote-ja-JP.js │ │ │ ├── summernote-ja-JP.min.js.LICENSE.txt │ │ │ ├── summernote-ko-KR.js │ │ │ ├── summernote-ko-KR.min.js.LICENSE.txt │ │ │ ├── summernote-lt-LT.js │ │ │ ├── summernote-lt-LT.min.js.LICENSE.txt │ │ │ ├── summernote-lt-LV.js │ │ │ ├── summernote-lt-LV.min.js.LICENSE.txt │ │ │ ├── summernote-mn-MN.js │ │ │ ├── summernote-mn-MN.min.js.LICENSE.txt │ │ │ ├── summernote-nb-NO.js │ │ │ ├── summernote-nb-NO.min.js.LICENSE.txt │ │ │ ├── summernote-nl-NL.js │ │ │ ├── summernote-nl-NL.min.js.LICENSE.txt │ │ │ ├── summernote-pl-PL.js │ │ │ ├── summernote-pl-PL.min.js.LICENSE.txt │ │ │ ├── summernote-pt-BR.js │ │ │ ├── summernote-pt-BR.min.js.LICENSE.txt │ │ │ ├── summernote-pt-PT.js │ │ │ ├── summernote-pt-PT.min.js.LICENSE.txt │ │ │ ├── summernote-ro-RO.js │ │ │ ├── summernote-ro-RO.min.js.LICENSE.txt │ │ │ ├── summernote-ru-RU.js │ │ │ ├── summernote-ru-RU.min.js.LICENSE.txt │ │ │ ├── summernote-sk-SK.js │ │ │ ├── summernote-sk-SK.min.js.LICENSE.txt │ │ │ ├── summernote-sl-SI.js │ │ │ ├── summernote-sl-SI.min.js.LICENSE.txt │ │ │ ├── summernote-sr-RS-Latin.js │ │ │ ├── summernote-sr-RS-Latin.min.js.LICENSE.txt │ │ │ ├── summernote-sr-RS.js │ │ │ ├── summernote-sr-RS.min.js.LICENSE.txt │ │ │ ├── summernote-sv-SE.js │ │ │ ├── summernote-sv-SE.min.js.LICENSE.txt │ │ │ ├── summernote-ta-IN.js │ │ │ ├── summernote-ta-IN.min.js.LICENSE.txt │ │ │ ├── summernote-th-TH.js │ │ │ ├── summernote-th-TH.min.js.LICENSE.txt │ │ │ ├── summernote-tr-TR.js │ │ │ ├── summernote-tr-TR.min.js.LICENSE.txt │ │ │ ├── summernote-uk-UA.js │ │ │ ├── summernote-uk-UA.min.js.LICENSE.txt │ │ │ ├── summernote-uz-UZ.js │ │ │ ├── summernote-uz-UZ.min.js.LICENSE.txt │ │ │ ├── summernote-vi-VN.js │ │ │ ├── summernote-vi-VN.min.js.LICENSE.txt │ │ │ ├── summernote-zh-CN.js │ │ │ ├── summernote-zh-CN.min.js.LICENSE.txt │ │ │ ├── summernote-zh-TW.js │ │ │ └── summernote-zh-TW.min.js.LICENSE.txt │ │ ├── plugin/ │ │ │ ├── databasic/ │ │ │ │ ├── summernote-ext-databasic.css │ │ │ │ └── summernote-ext-databasic.js │ │ │ ├── embed/ │ │ │ │ └── summernote-embed-plugin.js │ │ │ ├── hello/ │ │ │ │ └── summernote-ext-hello.js │ │ │ ├── rtl/ │ │ │ │ └── summernote-ext-rtl.js │ │ │ ├── specialchars/ │ │ │ │ └── summernote-ext-specialchars.js │ │ │ ├── summernote-aroba-kanka/ │ │ │ │ └── summernote-aroba.js │ │ │ ├── summernote-image-attribute.js │ │ │ ├── summernote-table-ext.js │ │ │ ├── summernote-table-headers/ │ │ │ │ ├── Example/ │ │ │ │ │ └── example.html │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ └── summernote-table-headers.js │ │ │ ├── summernote-table-styles/ │ │ │ │ ├── Example/ │ │ │ │ │ └── example.html │ │ │ │ ├── LICENSE │ │ │ │ ├── README.md │ │ │ │ └── summernote-table-styles.js │ │ │ ├── summernote-toc-kanka/ │ │ │ │ └── summernote-toc.js │ │ │ └── summernote-toc.js │ │ ├── summernote-bs4.css │ │ ├── summernote-bs4.js │ │ ├── summernote-bs4.min.js.LICENSE.txt │ │ ├── summernote-lite.css │ │ ├── summernote-lite.js │ │ ├── summernote-lite.min.js.LICENSE.txt │ │ ├── summernote.css │ │ ├── summernote.js │ │ └── summernote.min.js.LICENSE.txt │ └── telescope/ │ ├── app-dark.css │ ├── app.css │ ├── app.js │ └── mix-manifest.json ├── resources/ │ ├── api-docs/ │ │ └── 1.0/ │ │ ├── abilities.md │ │ ├── archives.md │ │ ├── attribute-templates.md │ │ ├── bookmarks.md │ │ ├── calendar-weathers.md │ │ ├── calendars.md │ │ ├── campaigns/ │ │ │ ├── applications.md │ │ │ ├── categories.md │ │ │ ├── dashboard-widgets.md │ │ │ ├── default-thumbnails.md │ │ │ ├── images.md │ │ │ ├── members.md │ │ │ ├── modules.md │ │ │ ├── roles.md │ │ │ ├── statuses.md │ │ │ └── styles.md │ │ ├── campaigns.md │ │ ├── characters.md │ │ ├── conversations.md │ │ ├── creatures.md │ │ ├── dice-rolls.md │ │ ├── entities/ │ │ │ ├── attributes.md │ │ │ ├── connections.md │ │ │ ├── entity-abilities.md │ │ │ ├── entity-aliases.md │ │ │ ├── entity-assets.md │ │ │ ├── entity-files.md │ │ │ ├── entity-image.md │ │ │ ├── entity-inventory.md │ │ │ ├── entity-links.md │ │ │ ├── entity-mentions.md │ │ │ ├── entity-permissions.md │ │ │ ├── entity-tags.md │ │ │ ├── entity-types.md │ │ │ ├── posts.md │ │ │ ├── reminders.md │ │ │ └── templates.md │ │ ├── entities.md │ │ ├── events.md │ │ ├── families.md │ │ ├── index.md │ │ ├── items.md │ │ ├── journals.md │ │ ├── locations.md │ │ ├── map_groups.md │ │ ├── map_layers.md │ │ ├── map_markers.md │ │ ├── maps.md │ │ ├── misc/ │ │ │ ├── filters.md │ │ │ ├── last-sync.md │ │ │ ├── pagination.md │ │ │ ├── permissions-test.md │ │ │ └── visibilities.md │ │ ├── notes.md │ │ ├── organisation-members.md │ │ ├── organisations.md │ │ ├── overview.md │ │ ├── post-layout.md │ │ ├── profile.md │ │ ├── quests.md │ │ ├── races.md │ │ ├── relations.md │ │ ├── search.md │ │ ├── setup.md │ │ ├── tags.md │ │ ├── timeline-elements.md │ │ ├── timeline-eras.md │ │ ├── timelines.md │ │ └── troubleshooting.md │ ├── assets/ │ │ └── components/ │ │ └── rpg/ │ │ └── scss/ │ │ ├── .scss-lint.yml │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _spinning.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ └── rpg-awesome.scss │ ├── css/ │ │ ├── alerts.css │ │ ├── app.css │ │ ├── attributes/ │ │ │ └── attributes.css │ │ ├── auth.css │ │ ├── badge.css │ │ ├── buttons.css │ │ ├── calendar.css │ │ ├── campaign.css │ │ ├── code.css │ │ ├── dashboard.css │ │ ├── entity.css │ │ ├── families/ │ │ │ └── tree.css │ │ ├── freyja/ │ │ │ ├── freyja.css │ │ │ ├── header.css │ │ │ └── sidebar.css │ │ ├── front.css │ │ ├── html/ │ │ │ ├── dialog.css │ │ │ ├── dl.css │ │ │ ├── summary.css │ │ │ └── table.css │ │ ├── maps/ │ │ │ ├── leaflet.zoomdisplay.css │ │ │ └── maps.css │ │ ├── mobile.css │ │ ├── premium.css │ │ ├── print/ │ │ │ ├── general.css │ │ │ └── print.css │ │ ├── quest.css │ │ ├── quick-creator.css │ │ ├── range.css │ │ ├── relations.css │ │ ├── sortable.css │ │ ├── subscription.css │ │ ├── tabs.css │ │ ├── themes/ │ │ │ ├── base.css │ │ │ ├── colour.css │ │ │ ├── dark.css │ │ │ ├── light.css │ │ │ └── midnight.css │ │ ├── timeline.css │ │ ├── toast.css │ │ ├── toggle.css │ │ ├── tooltip.css │ │ ├── typography.css │ │ ├── vendor.css │ │ └── vendors/ │ │ ├── adminlte.css │ │ ├── editor.css │ │ └── tinymce.css │ ├── js/ │ │ ├── abilities.js │ │ ├── ads.js │ │ ├── animations.js │ │ ├── app.js │ │ ├── attributes-manager.js │ │ ├── attributes.js │ │ ├── auth.js │ │ ├── banner.js │ │ ├── billing.js │ │ ├── bookmarks.js │ │ ├── calendar.js │ │ ├── campaign.js │ │ ├── campaigns/ │ │ │ ├── import.js │ │ │ └── theme-builder.js │ │ ├── clipboard.js │ │ ├── components/ │ │ │ ├── Dropdown.vue │ │ │ ├── abilities/ │ │ │ │ ├── Abilities.vue │ │ │ │ ├── Ability.vue │ │ │ │ └── Parent.vue │ │ │ ├── attributes/ │ │ │ │ ├── Attribute.vue │ │ │ │ ├── Form.vue │ │ │ │ ├── Manager.vue │ │ │ │ └── MentionField.vue │ │ │ ├── connections/ │ │ │ │ └── Web.vue │ │ │ ├── conversation/ │ │ │ │ ├── Conversation.vue │ │ │ │ ├── Form.vue │ │ │ │ ├── Message.vue │ │ │ │ └── Messages.vue │ │ │ ├── delete-confirm.js │ │ │ ├── families/ │ │ │ │ ├── ChildrenLine.vue │ │ │ │ ├── FamilyChildren.vue │ │ │ │ ├── FamilyEntity.vue │ │ │ │ ├── FamilyNode.vue │ │ │ │ ├── FamilyParentChildrenLine.vue │ │ │ │ ├── FamilyRelation.vue │ │ │ │ ├── FamilyRelations.vue │ │ │ │ ├── FamilyTree.vue │ │ │ │ └── RelationLine.vue │ │ │ ├── fields/ │ │ │ │ ├── AliasPill.vue │ │ │ │ ├── EntityName.vue │ │ │ │ └── SimilarEntityAlert.vue │ │ │ ├── icons/ │ │ │ │ └── GridSvg.vue │ │ │ ├── layout/ │ │ │ │ ├── Campaign.vue │ │ │ │ ├── Lookup/ │ │ │ │ │ ├── EntityPreview.vue │ │ │ │ │ ├── LookupEntity.vue │ │ │ │ │ └── LookupPage.vue │ │ │ │ ├── NavSearch.vue │ │ │ │ ├── NavSwitcher.vue │ │ │ │ ├── NavToggler.vue │ │ │ │ ├── Notification.vue │ │ │ │ └── Release.vue │ │ │ ├── select2.js │ │ │ ├── subscription/ │ │ │ │ └── BillingManagement.vue │ │ │ └── whiteboards/ │ │ │ ├── Entity.vue │ │ │ ├── EntitySearch.vue │ │ │ ├── Reset.vue │ │ │ ├── Settings.vue │ │ │ └── Whiteboard.vue │ │ ├── composables/ │ │ │ └── useEntitySimilarity.js │ │ ├── connections/ │ │ │ └── web.js │ │ ├── conversation.js │ │ ├── cookieconsent.js │ │ ├── crud.js │ │ ├── dashboard.js │ │ ├── dashboards/ │ │ │ ├── onboarding/ │ │ │ │ ├── Onboarding.vue │ │ │ │ └── onboarding.js │ │ │ └── widgets/ │ │ │ └── getting-started/ │ │ │ └── GettingStarted.vue │ │ ├── datagrids.js │ │ ├── datagrids2.js │ │ ├── echo.js │ │ ├── editors/ │ │ │ ├── summernote.js │ │ │ └── tiptap/ │ │ │ ├── SourceEditor.vue │ │ │ ├── Tiptap.vue │ │ │ ├── bubblemenus/ │ │ │ │ ├── ColorPicker.vue │ │ │ │ ├── ImageBubbleMenu.vue │ │ │ │ ├── LinkBubbleMenu.vue │ │ │ │ ├── MentionBubbleMenu.vue │ │ │ │ ├── TableBubbleMenu.vue │ │ │ │ └── TextBubbleMenu.vue │ │ │ ├── extensions/ │ │ │ │ ├── CustomHeading.ts │ │ │ │ ├── CustomImage.ts │ │ │ │ ├── Details.ts │ │ │ │ ├── DetailsWrapper.vue │ │ │ │ ├── Div.ts │ │ │ │ ├── Iframe.ts │ │ │ │ ├── gallery/ │ │ │ │ │ ├── Gallery.ts │ │ │ │ │ └── GalleryDialog.vue │ │ │ │ ├── images/ │ │ │ │ │ ├── Image.ts │ │ │ │ │ └── Images.vue │ │ │ │ ├── mentions/ │ │ │ │ │ ├── Mention.ts │ │ │ │ │ ├── MentionList.vue │ │ │ │ │ ├── MentionParser.ts │ │ │ │ │ └── suggestion.ts │ │ │ │ ├── slashcommand/ │ │ │ │ │ ├── SlashCommand.ts │ │ │ │ │ ├── SlashCommandList.vue │ │ │ │ │ └── suggestion.ts │ │ │ │ └── table/ │ │ │ │ ├── CustomTableCell.ts │ │ │ │ ├── CustomTableHeader.ts │ │ │ │ └── TableWithControls.ts │ │ │ ├── index.js │ │ │ ├── toolbar/ │ │ │ │ └── Button.vue │ │ │ └── utils.ts │ │ ├── entities/ │ │ │ ├── EntityAdSlot.vue │ │ │ ├── EntityCard.vue │ │ │ ├── EntityGrid.vue │ │ │ ├── EntityListing.vue │ │ │ ├── EntityRow.vue │ │ │ ├── EntityTable.vue │ │ │ ├── composables/ │ │ │ │ ├── useBulkActions.ts │ │ │ │ ├── useColumns.ts │ │ │ │ ├── useEntityApi.ts │ │ │ │ ├── useLayout.ts │ │ │ │ ├── useLongPress.ts │ │ │ │ ├── useNesting.ts │ │ │ │ ├── useOrdering.ts │ │ │ │ └── usePerPage.ts │ │ │ └── explore.js │ │ ├── entities.js │ │ ├── events.js │ │ ├── family-tree-vue.js │ │ ├── forms/ │ │ │ ├── calendar-date.js │ │ │ ├── calendar.js │ │ │ ├── character.js │ │ │ └── entity-name.js │ │ ├── front.js │ │ ├── gallery/ │ │ │ ├── Browser.vue │ │ │ ├── File.vue │ │ │ ├── Gallery.vue │ │ │ ├── Preview.vue │ │ │ ├── Selection.vue │ │ │ ├── gallery.js │ │ │ └── selection.js │ │ ├── header.js │ │ ├── history.js │ │ ├── keep-alive.js │ │ ├── keyboard.js │ │ ├── location/ │ │ │ └── map-v3.js │ │ ├── maintenance.js │ │ ├── members.js │ │ ├── post.js │ │ ├── profile.js │ │ ├── quick-creator.js │ │ ├── recovery/ │ │ │ ├── Element.vue │ │ │ ├── Recovery.vue │ │ │ └── recovery.js │ │ ├── relations.js │ │ ├── settings.js │ │ ├── share.js │ │ ├── story.js │ │ ├── subscription.js │ │ ├── tags.js │ │ ├── timelines.js │ │ ├── toast.js │ │ ├── utility/ │ │ │ ├── CampaignShareModal.vue │ │ │ ├── EntityShareModal.vue │ │ │ ├── colour-picker.js │ │ │ ├── colours.ts │ │ │ ├── dialog.js │ │ │ ├── formError.js │ │ │ ├── sortable.js │ │ │ └── tippy.js │ │ ├── vendor-final.js │ │ ├── vendor.js │ │ ├── visibility-picker.js │ │ ├── webhooks.js │ │ └── whiteboards.js │ ├── vendor/ │ │ └── tinymce/ │ │ ├── langs/ │ │ │ ├── ca.js │ │ │ ├── de.js │ │ │ ├── es.js │ │ │ ├── fr.js │ │ │ ├── he.js │ │ │ ├── hr.js │ │ │ ├── hu.js │ │ │ ├── it.js │ │ │ ├── nl.js │ │ │ ├── pt-BR.js │ │ │ ├── readme.md │ │ │ ├── ru.js │ │ │ └── sk.js │ │ └── plugins/ │ │ └── mention/ │ │ ├── .gitignore │ │ ├── .jshintrc │ │ ├── .travis.yml │ │ ├── README.md │ │ ├── bower.json │ │ ├── css/ │ │ │ ├── autocomplete.css │ │ │ └── rte-content.css │ │ ├── examples/ │ │ │ └── commonjs/ │ │ │ ├── .gitignore │ │ │ ├── index.html │ │ │ ├── main.js │ │ │ ├── package.json │ │ │ └── webpack.config.js │ │ ├── gulpfile.js │ │ ├── mention/ │ │ │ └── plugin.js │ │ ├── package.json │ │ ├── plugin.js │ │ └── tests/ │ │ ├── test_mention.js │ │ └── test_runner.html │ └── views/ │ ├── abilities/ │ │ ├── abilities.blade.php │ │ ├── entities/ │ │ │ ├── _form.blade.php │ │ │ ├── actions/ │ │ │ │ └── delete.blade.php │ │ │ └── create.blade.php │ │ ├── entities.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── panels/ │ │ │ ├── abilities.blade.php │ │ │ └── entities.blade.php │ │ └── show.blade.php │ ├── account/ │ │ ├── billing/ │ │ │ └── information/ │ │ │ ├── _form.blade.php │ │ │ └── form.blade.php │ │ ├── email/ │ │ │ ├── _form.blade.php │ │ │ └── form.blade.php │ │ ├── password/ │ │ │ ├── _form.blade.php │ │ │ └── form.blade.php │ │ └── social/ │ │ └── form.blade.php │ ├── ads/ │ │ ├── anchor.blade.php │ │ ├── cta.blade.php │ │ ├── inline.blade.php │ │ ├── nitro/ │ │ │ └── styles.blade.php │ │ ├── siderail_left.blade.php │ │ ├── siderail_right.blade.php │ │ ├── table.blade.php │ │ ├── top.blade.php │ │ └── video.blade.php │ ├── attribute_templates/ │ │ ├── datagrid.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ └── show.blade.php │ ├── auth/ │ │ ├── login.blade.php │ │ ├── otp.blade.php │ │ ├── passwords/ │ │ │ ├── confirm.blade.php │ │ │ ├── email.blade.php │ │ │ └── reset.blade.php │ │ └── register.blade.php │ ├── billing/ │ │ ├── history.blade.php │ │ └── payment-method.blade.php │ ├── bookmarks/ │ │ ├── datagrid.blade.php │ │ ├── forms/ │ │ │ ├── _dashboard.blade.php │ │ │ ├── _entity.blade.php │ │ │ ├── _entry.blade.php │ │ │ ├── _random.blade.php │ │ │ ├── _type.blade.php │ │ │ ├── create.blade.php │ │ │ └── edit.blade.php │ │ └── reorder.blade.php │ ├── calendars/ │ │ ├── _calendar.blade.php │ │ ├── _day.blade.php │ │ ├── _intercalary.blade.php │ │ ├── _week.blade.php │ │ ├── events.blade.php │ │ ├── form/ │ │ │ ├── _calendar.blade.php │ │ │ ├── _entry.blade.php │ │ │ ├── _months.blade.php │ │ │ ├── _moons.blade.php │ │ │ ├── _panes.blade.php │ │ │ ├── _seasons.blade.php │ │ │ ├── _tabs.blade.php │ │ │ └── _weeks.blade.php │ │ ├── panels/ │ │ │ └── events.blade.php │ │ ├── reminders/ │ │ │ ├── _entity_form.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── _subform.blade.php │ │ │ ├── create.blade.php │ │ │ ├── create_from_entity.blade.php │ │ │ └── edit.blade.php │ │ ├── show.blade.php │ │ ├── weather/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ └── edit.blade.php │ │ └── year-switcher/ │ │ └── _footer.blade.php │ ├── campaigns/ │ │ ├── _overview.blade.php │ │ ├── achievements/ │ │ │ ├── _finished.blade.php │ │ │ ├── _locked.blade.php │ │ │ └── index.blade.php │ │ ├── applications/ │ │ │ ├── _apply.blade.php │ │ │ ├── _list.blade.php │ │ │ ├── _requirements.blade.php │ │ │ ├── _toggle.blade.php │ │ │ ├── _toggle_form.blade.php │ │ │ ├── _view.blade.php │ │ │ ├── apply.blade.php │ │ │ ├── dashboard_widget.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── index.blade.php │ │ │ ├── setup.blade.php │ │ │ ├── show.blade.php │ │ │ └── view.blade.php │ │ ├── default-images/ │ │ │ ├── _form.blade.php │ │ │ ├── _thumbnail.blade.php │ │ │ ├── create.blade.php │ │ │ └── index.blade.php │ │ ├── defaults/ │ │ │ └── index.blade.php │ │ ├── delete.blade.php │ │ ├── entity-types/ │ │ │ ├── _actions.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── box/ │ │ │ │ ├── custom.blade.php │ │ │ │ ├── default.blade.php │ │ │ │ └── new.blade.php │ │ │ ├── confirm.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── max-reached.blade.php │ │ │ └── not-premium.blade.php │ │ ├── export.blade.php │ │ ├── forms/ │ │ │ ├── _tabs.blade.php │ │ │ ├── _visibility.blade.php │ │ │ ├── create.blade.php │ │ │ ├── dashboard-header/ │ │ │ │ ├── _form.blade.php │ │ │ │ └── edit.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── modals/ │ │ │ │ ├── _image.blade.php │ │ │ │ ├── _visibility-form.blade.php │ │ │ │ ├── image.blade.php │ │ │ │ └── visibility.blade.php │ │ │ ├── panes/ │ │ │ │ ├── _discovery.blade.php │ │ │ │ ├── dashboard.blade.php │ │ │ │ ├── entry.blade.php │ │ │ │ ├── public.blade.php │ │ │ │ └── ui.blade.php │ │ │ └── standard.blade.php │ │ ├── import/ │ │ │ ├── index.blade.php │ │ │ └── process-csv.blade.php │ │ ├── invites/ │ │ │ ├── _form.blade.php │ │ │ └── create.blade.php │ │ ├── leave/ │ │ │ ├── _actions.blade.php │ │ │ └── _body.blade.php │ │ ├── leave.blade.php │ │ ├── logs/ │ │ │ ├── _list.blade.php │ │ │ └── index.blade.php │ │ ├── markdown.blade.php │ │ ├── members/ │ │ │ ├── _form.blade.php │ │ │ ├── _invites.blade.php │ │ │ ├── delete.blade.php │ │ │ ├── index.blade.php │ │ │ └── update.blade.php │ │ ├── modules/ │ │ │ ├── _custom.blade.php │ │ │ ├── _default.blade.php │ │ │ ├── _features.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── box.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── index.blade.php │ │ │ └── not-premium.blade.php │ │ ├── plugins/ │ │ │ ├── confirm.blade.php │ │ │ ├── index.blade.php │ │ │ └── info.blade.php │ │ ├── plugins.blade.php │ │ ├── recovery/ │ │ │ └── index.blade.php │ │ ├── roles/ │ │ │ ├── _form.blade.php │ │ │ ├── _members.blade.php │ │ │ ├── _pretty.blade.php │ │ │ ├── _public.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── index.blade.php │ │ │ ├── rows/ │ │ │ │ └── permissions.blade.php │ │ │ ├── show.blade.php │ │ │ └── users/ │ │ │ ├── _form.blade.php │ │ │ └── create.blade.php │ │ ├── roles.blade.php │ │ ├── share/ │ │ │ └── setup.blade.php │ │ ├── show.blade.php │ │ ├── sidebar/ │ │ │ └── index.blade.php │ │ ├── stats/ │ │ │ └── index.blade.php │ │ ├── styles/ │ │ │ ├── _form-footer.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── _preview.blade.php │ │ │ ├── _reorder.blade.php │ │ │ ├── builder.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── index.blade.php │ │ │ └── theme.blade.php │ │ ├── webhooks/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ └── index.blade.php │ │ └── webhooks.blade.php │ ├── characters/ │ │ ├── families/ │ │ │ ├── _reorder.blade.php │ │ │ └── reorder.blade.php │ │ ├── form/ │ │ │ ├── _entry.blade.php │ │ │ ├── _organisations.blade.php │ │ │ ├── _panes.blade.php │ │ │ ├── _tabs.blade.php │ │ │ └── _traits.blade.php │ │ ├── organisations/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ └── edit.blade.php │ │ ├── organisations.blade.php │ │ ├── panels/ │ │ │ ├── _appearance.blade.php │ │ │ ├── _buttons.blade.php │ │ │ ├── _personality.blade.php │ │ │ └── organisations.blade.php │ │ └── races/ │ │ ├── _reorder.blade.php │ │ └── reorder.blade.php │ ├── components/ │ │ ├── ad.blade.php │ │ ├── ads/ │ │ │ └── native.blade.php │ │ ├── alert.blade.php │ │ ├── badge.blade.php │ │ ├── box/ │ │ │ └── footer.blade.php │ │ ├── box.blade.php │ │ ├── button/ │ │ │ └── delete-confirm.blade.php │ │ ├── buttons/ │ │ │ ├── confirm-delete.blade.php │ │ │ └── confirm.blade.php │ │ ├── campaigns/ │ │ │ └── module-box.blade.php │ │ ├── character-sheet.blade.php │ │ ├── checkbox.blade.php │ │ ├── dashboards/ │ │ │ └── widgets/ │ │ │ └── selection.blade.php │ │ ├── date.blade.php │ │ ├── dialog/ │ │ │ ├── article.blade.php │ │ │ ├── close.blade.php │ │ │ ├── footer.blade.php │ │ │ └── header.blade.php │ │ ├── dialog.blade.php │ │ ├── dropdowns/ │ │ │ ├── divider.blade.php │ │ │ ├── item.blade.php │ │ │ └── section.blade.php │ │ ├── entities/ │ │ │ ├── submenu.blade.php │ │ │ └── thumbnail.blade.php │ │ ├── entity-link.blade.php │ │ ├── faq-element.blade.php │ │ ├── form/ │ │ │ ├── abilities.blade.php │ │ │ ├── characters.blade.php │ │ │ ├── entity_types.blade.php │ │ │ ├── families.blade.php │ │ │ ├── family_members.blade.php │ │ │ ├── genres.blade.php │ │ │ ├── locations.blade.php │ │ │ ├── members.blade.php │ │ │ ├── organisations.blade.php │ │ │ ├── playstyles.blade.php │ │ │ ├── races.blade.php │ │ │ ├── role.blade.php │ │ │ └── user.blade.php │ │ ├── form.blade.php │ │ ├── forms/ │ │ │ ├── field.blade.php │ │ │ ├── foreign.blade.php │ │ │ ├── select.blade.php │ │ │ ├── tags.blade.php │ │ │ ├── visibility-picker-field.blade.php │ │ │ └── visibility-picker.blade.php │ │ ├── grid.blade.php │ │ ├── helper.blade.php │ │ ├── helpers/ │ │ │ └── tooltip.blade.php │ │ ├── hero.blade.php │ │ ├── icon.blade.php │ │ ├── info-box.blade.php │ │ ├── learn-more.blade.php │ │ ├── lists/ │ │ │ └── empty-state.blade.php │ │ ├── menu/ │ │ │ └── element.blade.php │ │ ├── menu.blade.php │ │ ├── posts/ │ │ │ └── tags.blade.php │ │ ├── premium-cta-footer.blade.php │ │ ├── premium-cta.blade.php │ │ ├── premium-dialog.blade.php │ │ ├── profile/ │ │ │ └── social-link.blade.php │ │ ├── reorder/ │ │ │ └── child.blade.php │ │ ├── sidebar/ │ │ │ ├── account.blade.php │ │ │ ├── campaign.blade.php │ │ │ ├── element-link.blade.php │ │ │ ├── element-text.blade.php │ │ │ ├── profile.blade.php │ │ │ ├── section.blade.php │ │ │ └── settings.blade.php │ │ ├── since.blade.php │ │ ├── tab/ │ │ │ ├── nav.blade.php │ │ │ └── tab.blade.php │ │ ├── tags/ │ │ │ └── bubble.blade.php │ │ ├── toggles/ │ │ │ └── filter-button.blade.php │ │ ├── tutorial.blade.php │ │ ├── users/ │ │ │ ├── avatar.blade.php │ │ │ └── link.blade.php │ │ ├── widgets/ │ │ │ ├── filtered-link.blade.php │ │ │ ├── forms/ │ │ │ │ └── advanced.blade.php │ │ │ └── previews/ │ │ │ ├── body.blade.php │ │ │ └── head.blade.php │ │ └── word-count.blade.php │ ├── confirms/ │ │ ├── delete.blade.php │ │ └── editing.blade.php │ ├── connections/ │ │ ├── web-premium.blade.php │ │ └── web.blade.php │ ├── conversations/ │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── participants/ │ │ │ ├── _actions.blade.php │ │ │ └── _form.blade.php │ │ ├── participants.blade.php │ │ └── show.blade.php │ ├── creatures/ │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── panels/ │ │ │ └── creatures.blade.php │ │ └── show.blade.php │ ├── cruds/ │ │ ├── _table.blade.php │ │ ├── clear-filters.blade.php │ │ ├── datagrids/ │ │ │ ├── _grid.blade.php │ │ │ ├── _row-actions.blade.php │ │ │ ├── bulks/ │ │ │ │ ├── actions.blade.php │ │ │ │ ├── modals/ │ │ │ │ │ ├── _batch-footer.blade.php │ │ │ │ │ ├── _batch.blade.php │ │ │ │ │ ├── _copy_campaign.blade.php │ │ │ │ │ ├── _permissions.blade.php │ │ │ │ │ ├── _templates.blade.php │ │ │ │ │ ├── _transform.blade.php │ │ │ │ │ ├── ajax.blade.php │ │ │ │ │ ├── batch.blade.php │ │ │ │ │ ├── delete/ │ │ │ │ │ │ ├── _footer.blade.php │ │ │ │ │ │ ├── _form.blade.php │ │ │ │ │ │ ├── _mirrored.blade.php │ │ │ │ │ │ ├── delete.blade.php │ │ │ │ │ │ └── relation.blade.php │ │ │ │ │ └── forms/ │ │ │ │ │ ├── _batch.blade.php │ │ │ │ │ ├── _copy.blade.php │ │ │ │ │ ├── _permissions.blade.php │ │ │ │ │ ├── _templates.blade.php │ │ │ │ │ └── _transform.blade.php │ │ │ │ └── modals.blade.php │ │ │ ├── explore.blade.php │ │ │ ├── filters/ │ │ │ │ ├── _archived.blade.php │ │ │ │ ├── _array.blade.php │ │ │ │ ├── _attributes.blade.php │ │ │ │ ├── _choice.blade.php │ │ │ │ ├── _connection.blade.php │ │ │ │ ├── _date-range.blade.php │ │ │ │ ├── _date.blade.php │ │ │ │ ├── _element-role.blade.php │ │ │ │ ├── _select.blade.php │ │ │ │ ├── _sex.blade.php │ │ │ │ ├── _status.blade.php │ │ │ │ ├── _tag.blade.php │ │ │ │ ├── _template.blade.php │ │ │ │ ├── _type.blade.php │ │ │ │ └── datagrid-filter.blade.php │ │ │ └── sorters/ │ │ │ └── simple-sorter.blade.php │ │ ├── fields/ │ │ │ ├── _image_preview.blade.php │ │ │ ├── ability.blade.php │ │ │ ├── age.blade.php │ │ │ ├── attitude.blade.php │ │ │ ├── attribute_template.blade.php │ │ │ ├── author.blade.php │ │ │ ├── auto_applied_choice.blade.php │ │ │ ├── calendar.blade.php │ │ │ ├── character.blade.php │ │ │ ├── characters.blade.php │ │ │ ├── charges.blade.php │ │ │ ├── closed.blade.php │ │ │ ├── colour.blade.php │ │ │ ├── colour_picker.blade.php │ │ │ ├── completed_choice.blade.php │ │ │ ├── creators.blade.php │ │ │ ├── creature.blade.php │ │ │ ├── date.blade.php │ │ │ ├── dead_choice.blade.php │ │ │ ├── defunct_choice.blade.php │ │ │ ├── destroyed.blade.php │ │ │ ├── destroyed_choice.blade.php │ │ │ ├── draggable_choice.blade.php │ │ │ ├── enabled_choice.blade.php │ │ │ ├── entity-name.blade.php │ │ │ ├── entity.blade.php │ │ │ ├── entity_header.blade.php │ │ │ ├── entity_image.blade.php │ │ │ ├── entity_link.blade.php │ │ │ ├── entity_locations.blade.php │ │ │ ├── entity_type.blade.php │ │ │ ├── entry.blade.php │ │ │ ├── entry2.blade.php │ │ │ ├── event.blade.php │ │ │ ├── extinct_choice.blade.php │ │ │ ├── families.blade.php │ │ │ ├── family.blade.php │ │ │ ├── format.blade.php │ │ │ ├── helpers/ │ │ │ │ ├── boosted.blade.php │ │ │ │ ├── private.blade.php │ │ │ │ ├── share.blade.php │ │ │ │ └── superboosted.blade.php │ │ │ ├── hide_choice.blade.php │ │ │ ├── icon.blade.php │ │ │ ├── image-gallery.blade.php │ │ │ ├── image-old.blade.php │ │ │ ├── image.blade.php │ │ │ ├── instigator.blade.php │ │ │ ├── is_active.blade.php │ │ │ ├── is_pinned.blade.php │ │ │ ├── item.blade.php │ │ │ ├── journal.blade.php │ │ │ ├── location.blade.php │ │ │ ├── locations.blade.php │ │ │ ├── map.blade.php │ │ │ ├── name.blade.php │ │ │ ├── note.blade.php │ │ │ ├── organisation.blade.php │ │ │ ├── organisations.blade.php │ │ │ ├── owner.blade.php │ │ │ ├── parent.blade.php │ │ │ ├── parent_attribute_template.blade.php │ │ │ ├── pinned.blade.php │ │ │ ├── pinned_choice.blade.php │ │ │ ├── position.blade.php │ │ │ ├── price.blade.php │ │ │ ├── privacy_callout.blade.php │ │ │ ├── private_choice.blade.php │ │ │ ├── pronouns.blade.php │ │ │ ├── quest.blade.php │ │ │ ├── race.blade.php │ │ │ ├── races.blade.php │ │ │ ├── relation.blade.php │ │ │ ├── save.blade.php │ │ │ ├── sex.blade.php │ │ │ ├── size.blade.php │ │ │ ├── status.blade.php │ │ │ ├── tag.blade.php │ │ │ ├── tags.blade.php │ │ │ ├── target.blade.php │ │ │ ├── template.blade.php │ │ │ ├── timeline.blade.php │ │ │ ├── title.blade.php │ │ │ ├── tooltip_choice.blade.php │ │ │ ├── type.blade.php │ │ │ ├── unmirror.blade.php │ │ │ ├── update_mirrored.blade.php │ │ │ ├── visibility.blade.php │ │ │ ├── visibility_id.blade.php │ │ │ └── weight.blade.php │ │ ├── forms/ │ │ │ ├── _attributes.blade.php │ │ │ ├── _calendar.blade.php │ │ │ ├── _copy.blade.php │ │ │ ├── _errors.blade.php │ │ │ ├── _permission.blade.php │ │ │ ├── _premium.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── edit_warning.blade.php │ │ │ └── limit.blade.php │ │ ├── helpers/ │ │ │ └── pagination.blade.php │ │ ├── index.blade.php │ │ ├── lists/ │ │ │ └── _create.blade.php │ │ ├── overview.blade.php │ │ ├── permissions/ │ │ │ └── permissions_table.blade.php │ │ ├── permissions.blade.php │ │ ├── show.blade.php │ │ └── subview.blade.php │ ├── dashboard/ │ │ ├── _widget.blade.php │ │ ├── dashboards/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ └── update.blade.php │ │ ├── dialogs/ │ │ │ └── onboarding.blade.php │ │ ├── setup.blade.php │ │ └── widgets/ │ │ ├── _actions.blade.php │ │ ├── _calendar.blade.php │ │ ├── _campaign.blade.php │ │ ├── _gallery.blade.php │ │ ├── _header.blade.php │ │ ├── _help.blade.php │ │ ├── _join.blade.php │ │ ├── _onboarding.blade.php │ │ ├── _preview.blade.php │ │ ├── _random.blade.php │ │ ├── _recent.blade.php │ │ ├── _recent_list.blade.php │ │ ├── _selection.blade.php │ │ ├── _welcome.blade.php │ │ ├── calendar/ │ │ │ ├── _reminder.blade.php │ │ │ └── body.blade.php │ │ ├── forms/ │ │ │ ├── _boosted.blade.php │ │ │ ├── _calendar.blade.php │ │ │ ├── _campaign.blade.php │ │ │ ├── _class.blade.php │ │ │ ├── _dashboard.blade.php │ │ │ ├── _display.blade.php │ │ │ ├── _gallery.blade.php │ │ │ ├── _header.blade.php │ │ │ ├── _header_select.blade.php │ │ │ ├── _help.blade.php │ │ │ ├── _join.blade.php │ │ │ ├── _name.blade.php │ │ │ ├── _onboarding.blade.php │ │ │ ├── _preview.blade.php │ │ │ ├── _random.blade.php │ │ │ ├── _recent.blade.php │ │ │ ├── _related.blade.php │ │ │ ├── _size.blade.php │ │ │ ├── _tags.blade.php │ │ │ ├── _welcome.blade.php │ │ │ ├── _width.blade.php │ │ │ ├── create.blade.php │ │ │ └── edit.blade.php │ │ ├── previews/ │ │ │ ├── _attributes.blade.php │ │ │ ├── _full.blade.php │ │ │ ├── _members.blade.php │ │ │ ├── _preview.blade.php │ │ │ ├── _relations.blade.php │ │ │ ├── character.blade.php │ │ │ ├── conversation.blade.php │ │ │ ├── map.blade.php │ │ │ ├── quest.blade.php │ │ │ └── random-map.blade.php │ │ ├── selection/ │ │ │ └── footer.blade.php │ │ └── selection.blade.php │ ├── datagrids/ │ │ └── subscription.blade.php │ ├── dice_rolls/ │ │ ├── _results.blade.php │ │ ├── datagrid.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── results.blade.php │ │ └── show.blade.php │ ├── editors/ │ │ ├── ckeditor.blade.php │ │ ├── editor.blade.php │ │ ├── summernote.blade.php │ │ ├── tinymce.blade.php │ │ ├── tiptap.blade.php │ │ └── tiptap_editor.blade.php │ ├── emails/ │ │ ├── 2024/ │ │ │ ├── base.blade.php │ │ │ ├── footer.blade.php │ │ │ └── header.blade.php │ │ ├── activity/ │ │ │ ├── email-change-md.blade.php │ │ │ └── password.blade.php │ │ ├── base.blade.php │ │ ├── purge/ │ │ │ ├── first/ │ │ │ │ └── md.blade.php │ │ │ └── second/ │ │ │ └── md.blade.php │ │ ├── subscriptions/ │ │ │ ├── cancelled/ │ │ │ │ ├── md.blade.php │ │ │ │ └── user-md.blade.php │ │ │ ├── changed/ │ │ │ │ └── md.blade.php │ │ │ ├── charge-failed/ │ │ │ │ └── user-md.blade.php │ │ │ ├── deleted/ │ │ │ │ └── user-html.blade.php │ │ │ ├── expiring/ │ │ │ │ └── user-md.blade.php │ │ │ ├── failed/ │ │ │ │ └── user-html.blade.php │ │ │ ├── new/ │ │ │ │ ├── elemental.blade.php │ │ │ │ ├── md.blade.php │ │ │ │ ├── owlbear.blade.php │ │ │ │ └── wyvern.blade.php │ │ │ ├── paypal-expiring/ │ │ │ │ └── user.blade.php │ │ │ ├── trial/ │ │ │ │ └── md.blade.php │ │ │ ├── upcoming/ │ │ │ │ └── user.blade.php │ │ │ └── validation/ │ │ │ └── user-html.blade.php │ │ └── welcome/ │ │ ├── 2024/ │ │ │ ├── html.blade.php │ │ │ └── text.blade.php │ │ ├── html.blade.php │ │ └── text.blade.php │ ├── entities/ │ │ ├── components/ │ │ │ ├── _files.blade.php │ │ │ ├── assets.blade.php │ │ │ ├── attributes.blade.php │ │ │ ├── calendar.blade.php │ │ │ ├── elasped_events.blade.php │ │ │ ├── entry.blade.php │ │ │ ├── header.blade.php │ │ │ ├── history.blade.php │ │ │ ├── links.blade.php │ │ │ ├── members.blade.php │ │ │ ├── menu.blade.php │ │ │ ├── menu_v2.blade.php │ │ │ ├── og.blade.php │ │ │ ├── pins.blade.php │ │ │ ├── posts/ │ │ │ │ ├── children.blade.php │ │ │ │ ├── custom.blade.php │ │ │ │ └── standard.blade.php │ │ │ ├── posts.blade.php │ │ │ ├── profile/ │ │ │ │ ├── _aliases.blade.php │ │ │ │ ├── _events.blade.php │ │ │ │ ├── _location.blade.php │ │ │ │ ├── _locations.blade.php │ │ │ │ ├── _reminder.blade.php │ │ │ │ ├── _type.blade.php │ │ │ │ ├── abilities.blade.php │ │ │ │ ├── attribute_templates.blade.php │ │ │ │ ├── character_families.blade.php │ │ │ │ ├── character_races.blade.php │ │ │ │ ├── characters.blade.php │ │ │ │ ├── conversations.blade.php │ │ │ │ ├── creatures.blade.php │ │ │ │ ├── custom.blade.php │ │ │ │ ├── dice_rolls.blade.php │ │ │ │ ├── events.blade.php │ │ │ │ ├── families.blade.php │ │ │ │ ├── items.blade.php │ │ │ │ ├── journals.blade.php │ │ │ │ ├── locations.blade.php │ │ │ │ ├── maps.blade.php │ │ │ │ ├── notes.blade.php │ │ │ │ ├── organisations.blade.php │ │ │ │ ├── quests.blade.php │ │ │ │ ├── races.blade.php │ │ │ │ ├── tags.blade.php │ │ │ │ └── timelines.blade.php │ │ │ ├── relations.blade.php │ │ │ └── tooltip.blade.php │ │ ├── creator/ │ │ │ ├── _created.blade.php │ │ │ ├── form.blade.php │ │ │ ├── forms/ │ │ │ │ ├── ability.blade.php │ │ │ │ ├── attribute_template.blade.php │ │ │ │ ├── calendar.blade.php │ │ │ │ ├── character.blade.php │ │ │ │ ├── conversation.blade.php │ │ │ │ ├── creature.blade.php │ │ │ │ ├── custom.blade.php │ │ │ │ ├── dice_roll.blade.php │ │ │ │ ├── event.blade.php │ │ │ │ ├── family.blade.php │ │ │ │ ├── item.blade.php │ │ │ │ ├── journal.blade.php │ │ │ │ ├── location.blade.php │ │ │ │ ├── map.blade.php │ │ │ │ ├── note.blade.php │ │ │ │ ├── organisation.blade.php │ │ │ │ ├── post.blade.php │ │ │ │ ├── quest.blade.php │ │ │ │ ├── race.blade.php │ │ │ │ ├── tag.blade.php │ │ │ │ ├── template.blade.php │ │ │ │ ├── timeline.blade.php │ │ │ │ └── whiteboard.blade.php │ │ │ ├── header/ │ │ │ │ ├── _dropdown.blade.php │ │ │ │ └── header.blade.php │ │ │ ├── selection/ │ │ │ │ ├── _abilities.blade.php │ │ │ │ ├── _attribute_templates.blade.php │ │ │ │ ├── _calendars.blade.php │ │ │ │ ├── _characters.blade.php │ │ │ │ ├── _conversations.blade.php │ │ │ │ ├── _creatures.blade.php │ │ │ │ ├── _dice_rolls.blade.php │ │ │ │ ├── _events.blade.php │ │ │ │ ├── _families.blade.php │ │ │ │ ├── _full.blade.php │ │ │ │ ├── _items.blade.php │ │ │ │ ├── _journals.blade.php │ │ │ │ ├── _locations.blade.php │ │ │ │ ├── _main.blade.php │ │ │ │ ├── _maps.blade.php │ │ │ │ ├── _notes.blade.php │ │ │ │ ├── _organisations.blade.php │ │ │ │ ├── _posts.blade.php │ │ │ │ ├── _quests.blade.php │ │ │ │ ├── _races.blade.php │ │ │ │ ├── _tags.blade.php │ │ │ │ ├── _timelines.blade.php │ │ │ │ ├── all.blade.php │ │ │ │ └── popular.blade.php │ │ │ ├── selection.blade.php │ │ │ └── templates.blade.php │ │ ├── forms/ │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ └── entry.blade.php │ │ ├── headers/ │ │ │ ├── __entity-locations.blade.php │ │ │ ├── __location.blade.php │ │ │ ├── __parent.blade.php │ │ │ ├── __parent_location.blade.php │ │ │ ├── _ability.blade.php │ │ │ ├── _calendar.blade.php │ │ │ ├── _character.blade.php │ │ │ ├── _creature.blade.php │ │ │ ├── _custom.blade.php │ │ │ ├── _event.blade.php │ │ │ ├── _family.blade.php │ │ │ ├── _item.blade.php │ │ │ ├── _journal.blade.php │ │ │ ├── _location.blade.php │ │ │ ├── _map.blade.php │ │ │ ├── _note.blade.php │ │ │ ├── _organisation.blade.php │ │ │ ├── _quest.blade.php │ │ │ ├── _race.blade.php │ │ │ ├── _tag.blade.php │ │ │ ├── _timeline.blade.php │ │ │ ├── actions.blade.php │ │ │ └── toggle.blade.php │ │ ├── index/ │ │ │ ├── _actions.blade.php │ │ │ ├── _create.blade.php │ │ │ ├── _entity.blade.php │ │ │ ├── actions/ │ │ │ │ ├── attribute_template.blade.php │ │ │ │ ├── bookmark.blade.php │ │ │ │ ├── connection.blade.php │ │ │ │ ├── conversation.blade.php │ │ │ │ ├── dice_roll.blade.php │ │ │ │ └── parent.blade.php │ │ │ ├── explore.blade.php │ │ │ ├── filters.blade.php │ │ │ └── index.blade.php │ │ ├── markdown/ │ │ │ ├── _locations.blade.php │ │ │ ├── aliases.blade.php │ │ │ ├── base.blade.php │ │ │ ├── characters.blade.php │ │ │ ├── creatures.blade.php │ │ │ ├── events.blade.php │ │ │ ├── families.blade.php │ │ │ ├── index.blade.php │ │ │ ├── items.blade.php │ │ │ ├── locations.blade.php │ │ │ ├── organisations.blade.php │ │ │ ├── quests.blade.php │ │ │ └── races.blade.php │ │ └── pages/ │ │ ├── abilities/ │ │ │ ├── _abilities.blade.php │ │ │ ├── _buttons.blade.php │ │ │ ├── _edit_form.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── _import.blade.php │ │ │ ├── create.blade.php │ │ │ ├── import.blade.php │ │ │ ├── index.blade.php │ │ │ ├── render.blade.php │ │ │ ├── reorder/ │ │ │ │ ├── _reorder.blade.php │ │ │ │ └── index.blade.php │ │ │ └── update.blade.php │ │ ├── aliases/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ ├── not-premium.blade.php │ │ │ └── update.blade.php │ │ ├── assets/ │ │ │ ├── _assets.blade.php │ │ │ ├── _buttons.blade.php │ │ │ ├── _file.blade.php │ │ │ ├── _link.blade.php │ │ │ └── index.blade.php │ │ ├── attribute-templates/ │ │ │ ├── _actions.blade.php │ │ │ ├── _form.blade.php │ │ │ └── apply.blade.php │ │ ├── attributes/ │ │ │ ├── _buttons.blade.php │ │ │ ├── _story.blade.php │ │ │ ├── dashboard.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── index.blade.php │ │ │ ├── live/ │ │ │ │ ├── _form.blade.php │ │ │ │ └── edit.blade.php │ │ │ ├── main.blade.php │ │ │ ├── render.blade.php │ │ │ └── rendering/ │ │ │ ├── default.blade.php │ │ │ └── marketplace.blade.php │ │ ├── children/ │ │ │ ├── children.blade.php │ │ │ └── index.blade.php │ │ ├── entry/ │ │ │ └── edit.blade.php │ │ ├── files/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ ├── max.blade.php │ │ │ └── update.blade.php │ │ ├── image/ │ │ │ ├── _form.blade.php │ │ │ ├── focus.blade.php │ │ │ └── replace.blade.php │ │ ├── inventory/ │ │ │ ├── _buttons.blade.php │ │ │ ├── _copy.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── _generate.blade.php │ │ │ ├── _grid.blade.php │ │ │ ├── _inventory.blade.php │ │ │ ├── _item.blade.php │ │ │ ├── _table.blade.php │ │ │ ├── _thumbnail.blade.php │ │ │ ├── copy.blade.php │ │ │ ├── create.blade.php │ │ │ ├── details.blade.php │ │ │ ├── generate.blade.php │ │ │ ├── grid.blade.php │ │ │ ├── index.blade.php │ │ │ ├── render.blade.php │ │ │ └── update.blade.php │ │ ├── links/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ ├── go.blade.php │ │ │ └── update.blade.php │ │ ├── logs/ │ │ │ ├── _logs.blade.php │ │ │ ├── _modal.blade.php │ │ │ ├── history.blade.php │ │ │ └── index.blade.php │ │ ├── mentions/ │ │ │ ├── mentions.blade.php │ │ │ └── render.blade.php │ │ ├── move/ │ │ │ └── index.blade.php │ │ ├── posts/ │ │ │ ├── _actions.blade.php │ │ │ ├── _boosted.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ ├── dialogs/ │ │ │ │ ├── _role-footer.blade.php │ │ │ │ ├── _user-footer.blade.php │ │ │ │ ├── _visibility.blade.php │ │ │ │ └── visibility.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── forms/ │ │ │ │ ├── _layout.blade.php │ │ │ │ ├── _main.blade.php │ │ │ │ ├── _permissions.blade.php │ │ │ │ ├── _save-options.blade.php │ │ │ │ ├── _templates.blade.php │ │ │ │ └── _visibility.blade.php │ │ │ ├── layouts/ │ │ │ │ └── index.blade.php │ │ │ ├── logs/ │ │ │ │ ├── _logs.blade.php │ │ │ │ └── index.blade.php │ │ │ ├── move/ │ │ │ │ ├── form.blade.php │ │ │ │ └── index.blade.php │ │ │ └── show.blade.php │ │ ├── print/ │ │ │ ├── _abilities.blade.php │ │ │ ├── _attributes.blade.php │ │ │ ├── _inventory.blade.php │ │ │ ├── _relations.blade.php │ │ │ ├── print-bulk.blade.php │ │ │ ├── print.blade.php │ │ │ └── profile/ │ │ │ ├── _events.blade.php │ │ │ ├── _location.blade.php │ │ │ ├── _locations.blade.php │ │ │ ├── _reminder.blade.php │ │ │ ├── _type.blade.php │ │ │ ├── abilities.blade.php │ │ │ ├── attribute_templates.blade.php │ │ │ ├── characters.blade.php │ │ │ ├── conversations.blade.php │ │ │ ├── creatures.blade.php │ │ │ ├── dice_rolls.blade.php │ │ │ ├── events.blade.php │ │ │ ├── families.blade.php │ │ │ ├── items.blade.php │ │ │ ├── journals.blade.php │ │ │ ├── locations.blade.php │ │ │ ├── maps.blade.php │ │ │ ├── notes.blade.php │ │ │ ├── organisations.blade.php │ │ │ ├── quests.blade.php │ │ │ ├── races.blade.php │ │ │ ├── tags.blade.php │ │ │ └── timelines.blade.php │ │ ├── privacy/ │ │ │ ├── _body.blade.php │ │ │ ├── _footer.blade.php │ │ │ └── index.blade.php │ │ ├── profile/ │ │ │ ├── _character.blade.php │ │ │ └── index.blade.php │ │ ├── relations/ │ │ │ ├── _buttons.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── _map.blade.php │ │ │ ├── _related.blade.php │ │ │ ├── _tables.blade.php │ │ │ ├── create.blade.php │ │ │ ├── fields/ │ │ │ │ ├── mirrored.blade.php │ │ │ │ └── unmirror.blade.php │ │ │ ├── full-form/ │ │ │ │ ├── _entry.blade.php │ │ │ │ ├── create.blade.php │ │ │ │ └── update.blade.php │ │ │ ├── index.blade.php │ │ │ ├── render.blade.php │ │ │ └── update.blade.php │ │ ├── reminders/ │ │ │ ├── _list.blade.php │ │ │ ├── _post.blade.php │ │ │ └── index.blade.php │ │ ├── share/ │ │ │ └── setup.blade.php │ │ ├── story/ │ │ │ ├── _reorder.blade.php │ │ │ ├── reorder/ │ │ │ │ └── _story.blade.php │ │ │ └── reorder.blade.php │ │ ├── subpage.blade.php │ │ ├── tags/ │ │ │ ├── _form.blade.php │ │ │ └── create.blade.php │ │ └── transform/ │ │ └── index.blade.php │ ├── errors/ │ │ ├── 403.blade.php │ │ ├── 404.blade.php │ │ ├── 500.blade.php │ │ ├── 503.blade.php │ │ ├── images/ │ │ │ ├── 403-image.blade.php │ │ │ ├── 404-image.blade.php │ │ │ ├── 500-image.blade.php │ │ │ └── 503-image.blade.php │ │ ├── maintenance.blade.php │ │ └── private-campaign.blade.php │ ├── events/ │ │ ├── events.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ └── panels/ │ │ └── events.blade.php │ ├── families/ │ │ ├── families.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── members/ │ │ │ ├── _form.blade.php │ │ │ └── create.blade.php │ │ ├── members.blade.php │ │ ├── panels/ │ │ │ ├── _members.blade.php │ │ │ ├── families.blade.php │ │ │ └── tree.blade.php │ │ ├── show.blade.php │ │ └── trees/ │ │ └── index.blade.php │ ├── filters/ │ │ ├── form.blade.php │ │ └── save_form.blade.php │ ├── front/ │ │ ├── _campaign.blade.php │ │ ├── boosters.blade.php │ │ ├── features.blade.php │ │ ├── footer.blade.php │ │ ├── home.blade.php │ │ └── sitemap.blade.php │ ├── gallery/ │ │ ├── _image.blade.php │ │ ├── file/ │ │ │ ├── _actions.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── focus/ │ │ │ │ ├── _actions.blade.php │ │ │ │ ├── _form.blade.php │ │ │ │ └── edit.blade.php │ │ │ └── visibility/ │ │ │ ├── _form.blade.php │ │ │ └── edit.blade.php │ │ ├── folders/ │ │ │ ├── _form.blade.php │ │ │ └── create.blade.php │ │ ├── images.blade.php │ │ └── index.blade.php │ ├── health.blade.php │ ├── helpers/ │ │ ├── api-filters.blade.php │ │ └── troubleshooting/ │ │ └── index.blade.php │ ├── history/ │ │ └── index.blade.php │ ├── home.blade.php │ ├── icons/ │ │ ├── kanka-svg.blade.php │ │ ├── svg/ │ │ │ └── cog.blade.php │ │ └── visibility.blade.php │ ├── items/ │ │ ├── entities.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── inventories.blade.php │ │ ├── items.blade.php │ │ ├── panels/ │ │ │ ├── entities.blade.php │ │ │ ├── inventories.blade.php │ │ │ └── items.blade.php │ │ └── show.blade.php │ ├── journals/ │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── journals.blade.php │ │ └── panels/ │ │ └── journals.blade.php │ ├── layouts/ │ │ ├── _breadcrumbs.blade.php │ │ ├── _lang-switcher.blade.php │ │ ├── _socials.blade.php │ │ ├── _theme.blade.php │ │ ├── ajax.blade.php │ │ ├── app.blade.php │ │ ├── banner.blade.php │ │ ├── callouts/ │ │ │ └── recoverable.blade.php │ │ ├── datagrid/ │ │ │ ├── _column.blade.php │ │ │ ├── _head.blade.php │ │ │ ├── _header.blade.php │ │ │ ├── _table.blade.php │ │ │ ├── _togglers.blade.php │ │ │ ├── actions.blade.php │ │ │ ├── bulks/ │ │ │ │ ├── _calendar-event.blade.php │ │ │ │ ├── _map-group.blade.php │ │ │ │ ├── _map-layer.blade.php │ │ │ │ ├── _map-marker.blade.php │ │ │ │ └── update.blade.php │ │ │ ├── bulks.blade.php │ │ │ ├── delete-forms.blade.php │ │ │ ├── fulltext_search.blade.php │ │ │ ├── rows/ │ │ │ │ ├── character.blade.php │ │ │ │ ├── date.blade.php │ │ │ │ ├── entitylink.blade.php │ │ │ │ ├── entitylist.blade.php │ │ │ │ ├── image.blade.php │ │ │ │ ├── location.blade.php │ │ │ │ ├── locations.blade.php │ │ │ │ ├── mention-link.blade.php │ │ │ │ ├── parentlink.blade.php │ │ │ │ ├── since.blade.php │ │ │ │ ├── tags.blade.php │ │ │ │ ├── view.blade.php │ │ │ │ ├── visibility.blade.php │ │ │ │ └── visibility_pivot.blade.php │ │ │ └── search.blade.php │ │ ├── dialogs/ │ │ │ ├── languages.blade.php │ │ │ └── subscription.blade.php │ │ ├── error.blade.php │ │ ├── footer.blade.php │ │ ├── front/ │ │ │ └── nav.blade.php │ │ ├── front.blade.php │ │ ├── header/ │ │ │ └── qq.blade.php │ │ ├── header.blade.php │ │ ├── links/ │ │ │ └── icons.blade.php │ │ ├── login.blade.php │ │ ├── map.blade.php │ │ ├── print.blade.php │ │ ├── rich.blade.php │ │ ├── scripts/ │ │ │ └── fontawesome.blade.php │ │ ├── sidebars/ │ │ │ ├── _campaign.blade.php │ │ │ ├── app.blade.php │ │ │ ├── bookmark.blade.php │ │ │ ├── bookmarks.blade.php │ │ │ ├── campaign.blade.php │ │ │ └── settings.blade.php │ │ ├── styles/ │ │ │ └── fontawesome.blade.php │ │ ├── tracking/ │ │ │ ├── fallback.blade.php │ │ │ └── tracking.blade.php │ │ ├── whiteboard.blade.php │ │ ├── widget.blade.php │ │ └── widgets/ │ │ ├── editor.blade.php │ │ └── summernote.blade.php │ ├── livewire/ │ │ ├── campaigns/ │ │ │ ├── csv-import.blade.php │ │ │ ├── exports-table.blade.php │ │ │ └── tags.blade.php │ │ ├── dashboards/ │ │ │ └── entity-listing.blade.php │ │ ├── front-pagination.blade.php │ │ ├── roadmap/ │ │ │ ├── form.blade.php │ │ │ ├── ideas.blade.php │ │ │ └── upvote.blade.php │ │ ├── roadmap.blade.php │ │ ├── users/ │ │ │ └── otp.blade.php │ │ └── widgets/ │ │ ├── gallery-carousel.blade.php │ │ └── random-entity.blade.php │ ├── locations/ │ │ ├── characters.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── locations.blade.php │ │ └── panels/ │ │ ├── characters.blade.php │ │ ├── events.blade.php │ │ ├── locations.blade.php │ │ └── quests.blade.php │ ├── maps/ │ │ ├── _explore-link.blade.php │ │ ├── _marker.blade.php │ │ ├── _preview.blade.php │ │ ├── _setup.blade.php │ │ ├── _ticker.blade.php │ │ ├── bulk/ │ │ │ └── modals/ │ │ │ └── _copy_to_campaign.blade.php │ │ ├── explore/ │ │ │ └── legend.blade.php │ │ ├── explore.blade.php │ │ ├── form/ │ │ │ ├── _copy.blade.php │ │ │ ├── _entry.blade.php │ │ │ ├── _groups_max.blade.php │ │ │ ├── _layers_max.blade.php │ │ │ ├── _markers.blade.php │ │ │ ├── _panes.blade.php │ │ │ ├── _settings.blade.php │ │ │ └── _tabs.blade.php │ │ ├── groups/ │ │ │ ├── _actions.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── _reorder.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ └── index.blade.php │ │ ├── layers/ │ │ │ ├── _form.blade.php │ │ │ ├── _reorder.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── index.blade.php │ │ │ └── migrate.blade.php │ │ ├── maps.blade.php │ │ ├── markers/ │ │ │ ├── _actions.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── _new-footer.blade.php │ │ │ ├── create.blade.php │ │ │ ├── details.blade.php │ │ │ ├── dialog_details.blade.php │ │ │ ├── edit.blade.php │ │ │ ├── fields/ │ │ │ │ ├── background_colour.blade.php │ │ │ │ ├── custom_icon.blade.php │ │ │ │ ├── font_colour.blade.php │ │ │ │ ├── icon.blade.php │ │ │ │ ├── opacity.blade.php │ │ │ │ └── pin_size.blade.php │ │ │ └── index.blade.php │ │ ├── panels/ │ │ │ ├── groups.blade.php │ │ │ ├── layers.blade.php │ │ │ ├── maps.blade.php │ │ │ └── markers.blade.php │ │ ├── preview.blade.php │ │ └── show.blade.php │ ├── notes/ │ │ ├── _subnotes.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ └── show.blade.php │ ├── notifications/ │ │ ├── _notification.blade.php │ │ └── index.blade.php │ ├── organisations/ │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── members/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ └── edit.blade.php │ │ ├── members.blade.php │ │ ├── organisations.blade.php │ │ ├── panels/ │ │ │ ├── members.blade.php │ │ │ └── organisations.blade.php │ │ └── show.blade.php │ ├── partials/ │ │ ├── boost_icon.blade.php │ │ ├── cookieconsent.blade.php │ │ ├── errors.blade.php │ │ ├── footer_cancel.blade.php │ │ ├── forms/ │ │ │ ├── _dialog.blade.php │ │ │ ├── _panel.blade.php │ │ │ ├── dialog/ │ │ │ │ └── footer.blade.php │ │ │ └── form.blade.php │ │ ├── helper-modal.blade.php │ │ ├── images/ │ │ │ └── boosted-image.blade.php │ │ ├── impersonate.blade.php │ │ ├── koinks.blade.php │ │ ├── modals/ │ │ │ └── close.blade.php │ │ ├── newsletter.blade.php │ │ ├── or_cancel.blade.php │ │ ├── success.blade.php │ │ └── superboosted.blade.php │ ├── presets/ │ │ ├── forms/ │ │ │ ├── _form.blade.php │ │ │ ├── _marker.blade.php │ │ │ ├── create.blade.php │ │ │ └── edit.blade.php │ │ └── list.blade.php │ ├── quests/ │ │ ├── elements/ │ │ │ ├── _actions.blade.php │ │ │ ├── _buttons.blade.php │ │ │ ├── _element.blade.php │ │ │ ├── _elements.blade.php │ │ │ ├── _form.blade.php │ │ │ ├── _post.blade.php │ │ │ ├── create.blade.php │ │ │ ├── index.blade.php │ │ │ └── update.blade.php │ │ ├── form/ │ │ │ ├── _copy.blade.php │ │ │ └── _entry.blade.php │ │ ├── panels/ │ │ │ └── quests.blade.php │ │ └── show.blade.php │ ├── races/ │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── members/ │ │ │ ├── _form.blade.php │ │ │ └── create.blade.php │ │ ├── panels/ │ │ │ ├── characters.blade.php │ │ │ └── races.blade.php │ │ ├── races.blade.php │ │ └── show.blade.php │ ├── relations/ │ │ └── datagrid.blade.php │ ├── roadmap/ │ │ ├── feature/ │ │ │ ├── _form.blade.php │ │ │ ├── _idea.blade.php │ │ │ ├── _progress.blade.php │ │ │ ├── _upvote.blade.php │ │ │ └── show.blade.php │ │ └── index.blade.php │ ├── search/ │ │ └── fulltext.blade.php │ ├── settings/ │ │ ├── _tfa.blade.php │ │ ├── account.blade.php │ │ ├── api/ │ │ │ ├── _form.blade.php │ │ │ └── create.blade.php │ │ ├── api.blade.php │ │ ├── appearance.blade.php │ │ ├── apps.blade.php │ │ ├── boosters/ │ │ │ ├── _campaign.blade.php │ │ │ ├── create/ │ │ │ │ ├── _actions.blade.php │ │ │ │ ├── _footer.blade.php │ │ │ │ └── _form.blade.php │ │ │ ├── create.blade.php │ │ │ ├── index.blade.php │ │ │ ├── unboost/ │ │ │ │ ├── _actions.blade.php │ │ │ │ └── _form.blade.php │ │ │ ├── unboost.blade.php │ │ │ ├── update/ │ │ │ │ ├── _actions.blade.php │ │ │ │ └── _form.blade.php │ │ │ └── update.blade.php │ │ ├── bragi.blade.php │ │ ├── client/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ └── update.blade.php │ │ ├── notifications.blade.php │ │ ├── patreon.blade.php │ │ ├── premium/ │ │ │ ├── create/ │ │ │ │ ├── _actions.blade.php │ │ │ │ └── _form.blade.php │ │ │ ├── create.blade.php │ │ │ ├── index.blade.php │ │ │ ├── remove/ │ │ │ │ ├── _actions.blade.php │ │ │ │ └── _form.blade.php │ │ │ └── remove.blade.php │ │ ├── profile.blade.php │ │ ├── referrals/ │ │ │ └── index.blade.php │ │ ├── subscription/ │ │ │ ├── _cancel.blade.php │ │ │ ├── _promo.blade.php │ │ │ ├── _recap.blade.php │ │ │ ├── cancellation/ │ │ │ │ ├── form.blade.php │ │ │ │ └── grace.blade.php │ │ │ ├── cancelled.blade.php │ │ │ ├── change.blade.php │ │ │ ├── change_blocked.blade.php │ │ │ ├── currency/ │ │ │ │ ├── _blocked.blade.php │ │ │ │ ├── _form.blade.php │ │ │ │ ├── _reset.blade.php │ │ │ │ └── edit.blade.php │ │ │ ├── faq.blade.php │ │ │ ├── finish.blade.php │ │ │ ├── free-trial.blade.php │ │ │ ├── index.blade.php │ │ │ ├── paypal-renew.blade.php │ │ │ ├── renew.blade.php │ │ │ └── tiers/ │ │ │ ├── actions/ │ │ │ │ ├── _elemental.blade.php │ │ │ │ ├── _kobold.blade.php │ │ │ │ ├── _owlbear.blade.php │ │ │ │ └── _wyvern.blade.php │ │ │ └── benefits/ │ │ │ ├── _elemental.blade.php │ │ │ ├── _kobold.blade.php │ │ │ ├── _owlbear.blade.php │ │ │ └── _wyvern.blade.php │ │ └── tiers/ │ │ ├── _elemental.blade.php │ │ ├── _goblin.blade.php │ │ ├── _kobold.blade.php │ │ └── _owlbear.blade.php │ ├── setup.blade.php │ ├── spotlights/ │ │ ├── field.blade.php │ │ ├── form.blade.php │ │ └── index.blade.php │ ├── tags/ │ │ ├── _badge.blade.php │ │ ├── children.blade.php │ │ ├── entities/ │ │ │ ├── _form.blade.php │ │ │ └── create.blade.php │ │ ├── form/ │ │ │ └── _entry.blade.php │ │ ├── panels/ │ │ │ ├── children.blade.php │ │ │ ├── posts.blade.php │ │ │ └── tags.blade.php │ │ ├── show.blade.php │ │ ├── tags.blade.php │ │ └── transfer/ │ │ ├── entities/ │ │ │ ├── form.blade.php │ │ │ └── transfer.blade.php │ │ └── posts/ │ │ ├── form.blade.php │ │ └── transfer.blade.php │ ├── timelines/ │ │ ├── _element.blade.php │ │ ├── _timeline.blade.php │ │ ├── elements/ │ │ │ ├── _form.blade.php │ │ │ ├── _icon.blade.php │ │ │ ├── create.blade.php │ │ │ └── edit.blade.php │ │ ├── eras/ │ │ │ ├── _form.blade.php │ │ │ ├── create.blade.php │ │ │ ├── edit.blade.php │ │ │ └── index.blade.php │ │ ├── form/ │ │ │ ├── _copy.blade.php │ │ │ └── _entry.blade.php │ │ ├── panels/ │ │ │ ├── eras.blade.php │ │ │ └── timelines.blade.php │ │ ├── reorder/ │ │ │ ├── _reorder.blade.php │ │ │ └── index.blade.php │ │ ├── show.blade.php │ │ └── timelines.blade.php │ ├── tutorials/ │ │ ├── content.blade.php │ │ ├── dashboard_1.blade.php │ │ └── modal.blade.php │ ├── tw.blade.php │ ├── users/ │ │ ├── _badges.blade.php │ │ └── profile.blade.php │ ├── vendor/ │ │ ├── cashier/ │ │ │ └── invoice.blade.php │ │ ├── larecipe/ │ │ │ ├── default.blade.php │ │ │ └── partials/ │ │ │ └── logo.blade.php │ │ ├── livewire/ │ │ │ └── tailwind.blade.php │ │ ├── mail/ │ │ │ ├── html/ │ │ │ │ ├── button.blade.php │ │ │ │ ├── footer.blade.php │ │ │ │ ├── header.blade.php │ │ │ │ ├── layout.blade.php │ │ │ │ ├── message.blade.php │ │ │ │ ├── panel.blade.php │ │ │ │ ├── subcopy.blade.php │ │ │ │ ├── table.blade.php │ │ │ │ └── themes/ │ │ │ │ └── default.css │ │ │ └── text/ │ │ │ ├── button.blade.php │ │ │ ├── footer.blade.php │ │ │ ├── header.blade.php │ │ │ ├── layout.blade.php │ │ │ ├── message.blade.php │ │ │ ├── panel.blade.php │ │ │ ├── subcopy.blade.php │ │ │ └── table.blade.php │ │ ├── pagination/ │ │ │ └── tailwind.blade.php │ │ └── passport/ │ │ └── authorize.blade.php │ └── whiteboards/ │ ├── _draw-link.blade.php │ ├── cta.blade.php │ ├── draw.blade.php │ ├── form/ │ │ ├── _copy.blade.php │ │ └── _entry.blade.php │ ├── index.blade.php │ └── show.blade.php ├── routes/ │ ├── api-public.php │ ├── api.php │ ├── api.v1.php │ ├── auth.php │ ├── campaigns/ │ │ ├── bulks.php │ │ ├── campaign.php │ │ ├── entities.php │ │ └── search.php │ ├── channels.php │ ├── console.php │ ├── oauth.php │ ├── settings.php │ ├── vendor.php │ ├── web-i18n.php │ ├── web.php │ └── webhooks.php ├── server.php ├── sonar-project.properties ├── tailwind.config.js ├── tests/ │ ├── CreatesApplication.php │ ├── Feature/ │ │ ├── AuthTest.php │ │ ├── CampaignTest.php │ │ ├── Entities/ │ │ │ ├── AbilityTest.php │ │ │ ├── BookmarkTest.php │ │ │ ├── CalendarTest.php │ │ │ ├── CampaignDashboardWidgetTest.php │ │ │ ├── CampaignImageTest.php │ │ │ ├── CampaignMemberTest.php │ │ │ ├── CampaignStyleTest.php │ │ │ ├── CharacterTest.php │ │ │ ├── ConversationMessageTest.php │ │ │ ├── ConversationParticipantTest.php │ │ │ ├── ConversationTest.php │ │ │ ├── CreatureTest.php │ │ │ ├── DiceRollTest.php │ │ │ ├── EntityAssetTest.php │ │ │ ├── EntityAttributeTest.php │ │ │ ├── EntityDefaultThumbnailTest.php │ │ │ ├── EntityEventTest.php │ │ │ ├── EntityPostTest.php │ │ │ ├── EntityRelationTest.php │ │ │ ├── EntityTagTest.php │ │ │ ├── EntityTest.php │ │ │ ├── EventTest.php │ │ │ ├── FamilyTest.php │ │ │ ├── ItemTest.php │ │ │ ├── JournalTest.php │ │ │ ├── LocationTest.php │ │ │ ├── MapGroupTest.php │ │ │ ├── MapLayerTest.php │ │ │ ├── MapMarkerTest.php │ │ │ ├── MapTest.php │ │ │ ├── NoteTest.php │ │ │ ├── OrganisationTest.php │ │ │ ├── PermissionTest.php │ │ │ ├── QuestElementTest.php │ │ │ ├── QuestTest.php │ │ │ ├── RaceTest.php │ │ │ ├── RateLimitingTest.php │ │ │ ├── TagTest.php │ │ │ ├── TimelineElementTest.php │ │ │ ├── TimelineEraTest.php │ │ │ └── TimelineTest.php │ │ ├── EnvTest.php │ │ ├── FrontCampaignTest.php │ │ └── ProfileTest.php │ ├── Pest.php │ └── TestCase.php └── vite.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .claude/commands/translate.md ================================================ # Translate Missing Strings Translate missing translations for the given locale. Usage: /translate [locale_code] [locale_name] Example: /translate fr French ## Steps 1. Run `php artisan translations:missing $ARG1` and capture the output 2. For each missing key/string: - Translate the English string to the target language naturally (not literally) - Preserve `:variable` placeholders, `` tags, and pluralization syntax exactly - Match tone/formality of existing translations (check a few rows in ltm_translations first) 3. Insert each translation into `ltm_translations`. status field = 1. saved_value = null. 4. Confirm count of translations added ## Rules - Never guess at technical terms — keep English if unsure - Kanka-specific terms (Whiteboard) stay in English unless an equivalent already exists in the DB for that locale ``` Then invoke as: ``` /translate fr French /translate de German /translate es Spanish ## Locale rules Apply these rules based on the target locale: **fr** - Use Swiss French spelling and idioms - No space before `:` `!` `?` `;` (Swiss convention, unlike standard French) - Prefer Swiss vocabulary where it differs (e.g. "septante" vs "soixante-dix" if numbers appear) **es** - Default to neutral Latin American Spanish unless specified otherwise ================================================ FILE: .claude/skills/livewire-development/SKILL.md ================================================ --- name: livewire-development description: >- Develops reactive Livewire 3 components. Activates when creating, updating, or modifying Livewire components; working with wire:model, wire:click, wire:loading, or any wire: directives; adding real-time updates, loading states, or reactivity; debugging component behavior; writing Livewire tests; or when the user mentions Livewire, component, counter, or reactive UI. --- # Livewire Development ## When to Apply Activate this skill when: - Creating new Livewire components - Modifying existing component state or behavior - Debugging reactivity or lifecycle issues - Writing Livewire component tests - Adding Alpine.js interactivity to components - Working with wire: directives ## Documentation Use `search-docs` for detailed Livewire 3 patterns and documentation. ## Basic Usage ### Creating Components Use the `php artisan make:livewire [Posts\CreatePost]` Artisan command to create new components. ### Fundamental Concepts - State should live on the server, with the UI reflecting it. - All Livewire requests hit the Laravel backend; they're like regular HTTP requests. Always validate form data and run authorization checks in Livewire actions. ## Livewire 3 Specifics ### Key Changes From Livewire 2 These things changed in Livewire 3, but may not have been updated in this application. Verify this application's setup to ensure you follow existing conventions. - Use `wire:model.live` for real-time updates, `wire:model` is now deferred by default. - Components now use the `App\Livewire` namespace (not `App\Http\Livewire`). - Use `$this->dispatch()` to dispatch events (not `emit` or `dispatchBrowserEvent`). - Use the `components.layouts.app` view as the typical layout path (not `layouts.app`). ### New Directives - `wire:show`, `wire:transition`, `wire:cloak`, `wire:offline`, `wire:target` are available for use. ### Alpine Integration - Alpine is now included with Livewire; don't manually include Alpine.js. - Plugins included with Alpine: persist, intersect, collapse, and focus. ## Best Practices ### Component Structure - Livewire components require a single root element. - Use `wire:loading` and `wire:dirty` for delightful loading states. ### Using Keys in Loops @foreach ($items as $item)
{{ $item->name }}
@endforeach
### Lifecycle Hooks Prefer lifecycle hooks like `mount()`, `updatedFoo()` for initialization and reactive side effects: public function mount(User $user) { $this->user = $user; } public function updatedSearch() { $this->resetPage(); } ## JavaScript Hooks You can listen for `livewire:init` to hook into Livewire initialization: document.addEventListener('livewire:init', function () { Livewire.hook('request', ({ fail }) => { if (fail && fail.status === 419) { alert('Your session expired'); } }); Livewire.hook('message.failed', (message, component) => { console.error(message); }); }); ## Testing Livewire::test(Counter::class) ->assertSet('count', 0) ->call('increment') ->assertSet('count', 1) ->assertSee(1) ->assertStatus(200); $this->get('/posts/create') ->assertSeeLivewire(CreatePost::class); ## Common Pitfalls - Forgetting `wire:key` in loops causes unexpected behavior when items change - Using `wire:model` expecting real-time updates (use `wire:model.live` instead in v3) - Not validating/authorizing in Livewire actions (treat them like HTTP requests) - Including Alpine.js separately when it's already bundled with Livewire 3 ================================================ FILE: .claude/skills/pest-testing/SKILL.md ================================================ --- name: pest-testing description: >- Tests applications using the Pest 3 PHP framework. Activates when writing tests, creating unit or feature tests, adding assertions, testing Livewire components, architecture testing, debugging test failures, working with datasets or mocking; or when the user mentions test, spec, TDD, expects, assertion, coverage, or needs to verify functionality works. --- # Pest Testing 3 ## When to Apply Activate this skill when: - Creating new tests (unit or feature) - Modifying existing tests - Debugging test failures - Working with datasets, mocking, or test organization - Writing architecture tests ## Documentation Use `search-docs` for detailed Pest 3 patterns and documentation. ## Basic Usage ### Creating Tests All tests must be written using Pest. Use `php artisan make:test --pest {name}`. ### Test Organization - Tests live in the `tests/Feature` and `tests/Unit` directories. - Do NOT remove tests without approval - these are core application code. - Test happy paths, failure paths, and edge cases. ### Basic Test Structure it('is true', function () { expect(true)->toBeTrue(); }); ### Running Tests - Run minimal tests with filter before finalizing: `php artisan test --compact --filter=testName`. - Run all tests: `php artisan test --compact`. - Run file: `php artisan test --compact tests/Feature/ExampleTest.php`. ## Assertions Use specific assertions (`assertSuccessful()`, `assertNotFound()`) instead of `assertStatus()`: it('returns all', function () { $this->postJson('/api/docs', [])->assertSuccessful(); }); | Use | Instead of | |-----|------------| | `assertSuccessful()` | `assertStatus(200)` | | `assertNotFound()` | `assertStatus(404)` | | `assertForbidden()` | `assertStatus(403)` | ## Mocking Import mock function before use: `use function Pest\Laravel\mock;` ## Datasets Use datasets for repetitive tests (validation rules, etc.): it('has emails', function (string $email) { expect($email)->not->toBeEmpty(); })->with([ 'james' => 'james@laravel.com', 'taylor' => 'taylor@laravel.com', ]); ## Pest 3 Features ### Architecture Testing Pest 3 includes architecture testing to enforce code conventions: arch('controllers') ->expect('App\Http\Controllers') ->toExtendNothing() ->toHaveSuffix('Controller'); arch('models') ->expect('App\Models') ->toExtend('Illuminate\Database\Eloquent\Model'); arch('no debugging') ->expect(['dd', 'dump', 'ray']) ->not->toBeUsed(); ### Type Coverage Pest 3 provides improved type coverage analysis. Run with `--type-coverage` flag. ## Common Pitfalls - Not importing `use function Pest\Laravel\mock;` before using mock - Using `assertStatus(200)` instead of `assertSuccessful()` - Forgetting datasets for repetitive validation tests - Deleting tests without approval ================================================ FILE: .claude/skills/tailwindcss-development/SKILL.md ================================================ --- name: tailwindcss-development description: >- Styles applications using Tailwind CSS v4 utilities. Activates when adding styles, restyling components, working with gradients, spacing, layout, flex, grid, responsive design, dark mode, colors, typography, or borders; or when the user mentions CSS, styling, classes, Tailwind, restyle, hero section, cards, buttons, or any visual/UI changes. --- # Tailwind CSS Development ## When to Apply Activate this skill when: - Adding styles to components or pages - Working with responsive design - Implementing dark mode - Extracting repeated patterns into components - Debugging spacing or layout issues ## Documentation Use `search-docs` for detailed Tailwind CSS v4 patterns and documentation. ## Basic Usage - Use Tailwind CSS classes to style HTML. Check and follow existing Tailwind conventions in the project before introducing new patterns. - Offer to extract repeated patterns into components that match the project's conventions (e.g., Blade, JSX, Vue). - Consider class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child elements carefully to reduce repetition, and group elements logically. ## Tailwind CSS v4 Specifics - Always use Tailwind CSS v4 and avoid deprecated utilities. - `corePlugins` is not supported in Tailwind v4. ### CSS-First Configuration In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed: @theme { --color-brand: oklch(0.72 0.11 178); } ### Import Syntax In Tailwind v4, import Tailwind with a regular CSS `@import` statement instead of the `@tailwind` directives used in v3: - @tailwind base; - @tailwind components; - @tailwind utilities; + @import "tailwindcss"; ### Replaced Utilities Tailwind v4 removed deprecated utilities. Use the replacements shown below. Opacity values remain numeric. | Deprecated | Replacement | |------------|-------------| | bg-opacity-* | bg-black/* | | text-opacity-* | text-black/* | | border-opacity-* | border-black/* | | divide-opacity-* | divide-black/* | | ring-opacity-* | ring-black/* | | placeholder-opacity-* | placeholder-black/* | | flex-shrink-* | shrink-* | | flex-grow-* | grow-* | | overflow-ellipsis | text-ellipsis | | decoration-slice | box-decoration-slice | | decoration-clone | box-decoration-clone | ## Spacing Use `gap` utilities instead of margins for spacing between siblings:
Item 1
Item 2
## Dark Mode If existing pages and components support dark mode, new pages and components must support it the same way, typically using the `dark:` variant:
Content adapts to color scheme
## Common Patterns ### Flexbox Layout
Left content
Right content
### Grid Layout
Card 1
Card 2
Card 3
## Common Pitfalls - Using deprecated v3 utilities (bg-opacity-*, flex-shrink-*, etc.) - Using `@tailwind` directives instead of `@import "tailwindcss"` - Trying to use `tailwind.config.js` instead of CSS `@theme` directive - Using margins for spacing between siblings instead of gap utilities - Forgetting to add dark mode variants when the project uses dark mode ================================================ FILE: .dockerignore ================================================ vendor bootstrap/cache/* **/*.log ================================================ FILE: .editorconfig ================================================ ; This file is for unifying the coding style for different editors and IDEs. ; More information at http://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 4 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false [*.{yml,yaml}] indent_size = 2 [docker-compose.yml] indent_size = 4 ================================================ FILE: .gitattributes ================================================ * text=auto *.blade.php diff=html *.css diff=css *.html diff=html *.md diff=markdown *.php diff=php /.github export-ignore CHANGELOG.md export-ignore ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ Please report bugs in Discord [![Discord](https://img.shields.io/discord/413623253366603777.svg)](https://discord.gg/rhsyZJ4). This github isn't used for bug tracking. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ Please send feature requests to the [Roadmap](https://app.kanka.io/roadmap). This github isn't used for feature requests. ================================================ FILE: .github/workflows/claude.yml ================================================ name: Claude Code on: issue_comment: types: [created] pull_request_review_comment: types: [created] issues: types: [opened, assigned] pull_request_review: types: [submitted] jobs: claude: if: | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) runs-on: ubuntu-latest permissions: contents: read pull-requests: read issues: read id-token: write actions: read # Required for Claude to read CI results on PRs steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 1 - name: Run Claude Code id: claude uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # This is an optional setting that allows Claude to read CI results on PRs additional_permissions: | actions: read # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. # prompt: 'Update the pull request description to include a summary of changes.' # Optional: Add claude_args to customize behavior and configuration # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md # or https://code.claude.com/docs/en/cli-reference for available options # claude_args: '--allowed-tools Bash(gh pr:*)' ================================================ FILE: .github/workflows/lint.yml ================================================ name: Fix Code Style & Duplication Check on: pull_request: branches: - main - develop paths: - '**.php' push: branches: - main paths: - '**.php' jobs: lint: runs-on: ubuntu-latest permissions: # Give the default GITHUB_TOKEN write permission to commit and push the # added or changed files to the repository. contents: write strategy: fail-fast: true matrix: php: [8.4] steps: - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} extensions: json, dom, curl, libxml, mbstring coverage: none - name: Install Pint run: composer global require laravel/pint - name: Run Pint run: pint # can't run this in a github action # - name: Run PHPCPD # run: ./vendor/bin/phpcpd app/ --min-lines=5 --min-tokens=70 - name: Auto-commit fixes uses: stefanzweifel/git-auto-commit-action@v6 with: commit_message: Fix styling ================================================ FILE: .github/workflows/phpstan.yml ================================================ name: "Tests" # Controls when the workflow will run on: # Triggers the workflow on push or pull request events but only for the "develop" branch pull_request: branches: [ "develop" ] paths: - '**.php' # Allows you to run this workflow manually from the Actions tab workflow_dispatch: permissions: contents: read # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: runs-on: ubuntu-latest strategy: fail-fast: true matrix: php: [8.4] laravel: ["^11.0"] name: PHP ${{ matrix.php }} # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout code uses: actions/checkout@v3 - name: Setup PHP uses: "shivammathur/setup-php@v2" with: php-version: "${{ matrix.php }}" extensions: "dom, curl, libxml, mbstring, zip, fileinfo, sqlite, pdo_sqlite" coverage: "none" - name: Install Composer dependencies run: composer install --prefer-dist --no-interaction --no-progress - name: "Install highest dependencies from composer.json" run: "composer install --no-interaction --no-progress" - name: "Execute static analysis" run: "composer run-script test:types" #- name: Generate app key # run: php artisan key:generate #- name: Execute tests # run: vendor/bin/phpunit ================================================ FILE: .gitignore ================================================ /node_modules /public/hot /public/build/js/tinymce /public/storage /public/.htaccess /public/exports /public/info.php /public/robots.txt /public/ads.txt #/public/images/defaults/patreon /storage/*.key /storage /vendor /.scannerwork /.idea /.vagrant /.vscode /.sail /.superpowers /.worktrees /docs/superpowers Homestead.json Homestead.yaml npm-debug.log yarn-error.log .env .env.local .DS_Store .phpunit.result.cache *.sql package-lock.json .phpstorm.meta.php _ide_helper.php test.json ================================================ FILE: .jshintrc ================================================ { "undef": true, "esversion": 11, "unused": true, "browser": true, "node": true, "globals": { "$": false, "document": false, "require": false, "jQuery": false, "CodeMirror": false, "Stripe": false } } ================================================ FILE: .mariadb/10-create-logs-database.sh ================================================ #!/usr/bin/env bash mysql --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL CREATE DATABASE IF NOT EXISTS logs; GRANT ALL PRIVILEGES ON \`logs%\`.* TO '$MYSQL_USER'@'%'; EOSQL ================================================ FILE: .mariadb/conf.d/kanka.cnf ================================================ [mysqld] max_allowed_packet=4G net_read_timeout=600 net_write_timeout=600 wait_timeout=28800 interactive_timeout=28800 connect_timeout=3600 ================================================ FILE: .mcp.json ================================================ { "mcpServers": { "laravel-boost": { "command": "vendor/bin/sail", "args": [ "artisan", "boost:mcp" ] } } } ================================================ FILE: .nginx/thumbor.conf ================================================ upstream thumbor { server thumbor:8888; } server { listen 80 default; server_name localhost; add_header 'Access-Control-Allow-Origin' '*'; add_header 'Access-Control-Allow-Credentials' 'true'; add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Mx-ReqToken,X-Requested-With'; if ($request_method = 'OPTIONS') { return 204; } location ~* "^/(..)(..)(.+)?.jpg$" { proxy_pass http://thumbor$request_uri; } location ~* "^/(..)(..)(.+)?.jpeg$" { proxy_pass http://thumbor$request_uri; } location ~* "^/(..)(..)(.+)?.png" { proxy_pass http://thumbor$request_uri; } location ~ /\.ht { deny all; } location ~ /\.hg { deny all; } location ~ /\.svn { deny all; } } ================================================ FILE: .phpactor.json ================================================ { "$schema": "/phpactor.schema.json", "language_server_phpstan.enabled": true, "php_code_sniffer.enabled": true } ================================================ FILE: CLAUDE.md ================================================ === === foundation rules === # Laravel Boost Guidelines The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to ensure the best experience when building Laravel applications. ## Foundational Context This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions. - php - 8.4.14 - laravel/cashier (CASHIER) - v15 - laravel/framework (LARAVEL) - v11 - laravel/passport (PASSPORT) - v13 - laravel/prompts (PROMPTS) - v0 - laravel/reverb (REVERB) - v1 - laravel/scout (SCOUT) - v10 - laravel/socialite (SOCIALITE) - v5 - livewire/livewire (LIVEWIRE) - v3 - larastan/larastan (LARASTAN) - v3 - laravel/mcp (MCP) - v0 - laravel/pint (PINT) - v1 - laravel/sail (SAIL) - v1 - pestphp/pest (PEST) - v3 - phpunit/phpunit (PHPUNIT) - v11 - laravel-echo (ECHO) - v2 - tailwindcss (TAILWINDCSS) - v4 - vue (VUE) - v3 ## Skills Activation This project has domain-specific skills available. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck. - `livewire-development` — Develops reactive Livewire 3 components. Activates when creating, updating, or modifying Livewire components; working with wire:model, wire:click, wire:loading, or any wire: directives; adding real-time updates, loading states, or reactivity; debugging component behavior; writing Livewire tests; or when the user mentions Livewire, component, counter, or reactive UI. - `pest-testing` — No testing. No automated testing. Don't try running pest or phpunit. - `tailwindcss-development` — Styles applications using Tailwind CSS v4 utilities. Activates when adding styles, restyling components, working with gradients, spacing, layout, flex, grid, responsive design, dark mode, colors, typography, or borders; or when the user mentions CSS, styling, classes, Tailwind, restyle, hero section, cards, buttons, or any visual/UI changes. ## Conventions - You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming. - Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`. - Check for existing components to reuse before writing a new one. ## Verification Scripts - Do not create verification scripts or tinker when tests cover that functionality and prove they work. Unit and feature tests are more important. ## Application Structure & Architecture - Stick to existing directory structure; don't create new base folders without approval. - Do not change the application's dependencies without approval. ## Frontend Bundling - If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `vendor/bin/sail yarn run build`, `vendor/bin/sail yarn run dev`, or `vendor/bin/sail composer run dev`. Never ask them to run these commands, but remind them to do it in your closing summary. ## Documentation Files - You must only create documentation files if explicitly requested by the user. ## Replies - Be concise in your explanations - focus on what's important rather than explaining obvious details. === boost rules === # Laravel Boost - Laravel Boost is an MCP server that comes with powerful tools designed specifically for this application. Use them. ## Artisan - Use the `list-artisan-commands` tool when you need to call an Artisan command to double-check the available parameters. ## URLs - Whenever you share a project URL with the user, you should use the `get-absolute-url` tool to ensure you're using the correct scheme, domain/IP, and port. ## Tinker / Debugging - You should use the `tinker` tool when you need to execute PHP to debug code or query Eloquent models directly. - Use the `database-query` tool when you only need to read from the database. ## Reading Browser Logs With the `browser-logs` Tool - You can read browser logs, errors, and exceptions using the `browser-logs` tool from Boost. - Only recent browser logs will be useful - ignore old logs. ## Searching Documentation (Critically Important) - Boost comes with a powerful `search-docs` tool you should use before trying other approaches when working with Laravel or Laravel ecosystem packages. This tool automatically passes a list of installed packages and their versions to the remote Boost API, so it returns only version-specific documentation for the user's circumstance. You should pass an array of packages to filter on if you know you need docs for particular packages. - Search the documentation before making code changes to ensure we are taking the correct approach. - Use multiple, broad, simple, topic-based queries at once. For example: `['rate limiting', 'routing rate limiting', 'routing']`. The most relevant results will be returned first. - Do not add package names to queries; package information is already shared. For example, use `test resource table`, not `filament 4 test resource table`. ### Available Search Syntax 1. Simple Word Searches with auto-stemming - query=authentication - finds 'authenticate' and 'auth'. 2. Multiple Words (AND Logic) - query=rate limit - finds knowledge containing both "rate" AND "limit". 3. Quoted Phrases (Exact Position) - query="infinite scroll" - words must be adjacent and in that order. 4. Mixed Queries - query=middleware "rate limit" - "middleware" AND exact phrase "rate limit". 5. Multiple Queries - queries=["authentication", "middleware"] - ANY of these terms. === php rules === # PHP - Always use curly braces for control structures, even for single-line bodies. ## Constructors - Use PHP 8 constructor property promotion in `__construct()`. - public function __construct(public GitHub $github) { } - Do not allow empty `__construct()` methods with zero parameters unless the constructor is private. ## Type Declarations - Always use explicit return type declarations for methods and functions. - Use appropriate PHP type hints for method parameters. protected function isAccessible(User $user, ?string $path = null): bool { ... } ## Enums - Typically, keys in an Enum should be TitleCase. For example: `FavoritePerson`, `BestLake`, `Monthly`. ## Comments - Prefer PHPDoc blocks over inline comments. Never use comments within the code itself unless the logic is exceptionally complex. ## PHPDoc Blocks - Add useful array shape type definitions when appropriate. === sail rules === # Laravel Sail - This project runs inside Laravel Sail's Docker containers. You MUST execute all commands through Sail. - Start services using `vendor/bin/sail up -d` and stop them with `vendor/bin/sail stop`. - Open the application in the browser by running `vendor/bin/sail open`. - Always prefix PHP, Artisan, Composer, and Node commands with `vendor/bin/sail`. Examples: - Run Artisan Commands: `vendor/bin/sail artisan migrate` - Install Composer packages: `vendor/bin/sail composer install` - Execute Node commands: `vendor/bin/sail yarn run dev` - Execute PHP scripts: `vendor/bin/sail php [script]` - View all available Sail commands by running `vendor/bin/sail` without arguments. === laravel/core rules === # Do Things the Laravel Way - Use `vendor/bin/sail artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using the `list-artisan-commands` tool. - If you're creating a generic PHP class, use `vendor/bin/sail artisan make:class`. - Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior. ## Database - Always use proper Eloquent relationship methods with return type hints. Prefer relationship methods over raw queries or manual joins. - Use Eloquent models and relationships before suggesting raw database queries. - Avoid `DB::`; prefer `Model::query()`. Generate code that leverages Laravel's ORM capabilities rather than bypassing them. - Generate code that prevents N+1 query problems by using eager loading. - Use Laravel's query builder for very complex database operations. ### Model Creation - When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `list-artisan-commands` to check the available options to `vendor/bin/sail artisan make:model`. ### APIs & Eloquent Resources - For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention. ## Controllers & Validation - Always create Form Request classes for validation rather than inline validation in controllers. Include both validation rules and custom error messages. - Check sibling Form Requests to see if the application uses array or string based validation rules. ## Authentication & Authorization - Use Laravel's built-in authentication and authorization features (gates, policies, Sanctum, etc.). ## URL Generation - When generating links to other pages, prefer named routes and the `route()` function. ## Queues - Use queued jobs for time-consuming operations with the `ShouldQueue` interface. ## Configuration - Use environment variables only in configuration files - never use the `env()` function directly outside of config files. Always use `config('app.name')`, not `env('APP_NAME')`. ## Vite Error - If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `vendor/bin/sail yarn run build` or ask the user to run `vendor/bin/sail yarn run dev` or `vendor/bin/sail composer run dev`. === laravel/v11 rules === # Laravel 11 - CRITICAL: ALWAYS use `search-docs` tool for version-specific Laravel documentation and updated code examples. - This project upgraded from Laravel 10 without migrating to the new streamlined Laravel 11 file structure. - This is perfectly fine and recommended by Laravel. Follow the existing structure from Laravel 10. We do not need to migrate to the Laravel 11 structure unless the user explicitly requests it. ## Laravel 10 Structure - Middleware typically lives in `app/Http/Middleware/` and service providers in `app/Providers/`. - There is no `bootstrap/app.php` application configuration in a Laravel 10 structure: - Middleware registration is in `app/Http/Kernel.php` - Exception handling is in `app/Exceptions/Handler.php` - Console commands and schedule registration is in `app/Console/Kernel.php` - Rate limits likely exist in `RouteServiceProvider` or `app/Http/Kernel.php` ## Database - When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost. - Laravel 11 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`. ### Models - Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models. ## New Artisan Commands - List Artisan commands using Boost's MCP tool, if available. New commands available in Laravel 11: - `vendor/bin/sail artisan make:enum` - `vendor/bin/sail artisan make:class` - `vendor/bin/sail artisan make:interface` === livewire/core rules === # Livewire - Livewire allows you to build dynamic, reactive interfaces using only PHP — no JavaScript required. - Instead of writing frontend code in JavaScript frameworks, you use Alpine.js to build the UI when client-side interactions are required. - State lives on the server; the UI reflects it. Validate and authorize in actions (they're like HTTP requests). - IMPORTANT: Activate `livewire-development` every time you're working with Livewire-related tasks. === pint/core rules === # Laravel Pint Code Formatter - You must run `vendor/bin/sail bin pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style. - Do not run `vendor/bin/sail bin pint --test --format agent`, simply run `vendor/bin/sail bin pint --format agent` to fix any formatting issues. === pest/core rules === ## Pest - This project has no automated tests. Don't bother with them. === tailwindcss/core rules === # Tailwind CSS - Always use existing Tailwind conventions; check project patterns before adding new ones. - IMPORTANT: Always use `search-docs` tool for version-specific Tailwind CSS documentation and updated code examples. Never rely on training data. - IMPORTANT: Activate `tailwindcss-development` every time you're working with a Tailwind CSS or styling-related task. # i18n The app is translated in several languages by community members, but is developed in english. Never hardcode translations; use the `__()` function instead. ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@kanka.io. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: LICENSE ================================================ “Commons Clause” License Condition v1.0 The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition. Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software. For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice. Software: Kanka Licensor: Owlchester SNC ================================================ FILE: README.md ================================================ # Kanka # [![Kanka](./.github/logo.png)](https://kanka.io/en-US) [![Minimum PHP Version](http://img.shields.io/badge/php-%3E%3D%208.0-8892BF.svg)](https://php.net/) [![Discord](https://img.shields.io/discord/413623253366603777.svg)](https://kanka.io/go/discord) [Kanka](https://kanka.io/en-US) is a **source available** collaborative world building and campaign management tool tailored for tabletop RPG players and storytellers. This repository is the primary source for development of the Kanka platform. It's written in PHP, a mix of VueJS and jQuery, and runs with a MySQL database. A new version is released on average every a month. kanka-hero This repository hosts the core Kanka web application. ## Install Kanka - [Installing & Running Kanka](./docs/running.md). ## Getting involved - [Contribute to Kanka](./docs/contributing.md) - [Translating Kanka](./docs/translating.md) ## Technical guides - [Compiling assets](./docs/assets.md) ## Support and Community Issues are inevitable. When you encounter one when using Kanka, our team and community is here to help. 💬 Ask for help on [Discord](https://kanka.io/go/discord) 🏛️ Find a solution in our [Documentation](https://docs.kanka.io) 📧 Shoot us an email at [hello@kanka.io](mailto:hello@kanka.io) ================================================ FILE: app/Auth/PassportTokenGuard.php ================================================ getPsrRequestViaBearerToken()) { return null; } $client = $this->clients->findActive( $psr->getAttribute('oauth_client_id') ); if (! $client || ($client->provider && $client->provider !== $this->provider->getProviderName())) { return null; } $this->setClient($client); $oauthUserId = $psr->getAttribute('oauth_user_id'); if (empty($oauthUserId)) { return null; } // Passport 13.7 added a check: if oauth_user_id === oauth_client_id, reject // as a client credentials token. However, this is a false positive when the // user's primary key happens to equal the client's ID. We verify via the DB. if ($oauthUserId === $psr->getAttribute('oauth_client_id')) { $tokenId = $psr->getAttribute('oauth_access_token_id'); $token = Token::find($tokenId); if (! $token || $token->user_id === null) { return null; } } try { $user = $this->provider->retrieveById($oauthUserId); } catch (\Exception) { return null; } return $user?->withAccessToken(AccessToken::fromPsrRequest($psr)); } } ================================================ FILE: app/Console/Commands/Campaigns/CleanupCommand.php ================================================ info(Carbon::now()); Campaign::observe(CampaignObserver::class); $dry = $this->argument('dry'); if ($dry === '0') { $this->service->real(); } $count = $this->service->purgeEmpty(); if ($dry === '0') { $this->info(Carbon::now() . ': Deleted ' . $count . ' empty campaigns.'); } else { $this->info(Carbon::now() . ': There are ' . $count . ' empty campaigns that can be deleted.'); } } } ================================================ FILE: app/Console/Commands/Campaigns/DeleteCommand.php ================================================ argument('campaign'); $campaign = Campaign::where('id', $campaignId)->first(); if ($campaign) { Delete::dispatch($campaign); DeletedCampaignCleanupJob::dispatch($campaign); $this->info('Queued campaign #' . $campaignId . ' for deletion'); } else { $this->info('Invalid campaign ID'); } return 0; } } ================================================ FILE: app/Console/Commands/Campaigns/DummyEntities.php ================================================ argument('campaign'); $campaign = Campaign::findOrFail($campaignId); CampaignCache::campaign($campaign); EntityCache::campaign($campaign); CharacterCache::campaign($campaign); QuestCache::campaign($campaign); $this->loadObservers($campaign); // Generate Characters Abilities and Locations $firstLocation = Location::factory() ->state(['name' => 'Thaelia', 'campaign_id' => $campaign->id]) ->has( Character::factory()->state(['campaign_id' => $campaign->id]) ->has(Item::factory()->state(['name' => 'Sword of Cebolla', 'campaign_id' => $campaign->id, 'price' => rand(1, 15) . 'g'])) ) ->has(Location::factory() ->state(['name' => 'March', 'campaign_id' => $campaign->id]) ->has(Location::factory() ->state(['name' => 'Adestry', 'campaign_id' => $campaign->id]))) ->has(Location::factory() ->state(['name' => 'Tilley', 'campaign_id' => $campaign->id]) ->has(Location::factory() ->state(['name' => 'Carrothead', 'campaign_id' => $campaign->id]))) ->has(Character::factory()->state(['campaign_id' => $campaign->id])->count(2)) ->has(Location::factory()->state(['name' => 'Orlene', 'campaign_id' => $campaign->id])) ->has(Location::factory() ->state(['name' => 'Owlchester', 'campaign_id' => $campaign->id])) ->create(); $secondLocation = Location::factory() ->state(['name' => 'Medina', 'campaign_id' => $campaign->id]) ->has(Location::factory()->count(2)->state(new Sequence( ['name' => 'Torchio', 'campaign_id' => $campaign->id], ['name' => 'Urdino', 'campaign_id' => $campaign->id] ))) ->has(Character::factory()->state(['campaign_id' => $campaign->id])) ->has(Character::factory()->state(['campaign_id' => $campaign->id]) ->has(Item::factory()->state(['name' => 'Dagger of Longaniza', 'campaign_id' => $campaign->id, 'price' => rand(1, 15) . 'g']))) ->create(); $thirdLocation = Location::factory()->state(['campaign_id' => $campaign->id, 'name' => 'Middle Earth'])->create(); // Generate Characters $firstCharacter = Character::factory()->state(['campaign_id' => $campaign->id, 'name' => 'Biblo Swaggins'])->create(); $secondCharacter = Character::factory()->state(['campaign_id' => $campaign->id])->create(); $thirdCharacter = Character::factory()->state(['campaign_id' => $campaign->id])->create(); $fourthCharacter = Character::factory()->state(['campaign_id' => $campaign->id])->create(); $fifthCharacter = Character::factory()->state(['campaign_id' => $campaign->id])->create(); Ability::factory()->state(['name' => 'Loud shout', 'campaign_id' => $campaign->id])->has(EntityAbility::factory()->state(['entity_id' => $firstCharacter->entity->id]), 'ability')->create(); Attribute::factory()->count(7)->state( new Sequence( ['name' => 'Population', 'entity_id' => $firstLocation->entity->id, 'is_pinned' => 1], ['name' => 'Population', 'entity_id' => $secondLocation->entity->id, 'is_pinned' => 1], ['name' => 'Population', 'entity_id' => $thirdLocation->entity->id, 'is_pinned' => 1], ['name' => 'HP', 'value' => rand(1, 20), 'entity_id' => $firstCharacter->entity->id, 'is_pinned' => 1], ['name' => 'Level', 'value' => rand(1, 20), 'entity_id' => $firstCharacter->entity->id, 'is_pinned' => 1], ['name' => 'HP', 'value' => rand(1, 20), 'entity_id' => $secondCharacter->entity->id, 'is_pinned' => 1], ['name' => 'Level', 'value' => rand(1, 20), 'entity_id' => $secondCharacter->entity->id, 'is_pinned' => 1], ) ) ->create(); // Generate Families Family::factory() ->state(['name' => 'Graff', 'campaign_id' => $campaign->id]) ->has(Family::factory()->state(['name' => 'Market', 'campaign_id' => $campaign->id])) ->create(); Family::factory()->state(['name' => 'Joren', 'campaign_id' => $campaign->id])->create(); // Generate Organisations Organisation::factory() ->state(['name' => 'Kankappy Cult', 'campaign_id' => $campaign->id]) ->has(Organisation::factory()->state(['name' => 'Fun Police', 'campaign_id' => $campaign->id])) ->create(); Organisation::factory()->state(['name' => 'Great Reset', 'campaign_id' => $campaign->id])->create(); // Generate Events Event::factory()->count(4)->state( new Sequence( ['name' => 'The Great War', 'campaign_id' => $campaign->id], ['name' => 'Northern Rebellion', 'campaign_id' => $campaign->id], ['name' => 'Peace of the Sea', 'campaign_id' => $campaign->id], ['name' => 'Royal Wedding', 'campaign_id' => $campaign->id], ) ) ->create(); // Generate Items Item::factory()->count(5)->state( new Sequence( ['name' => 'Bow', 'campaign_id' => $campaign->id], ['name' => 'Crowbar', 'campaign_id' => $campaign->id], ['name' => 'Shield', 'campaign_id' => $campaign->id], ['name' => 'Sword', 'campaign_id' => $campaign->id], ['name' => 'Potion', 'campaign_id' => $campaign->id], ) ) ->create(); // Generate Notes Note::factory()->count(3)->state( new Sequence( ['name' => 'Aromas of Geneva', 'campaign_id' => $campaign->id], ['name' => 'Pottery Stacking', 'campaign_id' => $campaign->id], ['name' => 'Making Friends', 'campaign_id' => $campaign->id], ) ) ->create(); // Generate Races Race::factory() ->state(['name' => 'Elf', 'campaign_id' => $campaign->id]) ->has(Race::factory() ->state(['name' => 'Wood elf', 'campaign_id' => $campaign->id]) ->has(Race::factory() ->state(['name' => 'Leaf elf', 'campaign_id' => $campaign->id]))) ->has(Race::factory()->state(['name' => 'High elf', 'campaign_id' => $campaign->id])) ->create(); Race::factory()->count(2)->state( new Sequence( ['name' => 'Human', 'campaign_id' => $campaign->id], ['name' => 'Owlbear', 'campaign_id' => $campaign->id], ) ) ->create(); // Generate Tags Tag::factory()->count(3)->state( new Sequence( ['name' => '🧛🏻‍♂️', 'colour' => 'maroon', 'campaign_id' => $campaign->id], ['name' => 'Important', 'colour' => 'aqua', 'campaign_id' => $campaign->id], ['name' => 'NPC', 'colour' => 'grey', 'campaign_id' => $campaign->id], ) ) ->create(); // Generate Quests $itemFirstQuest = Item::factory()->state(['campaign_id' => $campaign->id])->create(); Quest::factory()->state(['name' => 'Salary Negotiations', 'campaign_id' => $campaign->id]) ->has(QuestElement::factory()->state(['name' => 'Main Character', 'entity_id' => $firstCharacter->entity->id, 'created_by' => $campaign->created_by]), 'elements') ->has(QuestElement::factory()->state(['name' => 'MacGuffin', 'entity_id' => $itemFirstQuest->entity->id, 'created_by' => $campaign->created_by]), 'elements') ->create(); Quest::factory()->state(['name' => 'Fixin Bugs', 'campaign_id' => $campaign->id]) ->create(); // Generate Journals Journal::factory()->count(2)->state( new Sequence( ['name' => 'Bilbo\'s journey to middle earth', 'campaign_id' => $campaign->id, 'author_id' => $firstCharacter->entity->id], ['name' => 'The tree rings', 'campaign_id' => $campaign->id], ) ) ->create(); // Generate Calendars Calendar::factory()->state([ 'name' => 'Gregorian', 'campaign_id' => $campaign->id, 'months' => '[{"name":"January","length":31,"type":"standard","alias":""},{"name":"February","length":28,"type":"standard","alias":""},{"name":"March","length":31,"type":"standard","alias":""},{"name":"April","length":30,"type":"standard","alias":""},{"name":"Mai","length":31,"type":"standard","alias":""},{"name":"June","length":30,"type":"standard","alias":""},{"name":"July","length":31,"type":"standard","alias":""},{"name":"August","length":31,"type":"standard","alias":""},{"name":"September","length":30,"type":"standard","alias":""},{"name":"October","length":31,"type":"standard","alias":""},{"name":"November","length":30,"type":"standard","alias":""},{"name":"December","length":31,"type":"standard","alias":""}]', 'weekdays' => '["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]', 'seasons' => '[{"name":"Spring","month":3,"day":21},{"name":"Summer","month":6,"day":21},{"name":"Autumn","month":9,"day":21},{"name":"Winter","month":12,"day":21}]', 'suffix' => 'AD', 'has_leap_year' => 1, 'leap_year_amount' => 1, 'leap_year_month' => 2, 'leap_year_offset' => 4, 'leap_year_start' => 4, 'start_offset' => 5, 'is_incrementing' => 1, 'date' => Carbon::now()->toDateString(), ]) ->create(); // Generate Relations $firstRelation = Relation::factory()->state(['relation' => 'Best Friend', 'campaign_id' => $campaign->id, 'owner_id' => $secondCharacter->entity->id, 'target_id' => $thirdCharacter->entity->id])->create(); Relation::factory()->state(['relation' => 'Mortal Enemy', 'campaign_id' => $campaign->id, 'owner_id' => $thirdCharacter->entity->id, 'target_id' => $secondCharacter->entity->id])->for($firstRelation, 'mirror')->create(); $secondRelation = Relation::factory()->state(['relation' => 'Best Friend', 'campaign_id' => $campaign->id, 'owner_id' => $secondCharacter->entity->id, 'target_id' => $thirdCharacter->entity->id])->create(); Relation::factory()->state(['relation' => 'Mortal Enemy', 'campaign_id' => $campaign->id, 'owner_id' => $thirdCharacter->entity->id, 'target_id' => $secondCharacter->entity->id])->for($secondRelation, 'mirror')->create(); return 0; } /** * Load observers. */ private function loadObservers(Campaign $campaign) { CampaignLocalization::forceCampaign($campaign); Ability::observe(AbilityObserver::class); Location::observe(LocationObserver::class); Calendar::observe(CalendarObserver::class); Quest::observe(QuestObserver::class); QuestElement::observe(QuestElementObserver::class); Relation::observe(RelationObserver::class); Journal::observe(JournalObserver::class); Event::observe(EventObserver::class); Tag::observe(TagObserver::class); Race::observe(RaceObserver::class); Note::observe(NoteObserver::class); EntityAbility::observe(EntityAbilityObserver::class); } } ================================================ FILE: app/Console/Commands/Campaigns/ExportCommand.php ================================================ info(Carbon::now()); $campaignID = $this->argument('campaign'); $campaign = Campaign::find($campaignID); $user = User::find(1); $this->service ->campaign($campaign) ->user($user) ->queue(); $this->info(Carbon::now() . ': Queues campaign #' . $campaign->id . ' export.'); } } ================================================ FILE: app/Console/Commands/Campaigns/ImportCommand.php ================================================ argument('campaign'); $campaign = Campaign::find($campaignID); $job = CampaignImport::where('campaign_id', $campaign->id)->orderBy('created_at', 'DESC')-> where('status_id', '<>', 1)->first(); if (! $job) { $this->info('No job for campaign ' . $campaign->id); return; } $job->status_id = CampaignImportStatus::QUEUED; $job->saveQuietly(); Import::dispatch($job); $this->info('Re-queued campaign ' . $campaign->id); } } ================================================ FILE: app/Console/Commands/Campaigns/PermissionsSyncCommand.php ================================================ argument('campaign')); if (! $campaign) { $this->error('Campaign not found.'); return 1; } $this->info("Campaign: {$campaign->name}"); /** @var Collection $roles */ $roles = CampaignRole::where('campaign_id', $campaign->id)->get(); if ($roles->count() < 2) { $this->error('Campaign must have at least two roles to sync permissions.'); return 1; } $roleChoices = $roles->mapWithKeys(fn (CampaignRole $role) => [$role->id => $role->name])->all(); $sourceName = $this->choice('Select the source role (permissions will be copied from)', $roleChoices); $sourceRole = $roles->firstWhere('name', $sourceName); $targetName = $this->choice('Select the target role (permissions will be copied to)', $roleChoices); $targetRole = $roles->firstWhere('name', $targetName); if ($sourceRole->id === $targetRole->id) { $this->error('Source and target roles must be different.'); return 1; } $existingPermissionsCount = CampaignPermission::where('campaign_role_id', $targetRole->id)->count(); if ($existingPermissionsCount > 0) { if (! $this->confirm("The target role \"{$targetRole->name}\" already has {$existingPermissionsCount} permission(s). Clear them before syncing?")) { $this->info('Sync cancelled.'); return 0; } CampaignPermission::where('campaign_role_id', $targetRole->id)->delete(); $this->info("Cleared {$existingPermissionsCount} existing permission(s) from \"{$targetRole->name}\"."); } $sourceRole->duplicate($targetRole); $createdCount = CampaignPermission::where('campaign_role_id', $targetRole->id)->count(); $this->info("Successfully copied {$createdCount} permission(s) from \"{$sourceRole->name}\" to \"{$targetRole->name}\"."); return 0; } } ================================================ FILE: app/Console/Commands/Campaigns/PopulateCommand.php ================================================ starterService = $starterService; parent::__construct(); } /** * Execute the console command. */ public function handle() { $campaignId = $this->argument('campaign'); $campaign = Campaign::where('id', $campaignId)->first(); if ($campaign) { Populate::dispatch($campaign); } else { $this->info('Invalid campaign ID'); } } } ================================================ FILE: app/Console/Commands/Campaigns/VisibileEntityCountCommand.php ================================================ chunk(1000, function ($campaigns): void { /** @var Campaign $campaign */ foreach ($campaigns as $campaign) { $this->count++; $this->countService->campaign($campaign)->process(); } }); $log = "Updated {$this->count} public campaigns."; $this->info($log); $this->log($log); } } ================================================ FILE: app/Console/Commands/Cleanup/AnonymiseUserLogs.php ================================================ service->anonymize(); $log = 'Cleaned up ' . $this->service->count() . ' logs PII.'; $this->info($log); $this->log($log); return 0; } } ================================================ FILE: app/Console/Commands/Cleanup/CleanupEntityLogs.php ================================================ subDays($amount)->toDateString()) ->whereNotNull('changes') ->chunkById(100, function ($models): void { $entityIds = []; foreach ($models as $model) { $entityIds[] = $model->id; $this->count++; } $statement = 'UPDATE entity_logs SET changes = null WHERE id in(' . implode(', ', $entityIds) . ') limit ' . count($entityIds); DB::statement($statement); }); $log = "Cleaned up {$this->count} entity logs."; $this->info($log); $this->log($log); return 0; } } ================================================ FILE: app/Console/Commands/Cleanup/CleanupImages.php ================================================ argument('folder'); $this->max = (int) $this->argument('max'); $dry = $this->argument('dry'); if ($dry === '1') { $this->dry = false; } $this->info('Cleaning up ' . $folder . '/'); if ($this->dry) { $this->warn('This is a dry run. Nothing will get deleted.'); } $directories = Storage::directories($folder . '/'); $chunks = array_chunk($directories, 500); foreach ($chunks as $chunk) { $ids = []; foreach ($chunk as $path) { $ids[] = Str::after($path, $folder . '/'); } // Get ids where a left join on the campaigns table has no result $select = 'with u(id) as (values (' . implode('), (', $ids) . ')) ' . 'select u.id from u ' . 'left join campaigns as c on c.id = u.id ' . 'where c.id is null'; $db = DB::select($select); $nullCampaigns = []; foreach ($db as $campaign) { $nullCampaigns[] = $campaign->id; } if (empty($nullCampaigns)) { continue; } foreach ($nullCampaigns as $id) { if (empty($id)) { continue; } if (! $this->dry) { $files = Storage::allFiles($folder . '/' . $id); if (! empty($files)) { Storage::delete($files); } Storage::deleteDirectory($folder . '/' . $id); } $this->count++; } if ($this->dry) { $this->info('Would delete ' . $this->count . ' images/folders.'); $this->info(implode(',', $nullCampaigns)); } if ($this->count > $this->max) { $this->info('Reached max amount of ' . $this->max); return; } } if ($this->dry) { return; } $this->info('Deleted ' . $this->count . ' images/folders.'); } } ================================================ FILE: app/Console/Commands/Cleanup/CleanupTrashed.php ================================================ service = $service; $this->postService = $postService; } /** * Execute the console command. */ public function handle() { $delay = Carbon::now()->subDays(config('entities.hard_delete'))->toDateString(); $log = ''; $this->info('Looking to purge entities and posts deleted since ' . $delay); DB::beginTransaction(); try { Entity::onlyTrashed() ->where('deleted_at', '<=', $delay) ->allCampaigns() ->with(['campaign', 'entityType']) ->has('campaign') ->chunkById(1000, function ($entities): void { $this->info('Chunk deleting ' . count($entities) . ' entities.'); foreach ($entities as $entity) { // dump($entity->name . ' (' . $entity->entityType->code . ')'); $this->service->trash($entity); } }); Post::onlyTrashed() ->where('deleted_at', '<=', $delay) ->chunkById(1000, function ($posts): void { $this->info('Chunk deleting ' . count($posts) . ' posts.'); /** @var Post $post */ foreach ($posts as $post) { $this->postService->trash($post); } }); DB::commit(); } catch (Exception $e) { $this->error($e->getMessage()); $log .= '
' . $e->getMessage(); DB::rollBack(); } $this->info(''); $this->info('Deleted ' . $this->service->count() . ' trashed entities.'); $log .= "\n" . 'Deleted ' . $this->service->count() . ' trashed entities.'; $this->info('Deleted ' . $this->postService->count() . ' trashed posts.'); $log .= "\n" . 'Deleted ' . $this->postService->count() . ' trashed posts.'; $this->log($log); return 0; } } ================================================ FILE: app/Console/Commands/Cleanup/CleanupTrashedCampaigns.php ================================================ service->purgeDeleted(); $log = 'Deleted ' . $count . ' trashed campaigns.'; $this->info($log); $this->log($log . ' ' . implode(',', $this->service->ids())); return 0; } } ================================================ FILE: app/Console/Commands/Cleanup/CleanupUsers.php ================================================ info(Carbon::now()); $dry = $this->argument('dry'); if ($dry === '0') { $this->service->real(); } $limit = (int) $this->argument('limit'); $this->service->limit($limit); /* $cutoff = Carbon::now()->subYears(1); $count = $service->date($cutoff)->empty(); $this->info(Carbon::now() . ': Empty scheduled ' . $count . ' users for cleanup.'); $cutoff = Carbon::now()->subYears(2); $count = $service->date($cutoff)->example(); $this->info(Carbon::now() . ': Example scheduled ' . $count . ' users for cleanup.'); */ $count = $this->service->firstWarning(); $this->info(Carbon::now() . ': ' . $count . ' inactive users notified (first warning)'); $count = $this->service->secondWarning(); $this->info(Carbon::now() . ': ' . $count . ' inactive users notified (second warning)'); $count = $this->service->purge(); $this->info(Carbon::now() . ': ' . $count . ' inactive users purged'); } } ================================================ FILE: app/Console/Commands/Entities/CalendarAdvancer.php ================================================ chunkById(500, function ($calendars): void { /** @var Calendar $calendar */ foreach ($calendars as $calendar) { try { $this->service->calendar($calendar)->advance(); // Consoles don't have observers at the moment because Jay makes terrible life choices CalendarsClearElapsed::dispatch($calendar); $this->count++; } catch (Exception $e) { $this->errors[$calendar->id] = $e->getMessage(); } } }); $log = "Advanced {$this->count} calendars."; $this->info($log); if (! empty($this->errors)) { $this->error('Errors for ' . count($this->errors) . ' calendars.'); $this->error(implode(', ', array_keys($this->errors))); $log .= "\n" . 'Errors for ' . count($this->errors) . ' calendars.'; $log .= "\n" . implode(', ', array_keys($this->errors)); } $this->log($log); return 0; } } ================================================ FILE: app/Console/Commands/Entities/ChildlessEntities.php ================================================ $id) { $entities[] = $type; } foreach ($entities as $type) { $plural = Str::plural($type); $this->info('checking ' . $plural); Entity::select('entities.*') ->where('entities.type', $type) ->leftJoin($plural . ' as f', 'f.id', 'entities.entity_id') ->whereNull('f.id') ->with(Str::camel($type)) ->chunk(1000, function ($models) use ($type): void { foreach ($models as $model) { $this->trace($model, $type); } if (! empty($this->traces)) { $this->info(implode(', ', $this->traces)); $this->traces = []; } }); $this->info(''); } $this->warn('Found ' . $this->total . ' childless entities.'); return 0; } protected function trace(Entity $entity, string $type): void { $this->traces[] = $entity->id; $this->total++; /*if (count($this->traces) > 100) { $this->info(implode(', ', $this->traces)); $this->traces = []; }*/ } } ================================================ FILE: app/Console/Commands/Entities/MapChunk.php ================================================ argument('map'); $map = Map::find($mapID); if (empty($map)) { $this->error('Unknown map #' . $mapID); return 0; } $this->dispatch($mapID); $this->info('ChunkMapJob queues for map #' . $mapID); return 0; } /** * @return PendingDispatch */ protected function dispatch(int $mapID) { return ChunkMapJob::dispatch($mapID); } } ================================================ FILE: app/Console/Commands/InstallCommand.php ================================================ error('Kanka has already been installed.'); $this->info('Check it out at ' . config('app.url') . ':' . env('APP_PORT')); return; } } catch (Exception) { } $this->call('key:generate'); $this->call('migrate'); $this->call('db:seed'); $this->call('passport:install'); $this->info('Kanka successfully installed.'); $this->info('Check it out at ' . config('app.url') . ':' . env('APP_PORT')); } } ================================================ FILE: app/Console/Commands/Metrics/MetricsGa4.php ================================================ option('days'); $credentialsPath = config('tracking.ga_credential_path'); $propertyId = config('tracking.ga_property_id'); if (empty($credentialsPath) || empty($propertyId)) { $this->error('GA4_CREDENTIALS_PATH and GA4_PROPERTY_ID must be configured.'); return Command::FAILURE; } $client = new BetaAnalyticsDataClient(['credentials' => $credentialsPath]); $property = "properties/{$propertyId}"; $dateRange = new DateRange([ 'start_date' => "{$days}daysAgo", 'end_date' => 'today', ]); $startDate = now()->subDays($days)->format('Y-m-d'); $endDate = now()->format('Y-m-d'); $sections = [ '# GA4 Metrics Report', "_Date range: {$startDate} — {$endDate} ({$days} days)_", $this->homepageSessions($client, $property, $dateRange), $this->registerPageSources($client, $property, $dateRange), $this->registerCtaClicks($client, $property, $dateRange), $this->scrollDepth($client, $property, $dateRange), ]; $report = implode("\n\n", $sections); $filename = 'metrics/ga4-' . now()->format('Y-m-d') . '.md'; Storage::put($filename, $report); $this->line($report); $this->newLine(); $this->info("Saved to storage/app/{$filename}"); return Command::SUCCESS; } private function homepageSessions(BetaAnalyticsDataClient $client, string $property, DateRange $dateRange): string { $response = $client->runReport(new RunReportRequest([ 'property' => $property, 'date_ranges' => [$dateRange], 'metrics' => [ new Metric(['name' => 'sessions']), new Metric(['name' => 'engagementRate']), ], 'dimension_filter' => new FilterExpression([ 'and_group' => new FilterExpressionList([ 'expressions' => [ new FilterExpression([ 'filter' => new Filter([ 'field_name' => 'pagePath', 'string_filter' => new StringFilter([ 'match_type' => MatchType::EXACT, 'value' => '/', ]), ]), ]), new FilterExpression([ 'not_expression' => new FilterExpression([ 'filter' => new Filter([ 'field_name' => 'country', 'in_list_filter' => new InListFilter([ 'values' => ['China', 'Singapore', 'Vietnam'], ]), ]), ]), ]), ], ]), ]), ])); $lines = [ '## Homepage Sessions (`/`)', '_Excludes traffic from China, Singapore, and Vietnam._', '', '| Sessions | Engagement Rate |', '| --- | --- |', ]; if ($response->getRowCount() === 0) { $lines[] = '| — | — |'; } else { foreach ($response->getRows() as $row) { $sessions = $row->getMetricValues()[0]->getValue(); $engagementRate = round((float) $row->getMetricValues()[1]->getValue() * 100, 2); $lines[] = "| {$sessions} | {$engagementRate}% |"; } } return implode("\n", $lines); } private function registerPageSources(BetaAnalyticsDataClient $client, string $property, DateRange $dateRange): string { $response = $client->runReport(new RunReportRequest([ 'property' => $property, 'date_ranges' => [$dateRange], 'dimensions' => [ new Dimension(['name' => 'sessionSource']), ], 'metrics' => [ new Metric(['name' => 'activeUsers']), ], 'dimension_filter' => new FilterExpression([ 'filter' => new Filter([ 'field_name' => 'pagePath', 'string_filter' => new StringFilter([ 'match_type' => MatchType::EXACT, 'value' => '/register', ]), ]), ]), 'order_bys' => [ new OrderBy([ 'metric' => new MetricOrderBy(['metric_name' => 'activeUsers']), 'desc' => true, ]), ], ])); $lines = [ '## Register Page Active Users by Session Source (`/register`)', '', '| Source | Active Users |', '| --- | --- |', ]; if ($response->getRowCount() === 0) { $lines[] = '| — | — |'; } else { foreach ($response->getRows() as $row) { $source = $row->getDimensionValues()[0]->getValue(); $users = $row->getMetricValues()[0]->getValue(); $lines[] = "| {$source} | {$users} |"; } } return implode("\n", $lines); } private function registerCtaClicks(BetaAnalyticsDataClient $client, string $property, DateRange $dateRange): string { $response = $client->runReport(new RunReportRequest([ 'property' => $property, 'date_ranges' => [$dateRange], 'dimensions' => [ new Dimension(['name' => 'customEvent:cta_location']), ], 'metrics' => [ new Metric(['name' => 'eventCount']), ], 'dimension_filter' => new FilterExpression([ 'filter' => new Filter([ 'field_name' => 'eventName', 'string_filter' => new StringFilter([ 'match_type' => MatchType::EXACT, 'value' => 'register_cta_click', ]), ]), ]), 'order_bys' => [ new OrderBy([ 'metric' => new MetricOrderBy(['metric_name' => 'eventCount']), 'desc' => true, ]), ], ])); $lines = [ '## `register_cta_click` Events by CTA Location', '', '| CTA Location | Event Count |', '| --- | --- |', ]; if ($response->getRowCount() === 0) { $lines[] = '| — | — |'; } else { foreach ($response->getRows() as $row) { $location = $row->getDimensionValues()[0]->getValue(); $count = $row->getMetricValues()[0]->getValue(); $lines[] = "| {$location} | {$count} |"; } } return implode("\n", $lines); } private function scrollDepth(BetaAnalyticsDataClient $client, string $property, DateRange $dateRange): string { $response = $client->runReport(new RunReportRequest([ 'property' => $property, 'date_ranges' => [$dateRange], 'dimensions' => [ new Dimension(['name' => 'percentScrolled']), ], 'metrics' => [ new Metric(['name' => 'sessions']), ], 'dimension_filter' => new FilterExpression([ 'filter' => new Filter([ 'field_name' => 'pagePath', 'string_filter' => new StringFilter([ 'match_type' => MatchType::EXACT, 'value' => '/', ]), ]), ]), 'order_bys' => [ new OrderBy([ 'dimension' => new DimensionOrderBy([ 'dimension_name' => 'percentScrolled', 'order_type' => OrderType::NUMERIC, ]), 'desc' => false, ]), ], ])); $lines = [ '## Scroll Depth Distribution (`/`)', '', '| Scroll Depth | Sessions |', '| --- | --- |', ]; if ($response->getRowCount() === 0) { $lines[] = '| — | — |'; } else { foreach ($response->getRows() as $row) { $depth = $row->getDimensionValues()[0]->getValue(); $sessions = $row->getMetricValues()[0]->getValue(); $lines[] = "| {$depth}% | {$sessions} |"; } } return implode("\n", $lines); } } ================================================ FILE: app/Console/Commands/Migrations/BookmarkEntityType.php ================================================ info('Migrating bookmarks to the new entity_type_id property'); $entityTypes = EntityType::default()->get(); foreach ($entityTypes as $entityType) { $statement = 'UPDATE bookmarks SET entity_type_id = ' . $entityType->id . ' WHERE type = \'' . $entityType->code . '\''; DB::statement($statement); } $this->info('Finished'); } } ================================================ FILE: app/Console/Commands/Migrations/Cdn.php ================================================ 'entry', // 'timeline_eras' => 'entry', // 'timeline_elements' => 'entry', // 'quest_elements' => 'entry', // 'attributes' => 'value', // 'campaigns' => 'entry', // 'entities' => 'entry', // 'map_layers' => 'entry', // 'character_traits' => 'entry', 'plugin_versions' => 'content', ]; $old = 'https://kanka-user-assets.s3.eu-central-1.amazonaws.com/'; $new = 'https://cdn-ugc.kanka.io/'; $batchSize = 1000; foreach ($tables as $tableName => $column) { $this->info("Migrating $tableName ($column)..."); do { $affected = DB::update(" UPDATE `$tableName` SET `$column` = REPLACE(`$column`, ?, ?) WHERE `$column` LIKE ? LIMIT $batchSize ", [$old, $new, "%$old%"]); $this->info(" Updated $affected rows..."); } while ($affected > 0); } $tableName = 'plugin_versions'; $column = 'json'; $old = 'kanka-user-assets.s3.eu-central-1.amazonaws.com'; $new = 'cdn-ugc.kanka.io'; $this->info("Migrating $tableName ($column)..."); do { $affected = DB::update(" UPDATE `$tableName` SET `$column` = REPLACE(`$column`, ?, ?) WHERE `$column` LIKE ? LIMIT $batchSize ", [$old, $new, "%$old%"]); $this->info(" Updated $affected rows..."); } while ($affected > 0); $this->info('URL replacement completed.'); } } ================================================ FILE: app/Console/Commands/Migrations/MigrateEntityStatuses.php ================================================ migrateCharacters(); $this->migrateQuests(); $this->migrateBooleans('creature', 'creatures', [ 'is_dead' => 'dead', 'is_extinct' => 'extinct', ]); $this->migrateBooleans('location', 'locations', [ 'is_destroyed' => 'destroyed', ]); $this->migrateBooleans('organisation', 'organisations', [ 'is_defunct' => 'defunct', ]); $this->migrateBooleans('race', 'races', [ 'is_extinct' => 'extinct', ]); $this->migrateBooleans('family', 'families', [ 'is_extinct' => 'extinct', ]); $this->info('Finished migrating entity statuses.'); } /** * Migrate character statuses (0=alive, 1=dead, 2=missing) to entities.status_id. */ protected function migrateCharacters(): void { $characterTypeId = config('entities.ids.character'); $mapping = [ 0 => 'alive', 1 => 'dead', 2 => 'missing', ]; $statusIds = $this->getCategoryStatusIds($characterTypeId, $mapping); foreach ($mapping as $oldValue => $key) { if (! isset($statusIds[$key])) { $this->warn("Category status '{$key}' not found for characters, skipping."); continue; } $updated = DB::table('entities') ->join('characters', function ($join) use ($characterTypeId) { $join->on('entities.entity_id', '=', 'characters.id') ->where('entities.type_id', '=', $characterTypeId); }) ->where('characters.status_id', $oldValue) ->update(['entities.status_id' => $statusIds[$key]]); $this->info("Characters [{$key}]: {$updated} entities updated."); } } /** * Migrate quest statuses (0=notStarted, 1=ongoing, 2=completed, 3=abandoned) to entities.status_id. */ protected function migrateQuests(): void { $questTypeId = config('entities.ids.quest'); $mapping = [ 0 => 'not_started', 1 => 'ongoing', 2 => 'completed', 3 => 'abandoned', ]; $statusIds = $this->getCategoryStatusIds($questTypeId, $mapping); foreach ($mapping as $oldValue => $key) { if (! isset($statusIds[$key])) { $this->warn("Category status '{$key}' not found for quests, skipping."); continue; } $updated = DB::table('entities') ->join('quests', function ($join) use ($questTypeId) { $join->on('entities.entity_id', '=', 'quests.id') ->where('entities.type_id', '=', $questTypeId); }) ->where('quests.status_id', $oldValue) ->update(['entities.status_id' => $statusIds[$key]]); $this->info("Quests [{$key}]: {$updated} entities updated."); } } /** * Migrate boolean columns (is_dead, is_extinct, etc.) to entities.status_id. * * @param array $columns [boolean_column => status_key] */ protected function migrateBooleans(string $entityTypeKey, string $table, array $columns): void { $typeId = config('entities.ids.' . $entityTypeKey); $statusIds = $this->getCategoryStatusIds($typeId, $columns); foreach ($columns as $column => $key) { if (! isset($statusIds[$key])) { $this->warn("Category status '{$key}' not found for {$table}, skipping."); continue; } $updated = DB::table('entities') ->join($table, function ($join) use ($table, $typeId) { $join->on('entities.entity_id', '=', $table . '.id') ->where('entities.type_id', '=', $typeId); }) ->where($table . '.' . $column, true) ->whereNull('entities.status_id') ->update(['entities.status_id' => $statusIds[$key]]); $this->info(ucfirst($entityTypeKey) . " [{$key}]: {$updated} entities updated."); } } /** * Get category_statuses.id keyed by status key for a given entity type. * * @param array $mapping * @return array */ protected function getCategoryStatusIds(int $categoryId, array $mapping): array { return DB::table('category_statuses') ->where('category_id', $categoryId) ->whereIn('key', array_values($mapping)) ->pluck('id', 'key') ->toArray(); } } ================================================ FILE: app/Console/Commands/Migrations/MigrateStatusFilters.php ================================================ > Old boolean column → status key, keyed by entity type code */ protected array $booleanMap = [ 'creature' => ['is_dead' => 'dead', 'is_extinct' => 'extinct'], 'location' => ['is_destroyed' => 'destroyed'], 'organisation' => ['is_defunct' => 'defunct'], 'race' => ['is_extinct' => 'extinct'], 'family' => ['is_extinct' => 'extinct'], ]; /** @var array Character old enum value → status key */ protected array $characterMap = [0 => 'alive', 1 => 'dead', 2 => 'missing']; /** @var array Quest old enum value → status key */ protected array $questMap = [0 => 'not_started', 1 => 'ongoing', 2 => 'completed', 3 => 'abandoned']; /** @var array> Cached category_status IDs keyed by entity_type_id then status key */ protected array $statusCache = []; public function handle(): void { $this->migrateBookmarks(); $this->migrateWidgets(); $this->info('Finished migrating status filters.'); } protected function migrateBookmarks(): void { $bookmarks = DB::table('bookmarks') ->whereNotNull('filters') ->where('filters', '!=', '') ->whereNotNull('entity_type_id') ->get(['id', 'filters', 'entity_type_id']); $updated = 0; foreach ($bookmarks as $bookmark) { $params = $this->parseFilterString($bookmark->filters); if ($this->migrateFilterParams($params, $bookmark->entity_type_id)) { DB::table('bookmarks') ->where('id', $bookmark->id) ->update(['filters' => $this->buildFilterString($params)]); $updated++; } } $this->info("Bookmarks: {$updated} updated."); } protected function migrateWidgets(): void { $widgets = DB::table('campaign_dashboard_widgets') ->whereNotNull('entity_type_id') ->whereNotNull('config') ->get(['id', 'config', 'entity_type_id']); $updated = 0; foreach ($widgets as $widget) { $config = json_decode($widget->config, true); if (empty($config['filters']) || ! is_string($config['filters'])) { continue; } $params = $this->parseFilterString($config['filters']); if ($this->migrateFilterParams($params, $widget->entity_type_id)) { $config['filters'] = $this->buildFilterString($params); DB::table('campaign_dashboard_widgets') ->where('id', $widget->id) ->update(['config' => json_encode($config)]); $updated++; } } $this->info("Widgets: {$updated} updated."); } /** * Replace old status filter params with the new status_id param. * * @return bool True if any changes were made */ protected function migrateFilterParams(array &$params, int $entityTypeId): bool { $changed = false; // Handle character/quest enum status_id values $characterTypeId = config('entities.ids.character'); $questTypeId = config('entities.ids.quest'); if (isset($params['status_id']) && ($entityTypeId === $characterTypeId || $entityTypeId === $questTypeId)) { $oldValue = (int) $params['status_id']; $map = $entityTypeId === $characterTypeId ? $this->characterMap : $this->questMap; if (isset($map[$oldValue])) { $statusId = $this->resolveStatusId($entityTypeId, $map[$oldValue]); if ($statusId !== null) { $params['status_id'] = (string) $statusId; $changed = true; } } } // Handle boolean columns (is_dead, is_defunct, is_destroyed, is_extinct) $entityTypeCode = $this->resolveEntityTypeCode($entityTypeId); if ($entityTypeCode === null || ! isset($this->booleanMap[$entityTypeCode])) { return $changed; } foreach ($this->booleanMap[$entityTypeCode] as $oldParam => $statusKey) { if (! isset($params[$oldParam])) { continue; } $oldValue = (int) $params[$oldParam]; unset($params[$oldParam]); $changed = true; // Value of 0 means "not this status", just remove the old param if ($oldValue === 0) { continue; } $statusId = $this->resolveStatusId($entityTypeId, $statusKey); if ($statusId !== null) { $params['status_id'] = (string) $statusId; } } return $changed; } protected function resolveStatusId(int $entityTypeId, string $key): ?int { if (! isset($this->statusCache[$entityTypeId])) { $this->statusCache[$entityTypeId] = DB::table('category_statuses') ->where('category_id', $entityTypeId) ->pluck('id', 'key') ->toArray(); } return $this->statusCache[$entityTypeId][$key] ?? null; } protected function resolveEntityTypeCode(int $entityTypeId): ?string { $ids = config('entities.ids'); foreach ($ids as $code => $id) { if ($id === $entityTypeId) { return $code; } } return null; } /** * @return array */ protected function parseFilterString(string $filters): array { parse_str($filters, $params); return $params; } /** * @param array $params */ protected function buildFilterString(array $params): string { return http_build_query($params); } } ================================================ FILE: app/Console/Commands/Migrations/MigrateSubMentions.php ================================================ warn('Start at ' . $start); EntityMention::with([ 'questElement' => function ($sub) { $sub->select('id', 'name', 'quest_id'); }, 'questElement.quest' => function ($sub) { $sub->select('id', 'name', 'is_private'); }, 'questElement.quest.entity' => function ($sub) { $sub->select('id', 'entity_id', 'type_id'); }]) ->whereNotNull('quest_element_id') ->whereNull('entity_id') ->has('questElement') ->has('questElement.quest') ->has('questElement.quest.entity') ->chunkById(500, function ($mentions) { foreach ($mentions as $mention) { $mention->entity_id = $mention->questElement->quest->entity->id; $mention->saveQuietly(); $this->count++; } }); $this->info('Migrated ' . $this->count . ' quest element mentions.'); $this->count = 0; EntityMention::with([ 'timelineElement' => function ($sub) { $sub->select('id', 'name', 'timeline_id'); }, 'timelineElement.timeline' => function ($sub) { $sub->select('id', 'name', 'is_private'); }, 'timelineElement.timeline.entity' => function ($sub) { $sub->select('id', 'entity_id', 'type_id'); }]) ->whereNotNull('timeline_element_id') ->whereNull('entity_id') ->has('timelineElement') ->has('timelineElement.timeline') ->has('timelineElement.timeline.entity') ->chunkById(500, function ($mentions) { foreach ($mentions as $mention) { $mention->entity_id = $mention->timelineElement->timeline->entity->id; $mention->saveQuietly(); $this->count++; } }); $this->info('Migrated ' . $this->count . ' timeline element mentions.'); $this->count = 0; EntityMention::with([ 'post' => function ($sub) { $sub->select('id', 'entity_id'); }]) ->whereNotNull('post_id') ->whereNull('entity_id') ->has('post') ->has('post.entity') ->chunkById(5000, function ($mentions) { foreach ($mentions as $mention) { $mention->entity_id = $mention->post->entity_id; $mention->saveQuietly(); $this->count++; } }); $this->info('Migrated ' . $this->count . ' post mentions.'); $now = Carbon::now(); $this->warn('End in ' . $now->diffInSeconds($start) . ' seconds at ' . $now); } } ================================================ FILE: app/Console/Commands/Migrations/NewsletterSubCommand.php ================================================ where('pledge', '<>', '') ->where('settings', 'like', '%mail_release%') ->chunk(100, function ($users) { foreach ($users as $user) { if (! $user->mail_release) { continue; } $options = [ 'releases' => (bool) $user->mail_release, ]; if ($this->service->user($user)->update($options)) { $this->count++; continue; } $this->error($this->service->error()->getMessage()); } sleep(60); }); $this->info('Processed ' . $this->count . ' users.'); } } ================================================ FILE: app/Console/Commands/Report/Accounts.php ================================================ option('days'); $current = $service->getStats(now()->subDays($days), now()); $previous = $service->getStats(now()->subDays($days * 2), now()->subDays($days)); $title = "{$service->name()} (Last {$days} days)"; $this->info($title); $this->info(str_repeat('=', 60)); $this->newLine(); foreach ($service->buildTerminalLines($current, $previous) as $line) { $this->line($line); } ReportJob::dispatch($title, $service->buildDiscordBody($current, $previous)); } } ================================================ FILE: app/Console/Commands/Report/Churn.php ================================================ option('days'); $current = $service->getStats(now()->subDays($days), now()); $previous = $service->getStats(now()->subDays($days * 2), now()->subDays($days)); $title = "{$service->name()} (Last {$days} days)"; $this->info($title); $this->info(str_repeat('=', 60)); $this->newLine(); foreach ($service->buildTerminalLines($current, $previous) as $line) { $this->line($line); } ReportJob::dispatch($title, $service->buildDiscordBody($current, $previous)); } } ================================================ FILE: app/Console/Commands/Report/Onboarding.php ================================================ option('days'); $current = $service->getStats(now()->subDays($days), now()); $previous = $service->getStats(now()->subDays($days * 2), now()->subDays($days)); $title = "{$service->name()} (Last {$days} days)"; $this->info($title); $this->info(str_repeat('=', 60)); $this->newLine(); foreach ($service->buildTerminalLines($current, $previous) as $line) { $this->line($line); } ReportJob::dispatch($title, $service->buildDiscordBody($current, $previous)); } } ================================================ FILE: app/Console/Commands/Report/Weekly.php ================================================ option('days'); // Ex: Feb 10-16, 2026 $period = now()->subDays($days)->format('M j') . '-' . now()->format('j, Y'); $current = $service->getStats(now()->subDays($days), now()); $previous = $service->getStats(now()->subDays($days * 2), now()->subDays($days)); $title = "**{$service->name()}** - $period"; $this->info($title); $this->info(str_repeat('=', 60)); $this->newLine(); foreach ($service->buildTerminalLines($current, $previous) as $line) { $this->line($line); } ReportJob::dispatch($title, $service->buildDiscordBody($current, $previous)); } } ================================================ FILE: app/Console/Commands/SetupMeilisearch.php ================================================ error('Config error:'); $this->error('Temporarily remove APP_LAZY=true from your .env file.'); return; } // Update Non Separator Tokens for entity mentions $start = Carbon::now(); $this->info('Meilisearch import started at ' . date('H:i:s')); $client = new Client(config('scout.meilisearch.host'), config('scout.meilisearch.key')); $client->getKeys(); $client->deleteIndex('entities'); $client->index('entities')->resetSeparatorTokens(); $client->index('entities')->updateNonSeparatorTokens([':']); $client->index('entities')->updateFilterableAttributes(['campaign_id']); $models = [ Attribute::class, Entity::class, Post::class, QuestElement::class, TimelineElement::class, ]; foreach ($models as $model) { $time = Carbon::now(); $object = new $model; $this->info('Importing ' . Number::format($object->count()) . ' [' . $model . '] at ' . date('H:i:s')); $object::makeAllSearchable($this->option('chunk')); $this->info('- Done in ' . round($time->diffInMinutes(), 4) . ' min'); Log::info('Meilisearch', ['model' => $model]); } $this->info('Ended at ' . date('H:i:s') . ' after ' . round($start->diffInMinutes(), 3) . ' min'); if (config('scout.queue')) { $this->newLine(); $this->warn('Meilisearch seeding has been queued.'); $this->warn('Now run `sail artisan queue:work` to finish importing.'); } } } ================================================ FILE: app/Console/Commands/SubscriptionEndPaypalFix.php ================================================ where('stripe_price', 'like', 'paypal_%') ->where('stripe_status', 'canceled') ->where('ends_at', '<', now()) ->whereHas('user', fn ($q) => $q->whereNotNull('pledge')->where('pledge', '<>', '')) ->whereNotExists(function ($sub) { $sub->select(DB::raw(1)) ->from('subscriptions as s2') ->whereColumn('s2.user_id', 'subscriptions.user_id') ->whereColumn('s2.id', '<>', 'subscriptions.id') ->whereColumn('s2.created_at', '>', 'subscriptions.ends_at'); }) ->get(); $count = $subscriptions->count(); $this->info("Found {$count} affected users."); if ($count === 0) { return self::SUCCESS; } if ($this->option('dry-run')) { $this->table( ['User ID', 'Name', 'Email', 'Pledge', 'Ended At'], $subscriptions->map(fn (Subscription $sub) => [ $sub->user->id, $sub->user->name, $sub->user->email, $sub->user->pledge, $sub->ends_at, ]) ); return self::SUCCESS; } foreach ($subscriptions as $subscription) { /** @var User $user */ $user = $subscription->user; SubscriptionEndJob::dispatch($user); $this->line("Dispatched for user {$user->id} ({$user->name})"); } $this->info("Dispatched {$count} jobs."); return self::SUCCESS; } } ================================================ FILE: app/Console/Commands/Subscriptions/EndFreeTrials.php ================================================ service->run(); // $this->info(Carbon::now()->subMinute()->startOfMinute() . ' and ' . Carbon::now()->subMinute()->endOfMinute()); $log = 'Ended ' . $count . ' free trials.'; $this->info($log); $this->log($log . ' ' . implode(',', $this->service->ids())); return 0; } } ================================================ FILE: app/Console/Commands/Subscriptions/EndSubscriptions.php ================================================ argument('fake'); $count = $this->service->run($fake === 'false'); $log = 'Ended ' . $count . ' subscriptions.'; $this->info($log); $this->log($log . ' ' . implode(', ', $this->service->ids())); return 0; } } ================================================ FILE: app/Console/Commands/Subscriptions/ExpiringCardCommand.php ================================================ endOfMonth(); $log = "Looking for cards expiring on {$now->format('Y-m-d')}"; $this->info($log); User::where('card_expires_at', $now) ->with('subscriptions') ->chunk(100, function ($users): void { foreach ($users as $user) { $this->notify($user); } }); $this->info('Alerted ' . $this->count . ' subscribers.'); $log .= '
' . 'Alerted ' . $this->count . ' subscribers.'; $this->log($log); return 0; } protected function notify(User $user): void { // Check the user has a card if (! $user->subscribed('kanka')) { // Has a card but isn't subbed, ignore $this->warn('Expired card but unsubbed user'); return; } // Notify the user about their soon expiring card ExpiringCardAlert::dispatch($user); $this->count++; } } ================================================ FILE: app/Console/Commands/Subscriptions/PaypalExpiringCommand.php ================================================ addDays(14)->toDateString(); $log = "Looking for PayPal subscriptions expiring on {$target}"; $this->info($log); User::whereHas('subscriptions', function ($query) use ($target): void { $query->where('stripe_price', 'like', 'paypal_%') ->whereDate('ends_at', $target); }) ->chunk(100, function ($users): void { foreach ($users as $user) { $this->notify($user); } }); $this->info('Alerted ' . $this->count . ' subscribers.'); $log .= '
' . 'Alerted ' . $this->count . ' subscribers.'; $this->log($log); return 0; } protected function notify(User $user): void { if (! $user->subscribed('kanka')) { return; } PaypalExpiringAlert::dispatch($user); $this->count++; } } ================================================ FILE: app/Console/Commands/Subscriptions/SubCleanupCommand.php ================================================ argument('user'); /** @var User $user */ $user = User::findOrFail($userID); SubscriptionEndJob::dispatch($user); $this->info('Sub cleaned up for user ' . $user->name . '#' . $user->id . '.'); return 0; } } ================================================ FILE: app/Console/Commands/Tests/Mailerlite.php ================================================ service->user($user)->isSubscribed()) { $this->service->remove(); } else { $options = [ 'releases' => (bool) $user->mail_release, ]; $this->service->update($options); } return Command::SUCCESS; } } ================================================ FILE: app/Console/Commands/Tests/SendNotification.php ================================================ argument('user'); $url = $this->argument('url'); if ($url !== '0') { $url = config('app.url') . '/pricing'; } /** @var User $user */ $user = User::findOrFail($userID); $user->notify(new Header('campaign.application.approved', 'download', 'info', ['link' => $url, 'campaign' => 'Fun & Games'])); $this->info('User ' . $user->name . '#' . $user->id . ' notified.'); return 0; } } ================================================ FILE: app/Console/Commands/Tests/SignImageCommand.php ================================================ argument('img'); $base = $this->argument('base'); $width = $height = $this->argument('size'); if (Str::contains($width, 'x')) { $full = $width; $width = Str::before($full, 'x'); $height = Str::after($full, 'x'); } $width = (int) $width; $height = (int) $height; if (! empty($height)) { $url = Img::console()->base($base)->crop($width, $height)->url($img); } else { $url = Img::console()->base($base)->url($img); } $this->info("Base: {$base}"); $this->info("Size: {$width} x {$height}"); $this->info('' . $url); return 0; } } ================================================ FILE: app/Console/Commands/Tests/TestEmail.php ================================================ argument('user'); $user = User::findOrFail($userId); $template = $this->argument('template'); if ($template === 'welcome') { WelcomeEmailJob::dispatch($user, 'en'); } elseif ($template === 'cancelled') { SubscriptionCancelEmailJob::dispatch($user, null, 'custom text'); } elseif ($template === 'downgrade') { SubscriptionDowngradedEmailJob::dispatch($user); } elseif ($template === 'elemental') { WelcomeSubscriptionEmailJob::dispatch($user, Tier::where('name', 'elemental')->first()); } elseif ($template === 'wyvern') { WelcomeSubscriptionEmailJob::dispatch($user, Tier::where('name', 'wyvern')->first()); } elseif ($template === 'owlbear') { WelcomeSubscriptionEmailJob::dispatch($user, Tier::where('name', 'owlbear')->first()); } elseif ($template === 'expiring') { ExpiringCardAlert::dispatch($user); } elseif ($template === 'failed') { SubscriptionFailedEmailJob::dispatch($user); } elseif ($template === 'upcoming') { UpcomingYearlyAlert::dispatch($user); } elseif ($template === 'password') { NewPassword::dispatch($user); } elseif ($template === 'first') { FirstWarningJob::dispatch($user->id); } elseif ($template === 'second') { SecondWarningJob::dispatch($user->id); } elseif ($template === 'feature') { $feature = Feature::latest()->first(); FeatureCreated::dispatch($feature); } else { $this->warn('Unknown template ' . $template); } return 0; } } ================================================ FILE: app/Console/Commands/Tests/TestWhiteboards.php ================================================ argument('id'); broadcast(new WhiteboardUpdated($id, [ 'test' => true, 'message' => "Test event sent to whiteboard {$id}", ])); $this->info("Broadcasted test event to whiteboard.{$id}"); return Command::SUCCESS; } } ================================================ FILE: app/Console/Commands/Translations/Missing.php ================================================ argument('locale'); $this->prompt = ''; $translations = DB::select( "select * from ltm_translations where locale = 'en' and not exists(select t.* from ltm_translations as t where t.locale = ? and t.group = ltm_translations.group and t.key = ltm_translations.key) order by ltm_translations.group ASC, ltm_translations.key ASC", [$locale] ); $groups = []; foreach ($translations as $translation) { if (! isset($groups[$translation->group])) { $groups[$translation->group] = []; $this->prompt .= "\n#" . $translation->group . "\n"; } $this->prompt .= ' - ' . $translation->value . "\n"; } $this->info($this->prompt); } } ================================================ FILE: app/Console/Commands/Users/OfferFreeTrial.php ================================================ service->run(); $log = 'Offered free trial to ' . $count . ' users.'; $this->info($log); $this->log($log . ' ' . implode(',', $this->service->ids())); } } ================================================ FILE: app/Console/Commands/Users/RegenerateDiscordToken.php ================================================ with('user') ->where('app', '=', 'discord') ->where('expires_at', '<=', Carbon::now()->toDateString()) ->get(); if ($tokens->count() === 0) { $this->error('No tokens to renew'); return 0; } $count = 0; foreach ($tokens as $token) { try { $this->service->app($token)->refresh(); $count++; } catch (ClientException $e) { if (Str::contains($e->getResponse()->getBody()->getContents(), 'invalid_grant')) { UnsyncDiscord::dispatch($token); } } catch (\Exception $e) { report($e); // Silence errors and ignore } } $log = 'Renewed ' . $count . ' tokens.'; $this->info($log); $this->log($log . ' ' . implode(',', $this->service->ids())); return 0; } } ================================================ FILE: app/Console/Commands/Users/ResetUserPassword.php ================================================ argument('user'); $user = User::find($userID); if (empty($user)) { $this->error('Invalid user id ' . $userID); return 0; } $password = $this->argument('password'); if ($password == 'auto') { $password = Str::random(); } $hash = Hash::make($password); $user->update(['password' => $hash]); $user->log(UserAction::passwordAdminUpdate); $this->info('User ' . $userID . ' updated to new password ' . $password); return 0; } } ================================================ FILE: app/Console/Commands/Users/SyncUserRoles.php ================================================ argument('user'); /** @var User $user */ $user = User::find($userID); if ($user->apps()->app('discord')->count() === 0) { $this->error('User has no discord sync.'); return 0; } $this->service->user($user) ->addRoles(); $logs = $this->service->logs(); foreach ($logs as $log) { $this->info($log); } return 0; } } ================================================ FILE: app/Console/Commands/VerifyParentIds.php ================================================ ['key' => 'ability_id', 'type_id' => config('entities.ids.ability')], 'attribute_templates' => ['key' => 'attribute_template_id', 'type_id' => config('entities.ids.attribute_template')], 'calendars' => ['key' => 'calendar_id', 'type_id' => config('entities.ids.calendar')], 'creatures' => ['key' => 'creature_id', 'type_id' => config('entities.ids.creature')], 'events' => ['key' => 'event_id', 'type_id' => config('entities.ids.event')], 'families' => ['key' => 'family_id', 'type_id' => config('entities.ids.family')], 'items' => ['key' => 'item_id', 'type_id' => config('entities.ids.item')], 'journals' => ['key' => 'journal_id', 'type_id' => config('entities.ids.journal')], 'locations' => ['key' => 'location_id', 'type_id' => config('entities.ids.location')], 'maps' => ['key' => 'map_id', 'type_id' => config('entities.ids.map')], 'notes' => ['key' => 'note_id', 'type_id' => config('entities.ids.note')], 'organisations' => ['key' => 'organisation_id', 'type_id' => config('entities.ids.organisation')], 'quests' => ['key' => 'quest_id', 'type_id' => config('entities.ids.quest')], 'races' => ['key' => 'race_id', 'type_id' => config('entities.ids.race')], 'tags' => ['key' => 'tag_id', 'type_id' => config('entities.ids.tag')], 'timelines' => ['key' => 'timeline_id', 'type_id' => config('entities.ids.timeline')], ]; $totalMismatches = 0; foreach ($models as $table => $config) { // Find entities where child has a parent but entities.parent_id doesn't match $mismatches = DB::select(" SELECT e.id AS entity_id, e.name, e.parent_id AS entity_parent_id, c.{$config['key']} AS child_parent_id, parent_entity.id AS expected_parent_id FROM entities e JOIN {$table} c ON e.entity_id = c.id AND e.type_id = {$config['type_id']} LEFT JOIN entities parent_entity ON parent_entity.entity_id = c.{$config['key']} AND parent_entity.type_id = {$config['type_id']} WHERE ( (c.{$config['key']} IS NOT NULL AND (e.parent_id IS NULL OR e.parent_id != parent_entity.id)) OR (c.{$config['key']} IS NULL AND e.parent_id IS NOT NULL) ) AND e.deleted_at IS NULL "); if (count($mismatches) > 0) { $this->warn("{$table}: " . count($mismatches) . ' mismatches'); foreach ($mismatches as $row) { $this->line(" Entity #{$row->entity_id} ({$row->name}): entity.parent_id={$row->entity_parent_id}, child.{$config['key']}={$row->child_parent_id}, expected={$row->expected_parent_id}"); } $totalMismatches += count($mismatches); } else { $this->info("{$table}: OK"); } } if ($totalMismatches === 0) { $this->info('All parent IDs are in sync.'); return self::SUCCESS; } $this->error("Total mismatches: {$totalMismatches}"); return self::FAILURE; } } ================================================ FILE: app/Console/Commands/WordCount.php ================================================ info('Started at ' . date('H:i:s')); $this->process(Entity::class); $this->process(Post::class); $this->process(TimelineElement::class); $this->process(TimelineEra::class); $this->process(QuestElement::class); $this->process(MapMarker::class); $this->process(MapLayer::class); $this->process(CharacterTrait::class); $this->info('Ended at ' . date('H:i:s') . ' after ' . round($start->diffInMinutes(), 3) . ' min'); } protected function process(string $className, string $field = 'entry') { $model = app()->make($className); $table = $model->getTable(); // DB::statement('UPDATE ' . $model->getTable() . ' SET words = null'); $this->info($className); $total = $model::whereNull('words')->whereNotNull($field)->count(); $progressBar = $this->output->createProgressBar($total); $batchSize = 5000; $processed = 0; do { // Process in batches using LIMIT/OFFSET or better yet, use a cursor approach $updated = DB::table($table) ->whereNotNull($field) ->whereNull('words') ->limit($batchSize) ->update([ 'words' => DB::raw(" CASE WHEN TRIM(REGEXP_REPLACE($field, '<[^>]*>', '')) = '' THEN 0 ELSE ( LENGTH(TRIM(REGEXP_REPLACE($field, '<[^>]*>', ''))) - LENGTH(REPLACE(TRIM(REGEXP_REPLACE($field, '<[^>]*>', '')), ' ', '')) + 1 ) END "), ]); $processed += $updated; $progressBar->advance($updated); // Small delay to prevent overwhelming the database usleep(10000); // 10ms delay } while ($updated > 0); $progressBar->finish(); $this->newLine(); $this->info("Processed {$processed} records total"); } } ================================================ FILE: app/Console/Kernel.php ================================================ command(ExpiringCardCommand::class)->onOneServer()->monthly(); $schedule->command('model:prune')->onOneServer()->daily(); $schedule->command(CalendarAdvancer::class)->onOneServer()->daily(); $schedule->command(AnonymiseUserLogs::class)->onOneServer()->daily(); $schedule->command(EndSubscriptions::class)->onOneServer()->dailyAt('00:05')->sentryMonitor(); $schedule->command(EndFreeTrials::class)->onOneServer()->dailyAt('00:01'); $schedule->command(PaypalExpiringCommand::class)->onOneServer()->dailyAt('06:30'); $schedule->command(RegenerateDiscordToken::class)->onOneServer()->dailyAt('00:15'); $schedule->command(VisibileEntityCountCommand::class)->onOneServer()->dailyAt('01:00'); $schedule->command(CleanupTrashed::class)->onOneServer()->dailyAt('01:15'); $schedule->command('backup:clean')->onOneServer()->dailyAt('01:20'); $schedule->command(CleanupEntityLogs::class)->onOneServer()->dailyAt('01:30'); $schedule->command(CleanupTrashedCampaigns::class)->onOneServer()->dailyAt('01:45'); // $schedule->command(CleanupUsers::class)->onOneServer()->dailyAt('01:50'); $schedule->command('backup:run')->onOneServer()->twiceDaily(2, 14); $schedule->command(Onboarding::class)->onOneServer()->weekly(); $schedule->command(Churn::class)->onOneServer()->weekly(); // $schedule->command(Accounts::class)->onOneServer()->weekly(); $schedule->command(Weekly::class)->onOneServer()->weekly(); // $schedule->command('backup:monitor')->daily()->at('03:00'); } /** * Register the commands for the application. */ protected function commands(): void { $this->load(__DIR__ . '/Commands'); require base_path('routes/console.php'); } } ================================================ FILE: app/Datagrids/Actions/BookmarkDatagridActions.php ================================================ bulkPermissions; } /** * Determine if the datagrid has bulk permissions. */ public function hasBulkEditing(): bool { return $this->bulkEditing; } /** * Determine if the datagrid has bulk copy to campaign. */ public function hasBulkCopy(): bool { return $this->bulkCopyToCampaign; } /** * Determine if the datagrid has bulk transforming entities. */ public function hasBulkTransform(): bool { return $this->bulkTransform; } /** * Determine if the datagrid has bulk transforming entities. */ public function hasBulkTemplate(): bool { return $this->bulkTemplate; } /** * Determine if the datagrid has bulk entity printing. */ public function hasBulkPrint(): bool { return $this->bulkPrint; } } ================================================ FILE: app/Datagrids/Actions/DefaultDatagridActions.php ================================================ fields)) { return $this->fields; } return $this->defaults(); } /** * The mapping, used for is_/has_ fields to be able to unset a value. For example a character's is_dead status */ public function booleans(): array { if (isset($this->booleans)) { return $this->booleans; } return []; } /** * The list of fields that are foreign fields, to be able to properly unset(detach) them if needed */ public function foreignRelations(): array { if (isset($this->foreignRelations)) { return $this->foreignRelations; } return []; } /** * Attributes that can support basic math */ public function maths(): array { if (isset($this->maths)) { return $this->maths; } return []; } /** * Default fields that are available in the bulk edit interface if no other are defined. */ protected function defaults(): array { return [ 'name', 'type', 'tags', 'private_choice', 'entity_image', 'entity_header', ]; } } ================================================ FILE: app/Datagrids/Bulks/CalendarBulk.php ================================================ bulkPermissions; } /** * Determine if the datagrid has bulk copy to campaign. */ public function hasBulkCopy(): bool { return $this->bulkCopyToCampaign; } /** * Determine if the datagrid has bulk transforming entities. */ public function hasBulkTransform(): bool { return $this->bulkTransform; } /** * Determine if the datagrid has bulk transforming entities. */ public function hasBulkTemplate(): bool { return $this->bulkTemplate; } /** * Determine if the datagrid has bulk entity printing. */ public function hasBulkPrint(): bool { return $this->bulkPrint; } } ================================================ FILE: app/Datagrids/Filters/AbilityFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.ability')) ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/AttributeTemplateFilter.php ================================================ add('name') ->parent(config('entities.ids.attribute_template')) ->add('is_enabled') ->isPrivate() ->template() ->archived() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/CalendarFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.calendar')) ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/CharacterFilter.php ================================================ add('name') ->add('title') ->families() ->locations() ->races() ->organisations() ->add('type') ->add('age') ->add('sex') ->add('pronouns') ->add('status_id') ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/ConversationFilter.php ================================================ add('name') ->add('type') // ->add([ // 'field' => 'target', // 'label' => __('conversations.fields.target'), // 'valueKey' => 'conversations.targets.', // 'type' => 'select', // 'placeholder' => __('conversations.placeholders.target'), // 'data' => __('conversations.targets') // ]) ->add('is_closed') ->isPrivate() ->archived() ->tags(); } } ================================================ FILE: app/Datagrids/Filters/CreatureFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.creature')) ->locations() ->add('status_id') ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/CustomEntityFilter.php ================================================ add('name') ->add('type') ->parent($this->entityType->id) ->location() ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/DatagridFilter.php ================================================ filters; } /** * @param string|array $name */ protected function add($name): self { $this->filters[] = $name; return $this; } /** * Add the location filters */ protected function location(): self { $name = Module::singular(config('entities.ids.location')); $placeholder = __('crud.placeholders.location'); if (! empty($name)) { $placeholder = __('crud.placeholders.fallback', ['module' => $name]); } $this->filters[] = [ 'field' => 'location_id', 'label' => Module::singular(config('entities.ids.location'), __('entities.location')), 'type' => 'select2', 'route' => route('search-list', [$this->campaign, config('entities.ids.location')]), 'placeholder' => $placeholder, 'model' => Location::class, 'withChildren' => true, ]; return $this; } /** * Add the locations filters */ protected function locations(): self { $name = Module::plural(config('entities.ids.location')); $placeholder = __('crud.placeholders.search'); if (! empty($name)) { $placeholder = __('crud.placeholders.fallback', ['module' => $name]); } $this->filters[] = [ 'field' => 'locations', 'label' => Module::plural(config('entities.ids.location'), __('entities.locations')), 'type' => 'selectMultiple', 'route' => route('search-list', [$this->campaign, config('entities.ids.location')]), 'placeholder' => $placeholder, 'model' => Location::class, 'multiple' => true, 'withChildren' => true, ]; return $this; } /** * Add the races filters */ protected function races(): self { $name = Module::plural(config('entities.ids.race')); $placeholder = __('crud.placeholders.search'); if (! empty($name)) { $placeholder = __('crud.placeholders.fallback', ['module' => $name]); } $this->filters[] = [ 'field' => 'races', 'label' => Module::plural(config('entities.ids.race'), __('entities.races')), 'type' => 'selectMultiple', 'route' => route('search-list', [$this->campaign, config('entities.ids.race')]), 'placeholder' => $placeholder, 'model' => Race::class, 'multiple' => true, 'withChildren' => true, ]; return $this; } /** * Add the organisations filters */ protected function organisations(): self { $name = Module::plural(config('entities.ids.organisation')); $placeholder = __('crud.placeholders.search'); if (! empty($name)) { $placeholder = __('crud.placeholders.fallback', ['module' => $name]); } $this->filters[] = [ 'field' => 'organisations', 'label' => Module::plural(config('entities.ids.organisation'), __('entities.organisations')), 'type' => 'selectMultiple', 'route' => route('search-list', [$this->campaign, config('entities.ids.organisation')]), 'placeholder' => $placeholder, 'model' => Organisation::class, 'multiple' => true, 'withChildren' => true, ]; return $this; } /** * Add the families filters */ protected function families(): self { $name = Module::plural(config('entities.ids.family')); $placeholder = __('crud.placeholders.search'); if (! empty($name)) { $placeholder = __('crud.placeholders.fallback', ['module' => $name]); } $this->filters[] = [ 'field' => 'families', 'label' => Module::plural(config('entities.ids.family'), __('entities.families')), 'type' => 'selectMultiple', 'route' => route('search-list', [$this->campaign, config('entities.ids.family')]), 'placeholder' => $placeholder, 'model' => Family::class, 'multiple' => true, 'withChildren' => true, ]; return $this; } /** * Add the character filters */ protected function character(string $field = 'character_id'): self { $name = Module::singular(config('entities.ids.character')); $placeholder = __('crud.placeholders.search'); if (! empty($name)) { $placeholder = __('crud.placeholders.fallback', ['module' => $name]); } $this->filters[] = [ 'field' => $field, 'label' => Module::singular(config('entities.ids.character'), __('entities.character')), 'type' => 'select2', 'route' => route('search-list', [$this->campaign, config('entities.ids.character')]), 'placeholder' => $placeholder, 'model' => Character::class, ]; return $this; } /** * Add the character filters */ protected function journal(): self { $name = Module::singular(config('entities.ids.journal')); $placeholder = __('crud.placeholders.journal'); if (! empty($name)) { $placeholder = __('crud.placeholders.fallback', ['module' => $name]); } $this->filters[] = [ 'field' => 'journal_id', 'label' => Module::singular(config('entities.ids.journal'), __('entities.journal')), 'type' => 'select2', 'route' => route('search-list', [$this->campaign, config('entities.ids.journal')]), 'placeholder' => $placeholder, 'model' => Journal::class, ]; return $this; } /** * Add the tags filters */ protected function tags(): self { $name = Module::singular(config('entities.ids.tag')); $placeholder = __('crud.placeholders.tag'); if (! empty($name)) { $placeholder = __('crud.placeholders.fallback', ['module' => $name]); } $this->filters[] = [ 'field' => 'tags', 'label' => Module::plural(config('entities.ids.tag'), __('entities.tags')), 'type' => 'tag', 'route' => route('search-list', [$this->campaign, config('entities.ids.tag')]), 'placeholder' => $placeholder, 'model' => Tag::class, ]; return $this; } /** * Add the is_private */ protected function isPrivate(): self { // Add the is_private filter only for admins. if (Auth::check() && Auth::user()->isAdmin()) { $this->filters[] = 'is_private'; } return $this; } /** * Add the entity has an image */ protected function hasImage(): self { $this->filters[] = 'has_image'; return $this; } /** * Add the entity parent filter as a select2 for the given entity type */ protected function parent(int $entityTypeId): self { $this->filters[] = [ 'field' => 'parent_id', 'label' => __('crud.fields.parent'), 'type' => 'select2', 'route' => route('search-list', [$this->campaign, $entityTypeId, 'entity' => true]), 'placeholder' => __('crud.placeholders.search'), 'model' => Entity::class, ]; return $this; } /** * Add the entity has posts */ protected function hasPosts(): self { $this->filters[] = 'has_posts'; return $this; } /** * Add the entity has entry */ protected function hasEntry(): self { $this->filters[] = 'has_entry'; return $this; } /** * Add the entity has attributes */ protected function hasAttributes(): self { $this->filters[] = 'has_attributes'; return $this; } /** * Add the has image */ protected function hasEntityFiles(): self { $this->filters[] = 'has_entity_files'; return $this; } /** * Add the (real) date filter */ protected function date(): self { $this->filters[] = 'date'; return $this; } /** * Add the attributes selector */ protected function attributes(): self { $this->filters[] = 'attributes'; return $this; } /** * Add the connection selector */ protected function connections(): self { $this->filters[] = 'connections'; return $this; } /** * Add the created by selector */ protected function createdBy(): self { $this->filters[] = [ 'field' => 'created_by', 'label' => __('crud.fields.created_by'), 'type' => 'select2', 'route' => route('find.members', [$this->campaign]), 'placeholder' => __('crud.placeholders.user'), 'model' => User::class, ]; return $this; } /** * Add the updated by selector */ protected function updatedBy(): self { $this->filters[] = [ 'field' => 'updated_by', 'label' => __('crud.fields.updated_by'), 'type' => 'select2', 'route' => route('find.members', [$this->campaign]), 'placeholder' => __('crud.placeholders.user'), 'model' => User::class, ]; return $this; } /** * Add the date range filter */ protected function dateRange(): self { $this->filters[] = 'date_range'; return $this; } /** * Add the is template filter */ protected function template(): self { $this->filters[] = 'template'; return $this; } /** * Add the is archived filter */ protected function archived(): self { $this->filters[] = 'archived'; return $this; } } ================================================ FILE: app/Datagrids/Filters/DiceRollFilter.php ================================================ add('name') ->character() ->isPrivate() ->archived() ->tags(); } } ================================================ FILE: app/Datagrids/Filters/EventFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.event')) ->add('date') ->locations() ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/FamilyFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.family')) ->location() ->character('member_id') ->add('status_id') ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/HistoryFilter.php ================================================ add([ 'field' => 'entity_id', 'label' => __('crud.fields.entity'), 'type' => 'select2', 'route' => route('search.entities-with-relations', $this->campaign), 'placeholder' => __('search.placeholders.entry'), 'model' => Entity::class, ]) ->add([ 'field' => 'created_by', 'label' => __('crud.permissions.fields.member'), 'type' => 'select2', 'route' => route('users.find', $this->campaign), 'placeholder' => __('crud.permissions.fields.member'), 'model' => Entity::class, ]); } } ================================================ FILE: app/Datagrids/Filters/ItemFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.item')) ->add('price') ->add('size') ->add('weight') ->add('is_equipped') ->location() ->character() ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/JournalFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.journal')) ->dateRange() ->add([ 'field' => 'author_id', 'label' => __('journals.fields.author'), 'type' => 'select2', 'route' => route('search.entities-with-relations', $this->campaign), 'placeholder' => __('journals.placeholders.author'), 'model' => Entity::class, ]) ->location() ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/LocationFilter.php ================================================ add('name') ->add('title') ->add('type') ->add('status_id') ->parent(config('entities.ids.location')) ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/MapFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.map')) ->location() ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/NoteFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.note')) ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/OrganisationFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.organisation')) ->locations() ->character('member_id') ->add('status_id') ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/QuestFilter.php ================================================ add('name') ->add('type') ->dateRange() ->add('status_id') ->add([ 'field' => 'instigator_id', 'label' => __('quests.fields.instigator'), 'type' => 'select2', 'route' => route('search.entities-with-relations', $this->campaign), 'placeholder' => __('search.placeholders.entry'), 'model' => Entity::class, ]) ->location() ->parent(config('entities.ids.quest')) ->add([ 'field' => 'quest_element_id', 'label' => __('fields.entry.label'), 'type' => 'select2', 'route' => route('search.entities-with-relations', $this->campaign), 'placeholder' => __('quests.placeholders.entity'), 'model' => Entity::class, ]) ->add('element_role') ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/RaceFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.race')) ->locations() ->add('status_id') ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/RelationFilter.php ================================================ add([ 'field' => 'owner_id', 'label' => __('entities/relations.fields.owner'), 'type' => 'select2', 'route' => route('search.entities-with-relations', $this->campaign), 'placeholder' => __('search.placeholders.entry'), 'model' => Entity::class, ]) ->add([ 'field' => 'target_id', 'label' => __('entities/relations.fields.target'), 'type' => 'select2', 'route' => route('search.entities-with-relations', $this->campaign), 'placeholder' => __('search.placeholders.entry'), 'model' => Entity::class, ]) ->add([ 'field' => 'relation', 'label' => __('entities/relations.fields.role'), 'type' => 'text', 'placeholder' => __('entities/relations.placeholders.role'), ]) ->add('attitude') ->add('is_pinned'); } } ================================================ FILE: app/Datagrids/Filters/TagFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.tag')) ->add('is_auto_applied') ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/TimelineFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.timeline')) ->isPrivate() ->template() ->archived() ->hasImage() ->hasEntry() ->hasPosts() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Filters/WhiteboardFilter.php ================================================ add('name') ->add('type') ->parent(config('entities.ids.whiteboard')) ->isPrivate() ->template() ->hasImage() ->hasEntityFiles() ->hasAttributes() ->tags() ->attributes() ->connections(); } } ================================================ FILE: app/Datagrids/Sorters/DatagridSorter.php ================================================ get($this->sessionkey()); if (! empty($session)) { $this->parse($session); } } /** * Build the list of filters */ public function options(?Campaign $campaign = null): array { // Clean up options that don't make sense for this campaign $options = []; foreach ($this->options as $key => $option) { if ($this->validOption($key, $campaign)) { $options[$key] = $option; } } return $options; } public function isSelected(string $key, bool $asc = true): bool { $key .= $this->direction($asc); return $this->selected === $key; } /** * Get the direction part of the key */ public function direction(bool $asc = true): string { return $asc ? '_asc' : '_desc'; } public function fieldname(): string { return $this->fieldname; } public function request(array $data): self { $selected = mb_strtolower(Arr::get($data, $this->fieldname())); $segments = explode('_', $selected); if (count($segments) < 2) { return $this; } $this->parse($selected); // Save these new values in the session session()->put($this->sessionkey(), $this->selected); return $this; } /** * The field to perform the order by on * * @return string|array */ public function column() { if (! empty($this->column)) { return $this->column; } return $this->default; } public function order(): string { return (string) $this->order; } protected function sessionkey(): string { // ReflectionClass is cheap but let's still avoid extra calls if ($this->sessionKey === false) { $this->sessionKey = 'dg-sorter-' . Str::kebab((new ReflectionClass($this))->getShortName()); } return (string) $this->sessionKey; } protected function parse(string $selected): self { $this->selected = $selected; // Handle order later $segments = explode('_', $selected); $order = array_pop($segments); // Validate fields. We can have "abc_asc" and "other.is_abs_desc" as fields $field = implode('_', $segments); if (in_array($field, array_keys($this->options))) { $this->column = $field; } // Validate order if (! empty($order) && in_array($order, ['asc', 'desc'])) { $this->order = $order; } return $this; } protected function validOption(string $key, ?Campaign $campaign = null) { $whitelist = ['tag.name', 'target.name']; if (! str_contains($key, '.name') || in_array($key, $whitelist)) { return true; } if ($key == 'entity.name') { return true; } // If it's a foreign key, it can probably be a module $module = str_replace('.name', '', $key); $module = Str::plural($module); return $campaign->enabled($module); } } ================================================ FILE: app/Datagrids/Sorters/QuestElementSorter.php ================================================ 'crud.fields.name', 'role' => 'quests.fields.role', ]; } ================================================ FILE: app/Definitions/CustomCssDefinitions.php ================================================ info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue; $borderRadius = new HTMLPurifier_AttrDef_CSS_Composite([ new HTMLPurifier_AttrDef_CSS_Length('0'), new HTMLPurifier_AttrDef_CSS_Percentage(true), ]); $definition->info['border-radius'] = $borderRadius; } } ================================================ FILE: app/Definitions/CustomDefinitions.php ================================================ addAttribute('a', 'data-toggle', 'Text'); $def->addAttribute('a', 'data-dropdown', 'Text'); $def->addAttribute('a', 'data-pulse', 'Text'); $def->addAttribute('a', 'data-animate', 'Text'); $def->addAttribute('a', 'data-tooltip', 'Text'); $def->addAttribute('a', 'data-title', 'Text'); $def->addAttribute('a', 'data-html', 'Text'); $def->addAttribute('a', 'data-entity-type', 'Text'); $def->addAttribute('a', 'target', 'Text'); $def->addAttribute('th', 'colwidth', 'Text'); $def->addAttribute('td', 'colwidth', 'Text'); // Gallery $def->addAttribute('img', 'data-gallery-id', 'Text'); $def->addAttribute('img', 'data-uuid', 'Text'); $def->addAttribute('ul', 'role', 'Text'); $def->addAttribute('ol', 'role', 'Text'); $def->addAttribute('li', 'role', 'Text'); $def->addAttribute('div', 'role', 'Text'); $def->addAttribute('a', 'role', 'Text'); // Task lists $def->addAttribute('ul', 'data-type', 'Text'); $def->addAttribute('li', 'data-type', 'Text'); $def->addAttribute('li', 'data-checked', 'Text'); $def->addElement('label', 'Inline', 'Inline', 'Common'); $def->addElement('input', 'Inline', 'Empty', 'Common', [ 'type' => new \HTMLPurifier_AttrDef_Enum(['checkbox']), 'checked' => new \HTMLPurifier_AttrDef_HTML_Bool(true), 'disabled' => new \HTMLPurifier_AttrDef_HTML_Bool(true), ]); $def->addElement( 'details', 'Block', 'Flow', 'Common', [ 'open' => new \HTMLPurifier_AttrDef_HTML_Bool(true), ] ); $def->addElement( 'section', 'Block', 'Flow', 'Common' ); $def->addElement( 'aside', 'Block', 'Flow', 'Common' ); $def->addElement( 'sidebar', 'Block', 'Flow', 'Common' ); $def->addElement('summary', 'Inline', 'Inline', 'Common'); $def->addElement('mark', 'Inline', 'Inline', 'Common'); // Ordered list attributes $def->addAttribute('ol', 'start', 'Number'); $def->addAttribute('ol', 'type', 'Text'); $def->addAttribute('li', 'value', 'Number'); // Table cell vertical alignment $def->addAttribute('td', 'valign', 'Text'); $def->addAttribute('th', 'valign', 'Text'); } } ================================================ FILE: app/Enums/AppReleaseCategory.php ================================================ true, default => false, }; } public function lowerName(): string { return mb_strtolower($this->name); } } ================================================ FILE: app/Enums/QuestStatus.php ================================================ true, default => false, }; } } ================================================ FILE: app/Events/AdminInviteCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Applications/Rejected.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Dashboards/DashboardCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Dashboards/DashboardDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Dashboards/DashboardUpdated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Deleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/EntityTypes/EntityTypeCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/EntityTypes/EntityTypeDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/EntityTypes/EntityTypeToggled.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/EntityTypes/EntityTypeUpdated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Exports/ExportCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Followers/FollowerCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Followers/FollowerRemoved.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Invites/InviteCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Invites/InviteDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Members/RoleUserAdded.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Members/UserJoined.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Members/UserLeft.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Plugins/PluginDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Plugins/PluginImported.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Plugins/PluginUpdated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Roles/RoleCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Roles/RoleDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Roles/RoleUpdated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Saved.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/SettingsSaved.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Sidebar/SidebarReset.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Sidebar/SidebarSaved.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Styles/StyleCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Styles/StyleDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Styles/StyleUpdated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Thumbnails/ThumbnailCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Thumbnails/ThumbnailDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Thumbnails/ThumbnailsDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Updated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Webhooks/WebhookCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Webhooks/WebhookDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Webhooks/WebhookTested.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Campaigns/Webhooks/WebhookUpdated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Entities/EntityRestored.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/FeatureCreated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Posts/PostDeleted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Posts/PostRestored.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Posts/PostUpdated.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/SpotlightSubmitted.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Subscriptions/Boost.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Subscriptions/Disable.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Subscriptions/Premium.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Subscriptions/SuperBoost.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Subscriptions/Upgrade.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/Users/EmailChanged.php ================================================ */ public function broadcastOn(): array { return [ new PrivateChannel('channel-name'), ]; } } ================================================ FILE: app/Events/WhiteboardUpdated.php ================================================ whiteboardId = $whiteboardId; $this->payload = $payload; $this->broadcastVia('reverb'); } public function broadcastAs() { return 'WhiteboardUpdated'; } /** * Get the channels the event should broadcast on. * * @return array */ public function broadcastOn(): array { return [ new Channel('kanka-whiteboard-' . $this->whiteboardId), ]; } } ================================================ FILE: app/Events/Whiteboards/Updated.php ================================================ broadcastVia('reverb'); } /** * Get the channels the event should broadcast on. * * @return array */ public function broadcastOn(): array { return [ new PresenceChannel("whiteboard.{$this->whiteboard->id}"), ]; } /** * The event's broadcast name. */ public function broadcastAs(): string { return 'shape'; } public function broadcastWith(): array { return [ 'action' => $this->action, 'shape' => new ShapeResource($this->shape), 'image' => $this->image, 'entity' => $this->entity, ]; } } ================================================ FILE: app/Exceptions/Campaign/AlreadyBoostedException.php ================================================ options = ['name' => $message->name]; parent::__construct('', $code, $previous); } } ================================================ FILE: app/Exceptions/Campaign/ExhaustedBoostsException.php ================================================ > */ protected $dontReport = [ \League\OAuth2\Server\Exception\OAuthServerException::class, NamespaceNotFoundException::class, CommandNotFoundException::class, HttpTransportException::class, FatalError::class, OAuthServerException::class, CannotUpdateLockedPropertyException::class, BroadcastException::class, NotFoundHttpException::class, DecoderException::class, ]; public function register(): void { $this->reportable(function (Throwable $e) { Integration::captureUnhandledException($e); }); } /** * Render an exception into an HTTP response. * * @param Request $request * @return JsonResponse|RedirectResponse|Response|\Symfony\Component\HttpFoundation\Response * * @throws Throwable */ public function render($request, Throwable $exception) { if ($exception instanceof TokenMismatchException) { return redirect() ->back() ->withInput($request->all()) ->withErrors(__('redirects.session_timeout')); } elseif ($exception instanceof AuthorizationException && auth()->guest()) { // User needs to be logged in, remember the page they visited session()->put('login_redirect', $request->getRequestUri()); } elseif (! $request->is('api/*') && $exception instanceof ModelNotFoundException) { // If the guest user tries accessing a private campaign, let's tell them about it $campaign = request()->route('campaign'); if (! empty($campaign) && ! ($campaign instanceof Campaign)) { session()->put('login_redirect', $request->getRequestUri()); /** @var Campaign $campaign */ $campaign = Campaign::select('id')->slug($campaign)->first(); if ($campaign && ! $campaign->isPublic()) { return response()->view('errors.private-campaign', [ 'campaign' => $campaign, ], 200); } } } elseif ($exception instanceof SymHttpException && $exception->getStatusCode() == 503) { if (request()->ajax()) { return response()->json([ 'title' => __('errors.503.title'), 'message' => __('errors.503.json'), ], 503); } // For stripe, we want them to try again later if (request()->is('stripe/*')) { return response()->json(['maintenance'], 503); } return response()->view('errors.maintenance', [ 'message' => $exception->getMessage(), // 'retry' => $exception->retryAfter ], 200); } elseif ($request->is('api/*') || Domain::isApi()) { // API error handling return $this->handleApiErrors($exception); } return parent::render($request, $exception); } /** * Unauthenticated exception handler * * @param Request $request * @return JsonResponse|RedirectResponse|\Symfony\Component\HttpFoundation\Response */ protected function unauthenticated($request, AuthenticationException $exception) { return $request->is('api/*') || Domain::isApi() ? response()->json([ 'message' => 'Unauthenticated (missing the authorization token in the request headers, or the token is invalid).', 'documentation' => 'https://app.kanka.io/api-docs/1.0/setup#authentication', ], 401) : redirect()->guest(route('login')); } /** * Handle all errors that happen in the API * * @return JsonResponse */ protected function handleApiErrors(Throwable $exception) { if ($exception instanceof ModelNotFoundException) { return response() ->json([ 'code' => 404, 'error' => $exception->getMessage(), ], 404); } elseif ($exception instanceof MethodNotAllowedHttpException) { return response() ->json([ 'code' => 405, 'error' => $exception->getMessage(), ], 405); } elseif ($exception instanceof ValidationException) { return response() ->json([ 'code' => $exception->status, 'error' => $exception->getMessage(), 'fields' => $exception->errors(), ], $exception->status); } elseif ($exception instanceof AuthorizationException) { return response() ->json([ 'code' => 403, 'error' => $exception->getMessage(), ], 403); } elseif ($exception instanceof NotFoundHttpException) { return response() ->json(['error' => 'Page not found'], 404); } elseif ($exception instanceof ThrottleRequestsException) { $amount = auth()->user()->rateLimit; $message = $amount != 90 ? ' Subscribe to Kanka to unlock higher limits' : null; return response() ->json(['Your account limit of ' . $amount . ' requests per minute has been reached.' . $message], 429); } elseif ($exception instanceof AuthenticationException) { return response() ->json([ 'code' => 401, 'error' => 'Invalid authentication token. Make sure you copy-pasted it correctly, or try using a new one at https://app.kanka.io/settings/api.', ], 401); } $limit = app()->isProduction() ? 100 : 2000; $trace = app()->hasDebugModeEnabled() ? $exception->getTrace() : null; ApiLog::exception($exception); return response() ->json([ 'code' => 500, 'error' => 'Unhandled API error. Contact us on Discord', 'hint' => Str::limit($exception->getMessage(), $limit), 'trace' => $trace, ], 500); } } ================================================ FILE: app/Exceptions/OpenAiException.php ================================================ context; } public function setContext($context) { $this->context = $context; } } ================================================ FILE: app/Exceptions/RequireLoginException.php ================================================ message, $this->options); } /** * @return $this */ public function setOptions(array $options) { $this->options = $options; return $this; } } ================================================ FILE: app/Facades/AdCache.php ================================================ campaign($campaign)->authEntityView($ability->entity); return redirect()->route('entities.children', [$campaign, $ability->entity]); } } ================================================ FILE: app/Http/Controllers/Abilities/EntityController.php ================================================ campaign($campaign)->authEntityView($ability->entity); $options = ['campaign' => $campaign, 'ability' => $ability]; Datagrid::layout(Entity::class) ->route('abilities.entities', $options); $this->rows = $ability ->entityAbilities() ->with(['entity.image', 'entity.tags', 'entity.entityType']) // ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('abilities.entities', $ability); } public function create(Campaign $campaign, Ability $ability) { $this->authorize('update', $ability->entity); $formOptions = ['abilities.entity-add.save', $campaign, 'ability' => $ability]; if (request()->has('from-children')) { $formOptions['from-children'] = true; } return view('abilities.entities.create', [ 'campaign' => $campaign, 'model' => $ability, 'formOptions' => $formOptions, ]); } public function store(StoreAbilityEntity $request, Campaign $campaign, Ability $ability) { $this->authorize('update', $ability->entity); $redirectUrlOptions = ['ability' => $ability->id]; if (request()->has('from-children')) { $redirectUrlOptions['ability_id'] = $ability->id; } $count = $ability->attachEntity($request->only('entities', 'visibility_id')); return redirect()->route('abilities.entities', [$campaign, 'ability' => $ability->id]) // ->with('success', __('abilities.children.create.success', ['name' => $ability->name])); ->with('success', trans_choice('abilities.children.create.attach_success', $count, ['count' => $count, 'name' => $ability->name])); } } ================================================ FILE: app/Http/Controllers/Account/Billing/InformationController.php ================================================ middleware(['identity']); } public function index() { return view('account.billing.information.form')->with('user', auth()->user()); } public function save(StoreBillingSettings $request) { if ($request->ajax()) { return response()->json(['success' => true]); } /** @var User $user */ $user = $request->user(); $user->updateBillingInfo($request->profile['billing']) ->update(); return redirect() ->route('billing.payment-method') ->with('success', trans('settings.profile.success')); } } ================================================ FILE: app/Http/Controllers/Account/DeleteController.php ================================================ middleware(['auth', 'identity', 'password.confirm']); } public function destroy(DeleteSettingsAccount $request) { if ($request->ajax()) { return response()->json(['success' => true]); } $this->deletionService ->user($request->user()) ->request($request) ->delete(); return redirect()->to(Domain::toFront('goodbye') . '?deleted=true'); } } ================================================ FILE: app/Http/Controllers/Account/EmailController.php ================================================ middleware(['identity', 'password.confirm']); } public function index() { return view('account.email.form')->with('user', auth()->user()); } public function save(StoreSettingsAccountEmail $request) { if ($request->ajax()) { return response()->json(['success' => true]); } auth()->user()->update($request->only('email')); return redirect() ->route('settings.account') ->with('success', __('settings.account.email_success')); } } ================================================ FILE: app/Http/Controllers/Account/PasswordController.php ================================================ middleware('password.confirm'); } public function index() { return view('account.password.form')->with('user', auth()->user()); } public function save(StoreSettingsAccount $request) { if ($request->ajax()) { return response()->json(['success' => true]); } $data = [ 'password' => Hash::make($request->post('password_new')), ]; auth()->user()->update($data); NewPassword::dispatch(auth()->user()); Auth::logoutOtherDevices($request->get('password_new')); return redirect() ->route('settings.account') ->with('success', __('settings.account.password_success')); } } ================================================ FILE: app/Http/Controllers/Account/SocialController.php ================================================ middleware(['identity', 'password.confirm']); } public function index() { if (empty(auth()->user()->provider)) { return redirect() ->route('settings.account') ->with('error', __('settings.account.social.error')); } return view('account.social.form')->with('user', auth()->user()); } public function save(StoreSettingsAccount $request) { if (empty(auth()->user()->provider)) { return redirect() ->route('settings.account') ->with('error', __('settings.account.social.error')); } if ($request->ajax()) { return response()->json(); } $data['provider'] = null; $data['provider_id'] = null; $data['password'] = Hash::make($request->post('password_new')); auth()->user()->update($data); Auth::logoutOtherDevices($request->get('password_new')); return redirect() ->route('settings.account') ->with('success', __('settings.account.social.success')); } } ================================================ FILE: app/Http/Controllers/Api/Public/CampaignController.php ================================================ service = $service; } public function index(Request $request) { return response() ->json( $this->service ->request($request) ->search() ) ->header('Expires', Carbon::now()->addDays(1)->toDateTimeString()); } public function setup() { return response() ->json( $this->service ->setup() ) ->header('Expires', Carbon::now()->addDays(1)->toDateTimeString()); } } ================================================ FILE: app/Http/Controllers/Api/Public/HallOfFameController.php ================================================ service = $service; } public function index() { return response()->json($this->service->subscribers()) ->header('Expires', Carbon::now()->addDays(1)->toDateTimeString()); } } ================================================ FILE: app/Http/Controllers/Api/Public/KbController.php ================================================ service = $service; } public function index() { return response()->json($this->service->api()) ->header('Expires', Carbon::now()->addDays(7)->toDateTimeString()); } } ================================================ FILE: app/Http/Controllers/Api/Public/ShowcaseController.php ================================================ json( $this->service ->request($request) ->search() ) ->header('Expires', Carbon::now()->addDays(1)->toDateTimeString()); } public function setup(Request $request) { return response() ->json( $this->service ->request($request) ->search() ) ->header('Expires', Carbon::now()->addDays(1)->toDateTimeString()); } } ================================================ FILE: app/Http/Controllers/Api/Public/VoteController.php ================================================ service = $service; } public function index() { $votes = CommunityVote::published() ->orderBy('visible_at', 'DESC') ->paginate(); return VoteResource::collection($votes); } public function show(CommunityVote $communityVote) { return new VoteResource($communityVote); } } ================================================ FILE: app/Http/Controllers/Api/v1/AbilityApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->abilities() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Ability $ability) { $this->authorize('access', $campaign); $this->authorize('view', $ability->entity); return new Resource($ability); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.ability')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Ability::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Ability $ability) { $this->authorize('access', $campaign); $this->authorize('update', $ability->entity); $ability->update($request->all()); $this->crudSave($ability, $request->validated()); return new Resource($ability); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Ability $ability) { $this->authorize('access', $campaign); $this->authorize('delete', $ability->entity); $ability->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/ApiController.php ================================================ middleware('auth'); } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('applications', $campaign); return Resource::collection($campaign ->applications() ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Application $application) { $this->authorize('applications', $campaign); return new Resource($application); } /** * @return JsonResponse */ public function reject(RejectApplication $request, Campaign $campaign, Application $application) { $this->authorize('applications', $campaign); $request->merge(['action' => 'reject']); $note = $this->service ->user(auth()->user()) ->campaign($campaign) ->application($application) ->process($request->only('action', 'reason')); return response()->json([$note]); } /** * @return JsonResponse */ public function approve(ApproveApplication $request, Campaign $campaign, Application $application) { $this->authorize('applications', $campaign); if (! $campaign->canHaveMoreMembers()) { return response()->json(['Campaign is full, please boost it.']); } $request->merge(['action' => 'approve']); $note = $this->service ->user(auth()->user()) ->campaign($campaign) ->application($application) ->process($request->only('role_id', 'action', 'reason')); return response()->json([$note]); } } ================================================ FILE: app/Http/Controllers/Api/v1/AttributeTemplateApiController.php ================================================ attributeService = $attributeService; } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('access', $campaign); return Resource::collection($campaign ->attributeTemplates() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, AttributeTemplate $attributeTemplate) { $this->authorize('access', $campaign); $this->authorize('view', $attributeTemplate->entity); return new Resource($attributeTemplate); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.attribute_template')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = AttributeTemplate::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, AttributeTemplate $attributeTemplate) { $this->authorize('access', $campaign); $this->authorize('update', $attributeTemplate->entity); $attributeTemplate->update($request->all()); $this->crudSave($attributeTemplate, $request->validated()); return new Resource($attributeTemplate); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, AttributeTemplate $attributeTemplate) { $this->authorize('access', $campaign); $this->authorize('delete', $attributeTemplate->entity); $attributeTemplate->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/BookmarkApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->bookmarks() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Bookmark $bookmark) { $this->authorize('access', $campaign); $this->authorize('view', $bookmark); return new Resource($bookmark); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', Bookmark::class); $data = $request->all(); $data['campaign_id'] = $campaign->id; /** @var Bookmark $model */ $model = Bookmark::create($data); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Bookmark $bookmark) { $this->authorize('access', $campaign); $this->authorize('update', $bookmark); $bookmark->update($request->all()); return new Resource($bookmark); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Bookmark $bookmark) { $this->authorize('access', $campaign); $this->authorize('delete', $bookmark); $bookmark->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/CalendarApiController.php ================================================ sanitizer = $sanitizer; } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('access', $campaign); return Resource::collection($campaign ->calendars() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Calendar $calendar) { $this->authorize('access', $campaign); $this->authorize('view', $calendar->entity); return new Resource($calendar); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.calendar')), $campaign]); $request->merge($this->sanitizer->request($request)->sanitize()); $data = $request->all(); $data['campaign_id'] = $campaign->id; /** @var Calendar $model */ $model = Calendar::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Calendar $calendar) { $this->authorize('access', $campaign); $this->authorize('update', $calendar->entity); $request->merge($this->sanitizer->request($request)->sanitize()); $calendar->update($request->all()); $this->crudSave($calendar, $request->validated()); return new Resource($calendar); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Calendar $calendar) { $this->authorize('access', $campaign); $this->authorize('delete', $calendar->entity); $calendar->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/CalendarEventApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $calendar->entity); return Resource::collection($calendar ->calendarEvents() ->paginate()); } } ================================================ FILE: app/Http/Controllers/Api/v1/CalendarWeatherApiController.php ================================================ authorize('access', $campaign); return Resource::collection($calendar ->calendarWeather() ->paginate()); } /** * @return resource * * @throws AuthorizationException */ public function show(Campaign $campaign, Calendar $calendar, CalendarWeather $calendarWeather) { $this->authorize('access', $campaign); $this->authorize('view', $calendar->entity); return new Resource($calendarWeather); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Calendar $calendar) { $this->authorize('access', $campaign); $this->authorize('update', $calendar->entity); $data = $request->all(); $data['calendar_id'] = $calendar->id; $model = CalendarWeather::create($data); return new Resource($model); } /** * @return resource * * @throws AuthorizationException */ public function update(Request $request, Campaign $campaign, Calendar $calendar, CalendarWeather $calendarWeather) { $this->authorize('access', $campaign); $this->authorize('update', $calendar->entity); $calendarWeather->update($request->all()); return new Resource($calendarWeather); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Calendar $calendar, CalendarWeather $calendarWeather) { $this->authorize('access', $campaign); $this->authorize('update', $calendar->entity); $calendarWeather->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/Calendars/AdvancerApiController.php ================================================ service = $service; } /** * @return JsonResponse */ public function advance(Campaign $campaign, Calendar $calendar) { $this->authorize('access', $campaign); $this->authorize('update', $calendar->entity); if ($calendar->missingDetails()) { return response()->json(['Invalid calendar config'], 406); } $this->service->calendar($calendar)->advance(); return response()->json(['date' => $calendar->date]); } /** * @return JsonResponse */ public function retreat(Campaign $campaign, Calendar $calendar) { $this->authorize('access', $campaign); $this->authorize('update', $calendar->entity); if ($calendar->missingDetails()) { return response()->json(['Invalid calendar config'], 406); } $this->service->calendar($calendar)->retreat(); return response()->json(['date' => $calendar->date]); } } ================================================ FILE: app/Http/Controllers/Api/v1/CampaignApiController.php ================================================ user() ->campaigns() ->with(['members', 'setting', 'roles', 'applications', 'members.user', 'description']) ->lastSync(request()->get('lastSync')) ->paginate(); return CampaignResource::collection($campaigns); } public function show(Campaign $campaign) { $this->authorize('access', $campaign); $campaign->load('description'); $resource = new CampaignResource($campaign); return $resource->withMentions(); } public function store(Request $request) { $this->authorize('create', Campaign::class); $campaign = $this->createService->user($request->user()) ->request($request) ->create(); $campaign->refresh(); return new CampaignResource($campaign); } public function update(Request $request, Campaign $campaign) { $this->authorize('update', $campaign); $campaign->update($request->all()); $descriptionData = $request->only(['description', 'excerpt']); if (! empty($descriptionData)) { CampaignDescription::updateOrCreate(['campaign_id' => $campaign->id], $descriptionData); } return new CampaignResource($campaign->refresh()); } public function destroy(Request $request, Campaign $campaign) { $this->authorize('delete', $campaign); $this->deletionService ->campaign($campaign) ->user($request->user()) ->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/CampaignDashboardWidgetApiController.php ================================================ authorize('access', $campaign); return Resource::collection( $campaign->widgets() ->lastSync(request()->get('lastSync')) ->paginate() ); } /** * @return resource */ public function show(Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { $this->authorize('update', $campaign); return new Resource($campaignDashboardWidget); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('update', $campaign); if ($request->input('widget') === Widget::Gallery->value) { $this->authorize('galleryWidget', $campaign); } $data = array_merge(['campaign_id' => $campaign->id], $request->all()); $model = CampaignDashboardWidget::create($data); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { $this->authorize('update', $campaign); $campaignDashboardWidget->update($request->all()); return new Resource($campaignDashboardWidget); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { $this->authorize('update', $campaign); $campaignDashboardWidget->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/CampaignImageApiController.php ================================================ service = $summernoteService; } public function index(Campaign $campaign) { $this->authorize('access', $campaign); return Resource::collection( $campaign ->images() ->where('is_default', false) ->defaultOrder() ->lastSync(request()->get('lastSync')) ->paginate(auth()->user()->isSubscriber() ? 100 : 45) ); } public function show(Campaign $campaign, Image $image) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); return new Resource($image); } public function store(GalleryImageStore $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); $images = $this->service ->user($request->user()) ->campaign($campaign) ->store($request); return Resource::collection($images); } public function update(GalleryImageUpdate $request, Campaign $campaign, Image $image) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); $image->update($request->only('name', 'folder_id')); return new Resource($image); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Image $image) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); $image->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/CampaignRoleApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->roles() ->paginate()); } } ================================================ FILE: app/Http/Controllers/Api/v1/CampaignStyleApiController.php ================================================ middleware(Boosted::class, ['except' => 'index']); } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('access', $campaign); return Resource::collection( $campaign ->styles() ->paginate() ); } /** * @return resource */ public function show(Campaign $campaign, CampaignStyle $campaignStyle) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); return new Resource($campaignStyle); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); $model = new CampaignStyle($request->all()); $model->campaign_id = $campaign->id; $model->save(); $model->refresh(); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, CampaignStyle $campaignStyle) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); $campaignStyle->update($request->all()); return new Resource($campaignStyle); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, CampaignStyle $campaignStyle) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); $campaignStyle->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/Campaigns/CategoryStatusController.php ================================================ authorize('access', $campaign); $statuses = CategoryStatus::get(); return CategoryStatusResource::collection($statuses); } } ================================================ FILE: app/Http/Controllers/Api/v1/Campaigns/EntityTypeApiController.php ================================================ middleware(Premium::class, ['except' => ['index', 'show']]); } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('access', $campaign); return Resource::collection(EntityType::inCampaign($campaign)->paginate()); } public function show(Campaign $campaign, EntityType $entityType) { $this->authorize('access', $campaign); if ($entityType->campaign_id !== $campaign->id) { abort(403); } return new Resource($entityType); } public function store(StoreEntityType $request, Campaign $campaign) { $this->authorize('setting', $campaign); $error = '. Consider upgrading to a higher tier to unlock a higher limit'; $limit = config('limits.campaigns.modules.premium'); if ($campaign->isWyvern()) { $limit = config('limits.campaigns.modules.wyvern'); } elseif ($campaign->isElemental()) { $limit = config('limits.campaigns.modules.elemental'); $error = ''; } if ($campaign->entityTypes->count() >= $limit) { return response()->json(['error' => 'Max number of custom modules reached (:count/:max)' . $error, [ 'count' => $campaign->entityTypes->count(), 'max' => $limit, ]], 401); } $entityType = $this->entityTypeService ->campaign($campaign) ->request($request) ->save(); return new Resource($entityType); } public function update(StoreEntityType $request, Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); $this->authorize('update', [$entityType, $campaign]); $entityType = $this->entityTypeService ->campaign($campaign) ->request($request) ->entityType($entityType) ->save(); return new Resource($entityType); } public function destroy(Request $request, Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); $this->authorize('delete', [$entityType, $campaign]); $this->entityTypeService ->campaign($campaign) ->entityType($entityType) ->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/Campaigns/UserApiController.php ================================================ service = $memberService; } public function index(Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); return UserResource::collection($campaign->users() ->lastSync(request()->get('lastSync')) ->paginate()); } public function show(Campaign $campaign, User $user) { $this->authorize('access', $campaign); $this->authorize('update', $campaign); return UserResource::collection($campaign->users()->where('user_id', '=', $user->id) ->lastSync(request()->get('lastSync')) ->paginate()); } /** * Add a single user to a role * * @return JsonResponse */ public function add(UpdateUserRole $request, Campaign $campaign) { $result = $this->service ->fromRequest($request) ->campaign($campaign) ->add(); if ($result) { return response()->json([ 'data' => 'role successfully added to user', ]); } return response()->json(['error' => 'Invalid input'], 422); } /** * Remove a role from a user * * @return JsonResponse */ public function remove(UpdateUserRole $request, Campaign $campaign) { $result = $this->service ->fromRequest($request) ->campaign($campaign) ->remove(); if ($result) { return response()->json([ 'data' => 'role successfully removed from the user', ]); } return response()->json(['error' => 'Invalid input'], 422); } } ================================================ FILE: app/Http/Controllers/Api/v1/CharacterApiController.php ================================================ authorize('access', $campaign); return CharacterResource::collection( $campaign ->characters() ->withApi() ->filter(request()->all()) ->lastSync(request()->get('lastSync')) ->paginate(), ); } /** * @return CharacterResource * * @throws AuthorizationException */ public function show(Campaign $campaign, Character $character) { $this->authorize('access', $campaign); $this->authorize('view', $character->entity); return new CharacterResource($character); } /** * @return CharacterResource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [ EntityType::find(config('entities.ids.character')), $campaign, ]); $data = $request->all(); $data['campaign_id'] = $campaign->id; /** @var Character $model */ $model = Character::create($data); $this->crudSave($model, $request->validated()); return new CharacterResource($model); } /** * @return CharacterResource * * @throws AuthorizationException */ public function update( Request $request, Campaign $campaign, Character $character, ) { $this->authorize('access', $campaign); $this->authorize('update', $character->entity); $character->update($request->all()); $this->crudSave($character, $request->validated()); return new CharacterResource($character); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Character $character) { $this->authorize('access', $campaign); $this->authorize('delete', $character->entity); $character->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/ConversationApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->conversations() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Conversation $conversation) { $this->authorize('access', $campaign); $this->authorize('view', $conversation->entity); return new Resource($conversation); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.conversation')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; /** @var Conversation $model */ $model = Conversation::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Conversation $conversation) { $this->authorize('access', $campaign); $this->authorize('update', $conversation->entity); $conversation->update($request->all()); $this->crudSave($conversation, $request->validated()); return new Resource($conversation); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Conversation $conversation) { $this->authorize('access', $campaign); $this->authorize('delete', $conversation->entity); $conversation->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/ConversationMessageApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $conversation->entity); return Resource::collection( $conversation ->messages() ->lastSync(request()->get('lastSync')) ->paginate() ); } /** * @return resource */ public function show( Campaign $campaign, Conversation $conversation, ConversationMessage $conversationMessage ) { $this->authorize('access', $campaign); $this->authorize('view', $conversation->entity); return new Resource($conversationMessage); } /** * @return resource * * @throws AuthorizationException */ public function store(RequestMessage $requestMessage, Campaign $campaign, Conversation $conversation) { $this->authorize('access', $campaign); $this->authorize('update', $conversation->entity); $model = ConversationMessage::create($requestMessage->all()); return new Resource($model); } /** * @return resource */ public function update( RequestMessage $requestMessage, Campaign $campaign, Conversation $conversation, ConversationMessage $conversationMessage ) { $this->authorize('access', $campaign); $this->authorize('update', $conversation->entity); $conversationMessage->update($requestMessage->all()); return new Resource($conversationMessage); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( Request $request, Campaign $campaign, Conversation $conversation, ConversationMessage $conversationMessage ) { $this->authorize('access', $campaign); $this->authorize('update', $conversation->entity); $conversationMessage->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/ConversationParticipantApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $conversation->entity); return Resource::collection($conversation->participants()->paginate()); } /** * @return resource */ public function show( Campaign $campaign, Conversation $conversation, ConversationParticipant $conversationParticipant ) { $this->authorize('access', $campaign); $this->authorize('view', $conversation->entity); return new Resource($conversationParticipant); } /** * @return resource * * @throws AuthorizationException */ public function store(RequestParticipant $requestParticipant, Campaign $campaign, Conversation $conversation) { $this->authorize('access', $campaign); $this->authorize('update', $conversation->entity); $model = ConversationParticipant::create($requestParticipant->all()); return new Resource($model); } /** * @return resource */ public function update( RequestParticipant $requestParticipant, Campaign $campaign, Conversation $conversation, ConversationParticipant $conversationParticipant ) { $this->authorize('access', $campaign); $this->authorize('update', $conversation->entity); $conversationParticipant->update($requestParticipant->all()); return new Resource($conversationParticipant); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( Request $request, Campaign $campaign, Conversation $conversation, ConversationParticipant $conversationParticipant ) { $this->authorize('access', $campaign); $this->authorize('update', $conversation->entity); $conversationParticipant->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/CreatureApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->creatures() ->filter(request()->all()) ->withApi() ->has('entity') ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Creature $creature) { $this->authorize('access', $campaign); $this->authorize('view', $creature->entity); return new Resource($creature); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.creature')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Creature::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Creature $creature) { $this->authorize('access', $campaign); $this->authorize('update', $creature->entity); $creature->update($request->all()); $this->crudSave($creature, $request->validated()); return new Resource($creature); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Creature $creature) { $this->authorize('access', $campaign); $this->authorize('delete', $creature->entity); $creature->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/DefaultThumbnailApiController.php ================================================ middleware(Boosted::class)->except('index'); } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('access', $campaign); return Resource::collection($campaign->defaultImages()); } /** * @return JsonResponse * * @throws AuthorizationException */ public function upload(StoreDefaultThumbnail $request, Campaign $campaign) { $this->authorize('access', $campaign); /** @var EntityType $entityType */ $entityType = EntityType::inCampaign($campaign)->find($request->post('entity_type_id')); $this->defaultImageService->campaign($campaign) ->user($request->user()) ->entityType($entityType); if ($this->defaultImageService->save($request)) { return response()->json([ 'data' => 'Default thumbnail successfully uploaded', ]); } return response()->json(['error' => 'Invalid input'], 422); } /** * @throws AuthorizationException */ public function delete(DestroyDefaultThumbnail $request, Campaign $campaign) { $this->authorize('recover', $campaign); /** @var EntityType $entityType */ $entityType = EntityType::inCampaign($campaign)->find($request->post('entity_type_id')); $result = $this->defaultImageService ->campaign($campaign) ->user($request->user()) ->entityType($entityType) ->destroy(); if ($result) { return response()->json([ 'data' => 'Default thumbnail successfully deleted', ]); } return response()->json(['error' => 'Invalid input'], 422); } } ================================================ FILE: app/Http/Controllers/Api/v1/DiceRollApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->diceRolls() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, DiceRoll $diceRoll) { $this->authorize('access', $campaign); $this->authorize('view', $diceRoll->entity); return new Resource($diceRoll); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.dice_roll')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = DiceRoll::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, DiceRoll $diceRoll) { $this->authorize('access', $campaign); $this->authorize('update', $diceRoll->entity); $diceRoll->update($request->all()); $this->crudSave($diceRoll, $request->validated()); return new Resource($diceRoll); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, DiceRoll $diceRoll) { $this->authorize('access', $campaign); $this->authorize('delete', $diceRoll->entity); $diceRoll->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/Entities/Attributes/PatchController.php ================================================ authorize('access', $campaign); $this->authorize('update', $entity); $attributes = $request->get('attribute', []); $this->service ->entity($entity) ->save($attributes) ->touch(); return Resource::collection($entity->attributes()->with('entity')->get()); } } ================================================ FILE: app/Http/Controllers/Api/v1/Entities/Attributes/PutController.php ================================================ authorize('access', $campaign); $this->authorize('update', $entity); $attributes = $request->get('attribute', []); $this->service ->entity($entity) ->deleteOld() ->save($attributes) ->touch(); return Resource::collection($entity->attributes()->with('entity')->get()); } } ================================================ FILE: app/Http/Controllers/Api/v1/Entities/ReminderApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->reminders()->paginate()); } public function show(Campaign $campaign, Entity $entity, Reminder $reminder) { $this->authorize('access', $campaign); $this->authorize('view', $entity); $this->authorize('entity', [$reminder, $entity]); return new Resource($reminder); } public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->all(); if (! isset($data['length'])) { $data['length'] = 1; } $model = new Reminder($data); $model->remindable_id = $entity->id; $model->remindable_type = Entity::class; $model->save(); return new Resource($model); } public function update(Request $request, Campaign $campaign, Entity $entity, Reminder $reminder) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $this->authorize('entity', [$reminder, $entity]); $reminder->update($request->all()); return new Resource($reminder); } public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Entity $entity, Reminder $reminder ) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $this->authorize('entity', [$reminder, $entity]); $reminder->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityAbilityApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->abilities()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Entity $entity, EntityAbility $entityAbility) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new Resource($entityAbility); } /** * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->all(); $data['entity_id'] = $entity->id; if (isset($data['abilities']) && is_array($data['abilities'])) { $models = new Collection; foreach ($data['abilities'] as $abilityId) { $ability = Ability::find($abilityId); if (! $ability) { continue; } $data['ability_id'] = $ability->id; $models->add(EntityAbility::create($data)); } return Resource::collection($models); } $model = EntityAbility::create($data); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Entity $entity, EntityAbility $entityAbility) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $entityAbility->update($request->all()); return new Resource($entityAbility); } /** * @param Request $request * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Entity $entity, EntityAbility $entityAbility ) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $entityAbility->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign->entities() ->apiFilter($campaign, request()->all()) ->lastSync(request()->get('lastSync')) ->paginate() ->appends(request()->except(['page', 'lastSync']))); } public function show(Campaign $campaign, Entity $entity) { if (config('app.debug')) { DB::enableQueryLog(); } $this->authorize('access', $campaign); $this->authorize('view', $entity); $resource = new Resource($entity); return $resource->withMisc(); } public function put(StoreEntities $request, Campaign $campaign, EntityType $entityType) { $this->authorize('access', $campaign); $this->authorize('create', [$campaign, $entityType]); $entities = []; $this->bulkEntityCreatorService ->campaign($campaign) ->entityType($entityType) ->user($request->user()); foreach ($request->get('entities', []) as $entity) { $entities[] = $this->bulkEntityCreatorService->data($entity)->create(); } return Resource::collection($entities); } public function edit(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); if ($entity->entityType->isStandard()) { return response()->json(['error' => 'Only entities of custom modules can be deleted here'], 401); } $entity->update($request->all()); return new Resource($entity); } public function patch(PatchEntity $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $keys = ['name', 'type', 'is_private', 'is_template', 'tooltip', 'entry', 'image_uuid', 'header_uuid']; if ($entity->entityType->isCustom()) { $keys[] = 'parent_id'; } $data = $request->only($keys); $this->entitySaveService->save($entity->fill($data), $data); return new Resource($entity); } public function destroy(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('delete', $entity); if ($entity->entityType->isStandard()) { return response()->json(['error' => 'Only entities of custom modules can be deleted here'], 401); } $entity->delete(); return response()->json(['success'], 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityArchiveApiController.php ================================================ service = $archiveService; } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { if (config('app.debug')) { DB::enableQueryLog(); } $this->authorize('access', $campaign); return Resource::collection($campaign->entities() ->apiFilter($campaign, request()->all()) ->lastSync(request()->get('lastSync')) ->whereNotNull('archived_at') ->paginate() ->appends(request()->except(['page', 'lastSync']))); } /** * @return resource * * @throws AuthorizationException */ public function switch(Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $this->service->entity($entity)->toggle(); $resource = new Resource($entity); return $resource->withMisc(); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityAssetApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->assets()->with(['entity', 'image'])->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Entity $entity, EntityAsset $entityAsset) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new Resource($entityAsset); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->all(); $data['entity_id'] = $entity->id; if ($request->get('type_id') == EntityAssetType::file->value) { /** @var EntityFileService $service */ $service = app()->make(EntityFileService::class); $files = $service ->entity($entity) ->campaign($campaign) ->upload($request, 'file') ->files(); return new Resource($files[0]); } $model = EntityAsset::create($data); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Entity $entity, EntityAsset $entityAsset) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $entityAsset->update($request->all()); return new Resource($entityAsset); } /** * @param Request $request * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Entity $entity, EntityAsset $entityAsset ) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $entityAsset->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityAttributeApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->attributes()->with('entity')->get()); } /** * @return resource */ public function show(Campaign $campaign, Entity $entity, Attribute $attribute) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new Resource($attribute); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->all(); $data['entity_id'] = $entity->id; $model = Attribute::create($data); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Entity $entity, Attribute $attribute) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->all(); $data['entity_id'] = $entity->id; $attribute->update($data); return new Resource($attribute); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Entity $entity, Attribute $attribute) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $attribute->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityImageApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return new EntityImagesResource($entity); } public function put(UploadFile $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $this->authorize('galleryUpload', $campaign); try { $this->uploadService ->campaign($campaign) ->user($request->user()) ->file($request->file('file')); $image = $this->uploadService->image(); $field = $request->filled('is_header') ? 'header_uuid' : 'image_uuid'; $entity->update([$field => $image->id]); return new EntityImagesResource($entity); } catch (TranslatableException $e) { return response()->json( ['error' => $e->getTranslatedMessage()], 421 ); } } public function destroy(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $field = $request->filled('is_header') ? 'header_uuid' : 'image_uuid'; $entity->update([$field => null]); return new EntityImagesResource($entity); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityInventoryApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->inventories()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Entity $entity, Inventory $inventory) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new Resource($inventory); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->all(); $data['entity_id'] = $entity->id; $model = Inventory::create($data); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Entity $entity, Inventory $inventory) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $inventory->update($request->all()); return new Resource($inventory); } /** * @param Request $request * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Entity $entity, Inventory $inventory ) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $inventory->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityMentionApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->mentions()->paginate()); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityMoveApiController.php ================================================ middleware('auth'); $this->service = $service; } /** * @throws AuthorizationException */ public function transfer(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $count = 0; $copy = $request->has('copy'); try { foreach ($request->entities as $id) { $entity = Entity::find($id); if ($this->authorize('update', $entity)) { $this->service ->entity($entity) ->campaign($campaign) ->user($request->user()) ->to($request->campaign_id) ->copy($copy) ->validate() ->process(); $count++; } } if ($copy) { return response()->json(['success' => 'Succesfully copied ' . $count . ' entities.']); } return response()->json(['success' => 'Succesfully transfered ' . $count . ' entities.']); } catch (TranslatableException $e) { return response()->json([ 'error' => true, 'message' => $e->getTranslatedMessage(), ]); } } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityPermissionApiController.php ================================================ apiPermissionService = $apiPermissionService; } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->permissions); } /** * @return resource */ public function show(Campaign $campaign, Entity $entity, CampaignPermission $permission) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new Resource($permission); } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $model = $this->apiPermissionService->saveEntity($request, $entity); return Resource::collection($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Entity $entity, CampaignPermission $permission) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $permission->update($request->only('access', 'action')); return new Resource($permission); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Entity $entity, CampaignPermission $permission) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $permission->delete(); return response()->json(null, 204); } /** * @return JsonResponse */ public function test(PermissionTestRequest $request, Campaign $campaign) { $permissionTest = $this->apiPermissionService->entityPermissionTest($request, $campaign); return response()->json($permissionTest); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityRecoveryApiController.php ================================================ middleware('auth'); $this->service = $service; } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('recover', $campaign); return Resource::collection($campaign ->entities()->onlyTrashed() ->paginate()); } /** * @throws AuthorizationException */ public function recover(Request $request, Campaign $campaign) { $this->authorize('recover', $campaign); if (! $campaign->boosted()) { return response()->json(null, 204); } $this->service->campaign($campaign)->user($request->user())->recover($request->entities); return response()->json(['success' => 'Successfully recovered deleted entities']); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityRelationApiController.php ================================================ relationService = $relationService; } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->relationships()->has('target')->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Entity $entity, Relation $relation) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new Resource($relation); } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $this->relationService->campaign($campaign)->createRelations($request); return Resource::collection($entity->relationships()->has('target')->whereIn('target_id', $this->relationService->getEntities())->paginate()); } /** * @return resource */ public function update(UpdateRequest $request, Campaign $campaign, Entity $entity, Relation $relation) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $relation->update($request->all()); return new Resource($relation); } /** * @param Request $request * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Entity $entity, Relation $relation ) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $relation->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityTagApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->tags()->withPivot('id')->paginate()); } public function show(Campaign $campaign, Entity $entity, EntityTag $entityTag) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new ApiResource($entityTag); } public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->only(['tag_id']); $data['entity_id'] = $entity->id; $model = EntityTag::create($data); return new ApiResource($model); } public function update(Request $request, Campaign $campaign, Entity $entity, EntityTag $entityTag) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $entityTag->update($request->all()); return new ApiResource($entityTag); } public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Entity $entity, EntityTag $entityTag ) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $entityTag->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityTemplateApiController.php ================================================ service = $templateService; } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { if (config('app.debug')) { DB::enableQueryLog(); } $this->authorize('access', $campaign); return Resource::collection($campaign->entities() ->apiFilter($campaign, request()->all()) ->lastSync(request()->get('lastSync')) ->where('is_template', true) ->paginate() ->appends(request()->except(['page', 'lastSync']))); } /** * @return resource * * @throws AuthorizationException */ public function switch(Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $this->service->entity($entity)->toggle(); $resource = new Resource($entity); return $resource->withMisc(); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityTransformApiController.php ================================================ middleware('auth'); $this->service = $service; } /** * @throws AuthorizationException */ public function transform(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $count = 0; $entityType = EntityType::inCampaign($campaign)->where('id', $request->entity_type)->first(); foreach ($request->entities as $id) { $entity = Entity::find($id); if ($this->authorize('update', $entity)) { $this->service ->entity($entity) ->entityType($entityType) ->transform(); $count++; } } return response()->json(['success' => 'Succesfully transformed ' . $count . ' entities.']); } } ================================================ FILE: app/Http/Controllers/Api/v1/EntityTypeApiController.php ================================================ paginate()); } } ================================================ FILE: app/Http/Controllers/Api/v1/EventApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->events() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Event $event) { $this->authorize('access', $campaign); $this->authorize('view', $event->entity); return new Resource($event); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.event')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Event::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Event $event) { $this->authorize('access', $campaign); $this->authorize('update', $event->entity); $event->update($request->all()); $this->crudSave($event, $request->validated()); return new Resource($event); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Event $event) { $this->authorize('access', $campaign); $this->authorize('delete', $event->entity); $event->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/FamilyApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->families() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Family $family) { $this->authorize('access', $campaign); $this->authorize('view', $family->entity); return new Resource($family); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.family')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Family::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Family $family) { $this->authorize('access', $campaign); $this->authorize('update', $family->entity); $family->update($request->all()); $this->crudSave($family, $request->validated()); return new Resource($family); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Family $family) { $this->authorize('access', $campaign); $this->authorize('delete', $family->entity); $family->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/FamilyTreeApiController.php ================================================ treeService = $treeService; $this->middleware(Premium::class, ['except' => ['show']]); } /** * @return resource */ public function show(Campaign $campaign, Family $family) { $this->authorize('access', $campaign); $this->authorize('view', $family->entity); return new Resource($family->familyTree); } /** * @return resource | JsonResponse * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Family $family) { $this->authorize('access', $campaign); $this->authorize('update', $family->entity); if (! $campaign->premium()) { return response()->json('You need to activate premium functions on the campaign to use this feature', 204); } $data = $request->input('tree'); $model = $this ->treeService ->family($family) ->user($request->user()) ->save($data) ->familyTree(); return new Resource($model); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Family $family) { $this->authorize('access', $campaign); $this->authorize('delete', $family->entity); if ($family->familyTree) { $family->familyTree->delete(); } return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/FilterApiController.php ================================================ filterService = $filterService; } public function index() { return response()->json($this->filterService->endpoints()); } public function show(EntityType $entityType) { return response()->json([ 'data' => $this->filterService ->entityType($entityType) ->filters(), ]); } } ================================================ FILE: app/Http/Controllers/Api/v1/FullTextSearchApiController.php ================================================ service = $service; } /** * return \Illuminate\Http\Resources\Json\AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('access', $campaign); $term = request()->term; $term2 = null; /** @var ?Entity $entity */ $entity = Entity::with('entityType')->where(['name' => request()->term, 'campaign_id' => $campaign->id])->first(); if ($entity) { $term2 = $entity->entityType->code . ':' . $entity->id; } $results = $this->service ->campaign($campaign) ->search($term, $term2); return $results; } } ================================================ FILE: app/Http/Controllers/Api/v1/HealthController.php ================================================ json(['success' => true]); } } ================================================ FILE: app/Http/Controllers/Api/v1/ItemApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->items() ->has('entity') ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Item $item) { $this->authorize('access', $campaign); $this->authorize('view', $item->entity); return new Resource($item); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.item')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; /** @var Item $model */ $model = Item::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Item $item) { $this->authorize('access', $campaign); $this->authorize('update', $item->entity); $item->update($request->all()); $this->crudSave($item, $request->validated()); return new Resource($item); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Item $item) { $this->authorize('access', $campaign); $this->authorize('delete', $item->entity); $item->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/JournalApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->journals() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Journal $journal) { $this->authorize('access', $campaign); $this->authorize('view', $journal->entity); return new Resource($journal); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.journal')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Journal::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Journal $journal) { $this->authorize('access', $campaign); $this->authorize('update', $journal->entity); $journal->update($request->all()); $this->crudSave($journal, $request->validated()); return new Resource($journal); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Journal $journal) { $this->authorize('access', $campaign); $this->authorize('delete', $journal->entity); $journal->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/LocationApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->locations() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Location $location) { $this->authorize('access', $campaign); $this->authorize('view', $location->entity); return new Resource($location); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.location')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Location::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Location $location) { $this->authorize('access', $campaign); $this->authorize('update', $location->entity); $location->update($request->all()); $this->crudSave($location, $request->validated()); return new Resource($location); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Location $location) { $this->authorize('access', $campaign); $this->authorize('delete', $location->entity); $location->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/MapApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->maps() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Map $map) { $this->authorize('access', $campaign); $this->authorize('view', $map->entity); return new Resource($map); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.map')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Map::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Map $map) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); $map->update($request->all()); $this->crudSave($map, $request->validated()); return new Resource($map); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Map $map) { $this->authorize('access', $campaign); $this->authorize('delete', $map->entity); $map->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/MapGroupApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $map->entity); return Resource::collection($map->groups()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Map $map, MapGroup $mapGroup) { $this->authorize('access', $campaign); $this->authorize('view', $map->entity); return new Resource($mapGroup); } public function store(Request $request, Campaign $campaign, Map $map) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); if (! auth()->user()->can('addGroup', [$map, $campaign])) { return response()->json(['error' => 'Max amount of map groups reached']); } $model = MapGroup::create($request->all()); return new Resource($model); } public function update( Request $request, Campaign $campaign, Map $map, MapGroup $mapGroup ) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); $mapGroup->update($request->all()); return new Resource($mapGroup); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Map $map, MapGroup $mapGroup ) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); $mapGroup->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/MapLayerApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $map->entity); return Resource::collection($map->layers()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Map $map, MapLayer $mapLayer) { $this->authorize('access', $campaign); $this->authorize('view', $map->entity); return new Resource($mapLayer); } public function store(Request $request, Campaign $campaign, Map $map) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); if (! auth()->user()->can('addLayer', [$map, $campaign])) { return response()->json(['error' => 'Max amount of map layers reached']); } $model = MapLayer::create($request->all()); $model->refresh(); return new Resource($model); } /** * @return resource * * @throws AuthorizationException */ public function update( Request $request, Campaign $campaign, Map $map, MapLayer $mapLayer ) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); $mapLayer->update($request->all()); return new Resource($mapLayer); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Map $map, MapLayer $mapLayer ) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); $mapLayer->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/MapMarkerApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $map->entity); return Resource::collection($map->markers()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Map $map, MapMarker $mapMarker) { $this->authorize('access', $campaign); $this->authorize('view', $map->entity); return new Resource($mapMarker); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Map $map) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); $data = $request->all(); $data['map_id'] = $map->id; $model = MapMarker::create($data); return new Resource($model); } /** * @return resource * * @throws AuthorizationException */ public function update( Request $request, Campaign $campaign, Map $map, MapMarker $mapMarker ) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); $mapMarker->update($request->all()); return new Resource($mapMarker); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Map $map, MapMarker $mapMarker ) { $this->authorize('access', $campaign); $this->authorize('update', $map->entity); $mapMarker->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/MiscApiController.php ================================================ relationsFactory->for($model->entity); if (request()->isMethod('patch')) { $service?->patch(); } $service?->save($model, $data); if (! empty($model->entity)) { $this->entitySaveService->save($model->entity, $data); if ($model->wasChanged() && ! $model->entity->wasChanged()) { $model->entity->touch(); } } $model->refresh(); } } ================================================ FILE: app/Http/Controllers/Api/v1/NoteApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->notes() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Note $note) { $this->authorize('access', $campaign); $this->authorize('view', $note->entity); return new Resource($note); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.note')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Note::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Note $note) { $this->authorize('access', $campaign); $this->authorize('update', $note->entity); $note->update($request->all()); $this->crudSave($note, $request->validated()); return new Resource($note); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Note $note) { $this->authorize('access', $campaign); $this->authorize('delete', $note->entity); $note->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/OrganisationApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->organisations() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Organisation $organisation) { $this->authorize('access', $campaign); $this->authorize('view', $organisation->entity); return new Resource($organisation); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.organisation')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Organisation::create($data); $this->crudSave($model, $request->validated()); $model->refresh(); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Organisation $organisation) { $this->authorize('access', $campaign); $this->authorize('update', $organisation->entity); $organisation->update($request->all()); $this->crudSave($organisation, $request->validated()); return new Resource($organisation); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Organisation $organisation) { $this->authorize('access', $campaign); $this->authorize('delete', $organisation->entity); $organisation->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/OrganisationMemberApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $organisation->entity); return Resource::collection($organisation->members()->has('character')->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Organisation $organisation, OrganisationMember $organisationMember) { $this->authorize('access', $campaign); $this->authorize('view', $organisation->entity); return new Resource($organisationMember); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Organisation $organisation) { $this->authorize('access', $campaign); $this->authorize('update', $organisation->entity); $model = OrganisationMember::create($request->all()); return new Resource($model); } /** * @return resource * * @throws AuthorizationException */ public function update( Request $request, Campaign $campaign, Organisation $organisation, OrganisationMember $organisationMember ) { $this->authorize('access', $campaign); $this->authorize('update', $organisation->entity); $organisationMember->update($request->all()); return new Resource($organisationMember); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Organisation $organisation, OrganisationMember $organisationMember ) { $this->authorize('access', $campaign); $this->authorize('update', $organisation->entity); $organisationMember->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/PostApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->posts()->with('permissions')->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Entity $entity, Post $post) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new Resource($post); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->all(); $data['entity_id'] = $entity->id; $model = Post::create($data); $model->refresh(); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Entity $entity, Post $post) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $post->update($request->all()); return new Resource($post); } /** * @param Request $request * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Entity $entity, Post $post ) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $post->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/PostLayoutApiController.php ================================================ middleware('auth'); $this->service = $service; } /** * @return AnonymousResourceCollection * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('recover', $campaign); return Resource::collection($campaign ->posts()->onlyTrashed() ->paginate()); } /** * @throws AuthorizationException */ public function recover(Request $request, Campaign $campaign) { $this->authorize('recover', $campaign); if (! $campaign->boosted()) { return response()->json(null, 204); } $this->service->campaign($campaign)->user($request->user())->recover($request->posts); return response()->json(['success' => 'Successfully recovered deleted posts']); } } ================================================ FILE: app/Http/Controllers/Api/v1/ProfileApiController.php ================================================ user()); } } ================================================ FILE: app/Http/Controllers/Api/v1/QuestApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->quests() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Quest $quest) { $this->authorize('access', $campaign); $this->authorize('view', $quest->entity); return new Resource($quest); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.quest')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; /** @var Quest $model */ $model = Quest::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Quest $quest) { $this->authorize('access', $campaign); $this->authorize('update', $quest->entity); $quest->update($request->all()); $this->crudSave($quest, $request->validated()); return new Resource($quest); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Quest $quest) { $this->authorize('access', $campaign); $this->authorize('delete', $quest->entity); $quest->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/QuestElementApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $quest->entity); return Resource::collection($quest->elements()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Quest $quest, QuestElement $questElement) { $this->authorize('access', $campaign); $this->authorize('view', $quest->entity); return new Resource($questElement); } /** * @return resource * * @throws AuthorizationException */ public function store(RequestElement $requestElement, Campaign $campaign, Quest $quest) { $this->authorize('access', $campaign); $this->authorize('update', $quest->entity); $data = $requestElement->all(); $data['quest_id'] = $quest->id; $model = QuestElement::create($data); $model->refresh(); return new Resource($model); } /** * @return resource */ public function update( RequestElement $requestElement, Campaign $campaign, Quest $quest, QuestElement $questElement ) { $this->authorize('access', $campaign); $this->authorize('update', $quest->entity); $questElement->update($requestElement->all()); return new Resource($questElement); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( Request $request, Campaign $campaign, Quest $quest, QuestElement $questElement ) { $this->authorize('access', $campaign); $this->authorize('update', $quest->entity); $questElement->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/RaceApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->races() ->filter(request()->all()) ->withApi() ->has('entity') ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Race $race) { $this->authorize('access', $campaign); $this->authorize('view', $race->entity); return new Resource($race); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.race')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Race::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Race $race) { $this->authorize('access', $campaign); $this->authorize('update', $race->entity); $race->update($request->all()); $this->crudSave($race, $request->validated()); return new Resource($race); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Race $race) { $this->authorize('access', $campaign); $this->authorize('delete', $race->entity); $race->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/RecentEntityApiController.php ================================================ authorize('access', $campaign); return Resource::collection( $campaign->entities() ->apiFilter($campaign, request()->all()) ->orderBy('updated_at', 'DESC') ->limit(min(max(1, request()->get('amount')), 10)) ->lastSync(request()->get('lastSync')) ->get() ); } } ================================================ FILE: app/Http/Controllers/Api/v1/RelationApiController.php ================================================ authorize('access', $campaign); return Resource::collection( $campaign->entityRelations() ->has('target') ->paginate() ); } } ================================================ FILE: app/Http/Controllers/Api/v1/ReminderApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $entity); return Resource::collection($entity->reminders()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Entity $entity, Reminder $reminder) { $this->authorize('access', $campaign); $this->authorize('view', $entity); return new Resource($reminder); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Entity $entity) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $data = $request->all(); $model = new Reminder($data); $model->remindable_id = $entity->id; $model->remindable_type = Entity::class; $model->save(); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Entity $entity, Reminder $reminder) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $reminder->update($request->all()); return new Resource($reminder); } /** * @param Request $request * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Entity $entity, Reminder $reminder ) { $this->authorize('access', $campaign); $this->authorize('update', $entity); $reminder->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/SearchApiController.php ================================================ authorize('access', $campaign); $term = mb_trim($search); $enabledEntities = array_keys($this->entityTypeService->campaign($campaign)->toSelect()); $models = Entity::with(['entityType', 'image']) ->whereIn('type_id', $enabledEntities) ->where('name', 'like', "%{$term}%") ->limit(10) ->get(); return \App\Http\Resources\Entity::collection($models); } } ================================================ FILE: app/Http/Controllers/Api/v1/TagApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->tags() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Tag $tag) { $this->authorize('access', $campaign); $this->authorize('view', $tag->entity); return new Resource($tag); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.tag')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Tag::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Tag $tag) { $this->authorize('access', $campaign); $this->authorize('update', $tag->entity); $tag->update($request->all()); $this->crudSave($tag, $request->validated()); return new Resource($tag); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Tag $tag) { $this->authorize('access', $campaign); $this->authorize('delete', $tag->entity); $tag->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/TimelineApiController.php ================================================ authorize('access', $campaign); return Resource::collection($campaign ->timelines() ->filter(request()->all()) ->withApi() ->lastSync(request()->get('lastSync')) ->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Timeline $timeline) { $this->authorize('access', $campaign); $this->authorize('view', $timeline->entity); return new Resource($timeline); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign) { $this->authorize('access', $campaign); $this->authorize('create', [EntityType::find(config('entities.ids.timeline')), $campaign]); $data = $request->all(); $data['campaign_id'] = $campaign->id; $model = Timeline::create($data); $this->crudSave($model, $request->validated()); return new Resource($model); } /** * @return resource */ public function update(Request $request, Campaign $campaign, Timeline $timeline) { $this->authorize('access', $campaign); $this->authorize('update', $timeline->entity); $timeline->update($request->all()); $this->crudSave($timeline, $request->validated()); return new Resource($timeline); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Timeline $timeline) { $this->authorize('access', $campaign); $this->authorize('delete', $timeline->entity); $timeline->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/TimelineElementApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $timeline->entity); return Resource::collection($timeline->elements()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Timeline $timeline, TimelineElement $timelineElement) { $this->authorize('access', $campaign); $this->authorize('view', $timeline->entity); return new Resource($timelineElement); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Timeline $timeline) { $this->authorize('access', $campaign); $this->authorize('update', $timeline->entity); $data = $request->all(); $data['timeline_id'] = $timeline->id; $model = TimelineElement::create($data); $model->refresh(); return new Resource($model); } /** * @return resource */ public function update( Request $request, Campaign $campaign, Timeline $timeline, TimelineElement $timelineElement ) { $this->authorize('access', $campaign); $this->authorize('update', $timeline->entity); $timelineElement->update($request->all()); return new Resource($timelineElement); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Timeline $timeline, TimelineElement $timelineElement ) { $this->authorize('access', $campaign); $this->authorize('update', $timeline->entity); $timelineElement->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/TimelineEraApiController.php ================================================ authorize('access', $campaign); $this->authorize('view', $timeline->entity); return Resource::collection($timeline->eras()->paginate()); } /** * @return resource */ public function show(Campaign $campaign, Timeline $timeline, TimelineEra $timelineEra) { $this->authorize('access', $campaign); $this->authorize('view', $timeline->entity); return new Resource($timelineEra); } /** * @return resource * * @throws AuthorizationException */ public function store(Request $request, Campaign $campaign, Timeline $timeline) { $this->authorize('access', $campaign); $this->authorize('update', $timeline->entity); $data = $request->all(); $data['timeline_id'] = $timeline->id; $model = TimelineEra::create($data); $model->refresh(); return new Resource($model); } /** * @return resource * * @throws AuthorizationException */ public function update( Request $request, Campaign $campaign, Timeline $timeline, TimelineEra $timelineEra ) { $this->authorize('access', $campaign); $this->authorize('update', $timeline->entity); $timelineEra->update($request->all()); return new Resource($timelineEra); } /** * @return JsonResponse * * @throws AuthorizationException */ public function destroy( \Illuminate\Http\Request $request, Campaign $campaign, Timeline $timeline, TimelineEra $timelineEra ) { $this->authorize('access', $campaign); $this->authorize('update', $timeline->entity); $timelineEra->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Api/v1/VisibilityController.php ================================================ has('source')) { $source = Entity::findOrFail((int) request()->get('source')); $this->apiService ->user(auth()->user()) ->copy() ->entityType($entityType); return $this->entity($campaign, $source); } return response()->json( $this->apiService ->user(auth()->user()) ->campaign($campaign) ->entityType($entityType) ->build() ); } public function entity(Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); // When copying an entity, the source might have private attributes, which blocks the attributes if (auth()->check() && auth()->user()->can('view-attributes', [$entity, $campaign])) { $this->apiService ->entity($entity); } return response()->json( $this->apiService ->user(auth()->user()) ->campaign($campaign) ->build() ); } } ================================================ FILE: app/Http/Controllers/Auth/AuthController.php ================================================ middleware('guest')->except('logout'); } /** * Redirect the user to the service authentication page. * * @param string $provider */ public function redirectToProvider($provider) { if (! in_array($provider, ['facebook', 'twitter', 'google'])) { return redirect()->route('login'); } try { return Socialite::driver($provider)->redirect(); } catch (Exception $e) { return redirect()->route('login')->withErrors('Error contacting ' . ucfirst($provider) . '.'); } } /** * Obtain the user information from provider. Check if the user already exists in our * database by looking up their provider_id in the database. * If the user exists, log them in. Otherwise, create a new user then log them in. After that * redirect them to the authenticated user's homepage. * * @param string $provider */ public function handleProviderCallback($provider) { try { // Twitter uses Oauth1 and doesn't support stateless if ($provider == 'twitter') { $user = Socialite::driver($provider)->user(); } else { // @phpstan-ignore-next-line $user = Socialite::driver($provider)->stateless()->user(); } $authUser = $this->findOrCreateUser($user, $provider); Auth::login($authUser, true); return redirect()->route('home'); } catch (Exception $ex) { // Send the exception to Sentry // if (app()->bound('sentry')) { // app('sentry')->captureException($ex); // } if ($ex->getCode() == '1') { return redirect()->route('login')->with('error', __('auth.register.errors.email_already_taken')); } else { return redirect()->route('register')->with('error', __('auth.register.errors.general_error')); } } } /** * If a user has registered before using social auth, return the user * else, create a new user object. * * @param mixed $user Socialite user object * @param mixed $provider Social auth provider * @return User */ public function findOrCreateUser(mixed $user, mixed $provider) { $authUser = User::where('provider_id', $user->id)->first(); if ($authUser) { return $authUser; } // Make sure the email doesn't already exist $emailExists = User::where('email', $user->email)->first(); if ($emailExists) { throw new Exception('', 1); } // Only allow creating if it's set that way if (! config('auth.register_enabled')) { throw new AccessDeniedHttpException('ACCOUNT REGISTRATION DISABLED'); } $referrer = $this->referralService->referrer(); $authUser = User::create([ 'name' => $user->name, 'email' => $user->email, 'password' => $user->email, 'provider' => $provider, 'provider_id' => $user->id, 'referred_by' => $referrer, ]); $this->referralService->event($authUser, ReferralEventType::register); // Call the registered event event(new Registered($authUser)); return $authUser; } /** * @return RedirectResponse */ public function logout(Request $request) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); // We also need to flush the session (campaign_id and other things) since this could cause // weird behaviour if the user registers a new account. $request->session()->flush(); return redirect()->route('login'); } } ================================================ FILE: app/Http/Controllers/Auth/ConfirmPasswordController.php ================================================ middleware('auth'); } } ================================================ FILE: app/Http/Controllers/Auth/ForgotPasswordController.php ================================================ validate($this->rules()); } protected function rules(): array { return [ 'email' => ['required', 'email', new SocialLogin], ]; } } ================================================ FILE: app/Http/Controllers/Auth/LoginController.php ================================================ middleware('guest')->except('logout'); $this->middleware('login.redirect')->except('logout'); $this->userAuthService = $userAuthService; } /** * @return RedirectResponse */ public function loginAsUser(Request $request, User $user) { if (! config('auth.user_list')) { return redirect()->route('login'); } Auth::login($user, true); return $this->sendLoginResponse($request); } /** * @return RedirectResponse */ public function loginAs(Request $request) { if (! config('auth.user_list')) { return redirect()->route('login'); } $user = User::where('id', $request->get('user'))->first(); Auth::login($user, true); return $this->sendLoginResponse($request); } protected function authenticated(Request $request, $user) { $response = $this->userAuthService->authenticated($user); return $response; } protected function redirectTo(Request $request): string { return route('home'); } /** * Make sure a social login can't log in with a password */ protected function validateLogin(Request $request) { $request->validate([ $this->username() => [ 'required', 'string', new SocialLogin, ], 'password' => 'required|string', ]); } } ================================================ FILE: app/Http/Controllers/Auth/RegisterController.php ================================================ middleware('guest'); $this->middleware('login.redirect'); } /** * Get a validator for an incoming registration request. * * @return \Illuminate\Contracts\Validation\Validator */ protected function validator(array $data) { $rules = [ 'name' => ['required', 'string', 'max:255', 'min:2', new AccountName], 'email' => ['required', 'string', 'email:rfc,dns', 'max:255', 'unique:users', new AccountEmail], 'password' => ['required', 'string', 'min:8'], ]; if (config('auth.recaptcha.enabled')) { $rules['g-recaptcha-response'] = ['required', 'string', new Recaptcha]; } return Validator::make($data, $rules); } /** * Create a new user instance after a valid registration. * * @return User */ protected function create(array $data) { $referrer = $this->referralService->referrer(); $user = User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => Hash::make($data['password']), 'referred_by' => $referrer, ]); $this->referralService->event($user, ReferralEventType::register); return $user; } protected function redirectTo(): string { return session()->pull('login_redirect', route('home')); } } ================================================ FILE: app/Http/Controllers/Auth/ResetPasswordController.php ================================================ middleware('guest'); } /** * Get the password reset validation rules. * * @return array */ protected function rules() { return [ 'token' => 'required', 'email' => ['required', 'email', new SocialLogin], 'password' => ['required', 'confirmed', Rules\Password::defaults()], ]; } } ================================================ FILE: app/Http/Controllers/Auth/VerificationController.php ================================================ middleware('auth'); $this->middleware('signed')->only('verify'); $this->middleware('throttle:6,1')->only('verify', 'resend'); } } ================================================ FILE: app/Http/Controllers/Billing/HistoryController.php ================================================ middleware(['auth', 'identity', 'subscriptions']); } /** * @return Factory|View */ public function index(Request $request) { /** @var User $user */ $user = $request->user(); $invoices = ! empty($user->stripe_id) ? $user->invoicesIncludingPending() : []; return view('billing.history', compact( 'invoices' )); } /** * @param string $invoice */ public function download(Request $request, $invoice) { /** @var User $user */ $user = $request->user(); $billing = ''; if ($user->profile && ! empty($user->profile['billing'])) { $billing = $user->profile['billing']; } return $user->downloadInvoice($invoice, [ 'vendor' => 'Owlchester SNC', 'product' => 'Kanka Subscription', 'url' => config('app.url'), 'street' => config('billing.street'), 'location' => config('billing.location'), 'country' => config('billing.country'), 'email' => config('app.email'), 'billing' => $billing, ]); } } ================================================ FILE: app/Http/Controllers/Billing/PaymentMethodController.php ================================================ middleware(['auth', 'identity', 'subscriptions']); $this->currencyService = $currencyService; } /** * @return Factory|View */ public function index() { $stripeApiToken = config('cashier.key', null); $user = Auth::user(); $translations = [ 'ending' => __('settings.subscription.payment_method.ending'), 'add_one' => __('settings.subscription.payment_method.add_one'), 'new_card' => __('settings.subscription.payment_method.new_card'), 'card_name' => __('settings.subscription.payment_method.card_name'), 'card' => __('settings.subscription.payment_method.card'), 'helper' => __('settings.subscription.payment_method.helper'), 'actions.add_new' => __('settings.subscription.payment_method.actions.add_new'), 'actions.save' => __('settings.subscription.payment_method.actions.save'), ]; $translations = json_encode($translations); $currencies = $this->currencyService->user($user)->availableCurrencies(); return view('billing.payment-method', compact( 'stripeApiToken', 'user', 'translations', 'currencies', )); } public function currency() { $content = auth()->user()->subscribed('kanka') ? '_blocked' : '_form'; return view('settings.subscription.currency.edit') ->with('content', $content) ->with('currencies', $this->currencyService->user(auth()->user())->availableCurrencies()); } public function save(UserBillingStore $request) { $user = $request->user(); $from = $request->get('from', 'billing.payment-method'); if ($request->get('reset_billing') && ($request->get('currency') != $user->currency())) { $paymentMethods = $user->paymentMethods(); foreach ($paymentMethods as $method) { $method->delete(); } $user->subscriptions()->delete(); $user->card_expires_at = null; $user->stripe_id = null; $user->log(UserAction::currencySwitch); } $user->saveSettings($request->only('currency')); $user->save(); return redirect() ->route($from) ->with('success', __('settings.subscription.success.currency')); } } ================================================ FILE: app/Http/Controllers/Bookmarks/RandomController.php ================================================ service->bookmark($bookmark)->url(); if (empty($route)) { return redirect() ->route('dashboard', $campaign) ->with( 'error', __('bookmarks.random_no_entity') ); } return redirect()->to($route); } } ================================================ FILE: app/Http/Controllers/Bookmarks/ReorderController.php ================================================ service = $service; } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('create', Bookmark::class); $links = Bookmark::ordered()->with(['target', 'entityType'])->get(); return view('bookmarks.reorder', compact( 'links', 'campaign' )); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function save(ReorderBookmarks $request, Campaign $campaign) { $this->authorize('create', Bookmark::class); $this->service ->reorder($request); return redirect() ->route('bookmarks.index', $campaign) ->with('success', __('bookmarks.reorder.success')); } } ================================================ FILE: app/Http/Controllers/Bragi/BragiController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { return response()->json( $this->service ->campaign($campaign) ->user(auth()->user()) ->prepare() ); } public function generate(BragiRequest $request, Campaign $campaign) { return response()->json( $this->service ->user($request->user()) ->campaign($campaign) ->generate($request) ); } } ================================================ FILE: app/Http/Controllers/BulkController.php ================================================ request = $request; $this->entity = $request->get('entity'); $models = $request->get('model', []); $action = $request->get('datagrid-action'); $page = $request->get('page'); if (! empty($page)) { $this->routeParams = ['page' => $page]; } $this->bulkService ->entities($models); if ($request->filled('entity_type')) { $this->entityType = EntityType::find($request->get('entity_type')); } try { if ($action === 'batch') { return $this->batch(); } } catch (TranslatableException $e) { if (config('app.debug')) { throw $e; } return redirect() ->back() ->with('error', __('crud.bulk.errors.general', ['hint' => $e->getTranslatedMessage()])); } catch (Exception $e) { if (config('app.debug')) { throw $e; } return redirect() ->back() ->with('error', __('crud.bulk.errors.general', ['hint' => $e->getMessage()])); } return redirect() ->back(); } /** * @return RedirectResponse * * @throws Exception */ protected function batch() { if (isset($this->entityType)) { $entityObj = $this->entityType->getClass(); $this->bulkService->entityType($this->entityType); } else { $classes = config('entities.classes-plural'); $entityObj = new $classes[$this->entity]; } $langFile = $this->entity === 'relations' ? 'entities/relations.bulk.success.' : 'entries/bulk.success.'; $models = $this->models(); $count = $this ->bulkService ->entities($models) ->user($this->request->user()) ->editing($this->request->all(), $this->bulkModel($entityObj)); $total = $this->bulkService->total(); $key = 'editing'; if ($count != $total) { $key = 'editing_partial'; } return redirect() ->back() ->with('success', trans_choice($langFile . $key, $count, ['count' => $count, 'total' => $total])); } protected function models(): array { return explode(',', $this->request->get('models')); } } ================================================ FILE: app/Http/Controllers/Bulks/BatchController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { $entities = $request->get('entities'); return view('cruds.datagrids.bulks.modals._batch') ->with('campaign', $campaign) ->with('bulk', $this->resolveBulk($entityType)) ->with('entityType', $entityType) ->with('entities', $entities); } public function apply(Request $request, Campaign $campaign, EntityType $entityType) { $models = explode(',', $request->get('models')); if ($request->has('entities')) { $models = $request->get('entities'); } $count = $this ->bulkService ->entityType($entityType) ->user($request->user()) ->campaign($campaign) ->entities($models) ->editing($request->all(), $this->resolveBulk($entityType)); $total = $this->bulkService->total(); $key = 'editing'; if ($count != $total) { $key = 'editing_partial'; } return redirect() ->back() ->with('success', trans_choice('entries/bulk.success.' . $key, $count, ['count' => $count, 'total' => $total])); } private function resolveBulk(EntityType $entityType): Bulk { $bulkClass = 'App\Datagrids\Bulks\\' . Str::studly($entityType->code) . 'Bulk'; if (class_exists($bulkClass)) { return new $bulkClass; } return new EntityBulk; } } ================================================ FILE: app/Http/Controllers/Bulks/CopyController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { $entities = $request->get('entities'); $campaigns = $this->campaignService ->user(auth()->user()) ->campaign($campaign) ->campaigns(); return view('cruds.datagrids.bulks.modals._copy_campaign') ->with('campaign', $campaign) ->with('campaigns', $campaigns) ->with('entityType', $entityType) ->with('type', $entityType->code) ->with('entities', $entities); } public function apply(Copy $request, Campaign $campaign, EntityType $entityType) { $models = explode(',', $request->get('models')); if ($request->has('entities')) { $models = $request->get('entities'); } /** @var Campaign $target */ $target = Campaign::findOrFail($request->get('campaign')); $count = $this ->bulkService ->entityType($entityType) ->campaign($campaign) ->user($request->user()) ->entities($models) ->copyToCampaign($target); $link = '' . $target->name . ''; return redirect() ->back() ->with('success_raw', trans_choice('entries/bulk.success.copy_to_campaign', $count, ['count' => $count, 'campaign' => $link])); } } ================================================ FILE: app/Http/Controllers/Bulks/DeleteController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { $datagrid = null; if ($entityType->id === config('entities.ids.bookmark')) { $datagrid = new BookmarkDatagridActions; } $entities = $request->get('entities'); return view('cruds.datagrids.bulks.modals.delete.delete') ->with('campaign', $campaign) ->with('entityType', $entityType) ->with('datagrid', $datagrid) ->with('entities', $entities); } public function apply(Request $request, Campaign $campaign, EntityType $entityType) { $models = explode(',', $request->get('models')); if ($request->has('entities')) { $models = $request->get('entities'); } $count = $this->bulkService ->entityType($entityType) ->entities($models) ->campaign($campaign) ->user($request->user()) ->request($request) ->delete(); $key = 'entries/bulk.success.delete'; return redirect() ->back() ->with('success', trans_choice($key, $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Bulks/DeleteRelationController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $datagrid = new RelationDatagridActions; return view('cruds.datagrids.bulks.modals.delete.relation') ->with('campaign', $campaign) ->with('datagrid', $datagrid); } public function apply(Request $request, Campaign $campaign) { $models = explode(',', request()->get('models')); $count = $this->bulkService ->entities($models) ->campaign($campaign) ->user(auth()->user()) ->request($request) ->delete(); $key = 'entities/relations.bulk.delete'; return redirect() ->back() ->with('success', trans_choice($key, $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Bulks/PermissionController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { $entities = $request->get('entities'); return view('cruds.datagrids.bulks.modals._permissions') ->with('campaign', $campaign) ->with('entityType', $entityType) ->with('entities', $entities); } public function apply(Request $request, Campaign $campaign, EntityType $entityType) { $models = explode(',', $request->get('models')); if ($request->has('entities')) { $models = $request->get('entities'); } $count = $this ->bulkService ->campaign($campaign) ->user($request->user()) ->entityType($entityType) ->entities($models) ->permissions( request()->only('user', 'role'), request()->has('permission-override') ); return redirect() ->back() ->with('success', trans_choice('entries/bulk.success.permissions', $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Bulks/PrintController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { Mentions::campaign($campaign); $entities = $this->bulkService ->campaign($campaign) ->user($request->user()) ->entityType($entityType) ->entities($request->get('model')) ->export(); return view('entities.pages.print.print-bulk') ->with('campaign', $campaign) ->with('entities', $entities) ->with('printing', true); } } ================================================ FILE: app/Http/Controllers/Bulks/TemplateController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { $templates = $this->attributeService ->campaign($campaign) ->templateList(); $entities = $request->get('entities'); return view('cruds.datagrids.bulks.modals._templates') ->with('campaign', $campaign) ->with('templates', $templates) ->with('entityType', $entityType) ->with('entities', $entities); } public function apply(Template $request, Campaign $campaign, EntityType $entityType) { $models = explode(',', $request->get('models')); if ($request->has('entities')) { $models = $request->get('entities'); } $target = $request->get('template_id'); $count = $this->bulkService ->campaign($campaign) ->user($request->user()) ->entityType($entityType) ->entities($models) ->templates($target); return redirect() ->back() ->with('success', trans_choice('entries/bulk.success.templates', $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Bulks/TransformController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { $entityTypes = $this->entityTypeService ->campaign($campaign) ->exclude([$entityType->id, config('entities.ids.bookmark')]) ->prepend(['' => __('entities/transform.fields.select_one')]) ->toSelect(); $entities = $request->get('entities'); return view('cruds.datagrids.bulks.modals._transform') ->with('campaign', $campaign) ->with('entityTypes', $entityTypes) ->with('entities', $entities) ->with('entityType', $entityType); } public function apply(Transform $request, Campaign $campaign, EntityType $entityType) { $models = explode(',', $request->get('models')); if ($request->has('entities')) { $models = $request->get('entities'); } /** @var EntityType $newEntityType */ $newEntityType = EntityType::inCampaign($campaign)->find($request->get('target')); $count = $this ->bulkService ->entities($models) ->entityType($entityType) ->campaign($campaign) ->user($request->user()) ->transform($newEntityType); $link = '' . $newEntityType->name() . ''; return redirect() ->back() ->with('success_raw', trans_choice('entities/transform.bulk.success', $count, ['count' => $count, 'type' => $link])); } } ================================================ FILE: app/Http/Controllers/Calendar/CalendarWeatherController.php ================================================ calendarService = $calendarService; } public function index(Campaign $campaign, Calendar $calendar) { return redirect()->route('entities.show', [$campaign, $calendar->entity]); } public function create(Campaign $campaign, Calendar $calendar) { $this->authorize('update', $calendar->entity); $date = request()->get('date'); [$year, $month, $day] = explode('-', $date); if (Str::startsWith($date, '-')) { [$year, $month, $day] = explode('-', mb_trim($date, '-')); $year = '-' . $year; } $weather = $this->calendarService ->calendar($calendar) ->findWeather((int) $year, (int) $month, (int) $day); return view('calendars.weather.' . (! empty($weather) ? 'edit' : 'create'), compact( 'calendar', 'campaign', 'day', 'month', 'year', 'weather' )); } public function store(AddCalendarWeather $request, Campaign $campaign, Calendar $calendar) { $this->authorize('update', $calendar->entity); if ($request->ajax()) { return response()->json(['success' => true]); } $weather = $this->calendarService ->calendar($calendar) ->saveWeather($request); $routeOptions = [$campaign, $calendar->entity, 'year' => $weather->year, 'month' => $weather->month]; if ($request->has('layout')) { $routeOptions['layout'] = $request->get('layout'); } return redirect()->route('entities.show', $routeOptions) ->with('success', __('calendars/weather.create.success')); } public function update(AddCalendarWeather $request, Campaign $campaign, Calendar $calendar, CalendarWeather $calendarWeather) { $this->authorize('update', $calendar->entity); if ($request->ajax()) { return response()->json(['success' => true]); } $weather = $this->calendarService ->calendar($calendar) ->saveWeather($request); $routeOptions = [$campaign, $calendar->entity, 'year' => $weather->year, 'month' => $weather->month]; if ($request->has('layout')) { $routeOptions['layout'] = $request->get('layout'); } return redirect()->route('entities.show', $routeOptions) ->with('success', __('calendars/weather.edit.success')); } public function destroy(Campaign $campaign, Calendar $calendar, CalendarWeather $calendarWeather) { $this->authorize('update', $calendar->entity); $calendarWeather->delete(); $routeOptions = [$campaign, $calendar->entity]; if (request()->has('layout')) { $routeOptions['layout'] = request()->get('layout'); } return redirect()->route('entities.show', $routeOptions) ->with('success', __('calendars/weather.destroy.success')); } } ================================================ FILE: app/Http/Controllers/Calendars/Bulks/EntityEventController.php ================================================ authorize('update', $calendar->entity); $action = $request->get('action'); $models = $request->get('model'); if (! in_array($action, $this->validBulkActions()) || empty($models)) { return redirect()->back(); } if ($action === 'edit') { return $this->campaign($campaign)->bulkBatch(route('calendars.entity-events.bulk', [ 'campaign' => $campaign, 'calendar' => $calendar]), '_calendar-event', $models, $calendar); } if (request()->ajax()) { return response()->json(['success' => true]); } $count = $this->campaign($campaign)->bulkProcess($request, Reminder::class); return redirect() ->route('calendars.events', [$campaign, 'calendar' => $calendar]) ->with('success', trans_choice('calendars.events.bulks.' . $action, $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Calendars/EventController.php ================================================ service = $calendarService; $this->lengthValidatorService = $lengthValidatorService; } public function index(Campaign $campaign, Calendar $calendar) { $this->campaign($campaign)->authEntityView($calendar->entity); $options = [$campaign, 'calendar' => $calendar]; $after = $before = false; if (request()->has('before_id')) { $options['before_id'] = 1; $before = true; } elseif (request()->has('after_id')) { $options['after_id'] = 1; $after = true; } Datagrid::layout(Reminder::class) ->route('calendars.events', $options) ->permissions(! (auth()->check() && auth()->user()->can('update', $calendar))); $rows = $calendar->calendarEvents(); if ($after) { $rows->after($calendar); } elseif ($before) { $rows->before($calendar); } // @phpstan-ignore-next-line $this->rows = $rows ->with(['calendar', 'calendar.entity', 'remindable' => function ($morphTo) { $morphTo->morphWith([ Entity::class => ['entityType', 'tags', 'image'], Post::class => ['tags', 'entity', 'entity.image', 'entity.entityType'], ]); }, ]) ->whereHas('remindable') ->sort(request()->only(['o', 'k'])) ->paginate(); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('calendars.events', $calendar); } public function create(Campaign $campaign, Calendar $calendar) { $this->authorize('update', $calendar->entity); $date = request()->get('date', '1-1-1'); [$year, $month, $day] = explode('-', $date); if (Str::startsWith($date, '-')) { [$year, $month, $day] = explode('-', mb_trim($date, '-')); $year = "-{$year}"; } return view('calendars.reminders.create', compact( 'campaign', 'calendar', 'day', 'month', 'year', )); } public function store(AddCalendarEvent $request, Campaign $campaign, Calendar $calendar) { // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $routeOptions = [$campaign, $calendar->entity, 'year' => $request->post('year')]; if ($request->has('layout')) { $routeOptions['layout'] = $request->get('layout'); } else { $routeOptions['month'] = $request->post('month'); } // We need to handle negative year dates (start with -) try { $link = $this->service ->user($request->user()) ->campaign($campaign) ->calendar($calendar) ->addEvent($request->all()); return redirect()->route('entities.show', $routeOptions) ->with('success', __('calendars.event.success', ['event' => $link->remindable->name])); } catch (TranslatableException $e) { return redirect() ->route('entities.show', $routeOptions) ->with('error', __('crud.bulk.errors.general', ['hint' => $e->getTranslatedMessage()])); } catch (Exception $e) { return redirect()->route('entities.show', $routeOptions); } } /** * @return JsonResponse * * @throws AuthorizationException */ public function eventLength(Campaign $campaign, Calendar $calendar, ValidateReminderLength $request) { $this->authorize('view', $calendar->entity); return response()->json($this->lengthValidatorService->validateLength($calendar, $request)); } } ================================================ FILE: app/Http/Controllers/Campaign/AchievementController.php ================================================ service->campaign($campaign)->stats(); return view('campaigns.achievements.index', compact('campaign', 'achievements')); } } ================================================ FILE: app/Http/Controllers/Campaign/ApplicationController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('applications', $campaign); if (request()->get('filter') && request()->get('filter') == 'approved') { $applications = $campaign->applications()->where('status', ApplicationStatus::Approved)->with('user')->paginate(); } elseif (request()->get('filter') && request()->get('filter') == 'rejected') { $applications = $campaign->applications()->where('status', ApplicationStatus::Rejected)->with('user')->paginate(); } elseif (request()->get('filter') && request()->get('filter') == 'all') { $applications = $campaign->applications()->with('user')->paginate(); } else { $applications = $campaign->applications()->where('status', ApplicationStatus::Pending)->with('user')->paginate(); } return view('campaigns.applications.index') ->with('applications', $applications) ->with('campaign', $campaign) ->with('filter', request()->get('filter')); } public function show(Campaign $campaign, Application $application) { $this->authorize('applications', $campaign); if (! $campaign->canHaveMoreMembers()) { return view('cruds.forms.limit') ->with('campaign', $campaign) ->with('key', 'members') ->with('name', 'campaign_roles'); } if ($application->status == ApplicationStatus::Pending) { return view('campaigns.applications.show') ->with('application', $application) ->with('campaign', $campaign); } return view('campaigns.applications.view') ->with('application', $application) ->with('campaign', $campaign); } public function edit(Campaign $campaign, Application $application) { $this->authorize('applications', $campaign); if (! $campaign->canHaveMoreMembers()) { return view('cruds.forms.limit') ->with('campaign', $campaign) ->with('limit', config('limits.campaigns.members')) ->with('thing', __('campaigns.show.tabs.members')) ->with('name', 'campaign_roles'); } $action = request()->get('action'); if (! in_array($action, ['approve', 'reject'])) { return redirect()->route('applications.index', $campaign); } return view('campaigns.applications.edit') ->with('application', $application) ->with('campaign', $campaign) ->with('action', $action); } public function update(PatchCampaignApplication $request, Campaign $campaign, Application $application) { $this->authorize('applications', $campaign); if (! $campaign->canHaveMoreMembers()) { return view('cruds.forms.limit') ->with('campaign', $campaign) ->with('limit', config('limits.campaigns.members')) ->with('thing', __('campaigns.show.tabs.members')) ->with('name', 'campaign_roles'); } if ($request->ajax()) { return response()->json(); } $note = $this->service ->user(auth()->user()) ->campaign($campaign) ->application($application) ->process($request->only('role_id', 'rejection', 'action', 'reason')); return redirect()->route('applications.index', $campaign) ->with('success', __('campaigns/applications.update.' . $note)); } public function toggle(Campaign $campaign) { $this->authorize('applications', $campaign); return view('campaigns.applications._toggle', compact('campaign')); } public function toggleSave(StoreCampaignApplicationStatus $request, Campaign $campaign) { $this->authorize('applications', $campaign); if ($request->ajax()) { return response()->json(); } $isOpen = (bool) $request->get('status'); $campaign->update([ 'is_open' => $isOpen, ]); $successKey = $isOpen ? 'campaigns/applications.toggle.success_open' : 'campaigns/applications.toggle.success'; return redirect() ->route('applications.index', $campaign) ->with('success', __($successKey)); } } ================================================ FILE: app/Http/Controllers/Campaign/ApplicationDashboardController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('applications', $campaign); $hasJoinWidget = $campaign->widgets()->where('widget', Widget::Join)->exists(); return view('campaigns.applications.dashboard_widget') ->with('campaign', $campaign) ->with('hasJoinWidget', $hasJoinWidget); } public function store(Campaign $campaign) { $this->authorize('applications', $campaign); if (! $campaign->widgets()->where('widget', Widget::Join)->exists()) { CampaignDashboardWidget::create([ 'campaign_id' => $campaign->id, 'widget' => Widget::Join, 'dashboard_id' => null, ]); } return redirect() ->route('applications.index', $campaign) ->with('success', __('campaigns/applications.dashboard_widget.success')); } } ================================================ FILE: app/Http/Controllers/Campaign/ApplicationSetupController.php ================================================ middleware('auth'); } public function setup(Campaign $campaign) { $this->authorize('applications', $campaign); $timezones = []; for ($i = -12; $i <= 14; $i++) { $prefix = ($i >= 0) ? '+' : '-'; // Formats to "UTC +05:00" or "UTC -11:00" $utcString = 'UTC ' . $prefix . Str::padLeft((string) abs($i), 2, '0') . ':00'; $timezones[$utcString] = $utcString; } $user = auth()->user(); $isElemental = $user->isElemental(); $prioritisedCampaign = null; if ($isElemental) { $adminCampaignIds = $user->campaignRoles()->where('is_admin', true)->pluck('campaign_id'); $prioritisedCampaign = Campaign::where('is_prioritised', true) ->where('id', '!=', $campaign->id) ->whereIn('id', $adminCampaignIds) ->first(); } $languages = $this->languageService->getSupportedLanguagesList(true); return view('campaigns.applications.setup') ->with('campaign', $campaign) ->with('timezones', $timezones) ->with('languages', $languages) ->with('isElemental', $isElemental) ->with('prioritisedCampaign', $prioritisedCampaign); } public function saveSetup(StoreCampaignSetup $request, Campaign $campaign) { $this->authorize('applications', $campaign); if ($request->ajax()) { return response()->json(); } $isPrioritised = false; $user = auth()->user(); if ($user->isElemental() && $request->boolean('is_prioritised')) { $adminCampaignIds = $user->campaignRoles()->where('is_admin', true)->pluck('campaign_id'); $conflicting = Campaign::where('is_prioritised', true) ->where('id', '!=', $campaign->id) ->whereIn('id', $adminCampaignIds) ->first(); if ($conflicting) { return redirect()->back() ->with('error', __('campaigns/applications.setup.prioritise_conflict', [ 'campaign' => '' . e($conflicting->name) . '', ])); } $isPrioritised = true; } $campaign->update([ 'locale' => $request->get('locale'), 'is_prioritised' => $isPrioritised, ]); $campaign->systems()->sync($request->input('systems', [])); $campaign->genres()->sync($request->input('genres', [])); $campaign->playstyles()->sync($request->input('playstyles', [])); // Map request keys to Enum types $filters = [ 'intro' => CampaignFilterType::Intro, 'timezone' => CampaignFilterType::Timezone, 'schedule' => CampaignFilterType::Schedule, 'players' => CampaignFilterType::PlayerCount, ]; foreach ($filters as $inputKey => $enumType) { // Only save if the user actually sent data for this field if ($request->filled($inputKey)) { CampaignFilter::updateOrCreate( [ 'campaign_id' => $campaign->id, 'type' => $enumType, ], [ 'entry' => Purify::clean($request->input($inputKey)), ] ); } } $campaign->refresh(); $successKey = $user->can('canOpen', $campaign) ? 'campaigns/applications.setup.success_complete' : 'campaigns/applications.setup.success'; return redirect() ->route('applications.index', $campaign) ->with('success', __($successKey)); } } ================================================ FILE: app/Http/Controllers/Campaign/ApplyController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('apply', $campaign); $application = auth()->user()->applications()->first(); $timezones = []; for ($i = -12; $i <= 14; $i++) { $prefix = ($i >= 0) ? '+' : '-'; // Formats to "UTC +05:00" or "UTC -11:00" $utcString = 'UTC ' . $prefix . Str::padLeft((string) abs($i), 2, '0') . ':00'; $timezones[$utcString] = $utcString; } return view('campaigns.applications.apply') ->with('application', $application) ->with('timezones', $timezones) ->with('campaign', $campaign); } public function save(StoreCampaignApplication $request, Campaign $campaign) { $this->authorize('apply', $campaign); /** @var ?Application $application */ $application = auth()->user()->applications()->first(); if (! empty($application)) { $application->update($request->validated()); $success = __('campaigns/applications.apply.success.update'); } else { $this->service ->user(auth()->user()) ->campaign($campaign) ->apply($request); $success = __('campaigns/applications.apply.success.apply'); } return redirect() ->route('dashboard', $campaign) ->with('success', $success); } public function remove(Campaign $campaign) { $this->authorize('apply', $campaign); /** @var ?Application $application */ $application = auth()->user()->applications()->first(); if (! empty($application)) { $application->delete(); } return redirect() ->route('dashboard', $campaign) ->with('success', __('campaigns/applications.apply.success.remove')); } } ================================================ FILE: app/Http/Controllers/Campaign/CreateController.php ================================================ middleware('auth'); } public function index(Request $request) { $this->authorize('create', new Campaign); // A user with campaigns doesn't need this process. $tracking = null; if (session()->has('user_registered')) { session()->remove('user_registered'); $tracking = 'pa10CJTvrssBEOaOq7oC'; } $languages = $this->languageService->getSupportedLanguagesList(true); $timezones = []; for ($i = -12; $i <= 14; $i++) { $prefix = ($i >= 0) ? '+' : '-'; // Formats to "UTC +05:00" or "UTC -11:00" $utcString = 'UTC ' . $prefix . Str::padLeft((string) abs($i), 2, '0') . ':00'; $timezones[$utcString] = $utcString; } return view('campaigns.forms.create', [ 'start' => auth()->user()->campaigns->count() === 0, 'gaTrackingEvent' => $tracking, 'languages' => $languages, 'timezones' => $timezones, ]); } public function store(StoreCampaign $request) { $this->authorize('create', new Campaign); if (request()->ajax()) { return response()->json(['success' => true]); } $first = auth()->user()->campaigns->count() === 0; $campaign = $this->createService ->request($request) ->user($request->user()) ->create(); $this->genreService->campaign($campaign)->save($request->post('genres', [])); $this->systemService->campaign($campaign)->save($request->post('systems', [])); if ($request->has('submit-update')) { return redirect() ->route('campaigns.edit', $campaign) ->with('success', __('campaigns.create.success', ['name' => $campaign->name])); } elseif ($request->has('submit-new')) { return redirect() ->route('start') ->with('success', __('campaigns.create.success', ['name' => $campaign->name])); } elseif ($first) { return redirect()->route('dashboard', $campaign); } return redirect()->route('dashboard', $campaign) ->with('success', __('campaigns.create.success', ['name' => $campaign->name])); } } ================================================ FILE: app/Http/Controllers/Campaign/CssController.php ================================================ boosted()) { $css = CampaignCache::campaign($campaign)->styles(); } $response = \Illuminate\Support\Facades\Response::make($css); $response->header('Content-Type', 'text/css'); // $response->header('Expires', Carbon::now()->addYear()->toDateTimeString()); $month = 31536000; $response->setLastModified($campaign->updated_at->toDateTime()); $response->header('Cache-Control', 'public, max_age=' . $month); return $response; } } ================================================ FILE: app/Http/Controllers/Campaign/DashboardController.php ================================================ middleware('auth'); $this->middleware(Boosted::class, ['except' => ['index', 'create']]); } /** * @return RedirectResponse */ public function index() { return redirect()->to('home'); } public function create(Campaign $campaign) { if (! $campaign->boosted()) { return $this->cta($campaign); } $this->authorize('dashboard', $campaign); $source = null; if (request()->has('source')) { $source = CampaignDashboard::findOrFail(request()->get('source')); } return view('dashboard.dashboards.create') ->with('campaign', $campaign) ->with('source', $source); } public function store(StoreCampaignDashboard $request, Campaign $campaign) { if (! $campaign->boosted()) { return $this->cta($campaign); } $this->authorize('dashboard', $campaign); if ($request->ajax()) { return response()->json(); } $dashboard = $this ->service ->campaign($campaign) ->request($request) ->user($request->user()) ->create(); return redirect()->route('dashboard.setup', [$campaign, 'dashboard' => $dashboard->id]) ->with('success', __('dashboard.dashboards.create.success', ['name' => $dashboard->name])); } public function edit(Campaign $campaign, CampaignDashboard $campaignDashboard) { $this->authorize('dashboard', $campaign); return view('dashboard.dashboards.update') ->with('campaign', $campaign) ->with('dashboard', $campaignDashboard); } public function update(Campaign $campaign, CampaignDashboard $campaignDashboard, StoreCampaignDashboard $request) { $this->authorize('dashboard', $campaign); if ($request->ajax()) { return response()->json(); } $dashboard = $this->service->campaign($campaign) ->dashboard($campaignDashboard) ->request($request) ->update(); return redirect()->route('dashboard.setup', [$campaign, 'dashboard' => $dashboard->id]) ->with('success', __('dashboard.dashboards.update.success', ['name' => $dashboard->name])); } public function destroy(Campaign $campaign, CampaignDashboard $campaignDashboard) { $this->authorize('dashboard', $campaign); if (request()->ajax()) { return response()->json(); } $campaignDashboard->delete(); return redirect()->route('dashboard.setup', $campaign) ->with('success', __('dashboard.dashboards.delete.success', ['name' => $campaignDashboard->name])); } protected function cta(Campaign $campaign) { return view('components.premium-dialog', [ 'campaign' => $campaign, 'title' => __('dashboards/premium.title'), 'pitch' => __('dashboards/premium.pitch'), 'doc' => 'guides/dashboard.html#custom-dashboards', ]); } } ================================================ FILE: app/Http/Controllers/Campaign/DashboardHeaderController.php ================================================ authorize('update', $campaign); if (! empty($campaignDashboardWidget) && ! empty($campaignDashboardWidget->campaign_id)) { if ($campaignDashboardWidget->campaign_id != $campaign->id) { abort(404); } } else { $campaignDashboardWidget = null; } return view('campaigns.forms.dashboard-header.edit') ->with('campaign', $campaign) ->with('widget', $campaignDashboardWidget); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function update(UpdateCampaignHeader $request, Campaign $campaign, ?CampaignDashboardWidget $campaignDashboardWidget) { $this->authorize('update', $campaign); $campaign->update($request->only('excerpt')); return redirect() ->route('dashboard.setup', $campaignDashboardWidget->dashboard_id ? [$campaign, 'dashboard' => $campaignDashboardWidget->dashboard_id] : [$campaign]) ->with('success', __('campaigns/dashboard-header.edit.success')); } } ================================================ FILE: app/Http/Controllers/Campaign/DashboardWidgetController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('dashboard', $campaign); $dashboard = null; if (request()->get('dashboard')) { $dashboard = CampaignDashboard::findOrFail(request()->get('dashboard')); } $withOnboarding = $campaign->widgets()->onDashboard($dashboard)->where('widget', Widget::Onboarding)->count() === 0; $withHelp = $campaign->widgets()->onDashboard($dashboard)->where('widget', Widget::Help)->count() === 0; return view('dashboard.widgets.selection') ->with('campaign', $campaign) ->with('dashboard', $dashboard) ->with('withOnboarding', $withOnboarding) ->with('withHelp', $withHelp); } public function create(Campaign $campaign) { $this->authorize('dashboard', $campaign); $widget = request()->get('widget', 'preview'); if (! view()->exists('dashboard.widgets.forms._' . $widget)) { abort(404); } if ($widget === 'gallery' && ! $campaign->premium()) { return view('components.premium-dialog', [ 'title' => __('dashboards/widgets/gallery.name'), 'campaign' => $campaign, 'doc' => 'guides/dashboard.html#gallery', 'pitch' => __('dashboards/widgets/gallery.helpers.premium'), ]); } $entityTypes = $this->entityTypeService ->campaign($campaign) ->exclude([config('entities.ids.bookmark')]) ->prepend(['' => __('dashboard.widgets.random.type.all')]) ->toSelect(); $dashboard = request()->has('dashboard') ? CampaignDashboard::where('id', request()->get('dashboard'))->first() : null; return view('dashboard.widgets.forms.create', [ 'campaign' => $campaign, 'widget' => $widget, 'entityTypes' => $entityTypes, 'dashboard' => $dashboard, ]); } public function store(StoreCampaignDashboardWidget $request, Campaign $campaign) { $this->authorize('dashboard', $campaign); if ($request->input('widget') === Widget::Gallery->value) { $this->authorize('galleryWidget', $campaign); } if ($request->ajax()) { return response()->json(['success' => true]); } $data = $request->all(); $data['campaign_id'] = $campaign->id; $widget = CampaignDashboardWidget::create($data); return redirect() ->route('dashboard.setup', $widget->dashboard_id ? [$campaign, 'dashboard' => $widget->dashboard_id] : $campaign) ->with('success', __('dashboard.widgets.create.success')); } public function show(Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { return redirect()->route('dashboard', $campaign); } public function edit(Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { $this->authorize('dashboard', $campaign); $dashboards = [null => __('dashboard.dashboards.default.title')]; foreach (CampaignDashboard::orderBy('name')->pluck('name', 'id')->toArray() as $id => $dashboard) { $dashboards[$id] = $dashboard; } $entityTypes = $this->entityTypeService ->campaign($campaign) ->exclude([config('entities.ids.bookmark')]) ->prepend(['' => __('dashboard.widgets.random.type.all')]) ->toSelect(); return view('dashboard.widgets.forms.edit', [ 'campaign' => $campaign, 'model' => $campaignDashboardWidget, 'widget' => $campaignDashboardWidget->widget->value, 'entityTypes' => $entityTypes, 'dashboards' => $dashboards, ]); } public function update(StoreCampaignDashboardWidget $request, Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { $this->authorize('dashboard', $campaign); if ($request->ajax()) { return response()->json(['success' => true]); } // get all request data $input = $request->all(); // force entity_id to take null if not present in data $input['entity_id'] = $request->input('entity_id'); $campaignDashboardWidget->update($input); return redirect() ->route('dashboard.setup', $campaignDashboardWidget->dashboard_id ? [$campaign, 'dashboard' => $campaignDashboardWidget->dashboard_id] : $campaign) ->with('success', __('dashboard.widgets.update.success')); } public function destroy(Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { $this->authorize('dashboard', $campaign); $campaignDashboardWidget->delete(); return redirect() ->route('dashboard.setup', $campaignDashboardWidget->dashboard_id ? [$campaign, 'dashboard' => $campaignDashboardWidget->dashboard_id] : $campaign) ->with('success', __('dashboard.widgets.delete.success')); } } ================================================ FILE: app/Http/Controllers/Campaign/DefaultImageController.php ================================================ middleware(Boosted::class, ['except' => 'index']); } /** * @return Factory|View * * @throws AuthorizationException */ public function index(Campaign $campaign) { $entityTypes = []; /** @var EntityType $entityType */ foreach (EntityType::inCampaign($campaign)->get() as $entityType) { $entityTypes[$entityType->pluralCode()] = $entityType; } $images = $campaign->defaultImages(); return view('campaigns.default-images.index') ->with('campaign', $campaign) ->with('images', $images) ->with('entityTypes', $entityTypes); } /** * @return Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign) { $this->authorize('recover', $campaign); $ignore = $campaign->existingDefaultImages(); $entityTypes = $this->entityTypeService ->campaign($campaign) ->exclude(config('entities.ids.bookmark')) ->skip($ignore) ->toSelect(); return view('campaigns.default-images.create', compact( 'campaign', 'entityTypes' )); } public function store(DefaultImageStore $request, Campaign $campaign) { $this->authorize('recover', $campaign); if (request()->ajax()) { return response()->json(['success' => true]); } /** @var EntityType $entityType */ $entityType = EntityType::inCampaign($campaign)->find($request->post('entity_type')); if ($this->service->campaign($campaign)->entityType($entityType)->user(auth()->user())->save($request)) { return redirect()->route('campaign.default-images', $campaign) ->with( 'success', __('campaigns/default-images.create.success', ['type' => $entityType->plural()]) ); } return redirect()->route('campaign.default-images', $campaign) ->with( 'error', __('campaigns/default-images.create.error', ['type' => $entityType->plural()]) ); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function destroy(DefaultImageDestroy $request, Campaign $campaign) { $this->authorize('recover', $campaign); /** @var EntityType $entityType */ $entityType = EntityType::inCampaign($campaign)->findOrFail($request->post('entity_type')); $this->service ->campaign($campaign) ->user(auth()->user()) ->entityType($entityType) ->destroy(); return redirect()->route('campaign.default-images', $campaign) ->with( 'success', __('campaigns/default-images.destroy.success', ['type' => $entityType->plural()]) ); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function reset(Campaign $campaign) { $this->authorize('recover', $campaign); $this->service ->campaign($campaign) ->user(auth()->user()) ->destroyAll(); return redirect()->route('campaign.default-images', $campaign) ->with( 'success', __('campaigns/default-images.reset.success') ); } } ================================================ FILE: app/Http/Controllers/Campaign/DefaultsController.php ================================================ authorize('update', $campaign); return view('campaigns.defaults.index', compact('campaign')); } public function save(StoreDefaults $request, Campaign $campaign) { $this->authorize('update', $campaign); $campaign->update($request->all()); return redirect()->route('campaign-defaults', $campaign) ->with('success', __('campaigns/defaults.update.success')); } } ================================================ FILE: app/Http/Controllers/Campaign/DeleteController.php ================================================ middleware('auth'); $this->deletionService = $deletionService; } public function show(Campaign $campaign) { $this->authorize('roles', $campaign); return view('campaigns.delete') ->with('campaign', $campaign); } public function destroy(DeleteCampaign $request, Campaign $campaign) { $this->authorize('delete', $campaign); if ($request->ajax()) { return response()->json(); } $this->deletionService ->campaign($campaign) ->user($request->user()) ->delete(); return redirect()->route('home') ->with('success', __('campaigns/delete.success', ['name' => $campaign->name])); } } ================================================ FILE: app/Http/Controllers/Campaign/EntityTypeController.php ================================================ authorize('setting', $campaign); if (! $campaign->premium()) { return view('campaigns.entity-types.not-premium') ->with('campaign', $campaign); } $limit = config('limits.campaigns.modules.premium'); if ($campaign->isWyvern()) { $limit = config('limits.campaigns.modules.wyvern'); } elseif ($campaign->isElemental()) { $limit = config('limits.campaigns.modules.elemental'); } if ($campaign->entityTypes->count() >= $limit) { return view('campaigns.entity-types.max-reached') ->with('campaign', $campaign) ->with('limit', $limit); } return view('campaigns.entity-types.create') ->with('campaign', $campaign); } public function store(StoreEntityType $request, Campaign $campaign) { $this->authorize('setting', $campaign); if (! $campaign->premium()) { return redirect()->route('campaign.modules', $campaign) ->with('error', __('This feature is only available on premium campaigns')); } elseif ($campaign->entityTypes->count() > config('limits.campaigns.modules')) { return view('campaigns.entity-types.max-reached') ->with('campaign', $campaign); } if (request()->ajax()) { return response()->json(['success' => true]); } $this->entityTypeService ->campaign($campaign) ->user(auth()->user()) ->request($request) ->save(); return redirect()->route('campaign.modules', $campaign) ->with('success', __('campaigns/modules.create.success')); } public function edit(Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); $this->authorize('update', [$entityType, $campaign]); if (! $campaign->premium()) { return view('campaigns.entity-types.not-premium') ->with('campaign', $campaign); } $image = null; $thumbnails = $campaign->defaultImages(); foreach ($thumbnails as $thumbnail) { if ($thumbnail['type'] == $entityType->pluralCode()) { $image = $thumbnail; } } return view('campaigns.entity-types.edit') ->with('campaign', $campaign) ->with('image', $image) ->with('entityType', $entityType); } public function update(StoreEntityType $request, Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); $this->authorize('update', [$entityType, $campaign]); if (! $campaign->premium()) { return redirect()->route('campaign.modules', $campaign) ->with('error', __('This feature is only available on premium campaigns')); } if (request()->ajax()) { return response()->json(['success' => true]); } $this->entityTypeService ->campaign($campaign) ->entityType($entityType) ->request($request) ->user(auth()->user()) ->save(); return redirect()->route('campaign.modules', $campaign) ->with('success', __('campaigns/modules.rename.success')); } /** * Toggle a module in the campaign's settings */ public function toggle(Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); $this->authorize('update', [$entityType, $campaign]); try { $this->entityTypeService ->campaign($campaign) ->user(auth()->user()) ->entityType($entityType) ->toggle(); return response()->json([ 'success' => true, 'status' => $entityType->isEnabled(), 'toast' => __('campaigns.settings.' . ($entityType->isEnabled() ? 'enabled' : 'disabled'), ['module' => $entityType->plural()]), ]); } catch (Exception $e) { return response()->json([ 'success' => false, ]); } } public function confirm(Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); $this->authorize('delete', [$entityType, $campaign]); $entityCount = $entityType->entities->count(); return view('campaigns.entity-types.confirm') ->with('campaign', $campaign) ->with('entityCount', $entityCount) ->with('entityType', $entityType); } public function destroy(DeleteEntityType $request, Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); $this->authorize('delete', [$entityType, $campaign]); if (request()->ajax()) { return response()->json(); } $this->entityTypeService ->campaign($campaign) ->user(auth()->user()) ->entityType($entityType) ->delete(); return redirect()->route('campaign.modules', $campaign) ->with('success', __('campaigns/modules.delete.success', ['name' => $entityType->name()])); } } ================================================ FILE: app/Http/Controllers/Campaign/ExportController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('setting', $campaign); return view('campaigns.export', compact('campaign')); } /** * Dispatch the campaign export jobs and have the user wait for a bit * * @throws AuthorizationException */ public function export(Request $request, Campaign $campaign) { $this->authorize('setting', $campaign); if (request()->ajax()) { return response()->json(); } if (! $request->user()->can('export', $campaign)) { return redirect() ->route('campaign.export', $campaign) ->withError(__('campaigns/export.errors.limit')); } if ($request->get('type') == 2 && ! $campaign->premium()) { return redirect() ->route('campaign.export', $campaign) ->withError(__('campaigns/export.errors.premium')); } $this->queueService ->campaign($campaign) ->user($request->user()) ->type($request->get('type')) ->queue(); $adminRoleName = $campaign->adminRoleName(); return redirect() ->route('campaign.export', $campaign) ->withSuccess(__('campaigns/export.success', [ 'admin' => $adminRoleName, ])); } } ================================================ FILE: app/Http/Controllers/Campaign/FollowController.php ================================================ middleware('auth'); $this->service = $service; } public function update(Campaign $campaign) { $this->authorize('follow', $campaign); return response()->json([ 'following' => $this->service ->campaign($campaign) ->user(auth()->user()) ->update(), ]); } } ================================================ FILE: app/Http/Controllers/Campaign/ImageController.php ================================================ authorize('update', $campaign); return view('campaigns.forms.modals.image') ->with('campaign', $campaign); } public function save(StoreImage $request, Campaign $campaign) { $this->authorize('update', $campaign); if ($request->ajax()) { return response()->json(['success' => true]); } $campaign->update($request->only('image', 'image_url')); return redirect()->route('dashboard', $campaign)->with('success', __('campaigns/sidebar.image-success')); } } ================================================ FILE: app/Http/Controllers/Campaign/ImportController.php ================================================ middleware('auth'); $this->service = $prepareService; } public function index(Campaign $campaign) { $this->authorize('setting', $campaign); Datagrid::layout(\App\Renderers\Layouts\Campaign\CampaignImport::class); $rows = $campaign->campaignImports() ->sort(request()->only(['o', 'k'])) ->where('status_id', '<>', CampaignImportStatus::PREPARED) ->with(['user']) ->orderBy('updated_at', 'DESC') ->paginate(); // Ajax Datagrid if (request()->ajax()) { $html = view('layouts.datagrid._table')->with('rows', $rows)->render(); return response()->json([ 'success' => true, 'html' => $html, ]); } $token = null; if (auth()->user()->can('import', $campaign)) { $token = $this->service ->campaign($campaign) ->user(auth()->user()) ->token(); } return view('campaigns.import.index') ->with('campaign', $campaign) ->with('token', $token) ->with('rows', $rows); } public function csv(Campaign $campaign, CampaignImport $campaignImport) { $this->authorize('setting', $campaign); return view('campaigns.import.process-csv') ->with('campaign', $campaign) ->with('import', $campaignImport); } } ================================================ FILE: app/Http/Controllers/Campaign/InviteController.php ================================================ middleware('auth'); } /** * @return RedirectResponse */ public function index() { return redirect()->route('home'); } public function show(Campaign $campaign, CampaignInvite $campaignInvite) { return redirect()->route('home'); } /** * Create a new invitation link form * * @return Application|Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign) { $this->authorize('invite', $campaign); if (! $campaign->canHaveMoreMembers()) { return view('cruds.forms.limit') ->with('campaign', $campaign) ->with('limit', config('limits.campaigns.members')) ->with('thing', __('campaigns.show.tabs.members')) ->with('name', 'campaign_roles'); } return view('campaigns.invites.create', compact('campaign')); } /** * Save the new invitation link to the database */ public function store(StoreCampaignInvite $request, Campaign $campaign) { $this->authorize('invite', $campaign); if ($request->ajax()) { return response()->json(); } if (! $campaign->canHaveMoreMembers()) { return redirect()->back(); } $data = $request->only('role_id', 'validity'); $data['campaign_id'] = $campaign->id; /** @var CampaignInvite $invitation */ $invitation = CampaignInvite::create($data); $link = route('campaigns.join', [$invitation->token]); $copy = ' ' . __('campaigns.invites.actions.copy') . ''; return redirect()->route('campaign_users.index', $campaign) ->with( 'success_raw', __( 'campaigns.invites.create.success_link', ['link' => $copy] ) ); } /** * Remove an invitation link */ public function destroy(Campaign $campaign, CampaignInvite $campaignInvite) { $this->authorize('invite', $campaignInvite->campaign); $campaignInvite->delete(); return redirect()->route('campaign_users.index', $campaign) ->with('success', __('campaigns.invites.destroy.success')); } } ================================================ FILE: app/Http/Controllers/Campaign/LeaveController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('member', $campaign); return view('campaigns.leave')->with('campaign', $campaign); } public function process(Campaign $campaign) { $this->authorize('leave', $campaign); if (request()->ajax()) { return response()->json(); } try { $this->leaveService ->campaign($campaign) ->user(auth()->user()) ->leave(); $this->campaignService ->user(auth()->user()) ->next(); return redirect()->route('home') ->with('success', __('campaigns.leave.success', ['name' => $campaign->name])); } catch (Exception $e) { $this->campaignService ->user(auth()->user()) ->next(); return redirect()->route('overview', $campaign)->withErrors($e->getMessage()); } } } ================================================ FILE: app/Http/Controllers/Campaign/LogController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('recover', $campaign); $cutoff = $campaign->premium() ? config('limits.campaigns.logs.premium') : config('limits.campaigns.logs.standard'); $premium = config('limits.campaigns.logs.premium'); $logs = UserLog::with(['user', 'impersonator']) ->where('campaign_id', $campaign->id) ->whereDate('created_at', '>=', Carbon::today()->subDays($premium)->format('Y-m-d')) ->latest() ->paginate(); return view('campaigns.logs.index') ->with('campaign', $campaign) ->with('cutoff', $cutoff) ->with('premium', $premium) ->with('logs', $logs); } } ================================================ FILE: app/Http/Controllers/Campaign/MemberController.php ================================================ middleware('auth'); } /** * Switch to another member * * @return RedirectResponse * * @throws AuthorizationException */ public function switch(Campaign $campaign, CampaignUser $campaignUser, ?Entity $entity = null) { $this->authorize('switch', $campaignUser); if (Identity::campaign($campaign)->switch($campaignUser)) { if ($entity) { return redirect() ->to($entity->url()); } return redirect() ->route('dashboard', $campaign); } return redirect() ->route('dashboard', $campaign); } /** * Switch back to the original user * * @return RedirectResponse */ public function back(Campaign $campaign) { if (Identity::back()) { return redirect() ->route('dashboard', $campaign) ->with('success', __('campaigns.members.switch_back_success')); } return redirect() ->route('dashboard', $campaign); } public function delete(Campaign $campaign, CampaignUser $campaignUser) { $this->authorize('delete', $campaignUser); return view('campaigns.members.delete', $campaign) ->with('campaign', $campaign) ->with('campaignUser', $campaignUser); } } ================================================ FILE: app/Http/Controllers/Campaign/Members/RoleController.php ================================================ middleware('auth'); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function index(Campaign $campaign, CampaignUser $campaignUser) { $this->authorize('members', $campaign); $roles = $campaign->roles->where('is_public', false)->all(); return view('campaigns.members.update', [ 'campaign' => $campaign, 'roles' => $roles, 'campaignUser' => $campaignUser, ]); } /** * @throws AuthorizationException */ public function save(UpdateUserRoles $request, Campaign $campaign, CampaignUser $campaignUser) { $this->authorize('update', $campaignUser); if (request()->ajax()) { return response()->json(); } try { $this->memberService ->user($request->user()) ->campaign($campaign) ->update($campaignUser, $request->get('roles', [])); } catch (TranslatableException $e) { return redirect() ->route('campaign_users.index', $campaign) ->with('error_raw', $e->getTranslatedMessage()); } return redirect() ->route('campaign_users.index', $campaign) ->with('success', __('campaigns/members.roles.success', [ 'user' => $campaignUser->user->name, ])); } } ================================================ FILE: app/Http/Controllers/Campaign/ModuleController.php ================================================ authorize('setting', $campaign); $entityTypes = $this->entityTypeService ->campaign($campaign) ->exclude(config('entities.ids.attribute_template')) ->withDisabled() ->ordered(); $customEntityTypes = $entityTypes->whereNotNull('campaign_id'); $entityTypes = $entityTypes->whereNull('campaign_id'); $thumbnails = $campaign->defaultImages(true); return view('campaigns.modules.index') ->with('campaign', $campaign) ->with('customEntityTypes', $customEntityTypes) ->with('entityTypes', $entityTypes) ->with('canReset', true) ->with('thumbnails', $thumbnails); } public function edit(Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); $image = null; $thumbnails = $campaign->defaultImages(); foreach ($thumbnails as $thumbnail) { if ($thumbnail['type'] == $entityType->pluralCode()) { $image = $thumbnail; } } $singular = $campaign->moduleName($entityType->id); $plural = $campaign->moduleName($entityType->id, true); $icon = $campaign->moduleIcon($entityType->id); return view('campaigns.modules.edit') ->with('campaign', $campaign) ->with('entityType', $entityType) ->with('singular', $singular) ->with('plural', $plural) ->with('icon', $icon) ->with('image', $image); } public function update(UpdateModuleName $request, Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); if (request()->ajax()) { return response()->json(['success' => true]); } $this->moduleEditService ->campaign($campaign) ->entityType($entityType) ->user($request->user()) ->update($request); return redirect()->route('campaign.modules', $campaign) ->with('success', __('campaigns/modules.rename.success')); } public function reset(Campaign $campaign) { $this->authorize('setting', $campaign); $this->moduleEditService ->user(auth()->user()) ->campaign($campaign) ->reset(); $this->sidebarService ->campaign($campaign) ->clearCache(); return redirect() ->route('campaign.modules', $campaign) ->with('success', __('campaigns/modules.reset.success')); } /** * Toggle a module in the campaign's settings */ public function toggle(Campaign $campaign, EntityType $entityType) { $this->authorize('setting', $campaign); try { $status = $this->moduleEditService ->campaign($campaign) ->user(auth()->user()) ->entityType($entityType) ->toggle(); return response()->json([ 'success' => true, 'status' => $campaign->enabled($entityType), 'toast' => __('campaigns.settings.' . ($status ? 'enabled' : 'disabled'), ['module' => $entityType->plural()]), ]); } catch (Exception $e) { return response()->json([ 'success' => false, ]); } } /** * Toggle a module in the campaign's settings */ public function toggleFeature(Campaign $campaign, string $module) { $this->authorize('setting', $campaign); try { $status = $this->moduleEditService ->campaign($campaign) ->toggleFeature($module); return response()->json([ 'success' => true, 'status' => $campaign->setting->{$module}, 'toast' => __('campaigns.settings.' . ($status ? 'enabled' : 'disabled'), ['module' => __('entities.' . $module)]), ]); } catch (Exception $e) { return response()->json([ 'success' => false, ]); } } } ================================================ FILE: app/Http/Controllers/Campaign/PluginController.php ================================================ get('highlight'); if (! empty($highlight)) { Datagrid::highlight(function () use ($highlight) { // @phpstan-ignore-next-line return $this->uuid === $highlight; }); } $plugins = $campaign->plugins() ->preparedSelect() ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->highlighted($highlight) ->has('user') ->with('versions'); if (auth()->guest() || ! auth()->user()->can('member', $campaign)) { $plugins->where('campaign_plugins.is_active', true); } $rows = $plugins->paginate(); // Ajax Datagrid if (request()->ajax()) { $html = view('layouts.datagrid._table')->with('rows', $rows)->with('campaign', $campaign)->render(); $deletes = view('layouts.datagrid.delete-forms')->with('models', Datagrid::deleteForms())->with('campaign', $campaign)->render(); return response()->json([ 'success' => true, 'html' => $html, 'deletes' => $deletes, ]); } return view('campaigns.plugins', compact('campaign', 'rows', 'highlight')); } public function delete(Request $request, Campaign $campaign, Plugin $plugin) { $this->authorize('recover', $campaign); try { $this->service->campaign($campaign)->user($request->user())->plugin($plugin)->remove(); return redirect()->route('campaign_plugins.index', $campaign) ->with( 'success', __('campaigns/plugins.destroy.success', ['plugin' => $plugin->name]) ); } catch (Exception $e) { return redirect()->route('campaign_plugins.index', $campaign) ->with( 'error', $e->getMessage() ); } } } ================================================ FILE: app/Http/Controllers/Campaign/Plugins/BulkController.php ================================================ middleware(['auth', Boosted::class]); } public function index(Campaign $campaign) { $this->authorize('recover', $campaign); $action = request()->get('action'); $models = request()->get('model'); if (! in_array($action, ['enable', 'disable', 'update', 'delete']) || empty($models)) { return redirect() ->route('campaign_plugins.index', $campaign); } $this->service->campaign($campaign)->user(auth()->user()); $count = 0; foreach ($models as $id) { /** @var Plugin|null $plugin */ $plugin = Plugin::find($id); if (empty($plugin)) { continue; } if ($action === 'enable') { if ($this->service->plugin($plugin)->enable()) { $count++; } } elseif ($action === 'disable') { if ($this->service->plugin($plugin)->disable()) { $count++; } } elseif ($action === 'update') { if ($this->service->plugin($plugin)->update()) { $count++; } } elseif ($action === 'delete') { $this->service->plugin($plugin)->remove(); $count++; } } CampaignCache::campaign($campaign)->clearTheme(); return redirect() ->route('campaign_plugins.index', $campaign) ->with('success', trans_choice('campaigns/plugins.bulks.' . $action, $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Campaign/Plugins/CssController.php ================================================ boosted()) { $css = CampaignCache::campaign($campaign)->themes(); } $response = Response::make($css); $response->header('Content-Type', 'text/css'); // $response->header('Expires', Carbon::now()->addMonth()->toDateTimeString()); $month = 31536000; $response->setLastModified($campaign->updated_at->toDateTime()); $response->header('Cache-Control', 'public, max_age=' . $month); return $response; } } ================================================ FILE: app/Http/Controllers/Campaign/Plugins/ImportController.php ================================================ middleware(['auth', Boosted::class]); } public function index(Campaign $campaign, Plugin $plugin) { $this->authorize('recover', $campaign); $version = CampaignPlugin::where('campaign_id', $campaign->id) ->where('plugin_id', $plugin->id) ->firstOrFail(); return view('campaigns.plugins.confirm') ->with('plugin', $plugin) ->with('campaign', $campaign) ->with('version', $version); } public function process(Request $request, Campaign $campaign, Plugin $plugin) { $this->authorize('recover', $campaign); if ($request->ajax()) { return response()->json(); } try { $count = $this->importerService ->plugin($plugin) ->campaign($campaign) ->user($request->user()) ->options($request->only(['force_private', 'only_new'])) ->import(); return redirect()->route('campaign_plugins.index', $campaign) ->with( 'success', trans_choice('campaigns/plugins.import.success', $count, ['plugin' => $plugin->name, 'count' => $count]) ) ->with('plugin_entities_created', $this->importerService->created()) ->with('plugin_entities_updated', $this->importerService->updated()) ->with('plugin_only_new', $request->get('only_new')); } catch (Exception $e) { return redirect()->route('campaign_plugins.index', $campaign) ->withError(__('campaigns/plugins.import.errors.' . $e->getMessage(), ['plugin' => $plugin->name])); } } } ================================================ FILE: app/Http/Controllers/Campaign/Plugins/ToggleController.php ================================================ middleware(['auth', Boosted::class]); } public function enable(Campaign $campaign, Plugin $plugin) { $this->authorize('recover', $campaign); $this->service->campaign($campaign)->user(auth()->user())->plugin($plugin)->enable(); return redirect()->route('campaign_plugins.index', $campaign) ->with( 'success', __('campaigns/plugins.enabled.success', ['plugin' => $plugin->name]) ); } public function disable(Campaign $campaign, Plugin $plugin) { $this->authorize('recover', $campaign); $this->service->campaign($campaign)->user(auth()->user())->plugin($plugin)->disable(); return redirect()->route('campaign_plugins.index', $campaign) ->with( 'success', __('campaigns/plugins.disabled.success', ['plugin' => $plugin->name]) ); } } ================================================ FILE: app/Http/Controllers/Campaign/Plugins/UpdateController.php ================================================ middleware(['auth', Boosted::class]); } public function index(Campaign $campaign, Plugin $plugin) { $this->authorize('recover', $campaign); $versions = $plugin ->versions() ->publishedVersions($plugin->created_by === auth()->user()->id) ->orderBy('id', 'desc') ->paginate(); $plugin = $campaign->plugins->where('id', $plugin->id)->first(); return view('campaigns.plugins.info', compact('plugin', 'campaign', 'versions')); } public function update(Request $request, Campaign $campaign, Plugin $plugin) { $this->authorize('recover', $campaign); if ($request->ajax()) { return response()->json(); } $this->service->plugin($plugin)->user(auth()->user())->campaign($campaign)->update(); return redirect()->route('campaign_plugins.index', $campaign) ->with( 'success', __('campaigns/plugins.update.success', ['plugin' => $plugin->name]) ); } } ================================================ FILE: app/Http/Controllers/Campaign/RecoveryController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('recover', $campaign); return view('campaigns.recovery.index', compact('campaign')); } public function setup(Campaign $campaign) { $this->authorize('recover', $campaign); return response()->json( $this->recoverySetupService ->user(auth()->user()) ->campaign($campaign) ->setup() ); } public function recover(Request $request, Campaign $campaign) { if (! $campaign->boosted()) { return redirect() ->route('recovery', $campaign) ->with('boosted-pitch', true); } $this->authorize('recover', $campaign); try { $entities = $this->entityService ->campaign($campaign) ->user($request->user()) ->recover($request->get('entities', [])); $posts = $this->postService ->campaign($campaign) ->user($request->user()) ->recover($request->get('posts', [])); $count = count($entities) + count($posts); return response()->json(['entities' => $entities, 'posts' => $posts, 'toast' => trans_choice('campaigns/recovery.success_v2', $count, ['count' => $count])]); } catch (Exception $e) { return redirect() ->route('recovery', $campaign) ->with('error', __('campaigns/recovery.error')); } } } ================================================ FILE: app/Http/Controllers/Campaign/RoleController.php ================================================ middleware('auth'); $this->service = $rolePermissionService; } /** * @return Application|Factory|View|JsonResponse * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('roles', $campaign); Datagrid::layout(\App\Renderers\Layouts\Campaign\CampaignRole::class); $roles = $campaign->roles() ->sort(request()->only(['o', 'k'])) ->withCount(['users', 'rolePermissions']) ->orderBy('is_admin', 'DESC') ->orderBy('is_public', 'DESC') ->orderBy('name') ->paginate(); $rows = $roles; // Ajax Datagrid if (request()->ajax()) { $html = view('layouts.datagrid._table')->with('rows', $rows)->with('campaign', $campaign)->render(); $deletes = view('layouts.datagrid.delete-forms')->with('models', Datagrid::deleteForms())->with('campaign', $campaign)->render(); return response()->json([ 'success' => true, 'html' => $html, 'deletes' => $deletes, ]); } $unlimited = $campaign->boosted() || empty(config('limits.campaigns.roles')); return view('campaigns.roles', compact('campaign', 'rows', 'roles', 'unlimited')); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign) { $this->authorize('create', CampaignRole::class); if (! $campaign->canHaveMoreRoles()) { return view('cruds.forms.limit') ->with('limit', config('limits.campaigns.roles')) ->with('thing', __('campaigns.show.tabs.roles')) ->with('campaign', $campaign) ->with('name', 'campaign_roles'); } return view($this->view . '.create', ['campaign' => $campaign, 'model' => $campaign]); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function duplicate(Campaign $campaign, CampaignRole $campaignRole) { $this->authorize('create', CampaignRole::class); if (! $campaign->canHaveMoreRoles()) { return view('cruds.forms.limit') ->with('limit', config('limits.campaigns.roles')) ->with('thing', __('campaigns.show.tabs.roles')) ->with('campaign', $campaign) ->with('name', 'campaign_roles'); } return view($this->view . '.create', ['campaign' => $campaign, 'model' => $campaign, 'roleId' => $campaignRole->id]); } public function store(StoreCampaignRole $request, Campaign $campaign) { $this->authorize('create', CampaignRole::class); if ($request->ajax()) { return response()->json(); } if (! $campaign->canHaveMoreRoles()) { return redirect()->back(); } $data = $request->all() + ['campaign_id' => $campaign->id]; $role = CampaignRole::create($data); if ($request->has('duplicate') && $request->get('duplicate') != 0) { /** @var CampaignRole $copy */ $copy = CampaignRole::where('id', $request->get('role_id'))->first(); if ($copy) { $copy->duplicate($role); } } return redirect()->route('campaign_roles.index', $campaign) ->with('success_raw', __($this->view . '.create.success', ['name' => $role->name])); } public function show(Campaign $campaign, CampaignRole $campaignRole) { $this->authorize('view', [$campaignRole, $campaign]); $members = $campaignRole ->users() ->with('user') ->paginate(); $modules = $this->entityTypeService ->campaign($campaign) ->exclude([config('entities.ids.bookmark')]) ->ordered(); return view($this->view . '.show', [ 'model' => $campaign, 'role' => $campaignRole, 'campaign' => $campaign, 'members' => $members, 'modules' => $modules, 'permissionService' => $this->service->campaign($campaign)->role($campaignRole), ]); } public function edit(Campaign $campaign, CampaignRole $campaignRole) { $this->authorize('view', [$campaignRole, $campaign]); $this->authorize('update', $campaignRole); return view($this->view . '.edit', [ 'campaign' => $campaign, 'model' => $campaign, 'role' => $campaignRole, ]); } public function update(StoreCampaignRole $request, Campaign $campaign, CampaignRole $campaignRole) { $this->authorize('view', [$campaignRole, $campaign]); $this->authorize('update', $campaignRole); $campaignRole->update($request->only('name')); return redirect()->route('campaign_roles.index', $campaign) ->with('success_raw', __($this->view . '.edit.success', ['name' => $campaignRole->name])); } public function destroy(Campaign $campaign, CampaignRole $campaignRole) { $this->authorize('view', [$campaignRole, $campaign]); $this->authorize('delete', $campaignRole); $campaignRole->delete(); return redirect()->route('campaign_roles.index', $campaign) ->with('success_raw', __($this->view . '.destroy.success', ['name' => $campaignRole->name])); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function savePermissions(Request $request, Campaign $campaign, CampaignRole $campaignRole) { $this->authorize('view', [$campaignRole, $campaign]); $this->authorize('update', $campaignRole); $this->service->role($campaignRole)->savePermissions($request->post('permissions', [])); return redirect()->route('campaign_roles.show', [$campaign, 'campaign_role' => $campaignRole]) ->with('success', trans('crud.permissions.success')); } /** * campaign//campaign_roles/admin fast url * * @return Application|Factory|View * * @throws AuthorizationException */ public function admin(Campaign $campaign) { $this->authorize('roles', $campaign); $adminRole = $campaign->roles()->admin()->firstOrFail(); return $this->show($campaign, $adminRole); } /** * campaign//campaign_roles/admin fast url * * @return Application|Factory|View * * @throws AuthorizationException */ public function public(Campaign $campaign) { $this->authorize('roles', $campaign); $adminRole = $campaign->roles()->public()->firstOrFail(); return $this->show($campaign, $adminRole); } /** * @return JsonResponse * * @throws AuthorizationException */ public function search(Request $request, Campaign $campaign) { $this->authorize('members', $campaign); $term = $request->get('q', null); if (empty($term)) { $members = $campaign->roles()->where('is_admin', 0)->where('is_public', 0)->orderBy('name', 'asc')->limit(5)->get(); } elseif ($request->get('with-admin', null)) { $members = $campaign->roles()->where('is_public', 0)->where('name', 'like', '%' . $term . '%')->limit(5)->get(); } else { $members = $campaign->roles()->where('is_admin', 0)->where('is_public', 0)->where('name', 'like', '%' . $term . '%')->limit(5)->get(); } $results = []; foreach ($members as $member) { $results[] = [ 'id' => $member->id, 'text' => $member->name, ]; } return response()->json($results); } /** * Toggle a permission on a role * * @return JsonResponse */ public function toggle(Campaign $campaign, CampaignRole $campaignRole, EntityType $entityType, int $action) { $this->authorize('view', [$campaignRole, $campaign]); $this->authorize('update', $campaignRole); if (! $campaignRole->is_public) { abort(404); } $enabled = $this->service->role($campaignRole)->entityType($entityType)->toggle($action); return response()->json([ 'success' => true, 'status' => $enabled, 'toast' => __('campaigns/roles.toggle.' . ($enabled ? 'enabled' : 'disabled'), [ 'role' => $campaignRole->name, 'action' => __('campaigns.roles.permissions.actions.read'), 'entities' => $entityType->plural(), ]), ]); } public function bulk(Campaign $campaign) { $this->authorize('roles', $campaign); $action = request()->get('action'); $models = request()->get('model'); if (! in_array($action, ['edit', 'delete']) || empty($models)) { return redirect() ->route('campaign_roles.index', $campaign); } $count = 0; foreach ($models as $id) { /** @var CampaignRole|null $role */ $role = CampaignRole::find($id); if ($role === null) { continue; } if ($action === 'delete' && ! $role->isAdmin() && ! $role->isPublic()) { $role->delete(); $count++; } } return redirect() ->route('campaign_roles.index', $campaign) ->with('success', trans_choice('campaigns.roles.bulks.' . $action, $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Campaign/RoleUserController.php ================================================ middleware('auth'); $this->service = $service; } public function index(Campaign $campaign) { return redirect()->route('campaign_roles.index', $campaign); } public function create(Campaign $campaign, CampaignRole $campaignRole) { $this->authorize('roles', $campaign); $this->authorize('user', $campaignRole); return view($this->view . '.create', ['campaign' => $campaign, 'role' => $campaignRole]); } public function store(StoreCampaignRoleUser $request, Campaign $campaign, CampaignRole $campaignRole) { $this->authorize('roles', $campaign); $this->authorize('create', CampaignRole::class); if (request()->ajax()) { return response()->json(['success' => true]); } $data = $request->only(['user_id']); $data['campaign_role_id'] = $campaignRole->id; $relation = CampaignRoleUser::create($data); return redirect()->route('campaign_roles.show', [ $campaign, 'campaign_role' => $campaignRole]) ->with('success', __($this->view . '.create.success', [ 'user' => $relation->user->name, 'role' => $relation->campaignRole->name, ])); } public function show(Campaign $campaign, CampaignRole $campaignRole, CampaignRoleUser $campaignRoleUser) { return redirect() ->route('campaign_roles.show', [$campaign, $campaignRole]); } public function destroy(Campaign $campaign, CampaignRole $campaignRole, CampaignRoleUser $campaignRoleUser) { $this->authorize('roles', $campaign); $this->authorize('view', [$campaignRoleUser, $campaign]); $this->authorize('delete', [$campaignRoleUser, $campaignRole]); try { $this->service ->user(auth()->user()) ->element($campaignRoleUser) ->delete(); } catch (TranslatableException $e) { return redirect()->route('campaign_roles.show', [$campaign, $campaignRole]) ->with('error_raw', $e->getTranslatedMessage()); } return redirect()->route('campaign_roles.show', [$campaign, $campaignRole]) ->with('success', __($this->view . '.destroy.success', [ 'user' => $campaignRoleUser->user->name, 'role' => $campaignRoleUser->campaignRole->name, ])); } } ================================================ FILE: app/Http/Controllers/Campaign/ShareController.php ================================================ middleware('auth'); } /** * @throws AuthorizationException */ public function setup(Campaign $campaign): View { $this->authorize('update', $campaign); return view('campaigns.share.setup', compact('campaign')); } /** * @throws AuthorizationException */ public function save(Request $request, Campaign $campaign): JsonResponse { $this->authorize('update', $campaign); $request->validate([ 'campaign_visibility' => ['required', 'string', 'in:public'], ]); $this->shareService ->campaign($campaign) ->makePublic(); return response()->json([ 'success' => true, 'campaign_public' => true, ]); } } ================================================ FILE: app/Http/Controllers/Campaign/SidebarController.php ================================================ middleware('auth'); $this->middleware(Boosted::class, ['except' => 'index']); } public function index(Campaign $campaign) { $this->authorize('update', $campaign); $layout = $this->service->campaign($campaign)->withDisabled()->layout(); return view( 'campaigns.sidebar.index', compact( 'campaign', 'layout', ) ); } public function save(Request $request, Campaign $campaign) { $this->authorize('update', $campaign); if ($request->ajax()) { return response()->json(); } $this->saveService ->campaign($campaign) ->user(auth()->user()) ->save(request()->all()); return redirect() ->route('campaign-sidebar', $campaign) ->with('success', __('campaigns/sidebar.success')); } public function reset(Campaign $campaign) { $this->authorize('update', $campaign); $this->saveService ->campaign($campaign) ->user(auth()->user()) ->reset(); return redirect() ->route('campaign-sidebar', $campaign) ->with('success', __('campaigns/sidebar.reset.success')); } } ================================================ FILE: app/Http/Controllers/Campaign/StatController.php ================================================ statService->campaign($campaign)->get(); $entityTypes = []; /** @var EntityType $entityType */ foreach (EntityType::inCampaign($campaign)->get() as $entityType) { $entityTypes[$entityType->id] = $entityType; } return view('campaigns.stats.index') ->with('campaign', $campaign) ->with('stats', $stats) ->with('entityTypes', $entityTypes); } } ================================================ FILE: app/Http/Controllers/Campaign/StyleController.php ================================================ middleware('auth'); $this->middleware(Boosted::class, ['except' => 'index']); } /** * @return Application|Factory|View|JsonResponse * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('recover', $campaign); $styles = $campaign->styles() ->sort(request()->only(['o', 'k'])) ->take(self::MAX_THEMES) ->paginate(config('limits.pagination')); Datagrid::layout(\App\Renderers\Layouts\Campaign\Theme::class)->permissions(false); // Ajax Datagrid if (request()->ajax()) { $html = view('layouts.datagrid._table')->with('rows', $styles)->with('campaign', $campaign)->render(); $deletes = view('layouts.datagrid.delete-forms')->with('models', Datagrid::deleteForms())->with('campaign', $campaign)->render(); return response()->json([ 'success' => true, 'html' => $html, 'deletes' => $deletes, ]); } $theme = $campaign->theme; $reorderStyles = $campaign->styles()->select(['id', 'name', 'is_enabled'])->defaultOrder()->take(self::MAX_THEMES)->get(); return view('campaigns.styles.index', compact('campaign', 'styles', 'theme', 'reorderStyles')); } public function show(Campaign $campaign, CampaignStyle $campaignStyle) { return redirect() ->route('campaign_styles.index', $campaign); } public function create(Campaign $campaign) { $this->authorize('update', $campaign); if ($campaign->styles()->count() >= self::MAX_THEMES) { return redirect()->route('campaign_styles.index', $campaign) ->with('error', __('campaigns/styles.errors.max_reached', ['max' => self::MAX_THEMES])); } return view('campaigns.styles.create', compact('campaign')); } public function store(StoreCampaignStyle $request, Campaign $campaign) { $this->authorize('update', $campaign); if (request()->ajax()) { return response()->json(['success' => true]); } if ($campaign->styles()->count() >= self::MAX_THEMES) { return redirect()->route('campaign_styles.index', $campaign) ->with('error', __('campaigns/styles.errors.max_reached', ['max' => self::MAX_THEMES])); } $style = new CampaignStyle($request->only('name', 'content', 'is_enabled')); $style->campaign_id = $campaign->id; $style->save(); if ($request->has('submit-update')) { return redirect() ->route('campaign_styles.edit', [$campaign, $style]) ->with('success', __('campaigns/styles.create.success', ['name' => $style->name])); } return redirect() ->route('campaign_styles.index', $campaign) ->with('success', __('campaigns/styles.create.success')); } public function edit(Campaign $campaign, CampaignStyle $campaignStyle) { $this->authorize('update', $campaign); if ($campaignStyle->isTheme()) { return redirect()->route('campaign_styles.builder', $campaign); } $style = $campaignStyle; return view('campaigns.styles.edit', compact('campaign', 'style')); } public function update(StoreCampaignStyle $request, Campaign $campaign, CampaignStyle $campaignStyle) { $this->authorize('update', $campaign); $campaignStyle->update($request->only('name', 'content', 'is_enabled')); if ($request->has('submit-update')) { return redirect() ->route('campaign_styles.edit', [$campaign, $campaignStyle]) ->with('success', __('campaigns/styles.update.success', ['name' => $campaignStyle->name])); } return redirect() ->route('campaign_styles.index', $campaign) ->with('success', __('campaigns/styles.update.success', ['name' => $campaignStyle->name])); } public function destroy(Campaign $campaign, CampaignStyle $campaignStyle) { $this->authorize('update', $campaign); $campaignStyle->delete(); return redirect() ->route('campaign_styles.index', $campaign) ->with('success', __('campaigns/styles.delete.success', ['name' => $campaignStyle->name])); } public function theme(Campaign $campaign) { $this->authorize('update', $campaign); $themes = [null => __('campaigns.themes.none')]; foreach (Theme::all() as $theme) { $themes[$theme->id] = $theme->__toString(); } return view('campaigns.styles.theme', compact('campaign', 'themes')); } public function themeSave(StoreCampaignTheme $request, Campaign $campaign) { $this->authorize('update', $campaign); $campaign->update([ 'theme_id' => $request->get('theme_id'), ]); return redirect() ->route('campaign_styles.index', $campaign) ->with('success', __('campaigns/styles.theme.success')); } /** * @return RedirectResponse * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function bulk(Campaign $campaign) { $action = request()->get('action'); $models = request()->get('model'); if (! in_array($action, ['enable', 'disable', 'delete']) || empty($models)) { return redirect() ->route('campaign_styles.index', $campaign); } $count = 0; foreach ($models as $id) { /** @var CampaignStyle|null $style */ $style = CampaignStyle::find($id); if ($style === null) { continue; } if ($action === 'enable' && ! $style->is_enabled) { $style->is_enabled = true; $style->update(); $count++; } elseif ($action === 'disable' && $style->is_enabled) { $style->is_enabled = false; $style->update(); $count++; } elseif ($action === 'delete') { $style->delete(); $count++; } } return redirect() ->route('campaign_styles.index', $campaign) ->with('success', trans_choice('campaigns/styles.bulks.' . $action, $count, ['count' => $count])); } public function reorder(ReorderStyles $request, Campaign $campaign) { $order = 1; $ids = $request->get('style'); foreach ($ids as $id) { $style = CampaignStyle::find($id); if (empty($style)) { continue; } $style->order = $order; $style->timestamps = false; $style->update(); $order++; } $order--; return redirect() ->route('campaign_styles.index', $campaign) ->with('success', trans_choice('campaigns/styles.reorder.success', $order, ['count' => $order])); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function toggle(Campaign $campaign, CampaignStyle $campaignStyle) { $this->authorize('update', $campaign); if ($campaignStyle->is_enabled) { $message = __('campaigns/styles.toggle.disable'); } else { $message = __('campaigns/styles.toggle.enable'); } $campaignStyle->update(['is_enabled' => ! $campaignStyle->is_enabled]); return redirect()->route('campaign_styles.index', $campaign) ->with( 'success', $message ); } } ================================================ FILE: app/Http/Controllers/Campaign/ThemeBuilderController.php ================================================ middleware('auth'); $this->middleware(Boosted::class, ['except' => 'index']); } public function index(Campaign $campaign) { $this->authorize('update', $campaign); $style = CampaignStyle::theme()->first(); $config = $style?->content; return view('campaigns.styles.builder', compact('campaign', 'config')); } public function save(StoreTheme $request, Campaign $campaign) { $this->authorize('update', $campaign); $this->themeBuilderService ->campaign($campaign) ->save($request->get('config')); CampaignCache::campaign($campaign)->clearStyles()->clear(); return redirect() ->route('campaign_styles.index', $campaign) ->with('success', __('campaigns/builder.success')); } public function reset(Campaign $campaign) { $this->authorize('update', $campaign); $theme = $campaign->styles()->theme()->first(); if (empty($theme)) { return redirect() ->route('campaign_styles.index', $campaign); } $theme->delete(); CampaignCache::campaign($campaign)->clearStyles()->clear(); return redirect() ->route('campaign_styles.index', $campaign) ->with('success', __('campaigns/builder.reset')); } } ================================================ FILE: app/Http/Controllers/Campaign/UserController.php ================================================ middleware('auth'); } /** * @return Application|Factory|View|JsonResponse * * @throws AuthorizationException */ public function index(Campaign $campaign) { $this->authorize('members', $campaign); $this->rows = $campaign ->members() ->sort(request()->only(['o', 'k']), ['id' => 'desc']) ->with([ 'user:id,name,avatar,last_login_at,has_last_login_sharing,banned_until', 'user.campaignRoles', ]) ->paginate(); $invitations = $campaign ->invites() ->where('is_active', true) ->with('role') ->paginate(); $roles = $campaign->roles->where('is_public', false)->all(); Datagrid::campaign($campaign)->layout(\App\Renderers\Layouts\Campaign\CampaignUser::class); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return view('campaigns.members.index', [ 'campaign' => $campaign, 'roles' => $roles, 'invitations' => $invitations, 'rows' => $this->rows, 'unlimited' => $campaign->boosted() || empty(config('limits.campaigns.members')), ]); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, CampaignUser $campaignUser) { $this->authorize('invite', $campaign); $this->authorize('view', [$campaignUser, $campaign]); $campaignUser->delete(); return redirect()->route('campaign_users.index', $campaign); } /** * @return JsonResponse * * @throws AuthorizationException */ public function search(Request $request, Campaign $campaign) { $this->authorize('members', $campaign); $term = $request->get('q', null); if (empty($term)) { $members = $campaign->users()->orderBy('name', 'asc')->limit(5)->get(); } else { $members = $campaign->users()->where('name', 'like', '%' . $term . '%')->limit(5)->get(); } $results = []; foreach ($members as $member) { $results[] = [ 'id' => $member->id, 'text' => $member->name, ]; } return response()->json($results); } } ================================================ FILE: app/Http/Controllers/Campaign/VanityController.php ================================================ authorize('update', $campaign); return response([ 'success' => true, 'vanity' => Str::slug($request->post('vanity')), ]); } } ================================================ FILE: app/Http/Controllers/Campaign/VisibilityController.php ================================================ middleware('auth'); } public function edit(Campaign $campaign) { $this->authorize('update', $campaign); $from = request()->get('from'); return view('campaigns.forms.modals.visibility', compact('campaign', 'from')); } public function save(StoreCampaignVisibility $request, Campaign $campaign) { $this->authorize('update', $campaign); if ($request->ajax()) { return response()->json(); } $campaign->update([ 'visibility_id' => $request->get('visibility_id'), ]); $key = $campaign->isUnlisted() ? 'unlisted' : ($campaign->isPublic() ? 'public' : 'private'); $success = __('campaigns/public.update.' . $key, [ 'public-campaigns' => '' . __('footer.public-campaigns') . '', ]); if ($request->get('from') === 'overview') { return redirect() ->route('overview', $campaign) ->with('success_raw', $success); } return redirect() ->back() ->with('success_raw', $success); } } ================================================ FILE: app/Http/Controllers/Campaign/WebController.php ================================================ authorize('access', $campaign); return view('connections.web') ->with('campaign', $campaign); } public function api(Campaign $campaign) { $this->authorize('access', $campaign); if (auth()->check()) { $this->webService->user(auth()->user()); } return response()->json( $this->webService->campaign($campaign)->build() ); } } ================================================ FILE: app/Http/Controllers/Campaign/WebhookController.php ================================================ middleware('auth'); } /** * @return Application|Factory|View|JsonResponse * * @throws AuthorizationException */ public function index(Campaign $campaign) { Datagrid::layout(\App\Renderers\Layouts\Campaign\Webhook::class); $this->authorize('webhooks', $campaign); $rows = $campaign->webhooks() ->sort(request()->only(['o', 'k'])) // ->with(['users', 'permissions', 'campaign']) ->orderBy('updated_at', 'DESC') // ->orderBy('name') ->paginate(); // Ajax Datagrid if (request()->ajax()) { $html = view('layouts.datagrid._table')->with('rows', $rows)->with('campaign', $campaign)->render(); $deletes = view('layouts.datagrid.delete-forms')->with('models', Datagrid::deleteForms())->with('campaign', $campaign)->render(); return response()->json([ 'success' => true, 'html' => $html, 'deletes' => $deletes, ]); } return view('campaigns.webhooks', compact('campaign', 'rows')); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign) { $this->authorize('webhooks', $campaign); if (! $campaign->premium()) { return view('components.premium-dialog', [ 'campaign' => $campaign, 'title' => __('campaigns/webhooks.title'), 'pitch' => __('campaigns/webhooks.premium'), 'doc' => 'features/campaigns/webhooks.html', ]); } return view('campaigns.webhooks.create', ['campaign' => $campaign]); } public function store(StoreWebhook $request, Campaign $campaign) { $this->authorize('webhooks', $campaign); if (! $campaign->premium()) { return redirect()->route('webhooks.index', $campaign) ->with( 'error', __('campaigns/webhooks.error.pitch') ); } if ($request->ajax()) { return response()->json(); } $new = $this->service->campaign($campaign)->user($request->user())->request($request)->save(); return redirect()->route('webhooks.index', $campaign) ->with('success', __('campaigns/webhooks.create.success')); } public function edit(Campaign $campaign, Webhook $webhook) { $this->authorize('webhooks', $campaign); return view('campaigns.webhooks.edit', [ 'campaign' => $campaign, 'webhook' => $webhook, ]); } public function update(StoreWebhook $request, Campaign $campaign, Webhook $webhook) { $this->authorize('webhooks', $campaign); $this->service ->campaign($campaign) ->user($request->user()) ->webhook($webhook) ->request($request) ->save(); $webhook->update($request->all()); return redirect()->route('webhooks.index', $campaign) ->with('success', __('campaigns/webhooks.edit.success')); } public function destroy(Campaign $campaign, Webhook $webhook) { $this->authorize('webhooks', $campaign); $webhook->delete(); return redirect()->route('webhooks.index', $campaign) ->with('success', __('campaigns/webhooks.destroy.success')); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function toggle(Campaign $campaign, Webhook $webhook) { $this->authorize('webhooks', $campaign); if ($webhook->status != 1) { $message = __('campaigns/webhooks.toggle.enable'); } else { $message = __('campaigns/webhooks.toggle.disable'); } $webhook->update(['status' => ! $webhook->status]); return redirect()->route('webhooks.index', $campaign) ->with( 'success', $message ); } public function bulk(Campaign $campaign) { $this->authorize('webhooks', $campaign); $action = request()->get('action'); $models = request()->get('model'); if (! in_array($action, ['enable', 'disable', 'delete']) || empty($models)) { return redirect() ->route('webhooks.index', $campaign); } $count = 0; foreach ($models as $id) { /** @var Webhook|null $webhook */ $webhook = Webhook::find($id); if ($webhook === null) { continue; } if ($action === 'delete') { $webhook->delete(); $count++; } elseif ($action === 'disable' && $webhook->status) { $webhook->update(['status' => 0]); $count++; } elseif ($action === 'enable' && ! $webhook->status) { $webhook->update(['status' => 1]); $count++; } } return redirect() ->route('webhooks.index', $campaign) ->with('success', trans_choice('campaigns/webhooks.actions.bulks.' . $action . '_success', $count, ['count' => $count])); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function test(Campaign $campaign, Webhook $webhook) { $this->authorize('webhooks', $campaign); if (! $campaign->premium()) { return redirect()->route('webhooks.index', $campaign) ->with( 'error', __('campaigns/webhooks.error.pitch') ); } TestWebhookJob::dispatch($campaign, auth()->user(), $webhook); WebhookTested::dispatch($webhook, auth()->user()); return redirect()->route('webhooks.index', $campaign) ->with( 'success', __('campaigns/webhooks.test.success') ); } } ================================================ FILE: app/Http/Controllers/CampaignBoostController.php ================================================ middleware(['auth', 'identity']); $this->campaignBoostService = $campaignBoostService; } public function create() { $campaignID = request()->get('campaign'); $campaign = Campaign::where('slug', $campaignID)->firstOrFail(); $user = auth()->user(); if ($user->hasBoosterNomenclature()) { $superboost = request()->has('superboost'); $cost = $superboost ? 3 : 1; return view('settings.boosters.create') ->with('campaign', $campaign) ->with('superboost', $superboost) ->with('cost', $cost) ->with('user', $user); } if (! $campaign->boosted() && $user->availableBoosts() < 1 && ! auth()->user()->can('boost', $user)) { return view('layouts.dialogs.subscription', [ 'title' => __('settings/premium.ready.title'), 'campaign' => $campaign, ]); } return view('settings.premium.create') ->with('campaign', $campaign) ->with('user', $user); } /** * @throws AuthorizationException */ public function store(Request $request) { $campaignId = $request->get('campaign_id'); /** @var Campaign $campaign */ $campaign = Campaign::findOrFail($campaignId); CampaignCache::campaign($campaign); $this->authorize('access', $campaign); if ($request->ajax()) { return response()->json(); } if (auth()->user()->hasBoosterNomenclature()) { try { $action = $request->post('action'); if ($request->has('superboost')) { $action = 'superboost'; } $this->campaignBoostService ->user(auth()->user()) ->campaign($campaign) ->action($action) ->boost(); CampaignCache::campaign($campaign)->clearSidebar()->clear(); $superboost = $action == 'superboost'; return redirect() ->route('settings.boost') ->with('success_raw', __('settings/boosters.' . ($superboost ? 'superboost' : 'boost') . '.success', ['campaign' => $campaign->name])); } catch (TranslatableException $e) { return redirect() ->route('settings.boost') ->with('error', $e->getTranslatedMessage()); } } try { $this->campaignBoostService ->user(auth()->user()) ->campaign($campaign) ->premium(); CampaignCache::campaign($campaign)->clearSidebar()->clear(); return redirect() ->route('settings.premium') ->with('success_raw', __('settings/premium.create.success', ['campaign' => $campaign->name])); } catch (TranslatableException $e) { return redirect() ->route('settings.premium') ->with('error', $e->getTranslatedMessage()); } } public function show(CampaignBoost $campaignBoost) { return redirect()->route('settings.boost'); } public function edit(CampaignBoost $campaignBoost) { $this->authorize('destroy', $campaignBoost); if (! auth()->user()->hasBoosterNomenclature()) { return redirect()->route('settings.premium'); } return view('settings.boosters.update') ->with('boost', $campaignBoost) ->with('campaign', $campaignBoost->campaign) ->with('cost', 2); } /** * @throws AuthorizationException */ public function update(Request $request, CampaignBoost $campaignBoost) { if (! auth()->user()->hasBoosterNomenclature()) { return redirect()->route('settings.premium'); } $campaign = $campaignBoost->campaign; // If the user created the boost, allow them to update it. We don't check the campaign because there is // no campaign in the url. $this->authorize('destroy', $campaignBoost); if ($request->ajax()) { return response()->json(); } try { $this->campaignBoostService ->user(auth()->user()) ->campaign($campaign) ->upgrade() ->action($request->post('action')) ->boost(); return redirect() ->route('settings.boost') ->with('success_raw', __('settings/boosters.superboost.success', ['campaign' => $campaign->name])); } catch (TranslatableException $e) { return redirect() ->route('settings.boost') ->with('error', $e->getTranslatedMessage()); } } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function confirm(CampaignBoost $campaignBoost) { $this->authorize('destroy', $campaignBoost); if (auth()->user()->hasBoosterNomenclature()) { return view('settings.boosters.unboost') ->with('campaign', $campaignBoost->campaign) ->with('boost', $campaignBoost); } return view('settings.premium.remove') ->with('campaign', $campaignBoost->campaign) ->with('boost', $campaignBoost); } /** * @throws AuthorizationException */ public function destroy(CampaignBoost $campaignBoost) { $this->authorize('destroy', $campaignBoost); if (request()->ajax()) { return response()->json(); } $this->campaignBoostService ->user(auth()->user()) ->campaign($campaignBoost->campaign) ->unboost($campaignBoost); CampaignCache::campaign($campaignBoost->campaign)->clearSidebar()->clear(); if (auth()->user()->hasBoosterNomenclature()) { return redirect() ->route('settings.boost') ->with('success_raw', __('settings/boosters.unboost.success', ['campaign' => $campaignBoost->campaign->name])); } return redirect() ->route('settings.premium') ->with('success_raw', __('settings/premium.remove.success', ['campaign' => $campaignBoost->campaign->name])); } } ================================================ FILE: app/Http/Controllers/Characters/Families/ManagementController.php ================================================ authorize('update', $character->entity); $this->campaign($campaign)->authEntityView($character->entity); $families = $character ->characterFamilies() ->with(['family', 'family.entity', 'family.entity.image']) ->get(); return view('characters.families.reorder', compact( 'campaign', 'families', 'character' )); } /** * @throws AuthorizationException */ public function save(Campaign $campaign, Character $character, ManageFamilies $request) { $this->authorize('update', $character->entity); $families = $character->families()->pluck('families.id')->toArray(); $privates = $request->get('family_privates'); // We need to delete the old ones to make way for the new ones. CharacterFamily::where('character_id', $character->id)->delete(); foreach ($request->get('character_family') as $newFamily) { // We just want to reorder, not add whatever the user sends as a request. if (in_array($newFamily, $families)) { $characterFamily = new CharacterFamily; $characterFamily->family_id = $newFamily; $characterFamily->character_id = $character->id; $characterFamily->is_private = $privates[$newFamily]; $characterFamily->save(); } } return redirect() ->route('entities.show', [$campaign, $character->entity]) ->withSuccess(__('characters.families.reorder.success', ['name' => $character->name])); } } ================================================ FILE: app/Http/Controllers/Characters/MemberController.php ================================================ campaign($campaign)->authEntityView($character->entity); Datagrid::layout(Organisation::class) ->route('characters.organisations', [$character]); $this->rows = $character ->organisationMemberships() ->with([ 'organisation.entity', 'organisation.entity.image', 'organisation.entity.entityType' => function ($sub) { $sub->select('id', 'code'); }, 'organisation.entity.tags', ]) ->rows() ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('characters.organisations', $character); } } ================================================ FILE: app/Http/Controllers/Characters/MembershipController.php ================================================ middleware('auth'); } /** * @return RedirectResponse */ public function index(Campaign $campaign, Character $character) { return redirect()->route('entities.show', [$campaign, $character->entity]); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign, Character $character) { $this->authorize('update', $character->entity); return view($this->view . '.create', ['model' => $character, 'campaign' => $campaign]); } /** * @throws AuthorizationException */ public function store(StoreCharacterOrganisation $request, Campaign $campaign, Character $character) { $this->authorize('update', $character->entity); if (request()->ajax()) { return response()->json(['success' => true]); } $relation = OrganisationMember::create(['character_id' => $character->id] + $request->all()); return redirect()->route('characters.organisations', [$campaign, $character->id]) ->with('success', __($this->view . '.create.success', [ 'character' => $character->name, 'organisation' => $relation->organisation->name, ])); } /** * @return void * * @throws AuthorizationException */ public function show(Campaign $campaign, Character $character, OrganisationMember $organisationMember) { $this->authorize('update', $character->entity); abort(404); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function edit(Campaign $campaign, Character $character, CharacterOrganisation $characterOrganisation) { $this->authorize('update', $character->entity); return view($this->view . '.edit', [ 'model' => $character, 'campaign' => $campaign, 'member' => $characterOrganisation, ]); } public function update( StoreCharacterOrganisation $request, Campaign $campaign, Character $character, CharacterOrganisation $characterOrganisation ) { $this->authorize('update', $character->entity); if (request()->ajax()) { return response()->json(['success' => true]); } $characterOrganisation->update($request->all()); if ($request->has('from') && $request->get('from') == 'org') { return redirect()->route('entities.show', [$campaign, $characterOrganisation->organisation->entity]) ->with('success', __($this->view . '.edit.success')); } return redirect()->route('characters.organisations', [$campaign, $character->id]) ->with('success', __($this->view . '.edit.success')); } /** * @return RedirectResponse * * @throws AuthorizationException * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function destroy(Campaign $campaign, Character $character, CharacterOrganisation $characterOrganisation) { $this->authorize('update', $character->entity); $characterOrganisation->delete(); if (request()->has('from') && request()->get('from') === 'org') { return redirect()->route('entities.show', [$campaign, $characterOrganisation->organisation->entity]) ->with('success', __($this->view . '.destroy.success')); } return redirect()->route('characters.organisations', [$campaign, $characterOrganisation->character_id]) ->with('success', __($this->view . '.destroy.success')); } } ================================================ FILE: app/Http/Controllers/Characters/Races/ManagementController.php ================================================ authorize('update', $character->entity); $this->campaign($campaign)->authEntityView($character->entity); $races = $character ->characterRaces() ->with(['race', 'race.entity', 'race.entity.image']) ->get(); return view('characters.races.reorder', compact( 'campaign', 'races', 'character' )); } /** * @throws AuthorizationException */ public function save(Campaign $campaign, Character $character, ManageRaces $request) { $this->authorize('update', $character->entity); $races = $character->races()->pluck('races.id')->toArray(); $privates = $request->get('race_privates'); // We need to delete the old ones to make way for the new ones. CharacterRace::where('character_id', $character->id)->delete(); foreach ($request->get('character_race') as $newRace) { // We just want to reorder, not add whatever the user sends as a request. if (in_array($newRace, $races)) { $characterRace = new CharacterRace; $characterRace->race_id = $newRace; $characterRace->character_id = $character->id; $characterRace->is_private = $privates[$newRace]; $characterRace->save(); } } return redirect() ->route('entities.show', [$campaign, $character->entity]) ->withSuccess(__('characters.races.reorder.success', ['name' => $character->name])); } } ================================================ FILE: app/Http/Controllers/ConfirmController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('access', $campaign); $route = request()->get('route'); $name = request()->get('name'); $permanent = request()->has('permanent'); $mirrored = request()->get('mirrored', false); return view('confirms.delete') ->with('route', $route) ->with('name', $name) ->with('mirrored', $mirrored) ->with('campaign', $campaign) ->with('permanent', $permanent); } } ================================================ FILE: app/Http/Controllers/Controller.php ================================================ authorize('update', $conversation->entity); $participant = new ConversationMessage; $data = $request->only('message', 'character_id'); if (! $conversation->forCharacters()) { $data['user_id'] = Auth::user()->id; } $data['conversation_id'] = $conversation->id; $participant = $participant->create($data); return new ConversationResource( $conversation ); } public function update(StoreConversationMessage $request, Campaign $campaign, Conversation $conversation, ConversationMessage $conversationMessage) { $this->authorize('update', $conversation->entity); $this->authorize('edit', $conversationMessage); $conversationMessage->update($request->only('message')); if (request()->ajax()) { return new ConversationMessageResource($conversationMessage); } } /** * @return JsonResponse|RedirectResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Conversation $conversation, ConversationMessage $conversationMessage) { $this->authorize('update', $conversation->entity); $this->authorize('delete', $conversationMessage); if (! $conversationMessage->delete()) { abort(500); } if (request()->ajax()) { return response()->json([ 'success' => true, ]); } return redirect() ->route('entities.show', [$campaign, $conversation->entity]) ->with('success', trans('conversations.messages.destroy.success', [ 'name' => $conversationMessage->author(), 'conversation' => $conversation->name, ])); } } ================================================ FILE: app/Http/Controllers/ConversationParticipantController.php ================================================ $campaign, 'model' => $conversation]); } public function store(StoreConversationParticipant $request, Campaign $campaign, Conversation $conversation) { $this->authorize('update', $conversation->entity); if (request()->ajax()) { return response()->json(['success' => true]); } $participant = new ConversationParticipant; $participant = $participant->create($request->all()); return redirect() ->route('entities.show', [$campaign, $conversation->entity]) ->with('success', __('conversations.participants.create.success', [ 'name' => $conversation->name, 'entity' => $participant->name(), ])); } public function edit(Campaign $campaign, Conversation $conversation, ConversationParticipant $conversationParticipant) { $this->authorize('update', $conversation->entity); dd('CPC 055'); } public function update( StoreConversationParticipant $request, Campaign $campaign, Conversation $conversation, ConversationParticipant $conversationParticipant ) { $this->authorize('update', $conversation->entity); $conversationParticipant->update($request->all()); return redirect() ->to($conversation->getLink()) ->with('success', trans('crud.notes.edit.success', [ 'name' => $conversationParticipant->entity()->name, 'entity' => $conversation->name, ])); } public function destroy(Campaign $campaign, Conversation $conversation, ConversationParticipant $conversationParticipant) { $this->authorize('update', $conversation->entity); if (! $conversationParticipant->delete()) { abort(500); } return redirect() ->to($conversation->getLink()) ->with('success', trans('conversations.participants.destroy.success', [ 'name' => $conversation->name, 'entity' => $conversationParticipant->entity()->name, ])); } } ================================================ FILE: app/Http/Controllers/CookieConsentController.php ================================================ service = $service; } public function index() { $country = $this->service->getCountry(); return response()->json([ 'country' => $country, ]); } } ================================================ FILE: app/Http/Controllers/Creatures/CreatureController.php ================================================ campaign($campaign)->authEntityView($creature->entity); return redirect()->route('entities.children', [$campaign, $creature->entity]); } } ================================================ FILE: app/Http/Controllers/Crud/AbilityController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Ability $ability) { return $this->campaign($campaign)->crudShow($ability); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Ability $ability) { return $this->campaign($campaign)->crudEdit($ability); } public function update(StoreAbility $request, Campaign $campaign, Ability $ability) { return $this->campaign($campaign)->crudUpdate($request, $ability); } public function destroy(Campaign $campaign, Ability $ability) { return $this->campaign($campaign)->crudDestroy($ability); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.ability'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/AttributeTemplateController.php ================================================ campaign($campaign)->crudStore($request, true); } /** * Display the specified resource. */ public function show(Campaign $campaign, AttributeTemplate $attributeTemplate) { return $this->campaign($campaign)->crudShow($attributeTemplate); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, AttributeTemplate $attributeTemplate) { return $this->campaign($campaign)->crudEdit($attributeTemplate); } public function update(StoreAttributeTemplate $request, Campaign $campaign, AttributeTemplate $attributeTemplate) { return $this->campaign($campaign)->crudUpdate($request, $attributeTemplate); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, AttributeTemplate $attributeTemplate) { return $this->campaign($campaign)->crudDestroy($attributeTemplate); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.attribute_template'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/BookmarkController.php ================================================ authorize('browse', [new Bookmark, $campaign])) { return redirect()->route('dashboard', $campaign); } return $this->campaign($campaign)->crudIndex($request); } public function create(Campaign $campaign) { return $this->campaign($campaign)->crudCreate([ 'dashboards' => $this->dashboardOptions(), 'parents' => $this->sidebarParents(), ]); } /** * Store a newly created resource in storage. */ public function store(StoreBookmark $request, Campaign $campaign) { return $this->campaign($campaign)->crudStore($request); } /** * Redirect to the edit screen */ public function show(Campaign $campaign, Bookmark $bookmark) { if (! auth()->check()) { abort(403); } return redirect()->route('bookmarks.edit', [$campaign, $bookmark]); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Bookmark $bookmark) { return $this->campaign($campaign)->crudEdit($bookmark, [ 'dashboards' => $this->dashboardOptions(), 'parents' => $this->sidebarParents(), ]); } /** * Update the specified resource in storage. */ public function update(StoreBookmark $request, Campaign $campaign, Bookmark $bookmark) { $this->authorize('update', $bookmark); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $data = $request->all(); $bookmark->update($data); $link = '' . $bookmark->name . ''; $success = __('general.success.updated', [ 'name' => $link, ]); $options = []; $options = [$campaign, $bookmark] + $options; $route = route($this->view . '.edit', $options + [$bookmark]); if ($request->has('submit-new')) { $route = route($this->route . '.create', $campaign); } elseif ($request->has('submit-update')) { $route = route($this->route . '.edit', [$campaign, $bookmark->id]); } elseif ($request->has('submit-close')) { $route = route($this->route . '.index', [$campaign]); } elseif ($request->has('submit-copy')) { $route = route($this->route . '.create', [$campaign, 'copy' => $bookmark->id]); return response()->redirectTo($route)->with('success_raw', $success); } return response()->redirectTo($route)->with('success_raw', $success); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Bookmark $bookmark) { return $this->campaign($campaign)->crudDestroy($bookmark); } protected function limitCheckReached(): bool { return ! $this->campaign->canHaveMoreBookmarks(); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.bookmark'))->first(); } protected function dashboardOptions(): array { if (auth()->guest()) { return []; } /** @var DashboardService $service */ $service = app()->make(DashboardService::class); $dashboards = $service->campaign($this->campaign)->user(auth()->user())->getDashboards(); $dashboardOptions = ['' => '']; foreach ($dashboards as $dashboard) { $dashboardOptions[$dashboard->id] = $dashboard->name; } return $dashboardOptions; } protected function sidebarParents(): array { /** @var SetupService $service */ $service = app()->make(SetupService::class); return $service->campaign($this->campaign)->availableParents(); } } ================================================ FILE: app/Http/Controllers/Crud/CalendarController.php ================================================ campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Calendar $calendar) { return $this->campaign($campaign)->crudShow($calendar); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Calendar $calendar) { return $this->campaign($campaign)->crudEdit($calendar); } /** * Update the specified resource in storage. */ public function update(StoreCalendar $request, Campaign $campaign, Calendar $calendar) { return $this->campaign($campaign)->crudUpdate($request, $calendar); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Calendar $calendar) { return $this->campaign($campaign)->crudDestroy($calendar); } public function monthList(Campaign $campaign, Calendar $calendar) { return response()->json([ 'months' => $calendar->months(), 'current' => [ 'year' => $calendar->currentDate('year'), 'month' => $calendar->currentDate('month'), 'day' => $calendar->currentDate('date'), ], 'recurring' => $calendar->recurringOptions(true), ]); } /** * Set the day as today */ public function today(Campaign $campaign, Calendar $calendar) { $this->authorize('update', $calendar->entity); $date = request()->get('date', null); if ($date) { $calendar->update([ 'date' => $date, ]); } return redirect()->back() ->with('success', __('calendars.edit.today')); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.calendar'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/CampaignController.php ================================================ middleware('auth', ['except' => ['index', 'show', 'css']]); } public function show(Campaign $campaign) { return view($this->view . '.show', compact('campaign')); } public function edit(Campaign $campaign) { $this->authorize('update', $campaign); $editingUsers = null; if ($campaign->hasEditingWarning()) { $editingUsers = $this->editingService ->model($campaign) ->user(auth()->user()) ->users(); // If no one is editing the model, we are now editing it if (empty($editingUsers)) { $this->editingService->edit(); } } $languages = $this->languageService->getSupportedLanguagesList(true); $timezones = []; for ($i = -12; $i <= 14; $i++) { $prefix = ($i >= 0) ? '+' : '-'; // Formats to "UTC +05:00" or "UTC -11:00" $utcString = 'UTC ' . $prefix . Str::padLeft((string) abs($i), 2, '0') . ':00'; $timezones[$utcString] = $utcString; } return view($this->view . '.forms.edit', [ 'campaign' => $campaign, 'model' => $campaign, 'start' => false, 'editingUsers' => $editingUsers, 'languages' => $languages, 'timezones' => $timezones, ]); } public function update(UpdateCampaign $request, Campaign $campaign) { $this->authorize('update', $campaign); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $data = $request->all(); // Missing sidebar config? Because we shouldn't have used the same array... if (! empty($campaign->ui_settings['sidebar'])) { $data['ui_settings']['sidebar'] = $campaign->ui_settings['sidebar']; } // Also, let's unset ui_settings that are set to true foreach ($data['ui_settings'] as $key => $value) { if ($value === '0') { unset($data['ui_settings'][$key]); } } // Same mumbo jumbo for module settings... if (! empty($campaign->settings['modules'])) { $data['settings']['modules'] = $campaign->settings['modules']; } if ($request->filled('vanity') && $campaign->premium()) { $data['slug'] = $request->post('vanity'); } $campaign->update($data); CampaignDescription::updateOrCreate( ['campaign_id' => $campaign->id], ['description' => $request->post('description'), 'excerpt' => $request->post('excerpt')] ); $this->genreService->campaign($campaign)->save($request->post('genres', [])); $this->systemService->campaign($campaign)->save($request->post('systems', [])); $this->editingService ->model($campaign) ->user($request->user()) ->finish(); if ($request->has('submit-update')) { return redirect() ->route('campaigns.edit', $campaign) ->with('success', __($this->view . '.edit.success', ['name' => $campaign->name])); } if ($request->has('submit-new')) { return redirect() ->route('start') ->with('success', __($this->view . '.edit.success', ['name' => $campaign->name])); } return redirect()->route('overview', $campaign) ->with('success', __($this->view . '.edit.success', ['name' => $campaign->name])); } } ================================================ FILE: app/Http/Controllers/Crud/CharacterController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Character $character) { return $this->campaign($campaign)->crudShow($character); } public function edit(Campaign $campaign, Character $character) { return $this->campaign($campaign)->crudEdit($character); } public function update(StoreCharacter $request, Campaign $campaign, Character $character) { return $this->campaign($campaign)->crudUpdate($request, $character); } public function destroy(Campaign $campaign, Character $character) { return $this->campaign($campaign)->crudDestroy($character); } protected function afterModelSave(MiscModel $model, array $data): void { $this->characterRelationsService->save($model, $data); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.character'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/ConversationController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Conversation $conversation) { return $this->campaign($campaign)->crudShow($conversation); } public function edit(Campaign $campaign, Conversation $conversation) { return $this->campaign($campaign)->crudEdit($conversation); } public function update(StoreConversation $request, Campaign $campaign, Conversation $conversation) { return $this->campaign($campaign)->crudUpdate($request, $conversation); } public function destroy(Campaign $campaign, Conversation $conversation) { return $this->campaign($campaign)->crudDestroy($conversation); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.conversation'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/CreatureController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Creature $creature) { return $this->campaign($campaign)->crudShow($creature); } public function edit(Campaign $campaign, Creature $creature) { return $this->campaign($campaign)->crudEdit($creature); } public function update(Campaign $campaign, StoreCreature $request, Creature $creature) { return $this->campaign($campaign)->crudUpdate($request, $creature); } public function destroy(Campaign $campaign, Creature $creature) { return $this->campaign($campaign)->crudDestroy($creature); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.creature'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/DiceRollController.php ================================================ campaign($campaign)->crudStore($request, true); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function show(Campaign $campaign, DiceRoll $diceRoll) { return $this->campaign($campaign)->crudShow($diceRoll); } /** * @return Application|Factory|View * * @throws AuthorizationException * @throws BindingResolutionException */ public function edit(Campaign $campaign, DiceRoll $diceRoll) { return $this->campaign($campaign)->crudEdit($diceRoll); } /** * @return JsonResponse|RedirectResponse * * @throws AuthorizationException * @throws BindingResolutionException * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function update(StoreDiceRoll $request, Campaign $campaign, DiceRoll $diceRoll) { return $this->campaign($campaign)->crudUpdate($request, $diceRoll); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, DiceRoll $diceRoll) { return $this->campaign($campaign)->crudDestroy($diceRoll); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function roll(Campaign $campaign, DiceRoll $diceRoll) { $this->authorize('view', $diceRoll->entity); try { $result = DiceRollResult::create([ 'dice_roll_id' => $diceRoll->id, 'created_by' => Auth::user()->id, ]); return redirect()->to($diceRoll->getLink()) ->with('success', trans('dice_rolls.results.success')); } catch (Exception $e) { return redirect()->to($diceRoll->getLink()) ->with('error', trans('dice_rolls.results.error')); } } /** * @return RedirectResponse * * @throws AuthorizationException */ public function destroyRoll(Campaign $campaign, DiceRoll $diceRoll, DiceRollResult $diceRollResult) { $this->authorize('delete', $diceRoll->entity); $diceRollResult->delete(); return redirect()->to($diceRoll->getLink()) ->with('success', trans('dice_rolls.destroy.dice_roll')); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.dice_roll'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/EventController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Event $event) { return $this->campaign($campaign)->crudShow($event); } public function edit(Campaign $campaign, Event $event) { return $this->campaign($campaign)->crudEdit($event); } public function update(StoreEvent $request, Campaign $campaign, Event $event) { return $this->campaign($campaign)->crudUpdate($request, $event); } public function destroy(Campaign $campaign, Event $event) { return $this->campaign($campaign)->crudDestroy($event); } protected function afterModelSave(MiscModel $model, array $data): void { app(EntityRelationsServiceFactory::class)->for($model->entity)?->save($model, $data); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.event'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/FamilyController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Family $family) { return $this->campaign($campaign)->crudShow($family); } public function edit(Campaign $campaign, Family $family) { return $this->campaign($campaign)->crudEdit($family); } public function update(StoreFamily $request, Campaign $campaign, Family $family) { return $this->campaign($campaign)->crudUpdate($request, $family); } public function destroy(Campaign $campaign, Family $family) { return $this->campaign($campaign)->crudDestroy($family); } protected function afterModelSave(MiscModel $model, array $data): void { $this->familyRelationsService->save($model, $data); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.family'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/ItemController.php ================================================ campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Item $item) { return $this->campaign($campaign)->crudShow($item); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Item $item) { return $this->campaign($campaign)->crudEdit($item); } /** * Update the specified resource in storage. */ public function update(StoreItem $request, Campaign $campaign, Item $item) { return $this->campaign($campaign)->crudUpdate($request, $item); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Item $item) { return $this->campaign($campaign)->crudDestroy($item); } protected function afterModelSave(MiscModel $model, array $data): void { $this->itemRelationsService->save($model, $data); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.item'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/JournalController.php ================================================ campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Journal $journal) { return $this->campaign($campaign)->crudShow($journal); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Journal $journal) { return $this->campaign($campaign)->crudEdit($journal); } /** * Update the specified resource in storage. */ public function update(StoreJournal $request, Campaign $campaign, Journal $journal) { return $this->campaign($campaign)->crudUpdate($request, $journal); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Journal $journal) { return $this->campaign($campaign)->crudDestroy($journal); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.journal'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/LocationController.php ================================================ campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Location $location) { return $this->campaign($campaign)->crudShow($location); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Location $location) { return $this->campaign($campaign)->crudEdit($location); } /** * Update the specified resource in storage. */ public function update(StoreLocation $request, Campaign $campaign, Location $location) { return $this->campaign($campaign)->crudUpdate($request, $location); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Location $location) { return $this->campaign($campaign)->crudDestroy($location); } protected function afterModelSave(MiscModel $model, array $data): void { $this->locationRelationsService->save($model, $data); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.location'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/MapController.php ================================================ campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Map $map) { return $this->campaign($campaign)->crudShow($map); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Map $map) { // Can't edit a map being chunked if ($map->isChunked() && $map->chunkingRunning()) { return redirect() ->to($map->getLink()) ->with('error', __('maps.errors.chunking.running.edit') . ' ' . __('maps.errors.chunking.running.time')); } return $this->campaign($campaign)->crudEdit($map); } /** * Update the specified resource in storage. */ public function update(StoreMap $request, Campaign $campaign, Map $map) { return $this->campaign($campaign)->crudUpdate($request, $map); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Map $map) { return $this->campaign($campaign)->crudDestroy($map); } protected function afterModelSave(MiscModel $model, array $data): void { /** @var Map $model */ $model->height = null; $model->width = null; $model->save(); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.map'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/NoteController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Note $note) { return $this->campaign($campaign)->crudShow($note); } public function edit(Campaign $campaign, Note $note) { return $this->campaign($campaign)->crudEdit($note); } public function update(StoreNote $request, Campaign $campaign, Note $note) { return $this->campaign($campaign)->crudUpdate($request, $note); } public function destroy(Campaign $campaign, Note $note) { return $this->campaign($campaign)->crudDestroy($note); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.note'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/OrganisationController.php ================================================ campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Organisation $organisation) { return $this->campaign($campaign)->crudShow($organisation); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Organisation $organisation) { return $this->campaign($campaign)->crudEdit($organisation); } /** * Update the specified resource in storage. */ public function update(StoreOrganisation $request, Campaign $campaign, Organisation $organisation) { return $this->campaign($campaign)->crudUpdate($request, $organisation); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Organisation $organisation) { return $this->campaign($campaign)->crudDestroy($organisation); } protected function afterModelSave(MiscModel $model, array $data): void { $this->organisationRelationsService->save($model, $data); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.organisation'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/QuestController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Quest $quest) { return $this->campaign($campaign)->crudShow($quest); } public function edit(Campaign $campaign, Quest $quest) { return $this->campaign($campaign)->crudEdit($quest); } public function update(StoreQuest $request, Campaign $campaign, Quest $quest) { return $this->campaign($campaign)->crudUpdate($request, $quest); } public function destroy(Campaign $campaign, Quest $quest) { return $this->campaign($campaign)->crudDestroy($quest); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.quest'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/RaceController.php ================================================ campaign($campaign)->crudStore($request); } public function show(Campaign $campaign, Race $race) { return $this->campaign($campaign)->crudShow($race); } public function edit(Campaign $campaign, Race $race) { return $this->campaign($campaign)->crudEdit($race); } public function update(StoreRace $request, Campaign $campaign, Race $race) { return $this->campaign($campaign)->crudUpdate($request, $race); } public function destroy(Campaign $campaign, Race $race) { return $this->campaign($campaign)->crudDestroy($race); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.race'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/TagController.php ================================================ campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Tag $tag) { return $this->campaign($campaign)->crudShow($tag); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Tag $tag) { return $this->campaign($campaign)->crudEdit($tag); } /** * Update the specified resource in storage. */ public function update(StoreTag $request, Campaign $campaign, Tag $tag) { return $this->campaign($campaign)->crudUpdate($request, $tag); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Tag $tag) { return $this->campaign($campaign)->crudDestroy($tag); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.tag'))->first(); } } ================================================ FILE: app/Http/Controllers/Crud/TimelineController.php ================================================ campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Timeline $timeline) { return $this->campaign($campaign)->crudShow($timeline); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Timeline $timeline) { return $this->campaign($campaign)->crudEdit($timeline); } /** * Update the specified resource in storage. */ public function update(StoreTimeline $request, Campaign $campaign, Timeline $timeline) { return $this->campaign($campaign)->crudUpdate($request, $timeline); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Timeline $timeline) { return $this->campaign($campaign)->crudDestroy($timeline); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.timeline'))->first(); } } ================================================ FILE: app/Http/Controllers/CrudController.php ================================================ filterService = $filterService; $this->datagrid = $datagridRenderer; $this->attributeService = $attributeService; $this->entitySaveService = $entitySaveService; $this->middleware([CachedResponse::class]); } protected function afterModelSave(MiscModel $model, array $data): void {} public function index(Request $request, Campaign $campaign) { return $this->campaign($campaign)->crudIndex($request); } public function crudIndex(Request $request) { if (! $this->moduleEnabled()) { return redirect() ->route('dashboard', $this->campaign) ->with( 'error_raw', __('campaigns/modules.errors.disabled', [ 'name' => $this->module, 'fix' => '' . __('crud.fix-this-issue') . '', ]), ); } if (method_exists($this, 'getEntityType')) { /** @var EntityType $entityType */ $entityType = $this->getEntityType(); if ($entityType->hasEntity()) { return redirect()->route('entities.index', [ $this->campaign, $entityType, ]); } } /** * Prepare a lot of variables that will be shared over to the view * * @var Bookmark|Relation $model */ $model = new $this->model; $campaign = $this->campaign; $this->request = $request; $this->filterService->request($request); if (method_exists($model, 'explicitFilters')) { $this->filterService->model($model)->make($this->view); } $name = $this->view; $langKey = $this->langKey ?? $name; /** @var ?DatagridFilter $filter */ $filter = ! empty($this->filter) ? new $this->filter : null; if (! empty($filter)) { $filter->campaign($this->campaign)->build(); } $route = $this->route; $bulk = $this->bulkModel(); $datagridActions = new $this->datagridActions; // Switch between the new explore/grid mode and the old table $mode = $this->mode(); $forceMode = null; $nested = $this->isNested(); $base = $model->preparedSelect()->preparedWith(); // dd(get_class($model), get_class($base)); if ($nested) { $this->datagrid->nested(); } $base ->search($this->filterService->search()) ->order($this->filterService->order()) ->distinct(); $parent = null; // Do this to avoid an extra sql query when no filters are selected if ($this->filterService->hasFilters()) { $unfilteredCount = $base->count(); $base = $base->filter($this->filterService->filters()); $models = $base->paginate(); // Don't use total as it won't use the distinct() filters (typically when doing // left join on the entities table) $filteredCount = $models->total(); } else { if (! $model instanceof Bookmark) { $relation = 'entity'; // If $model is a Relation, theres no entity, we have to handle it differently if ($model instanceof Relation) { $relation = 'owner'; $base = $base->whereHas('target', function ($query) { $query->whereNull('entities.archived_at'); }); } // Filter out archived entities $base = $base->whereHas($relation, function ($query) { $query->whereNull('entities.archived_at'); }); } /** @var Paginator $models */ $models = $base->paginate(); $unfilteredCount = $filteredCount = $models->count(); } // If the current page is higher than the max amount of pages, redirect the user if ((int) request()->get('page', 1) > $models->lastPage()) { return redirect()->route($this->route . '.index', [ $this->campaign, 'page' => $models->lastPage(), 'order' => request()->get('order'), ]); } $data = compact( 'campaign', 'models', 'name', 'langKey', 'model', 'filter', 'filteredCount', 'unfilteredCount', 'route', 'bulk', 'datagridActions', 'mode', 'parent', 'forceMode', ); if (method_exists($this, 'getEntityType')) { $data['entityType'] = $this->getEntityType(); $data['templates'] = $this->loadTemplates($data['entityType']); $this->datagrid->entityType($data['entityType']); } else { $data['singular'] = __('entities.' . Str::singular($route)); } if (method_exists($this, 'titleKey')) { $data['titleKey'] = $this->titleKey(); } elseif ($this->campaign->boosted()) { // Custom sidebar link, with fallback on custom module plural name $data['titleKey'] = Arr::get( $campaign->ui_settings, 'sidebar.labels.' . $langKey, ); if (empty($data['titleKey']) && isset($data['entityType'])) { $data['titleKey'] = $data['entityType']->plural(); } // If its a bookmark, override everything else if ($request->has('bookmark')) { $bookmark = Bookmark::where( 'id', $request->get('bookmark'), )->first(); if ($bookmark) { $this->datagrid->bookmark($bookmark); $data['bookmark'] = $bookmark; $data['titleKey'] = $bookmark->name; } } if ($request->has('order')) { $data['order'] = $request->get('order'); $data['desc'] = $request->get('desc'); } } if (method_exists($model, 'getParentKeyName')) { $data['nestable'] = $nested; } $this->datagrid ->models($models) ->campaign($campaign) ->request($request) ->service($this->filterService); if (auth()->check()) { $this->datagrid->user(auth()->user()); } $data['datagrid'] = $this->datagrid; $data['filterService'] = $this->filterService; return view('cruds.index', $data); } /** * Show the form for creating a new resource. * * @return Response */ public function create(Campaign $campaign) { return $this->campaign($campaign)->crudCreate(); } public function crudCreate($params = []) { // @phpstan-ignore-next-line $this->authorize('create', [$this->getEntityType(), $this->campaign]); if ($this->hasLimitCheck) { // @phpstan-ignore-next-line if ($this->limitCheckReached()) { $key = $this->view == 'bookmarks' ? 'bookmarks' : 'entities'; return view('cruds.forms.limit') ->with('campaign', $this->campaign) ->with('limit', config('limits.campaigns.' . $key)) ->with('thing', __('entities.bookmarks')) ->with('doc', '/advanced/bookmarks.html') ->with('name', $this->view); } } FormCopy::request(request()); if (! isset($params['source'])) { $copyId = request()->input('copy'); if (! empty($copyId)) { /** @var ?Entity $model */ $model = Entity::find(request()->get('copy')); if (! $model || $model->isMissingChild()) { abort(404); } $params['source'] = $model; FormCopy::source($params['source']); } else { $params['source'] = null; } } $model = new $this->model; $params['campaign'] = $this->campaign; $params['tabAttributes'] = $this->tabAttributes && $this->campaign->enabled('entity_attributes'); $params['tabPermissions'] = $this->tabPermissions; $params['tabCopy'] = $this->tabCopy; $params['tabBoosted'] = $this->tabBoosted; if (method_exists($this, 'getEntityType')) { $params['entityType'] = $this->getEntityType(); $params['tabPermissions'] = $this->tabPermissions && auth()->user()->can('permissions', $params['entityType']); } $params['title'] = __($this->view . '.create.title'); // Custom module names shenanigans $entityTypeID = $model->entityTypeId(); $plural = Module::plural($entityTypeID, __('entities.' . $this->view)); $singular = Module::singular($entityTypeID); $params['entityTypeId'] = $entityTypeID; $params['plural'] = $plural; if (! empty($singular)) { $params['title'] = __('crud.titles.new', ['module' => $singular]); } $view = 'cruds.forms.create'; $override = $this->view . '.forms.create'; if (view()->exists($override)) { $view = $override; } return view($view, array_merge(['name' => $this->view], $params)); } public function crudStore(Request $request, bool $redirectToCreated = false) { // @phpstan-ignore-next-line $this->authorize('create', [$this->getEntityType(), $this->campaign]); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } if ($this->hasLimitCheck) { // @phpstan-ignore-next-line if ($this->limitCheckReached()) { return redirect()->back(); } } try { // Sanitize the data if (isset($this->sanitizer)) { /** @var MiscSanitizer $sanitizer */ $sanitizer = app()->make($this->sanitizer); $request->merge($sanitizer->request($request)->sanitize()); } $data = $request->all(); $data['campaign_id'] = $this->campaign->id; /** @var MiscModel $model */ $model = new $this->model; /** @var MiscModel $new */ $new = $model->create($data); // Bookmarks have no entity attached to them. if (! ($new instanceof Bookmark) && $new->entity) { // Fire an event for the Entity Observer. $this->afterModelSave($new, $data); $this->entitySaveService->save($new->entity, $data); // Weird hack for prod issues if (! $new->entity->child) { $new->entity->child = $new; } /** @var CopyService $copyService */ $copyService = app()->make(CopyService::class); // First copy stuff like posts, since we might replace attribute mentions next $copyService ->entity($new->entity) ->request($request) ->fromId() ->copy(); if (auth()->user()->can('attributes', $new->entity)) { $this->attributeService ->campaign($this->campaign) ->entity($new->entity) ->save($request->get('attribute', [])); // When copying an entity, the user probably wants to update all mentions of attributes to ones on the new entity. if ( $request->has('replace_mentions') && $request->filled('replace_mentions') && $new->entity->isFillable('entry') ) { $this->attributeService->replaceMentions( (int) $request->post('copy_source_id'), ); } } /** @var AliasService $aliasService */ $aliasService = app()->make(AliasService::class); $aliasService->entity($new->entity)->request($request)->save(); } $link = '' . $new->name . ''; $success = __('general.success.created', [ 'name' => $link, ]); session()->flash('success_raw', $success); if (auth()->user()->editor === 'tiptap') { $count = session()->get('tiptap_survey_count', 0); $count++; session()->put('tiptap_survey_count', $count); if ($count % 5 === 0) { session()->flash('tiptap_survey', true); } } if ($request->has('submit-new')) { $route = route($this->route . '.create', $this->campaign); return response()->redirectTo($route); } elseif ($request->has('submit-update')) { $route = route($this->route . '.edit', [$this->campaign, $new]); if (! $new instanceof Bookmark) { $route = route('entities.edit', [ $this->campaign, $new->entity, ]); } return response()->redirectTo($route); } elseif ($request->has('submit-view') && $new->entity) { $route = route('entities.show', [ $this->campaign, $new->entity, ]); return response()->redirectTo($route); } elseif ($request->has('submit-copy')) { $route = route($this->route . '.create', [ $this->campaign, 'copy' => $new->id, ]); return response()->redirectTo($route); } if ($new->entity) { $route = route('entities.show', [ $this->campaign, $new->entity, ]); return response()->redirectTo($route); } $route = Breadcrumb::campaign($this->campaign)->index($this->route); return response()->redirectTo($route); } catch (LogicException $exception) { if (config('app.debug')) { throw $exception; } $error = str_replace( ' ', '_', mb_strtolower($exception->getMessage()), ); return redirect() ->back() ->withInput() ->with('error', __('crud.errors.' . $error)); } } public function crudShow(Model|MiscModel $model) { // @phpstan-ignore-next-line if (! $model->entity) { abort(404); } return redirect()->route('entities.show', [ $this->campaign, $model->entity, ]); } public function crudEdit(Model|MiscModel $model, array $params = []) { $this->authorize( 'update', $model instanceof MiscModel ? $model->entity : $model, ); if ($model instanceof MiscModel) { return redirect()->route('entities.edit', [ $this->campaign, $model->entity, ]); } /** @var MiscModel $model */ $editingUsers = null; if ($this->campaign->hasEditingWarning() && $model->entity) { /** @var MultiEditingService $editingService */ $editingService = app()->make(MultiEditingService::class); $editingUsers = $editingService ->model($model->entity) ->user(auth()->user()) ->users(); // If no one is editing the entity, we are now editing it if (empty($editingUsers)) { $editingService->edit(); } } $params = array_merge($params, [ 'campaign' => $this->campaign, 'model' => $model, 'name' => $this->view, 'tabPermissions' => $this->tabPermissions && auth()->user()->can('permissions', $model), 'tabAttributes' => $this->tabAttributes && auth()->user()->can('attributes', $model->entity) && $this->campaign->enabled('entity_attributes'), 'tabBoosted' => $this->tabBoosted, 'tabCopy' => $this->tabCopy, 'editingUsers' => $editingUsers, ]); if (! $model instanceof Bookmark && $model->entity) { $params['entity'] = $model->entity; } $view = 'cruds.forms.edit'; $override = $this->view . '.forms.edit'; if (view()->exists($override)) { $view = $override; } else { dd('Missing form override for ' . $this->view); } return view($view, $params); } /** * @return JsonResponse|RedirectResponse * * @throws AuthorizationException * @throws BindingResolutionException * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function crudUpdate(Request $request, Model|MiscModel $model) { $this->authorize( 'update', $model instanceof MiscModel ? $model->entity : $model, ); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } try { // Sanitize the data if (isset($this->sanitizer)) { /** @var MiscSanitizer $sanitizer */ $sanitizer = app()->make($this->sanitizer); $request->merge($sanitizer->request($request)->sanitize()); } /** @var MiscModel $model */ $data = $this->prepareData($request, $model); $model->update($data); // Bookmarks have no entity attached to them. if (! ($model instanceof Bookmark) && $model->entity) { // Fire an event for the Entity Observer $this->afterModelSave($model, $data); $this->entitySaveService->save($model->entity, $data); if (auth()->user()->can('attributes', $model->entity)) { $this->attributeService ->campaign($this->campaign) ->entity($model->entity) ->save($request->get('attribute', [])); } /** @var AliasService $aliasService */ $aliasService = app()->make(AliasService::class); $aliasService ->entity($model->entity) ->request($request) ->save(); } $link = '' . $model->name . ''; $success = __('general.success.updated', [ 'name' => $link, ]); if ($model->entity) { /** @var MultiEditingService $editingService */ $editingService = app()->make(MultiEditingService::class); $editingService ->model($model->entity) ->user($request->user()) ->finish(); } session()->flash('success_raw', $success); $options = []; if (request()->has('redirect')) { $redirect = explode('&', request()->get('redirect')); foreach ($redirect as $option) { $vals = explode('=', $option); $options[$vals[0]] = $vals[1]; } } if ($model->entity) { $route = route( 'entities.show', $options + [$this->campaign, $model->entity], ); } else { $options = [$this->campaign, $model] + $options; $route = route($this->view . '.show', $options + [$model]); } if ($request->has('submit-new')) { $route = route($this->route . '.create', $this->campaign); } elseif ($request->has('submit-update')) { $route = route($this->route . '.edit', [ $this->campaign, $model->id, ]); } elseif ($request->has('submit-close')) { $route = route($this->route . '.index', [$this->campaign]); } elseif ($request->has('submit-copy')) { $route = route($this->route . '.create', [ $this->campaign, 'copy' => $model->id, ]); return response()->redirectTo($route); } return response()->redirectTo($route); } catch (LogicException $exception) { $error = str_replace( ' ', '_', mb_strtolower(mb_rtrim($exception->getMessage(), '.')), ); return redirect() ->back() ->withInput() ->with('error', __('crud.errors.' . $error)); } } /** * @throws AuthorizationException */ public function crudDestroy(Model|MiscModel $model) { return abort(404); } /** * @return array */ protected function prepareData(Request $request, MiscModel $model) { $data = $request->all(); foreach ($model->nullableForeignKeys as $field) { if (! request()->has($field) && ! isset($data[$field])) { $data[$field] = null; } } return $data; } /** * Set the datagrid sorter for sub views */ protected function datagridSorter(string $datagridSorter): self { $this->datagridSorter = new $datagridSorter; $this->datagridSorter->request(request()->all()); return $this; } /** * Detect if a module is enabled */ protected function moduleEnabled(): bool { return ! isset($this->module) || $this->campaign->enabled($this->module); } /** * Set the controller as having a limit check */ protected function hasLimitCheck(bool $value = true): self { $this->hasLimitCheck = $value; return $this; } /** * Load a list of templates the user can create new entities from */ protected function loadTemplates(EntityType $entityType): Collection { // No valid user, or invalid entity type (ie relations) if (auth()->guest()) { return new Collection; } elseif ( ! auth() ->user() ->can('create', [$entityType, $this->campaign]) ) { return new Collection; } return Entity::select('id', 'name', 'entity_id') ->templates($entityType->id) ->orderBy('name') ->get(); } /** * Determine if the layout is in the nice grid mode, or the old table mode */ protected function mode(): string { if (! isset($this->module)) { return 'table'; } $key = $this->module . '_mode'; if ($this->request->has('m')) { $mode = $this->request->get('m'); if (! in_array($mode, ['grid', 'table'])) { return 'grid'; } $newMode = $mode; } if (isset($newMode)) { if (auth()->guest()) { Session::put($key, $newMode); } else { $settings = auth()->user()->settings; if (auth()->check() && Arr::get($settings, $key) !== $newMode) { if ($newMode === 'grid') { unset($settings[$key]); } else { $settings[$key] = $newMode; } auth()->user()->settings = $settings; auth()->user()->updateQuietly(); } } return $newMode; } if (auth()->guest()) { return Session::get($key, 'grid'); } // Else use the user's preferred stacking for this entity type return Arr::get(auth()->user()->settings, $key, 'grid'); } /** * Determine if the current layout should be nested or not */ protected function isNested(): bool { // Model isn't nested, not an option to start with if ( ! isset($this->module) || ! method_exists($this->model, 'getParentKeyName') ) { return false; } $key = $this->module . '_nested'; if ($this->request->has('n')) { return $this->saveNested($key); } if (auth()->guest()) { return (bool) Session::get($key, true); } // Else use the user's preferred stacking for this entity type return Arr::get(auth()->user()->settings, $key, true); } } ================================================ FILE: app/Http/Controllers/DashboardController.php ================================================ get('dashboard'); if ($requestedDashboard == 'default') { $requestedDashboard = -1; } if (auth()->check()) { $this->dashboardService->user(auth()->user()); } $dashboard = $this ->dashboardService ->campaign($campaign) ->getDashboard((int) $requestedDashboard); $dashboards = $this->dashboardService->getDashboards(); $widgets = CampaignDashboardWidget::onDashboard($dashboard) ->positioned() ->get(); // A user with campaigns doesn't need this process. $gaTrackingEvent = null; $welcome = $onboarding = false; if (session()->has('user_registered')) { session()->remove('user_registered'); $gaTrackingEvent = 'pa10CJTvrssBEOaOq7oC'; DataLayer::newAccount(); $welcome = true; } // Onboarding if (session()->has('onboarding') || request()->filled('onboarding')) { $onboarding = true; session()->remove('onboarding'); CampaignEvent::create([ 'campaign_id' => $campaign->id, 'created_by' => auth()->user()->id, 'event' => 'onboarding_shown', ]); } $hasMap = $hasCampaignHeader = false; foreach ($widgets as $w) { if ($w->widget === Widget::Preview && $w->entity && $w->visible() && $w->entity->isMap()) { $hasMap = true; } elseif ($w->widget === Widget::Campaign) { $hasCampaignHeader = true; } } if (empty($requestedDashboard)) { $hasCampaignHeader = true; } return view('home', compact( 'campaign', 'widgets', 'dashboard', 'dashboards', 'welcome', 'onboarding', 'gaTrackingEvent', )) ->with('hasMap', $hasMap) ->with('hasCampaignHeader', $hasCampaignHeader); } /** * @param int $id */ public function recent(Campaign $campaign, $id) { /** @var CampaignDashboardWidget $widget */ $widget = CampaignDashboardWidget::findOrFail($id); if ($widget->widget != Widget::Recent) { return response()->json([ 'success' => true, ]); } $entities = $widget->entities(); return view('dashboard.widgets._recent_list') ->with('campaign', $campaign) ->with('entities', $entities) ->with('widget', $widget) ->with('campaign', $campaign); } /** * @param int $id */ public function unmentioned(Campaign $campaign, $id) { /** @var CampaignDashboardWidget $widget */ $widget = CampaignDashboardWidget::findOrFail($id); if ($widget->widget != Widget::Unmentioned) { return response()->json([ 'success' => true, ]); } $entities = Entity::unmentioned() ->inTags($widget->tags->pluck('id')->toArray()) ->inTypes($widget->conf('entity')) ->with(['updater']) ->paginate(10); return view('dashboard.widgets._recent_list') ->with('campaign', $campaign) ->with('entities', $entities) ->with('widget', $widget) ->with('campaign', $campaign); } } ================================================ FILE: app/Http/Controllers/Dashboards/GettingStartedController.php ================================================ authorize('access', $campaign); return response()->json($this->gettingStartedService->campaign($campaign)->tasks()); } } ================================================ FILE: app/Http/Controllers/Dashboards/SetupController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('dashboard', $campaign); // Validate dashboard $dashboard = request()->has('dashboard') ? CampaignDashboard::where('id', request()->get('dashboard'))->first() : null; $dashboards = CampaignDashboard::exclude($dashboard)->orderBy('name')->get(); $widgets = CampaignDashboardWidget::onDashboard($dashboard)->with('tags')->positioned()->get(); return view('dashboard.setup') ->with('campaign', $campaign) ->with('dashboards', $dashboards) ->with('dashboard', $dashboard) ->with('widgets', $widgets); } /** * Save the new dashboard widgets order */ public function save(Campaign $campaign) { $this->authorize('dashboard', $campaign); $position = 0; $widgets = (array) request()->post('widgets', []); foreach ($widgets as $i => $widget) { $wi = CampaignDashboardWidget::findOrFail($widget); $wi->update([ 'position' => $position, ]); $position++; } return response()->json([ 'success' => true, 'message' => __('dashboard.setup.reorder.success'), ]); } } ================================================ FILE: app/Http/Controllers/Datagrid2/BulkControllerTrait.php ================================================ get('action'); $models = $request->get('model'); $count = 0; $patch = $request->except('models', 'action'); if ($action === 'patch') { // Clean up the request. Skip nulls foreach ($patch as $field => $value) { if ($value !== null) { continue; } unset($patch[$field]); } } foreach ($models as $id) { /** @var MapGroup|Model $modelClass */ $modelClass = new $className; $model = $modelClass->find($id); if (empty($model)) { continue; } if ($action === 'delete') { $model->delete(); $count++; } elseif ($action === 'patch') { /** @var MapGroup $model */ $model->patch($patch); $count++; } } return $count; } public function bulkBatch(string $route, string $view, array $models, mixed $model = null) { return view('layouts.datagrid.bulks.update') ->with('campaign', $this->campaign) ->with('route', $route) ->with('view', $view) ->with('models', $models) ->with('model', $model); } } ================================================ FILE: app/Http/Controllers/Datagrids/SubscriptionController.php ================================================ middleware('auth'); } public function index() { return view('datagrids.subscription'); } } ================================================ FILE: app/Http/Controllers/DiceRolls/ResultsController.php ================================================ orderByDesc('updated_at') ->has('diceRoll.entity') ->has('diceRoll.character.entity') ->paginate(); return view('dice_rolls.results') ->with('campaign', $campaign) ->with('models', $models); } } ================================================ FILE: app/Http/Controllers/EditingController.php ================================================ service = $service; } public function index(Campaign $campaign) { $model = request()->get('model'); $id = request()->get('id'); if (empty($model)) { $model = $campaign; } else { $modelName = app()->make('App\Models\\' . $model); $model = new $modelName; $model = $model->findOrFail($id); } $editingUsers = $this->service ->model($model) ->user(auth()->user()) ->users(); if ($model instanceof Post) { $url = route('posts.confirm-editing', [$campaign, 'post' => $model, 'entity' => $model->entity]); $show = $model->entity->url(); } elseif ($model instanceof Campaign) { $url = route('campaigns.confirm-editing', $model); $show = route('overview', $campaign); } elseif ($model instanceof TimelineElement) { $url = route('timeline-elements.confirm-editing', [$campaign, $model]); $show = $model->timeline->getLink(); } elseif ($model instanceof QuestElement) { $url = route('quest-elements.confirm-editing', [$campaign, $model]); $show = $model->quest->getLink(); } else { $url = route('entities.confirm-editing', [$campaign, $model]); $show = $model->url(); } return view('confirms.editing') ->with('url', $url) ->with('show', $show) ->with('editingUsers', $editingUsers); } private function confirmHandle(Model $model) { $this->service ->user(auth()->user()) ->model($model) ->confirm(); return response()->json(['success' => true]); } private function keepAliveHandle(Model $model) { $this->service->model($model) ->user(auth()->user()) ->keepAlive(); return response() ->json([ 'success' => true, ]); } public function confirm(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); return $this->confirmHandle($entity); } public function confirmCampaign(Campaign $campaign) { $this->authorize('update', $campaign); return $this->confirmHandle($campaign); } public function confirmPost(Campaign $campaign, Entity $entity, Post $post) { $this->authorize('post', [$entity, 'edit', $post]); return $this->confirmHandle($post); } public function confirmQuestElement(Campaign $campaign, QuestElement $questElement) { $this->authorize('update', $questElement->quest()->first()->entity); return $this->confirmHandle($questElement); } public function confirmTimelineElement(Campaign $campaign, TimelineElement $timelineElement) { $this->authorize('update', $timelineElement->timeline()->first()->entity); return $this->confirmHandle($timelineElement); } public function keepAlive(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); return $this->keepAliveHandle($entity); } public function keepAliveCampaign(Campaign $campaign) { $this->authorize('update', $campaign); return $this->keepAliveHandle($campaign); } public function keepAlivePost(Campaign $campaign, Entity $entity, Post $post) { $this->authorize('post', [$entity, 'edit', $post]); return $this->keepAliveHandle($post); } public function keepAliveTimelineElement(Campaign $campaign, TimelineElement $timelineElement) { $this->authorize('update', $timelineElement->timeline()->first()->entity); return $this->keepAliveHandle($timelineElement); } public function keepAliveQuestElement(Campaign $campaign, QuestElement $questElement) { $this->authorize('update', $questElement->quest()->first()->entity); return $this->keepAliveHandle($questElement); } } ================================================ FILE: app/Http/Controllers/Entities/Apis/DocumentController.php ================================================ authorize('view', $entity); return response()->json([ 'document' => $entity->entry, 'mentions' => $this->mentions($entity), ]); } protected function mentions(Entity $entity): array { // @phpstan-ignore-next-line return $entity->mentions()->with(['entity', 'entity.entityType', 'entity.aliases'])->get()->map(function ($mention) { if ($mention->isEntity()) { return [ 'id' => $mention->target_id, 'name' => $mention->target->name, 'type' => $mention->target->entityType->code, 'image' => Avatar::entity($mention->target)->fallback()->size(32)->thumbnail(), 'url' => $mention->target->url(), 'aliases' => $mention->target->aliases->map(fn ($alias) => [ 'id' => $alias->id, 'name' => $alias->name, ])->toArray(), ]; } return [ 'id' => $mention->id, // @phpstan-ignore-next-line 'label' => $mention->label, // @phpstan-ignore-next-line 'mention' => $mention->mention, ]; })->toArray(); } } ================================================ FILE: app/Http/Controllers/Entities/ChildrenController.php ================================================ campaign($campaign)->authEntityView($entity); $options = ['campaign' => $campaign, 'entity' => $entity, 'm' => $this->descendantsMode()]; /** @var Children $layout */ $layout = app()->make(Children::class); $layout->entityType($entity->entityType); Datagrid::layout($layout) ->route('entities.children', $options); $query = $entity ->descendants() ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->with([ 'image', 'entityType', 'tags', 'children', 'parent', ]); if ($this->filterToDirect()) { $query->where('parent_id', $entity->id); } $this->rows = $query->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return view('entities.pages.children.index') ->with([ 'entity' => $entity, 'campaign' => $this->campaign, 'rows' => $this->rows, 'mode' => $this->descendantsMode(), ]); } } ================================================ FILE: app/Http/Controllers/Entities/CreateController.php ================================================ authorize('create', [$entityType, $campaign]); $tabCopy = false; $source = null; FormCopy::request($request); if ($request->filled('copy')) { $source = Entity::inTypes([$entityType->id])->find($request->get('copy')); if ($source) { FormCopy::request($request)->source($source); $tabCopy = true; } } if ($entityType->isStandard()) { $options = [$campaign]; if ($tabCopy) { $options['copy'] = $source->id; $options['template'] = true; } return redirect()->route($entityType->pluralCode() . '.create', $options); } return view('entities.forms.create') ->with('campaign', $campaign) ->with('entityType', $entityType) ->with('tabCopy', $tabCopy) ->with('source', $source); } public function store(StoreCustomEntity $request, Campaign $campaign, EntityType $entityType) { $this->authorize('create', [$entityType, $campaign]); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $data = $request->all(); $data['campaign_id'] = $campaign->id; try { /** @var Entity $entity */ $entity = new Entity($data); $entity->type_id = $entityType->id; $entity->save(); $this->entitySaveService->save($entity, $data); $this->aliasService->entity($entity)->request($request)->save(); // First copy stuff like posts, since we might replace attribute mentions next $this->copyService->entity($entity)->request($request)->fromId()->copy(); if (auth()->user()->can('attributes', $entity)) { $this->attributeService ->campaign($campaign) ->entity($entity) ->save($request->get('attribute', [])); // When copying an entity, the user probably wants to update all mentions of attributes to ones on the new entity. if ($request->has('replace_mentions') && $request->filled('replace_mentions') && $entity->isFillable('entry')) { $this->attributeService ->replaceMentions((int) $request->post('copy_source_id')); } } $link = '' . $entity->name . ''; $success = __('general.success.created', [ 'name' => $link, ]); session()->flash('success_raw', $success); if ($request->has('submit-new')) { $route = route('entities.create', [$campaign, $entityType]); return response()->redirectTo($route); } elseif ($request->has('submit-update')) { $route = route('entities.edit', [$campaign, $entity]); return response()->redirectTo($route); } elseif ($request->has('submit-view') && $entity) { $route = route('entities.show', [$campaign, $entity]); return response()->redirectTo($route); } elseif ($request->has('submit-copy')) { $route = route('entities.create', [$campaign, $entityType, 'copy' => $entity->id]); return response()->redirectTo($route); } $route = route('entities.show', [$campaign, $entity]); return response()->redirectTo($route); } catch (LogicException $exception) { if (config('app.debug')) { throw $exception; } $error = str_replace(' ', '_', mb_strtolower($exception->getMessage())); return redirect() ->back() ->withInput() ->with('error', __('crud.errors.' . $error)); } } } ================================================ FILE: app/Http/Controllers/Entities/DeleteController.php ================================================ authorize('delete', $entity); if (request()->ajax()) { return response()->json(['success' => true]); } $entity->delete(); // Todo: we really have to unify this one day. We have to delete the child to keep the withCount() // to work until we migrate all parent data to the Entity model and out of the child model. if ($entity->hasChild()) { $entity->child->delete(); } $routeName = $entity->entityType->hasEntity() ? 'entities' : $entity->entityType->pluralCode(); $params = $entity->entityType->hasEntity() ? [$campaign, $entity->entityType] : [$campaign]; return redirect()->route($routeName . '.index', $params) ->with('success_raw', __('general.success.deleted-cancel', [ 'name' => $entity->name, 'cancel' => '' . __('crud.cancel') . '', ])); } } ================================================ FILE: app/Http/Controllers/Entities/EditController.php ================================================ campaign($campaign)->authorize('update', $entity); $editingUsers = null; if ($this->campaign->hasEditingWarning()) { /** @var MultiEditingService $editingService */ $editingService = app()->make(MultiEditingService::class); $editingUsers = $editingService->model($entity)->user(auth()->user())->users(); // If no one is editing the entity, we are now editing it if (empty($editingUsers)) { $editingService->edit(); } } $params = [ 'campaign' => $this->campaign, 'entity' => $entity, 'name' => $entity->entityType->pluralCode(), 'tabPermissions' => auth()->user()->can('permissions', $entity), 'tabAttributes' => auth()->user()->can('attributes', $entity) && $this->campaign->enabled('entity_attributes'), 'entityType' => $entity->entityType, 'editingUsers' => $editingUsers, ]; if ($entity->entityType->isStandard()) { $params['model'] = $entity->child; } return view('entities.forms.edit', $params); } public function save(Request $request, Campaign $campaign, Entity $entity) { // We need to validate the request $validationClass = $entity->entityType->isStandard() ? 'App\Http\Requests\Store' . Str::studly($entity->entityType->code) : StoreCustomEntity::class; if (class_exists($validationClass)) { $validator = app()->make($validationClass); if (method_exists($validator, 'resolvedFields')) { $resolved = $validator->resolvedFields(); if ($resolved) { $request->merge($resolved); } } $this->validate($request, $validator->rules()); } // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } try { // Sanitize the data $sanitizerClassName = 'App\Sanitizers\\' . Str::studly($entity->entityType->code) . 'Sanitizer'; if (class_exists($sanitizerClassName)) { $sanitizer = app()->make($sanitizerClassName); $request->merge($sanitizer->request($request)->sanitize()); } if ($entity->hasChild()) { $data = $this->prepareData($request, $entity->child); $entity->child->update($data); // Fire an event for the Entity Observer $this->relationsFactory->for($entity)?->save($entity->child, $data); // Sync parent_id from the request for all entity types // $entity->parent_id = $request->has('parent_id') ? $request->get('parent_id') : null; $this->entitySaveService->save($entity, $data); // If the child was changed but nothing changed on the entity, we still want to trigger an update if ($entity->child->wasChanged() && ! $entity->wasChanged()) { $entity->touch(); } } else { $preparedData = $this->fixRequestData($request, $entity); $entity->update($preparedData); $this->entitySaveService->save($entity, $preparedData); } $this->aliasService->entity($entity)->request($request)->save(); if (auth()->user()->can('attributes', $entity)) { $this->attributeService ->campaign($campaign) ->entity($entity) ->save($request->get('attribute', [])) ->touch(); } $link = '' . $entity->name . ''; $success = __('general.success.updated', [ 'name' => $link, ]); $this->multiEditingService->model($entity) ->user($request->user()) ->finish(); session()->flash('success_raw', $success); if (auth()->user()->editor === 'tiptap') { $count = session()->get('tiptap_survey_count', 0); $count++; session()->put('tiptap_survey_count', $count); if ($count % 5 === 0) { session()->flash('tiptap_survey', true); } } $options = []; if (request()->has('redirect')) { $redirect = explode('&', request()->get('redirect')); foreach ($redirect as $option) { $vals = explode('=', $option); $options[$vals[0]] = $vals[1]; } } $route = route('entities.show', $options + [$campaign, $entity]); if ($request->has('submit-new')) { $route = route('entities.create', [$campaign, $entity->entityType]); } elseif ($request->has('submit-update')) { $route = route('entities.edit', [$campaign, $entity]); } elseif ($request->has('submit-close')) { $route = route('entities.index', [$campaign, $entity->entityType]); } elseif ($request->has('submit-copy')) { $route = route('entities.create', [$campaign, $entity->entityType, 'copy' => $entity]); return response()->redirectTo($route); } return response()->redirectTo($route); } catch (\LogicException $exception) { $error = str_replace(' ', '_', mb_strtolower(mb_rtrim($exception->getMessage(), '.'))); return redirect()->back()->withInput()->with('error', __('crud.errors.' . $error)); } } protected function prepareData(Request $request, MiscModel $model): array { $data = $request->all(); foreach ($model->nullableForeignKeys as $field) { if (! $request->has($field) && ! isset($data[$field])) { $data[$field] = null; } } return $data; } protected function fixRequestData(Request $request, Entity $entity): array { $data = $request->all(); foreach (['parent_id'] as $field) { if (! $request->has($field) && ! isset($data[$field])) { $data[$field] = null; } } return $data; } } ================================================ FILE: app/Http/Controllers/Entities/IndexController.php ================================================ middleware([CachedResponse::class]); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { if (! $entityType->isEnabled()) { return redirect()->route('dashboard', $campaign)->with( 'error_raw', __('campaigns/modules.errors.disabled', [ 'name' => $entityType->plural(), 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } elseif ($entityType->isStandard() && ! $campaign->enabled($entityType->pluralCode())) { return redirect()->route('dashboard', $campaign)->with( 'error_raw', __('campaigns/modules.errors.disabled', [ 'name' => $entityType->plural(), 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } elseif ($entityType->isBookmark()) { return redirect()->route('dashboard', $campaign); } $this->entityType = $entityType; $this->campaign = $campaign; $this->request = $request; $this->filterService->request($request)->entityType($entityType)->campaign($campaign)->build( $this->columnDefinitionService->sortableFields($entityType, $campaign) ); $nested = $this->isNested(); $mode = $this->layoutMode(); $title = $entityType->plural(); $parent = null; if ($request->has('parent_id')) { $parent = Entity::select([ 'entities.id', 'entities.name', 'entities.type', 'entities.is_private', 'entities.type_id', 'entities.parent_id', 'entities.entity_id', 'entities.image_uuid', 'entities.focus_x', 'entities.focus_y', ]) ->inTypes([$entityType->id])->where('id', $request->get('parent_id'))->first(); } $apiParams = [$campaign, $entityType]; if ($parent) { $apiParams['parent_id'] = $parent; } if ($request->get('_from') === 'bookmark') { $apiParams['_from'] = 'bookmark'; $apiParams['bookmark'] = $request->get('bookmark'); } if ($request->has('bookmark')) { $bookmark = Bookmark::where('id', $request->get('bookmark'))->first(); if ($bookmark) { $title = $bookmark->name; } } return view('entities.index.index') ->with('campaign', $campaign) ->with('entityType', $entityType) ->with('mode', $mode) ->with('parent', $parent) ->with('filterService', $this->filterService) ->with('nestable', $nested) ->with('templates', new Collection) ->with('apiParams', $apiParams) ->with('title', $title); } protected function loadTemplates(Campaign $campaign, EntityType $entityType): Collection { // No valid user, or invalid entity type (ie relations) if (auth()->guest()) { return new Collection; } elseif (! auth()->user()->can('create', [$entityType, $campaign])) { return new Collection; } return Entity::select('id', 'name', 'entity_id') ->with('entityType') ->templates($entityType->id) ->orderBy('name') ->get(); } public function api(Request $request, Campaign $campaign, EntityType $entityType) { $this->entityType = $entityType; $this->campaign = $campaign; $this->request = $request; $filterService = $this->filterService->request($request)->entityType($entityType)->campaign($campaign); if ($request->boolean('children')) { $filterService->ignoring(['parent_id']); } $filterService->build($this->columnDefinitionService->sortableFields($entityType, $campaign)); // Column definitions $columns = $this->columnDefinitionService->columns($entityType, $campaign); $relations = $this->columnDefinitionService->relationMap($entityType, $campaign); $childCountRelations = $this->columnDefinitionService->childCountRelations($entityType, $campaign); $entityCountRelations = $this->columnDefinitionService->entityCountRelations($entityType, $campaign); // User preferences (query once, reuse in isNested/layoutMode) $this->preference = null; if (auth()->check()) { $this->preference = EntityListingPreference::query()->where([ 'user_id' => auth()->id(), 'campaign_id' => $campaign->id, 'type_id' => $entityType->id, ])->first(); } $preference = $this->preference; $columnPreferences = $preference->visible_columns ?? $this->columnDefinitionService->defaultVisibleColumns($entityType, $campaign); // Tell the resource which columns to serialize ExploreResource::withColumns(array_map(fn ($c) => $c['key'], $columns)); $nested = $this->isNested(); $layout = $this->layoutMode(); $perPage = $this->perPageValue(); $with = $relations; // Eager load child model with withCount for count columns (e.g. organisation.members) if ($entityType->isStandard()) { $childRelation = Str::camel($entityType->code); $with[$childRelation] = function ($query) use ($childCountRelations) { if (! empty($childCountRelations)) { $query->withCount($childCountRelations); } }; } // All entity types: nesting uses entities.parent_id $with['children'] = fn ($q) => $q->whereNull('archived_at')->with('image'); $base = Entity::inTypes($entityType->id) ->select([ 'entities.id', 'entities.name', 'entities.type', 'entities.is_private', 'entities.entity_id', 'entities.type_id', 'entities.parent_id', 'entities.status_id', 'entities.image_uuid', 'entities.focus_x', 'entities.focus_y', 'entities.image_path', ]) ->with($with) ->withCount(array_merge( ['children' => fn ($q) => $q->whereNull('archived_at')], $entityCountRelations, )) ->search($this->filterService->search()) ->order($this->filterService->order(), $entityType) ->when($entityType->isStandard(), fn ($q) => $q->whereHas(Str::camel($entityType->code))) ->distinct(); $parent = null; if ($request->has('parent_id')) { $parent = Entity::select([ 'entities.id', 'entities.name', 'entities.type', 'entities.is_private', 'entities.type_id', 'entities.parent_id', 'entities.entity_id', 'entities.image_uuid', 'entities.focus_x', 'entities.focus_y', ]) ->inTypes([$entityType->id])->where('id', $request->get('parent_id'))->first(); if ($parent) { $base->where('entities.parent_id', $request->get('parent_id')); } } if (empty($parent) && $nested && $this->filterService->activeFiltersCount() === 0) { $base->whereNull('entities.parent_id'); } $loadError = false; $models = null; $unfilteredCount = 0; try { if ($this->filterService->hasFilters()) { $unfilteredCount = $base->count(); $base = $base->filter($this->filterService->filters(), $entityType); } else { $base = $base->whereNull('entities.archived_at'); } $models = $base->orderBy('entities.name')->paginate($perPage); // All children share the same entity type — set it without an extra DB query $models->each(function (Entity $entity) use ($entityType): void { $entity->children->each(fn (Entity $child) => $child->setRelation('entityType', $entityType)); }); } catch (\Throwable $e) { report($e); $loadError = true; } // Children requests only need the entity list — skip the heavy metadata if ($request->boolean('children')) { return response()->json([ 'error' => $loadError, 'parent' => $parent ? new ExploreResource($parent) : null, 'entities' => $models ? ExploreResource::collection($models)->response()->getData(true) : null, ]); } $i18n = [ 'fields' => [ 'name' => __('crud.fields.name'), 'type' => __('crud.fields.type'), 'is_private' => __('crud.fields.is_private'), ], 'is_private' => __('crud.is_private'), 'select' => __('crud.select'), 'selected' => __('datagrids.bulks.selected'), 'selectAll' => __('general.select_all'), 'done' => __('general.done'), 'filters' => __('datagrids.actions.filters'), 'bookmark' => __('filters.actions.bookmark'), 'noResults' => __('search.no_results'), 'templates' => __('entries/archetypes.helpers.how'), 'actions' => __('crud.actions.actions'), 'display' => __('datagrids.display.title'), 'perPage' => __('datagrids.display.per_page'), 'sortBy' => __('datagrids.display.sort_by'), 'clearFilters' => __('datagrids.filters.clear'), 'flatten' => __('datagrids.modes.flatten'), 'nest' => __('datagrids.modes.nested'), 'layout_grid' => __('datagrids.modes.grid'), 'layout_table' => __('datagrids.modes.table'), 'bulkEdit' => __('crud.bulk.actions.edit'), 'bulkRemove' => __('crud.remove'), 'bulkPermissions' => __('crud.bulk.actions.permissions'), 'bulkTemplate' => __('crud.bulk.actions.kits'), 'bulkTransform' => __('crud.actions.transform'), 'bulkCopy' => __('crud.actions.copy_to_campaign'), 'bulkPrint' => __('crud.actions.print'), 'bulkDelete' => __('crud.remove'), 'columns' => __('datagrids.columns.title'), 'resetDefaults' => __('datagrids.columns.reset'), 'relations' => __('entries/tabs.relations'), 'inventory' => __('crud.tabs.inventory'), 'edit' => __('crud.edit'), 'loadMore' => __('entities/story.actions.load_more'), ]; $bookmarkable = $this->filterService->activeFiltersCount() > 0 && auth()->check() && auth()->user()->can('create', Bookmark::class) && ! $request->has('bookmark'); return response()->json([ 'error' => $loadError, 'parent' => $parent ? new ExploreResource($parent) : null, 'entities' => $models ? ExploreResource::collection($models)->response()->getData(true) : null, 'nested' => $nested, 'i18n' => $i18n, 'bookmarkable' => $bookmarkable, 'entityType' => new EntityTypeResource($entityType), 'templates' => TemplateResource::collection($this->loadTemplates($campaign, $entityType)), 'permissions' => auth()->check() ? [ 'create' => auth()->user()->can('create', [$entityType, $campaign]), 'delete' => auth()->user()->can('deleteEntities', [$entityType, $campaign]), 'template' => auth()->user()->can('useTemplates', $campaign), 'admin' => auth()->user()->isAdmin($campaign), ] : null, 'features' => [ 'inventories' => $campaign->enabled('inventories'), ], 'urls' => [ 'create' => $entityType->createRoute($campaign), 'batch' => route('bulk.batch', [$campaign, $entityType]), 'template' => route('bulk.templates', [$campaign, $entityType]), 'transform' => route('bulk.transform', [$campaign, $entityType]), 'permissions' => route('bulk.permissions', [$campaign, $entityType]), 'copy' => route('bulk.copy-to-campaign', [$campaign, $entityType]), 'delete' => route('bulk.delete', [$campaign, $entityType]), 'print' => route('bulk.print', [$campaign, $entityType]), 'bookmark' => route('filters.modal_form', [$campaign, $entityType]), 'preferences' => route('entities.listing-preferences.update', [$campaign, $entityType]), 'preferencesReset' => route('entities.listing-preferences.destroy', [$campaign, $entityType]), ], 'csrf' => csrf_token(), 'filters' => [ 'count' => $this->filterService->activeFiltersCount(), 'unfilteredCount' => $unfilteredCount, 'urls' => [ 'form' => route('filters.form', array_merge( [$campaign, $entityType, 'm' => $layout], $request->get('_from') === 'bookmark' ? ['_from' => 'bookmark', 'bookmark' => $request->get('bookmark')] : [], )), 'clear' => route('entities.index', array_merge( [$campaign, $entityType, 'reset-filter' => true], $request->get('_from') === 'bookmark' ? ['_from' => 'bookmark', 'bookmark' => $request->get('bookmark')] : [], )), ], ], 'order' => $this->filterService->order(), 'columns' => $columns, 'columnPreferences' => $columnPreferences, 'ads' => [ 'enabled' => false, // $this->showAds($campaign), 'frequency' => 7, ], 'preferences' => $preference ? [ 'layout' => $preference->layout, 'nested' => $preference->nested, 'per_page' => $perPage, ] : null, 'subscription' => [ 'isSubscriber' => auth()->check() && auth()->user()->isSubscriber(), 'url' => route('datagrids.subscription'), ], 'perPageOptions' => $this->paginationService->options(), 'subscriberPerPageOptions' => $this->paginationService->subscriberOnlyOptions(), 'emptyState' => [ 'title' => __('lists.empty.title', ['plural' => strtolower($entityType->plural())]), 'helper' => __($entityType->pluralCode() . '.lists.empty'), 'docsUrl' => 'https://docs.kanka.io/en/latest/entries/' . Str::replace('_', '-', $entityType->isAttributeTemplate() ? 'property-kits' : $entityType->pluralCode()) . '.html', 'publicUrl' => Domain::toFront('campaigns'), 'learn' => __('lists.actions.learn'), 'public' => __('lists.actions.public'), ], ]); } protected function showAds(Campaign $campaign): bool { if (! config('ads.nitro.enabled')) { return false; } if (request()->has('_showads')) { return true; } if (auth()->check()) { $user = auth()->user(); if ($user->isSubscriber()) { return false; } if ($user->created_at->diffInHours(now()) < 24) { return false; } } return ! $campaign->boosted(); } /** * Determine if the current layout should be nested or not */ protected function isNested(): bool { if (! $this->entityType->isNested()) { return false; } $key = $this->entityType->code . '_nested'; // URL override if ($this->request->has('n')) { return $this->saveNested($key); } // Check preferences table if ($this->preference && $this->preference->nested !== null) { return $this->preference->nested; } // Fallback to session for guests if (auth()->guest()) { return (bool) Session::get($key, true); } return true; } /** * Determine if the current layout should be grid or table */ protected function layoutMode(): string { $key = $this->entityType->code . '_layout'; if ($this->request->has('m') && in_array($this->request->get('m'), ['grid', 'table'])) { $new = $this->request->get('m', 'grid'); if (auth()->guest()) { Session::put($key, $new); } else { $settings = auth()->user()->settings; if (auth()->check() && Arr::get($settings, $key) != $new) { $settings = auth()->user()->settings; if ($new === 'grid') { unset($settings[$key]); } else { $settings[$key] = 'table'; } auth()->user()->settings = $settings; auth()->user()->updateQuietly(); } } return $new; } // Check preferences table if ($this->preference && $this->preference->layout !== null) { return $this->preference->layout; } if (auth()->guest()) { return Session::get($key, 'grid'); } return Arr::get(auth()->user()->settings, $key, 'grid'); } protected function perPageValue(): int { $allowed = $this->paginationService->options(); $subscriberOnly = $this->paginationService->subscriberOnlyOptions(); $isSubscriber = auth()->user()?->isSubscriber() ?? false; $default = 25; $cap = function (int $value) use ($subscriberOnly, $isSubscriber, $default): int { if (in_array($value, $subscriberOnly) && ! $isSubscriber) { return $default; } return $value; }; // URL override (e.g. from pagination links) if ($this->request->has('pp')) { $pp = (int) $this->request->get('pp'); if (in_array($pp, $allowed)) { return $cap($pp); } } // Preference if ($this->preference && in_array($this->preference->per_page, $allowed)) { return $cap($this->preference->per_page); } return $default; } } ================================================ FILE: app/Http/Controllers/Entities/ListingPreferenceController.php ================================================ auth()->id(), 'campaign_id' => $campaign->id, 'type_id' => $entityType->id, ], array_filter([ 'visible_columns' => $request->input('columns'), 'layout' => $request->has('layout') ? $request->input('layout') : null, 'nested' => $request->has('nested') ? $request->boolean('nested') : null, 'per_page' => $request->has('per_page') ? $request->integer('per_page') : null, ], fn ($value) => $value !== null) ); return response()->json(['success' => true]); } public function destroy( Campaign $campaign, EntityType $entityType ): JsonResponse { EntityListingPreference::query()->where([ 'user_id' => auth()->id(), 'campaign_id' => $campaign->id, 'type_id' => $entityType->id, ])->delete(); return response()->json(['success' => true]); } } ================================================ FILE: app/Http/Controllers/Entity/Abilities/ApiController.php ================================================ service = $service; } public function index(Campaign $campaign, Entity $entity) { if (auth()->check()) { $this->service->user(auth()->user()); } return response()->json([ 'data' => $this->service ->campaign($campaign) ->entity($entity) ->get(), ]); } } ================================================ FILE: app/Http/Controllers/Entity/Abilities/ChargeController.php ================================================ service = $chargeService; } public function use(Request $request, Campaign $campaign, Entity $entity, EntityAbility $entityAbility) { $this->authorize('update', $entity); return response()->json([ 'success' => $this->service ->entity($entity) ->ability($entityAbility) ->use((int) $request->post('used')), ]); } public function reset(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); $this->service ->entity($entity) ->reset(); return redirect()->route('entities.entity_abilities.index', [$campaign, $entity]) ->withSuccess(__('entities/abilities.recharge.success')); } } ================================================ FILE: app/Http/Controllers/Entity/Abilities/ImportController.php ================================================ service = $chargeService; } public function index(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if (! $entity->isCharacter()) { return redirect()->route('entities.entity_abilities.index', [$campaign, $entity]) ->with('error', __('entities/abilities.import.errors.not_character')); } $races = []; /** @var Character $character */ $character = $entity->child; $characterRaces = $character ->characterRaces() ->with('race', 'race.entity', 'race.entity.abilities') ->get(); foreach ($characterRaces as $race) { // Exclude races with no abilities from the list. if ($race->race->entity->abilities->count() > 0) { $races[$race->race->name] = $race->race->entity->abilities->count(); } } return view('entities.pages.abilities.import', compact( 'campaign', 'entity', 'races' )); } public function import(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); try { $count = $this->service ->entity($entity) ->import(); return redirect()->route('entities.entity_abilities.index', [$campaign, $entity]) ->with('success', trans_choice('entities/abilities.import.success', $count, ['count' => $count])); } catch (Exception $e) { return redirect()->route('entities.entity_abilities.index', [$campaign, $entity]) ->with('error', __('entities/abilities.import.errors.' . $e->getMessage())); } } } ================================================ FILE: app/Http/Controllers/Entity/Abilities/ReorderController.php ================================================ authorize('update', $entity); $abilities = $entity->abilities() ->select('entity_abilities.*') ->with(['ability', // entity 'ability.entity', 'ability.entity.image', 'ability.entity.attributes', // parent 'ability.entity.parent', 'ability.entity.parent', ]) ->join('abilities as a', 'a.id', 'entity_abilities.ability_id') ->leftJoin('entities as ae', function (JoinClause $join) { $join ->on('ae.entity_id', '=', 'a.id') ->where('ae.type_id', '=', config('entities.ids.ability')); }) ->defaultOrder() ->get(); $parents = []; foreach ($abilities as $ability) { // Missing permission to view the ability if (empty($ability->ability)) { continue; } if (array_key_exists($ability->ability->entity->parent_id, $parents)) { $parents[$ability->ability->entity->parent_id][] = $ability; } else { $parents[$ability->ability->entity->parent_id] = [$ability]; } } return view('entities.pages.abilities.reorder.index', compact( 'campaign', 'entity', 'parents' )); } public function save(Campaign $campaign, Entity $entity, ReorderAbility $request) { $this->authorize('update', $entity); $this->reorderService ->entity($entity) ->reorder($request); return redirect() ->route('entities.entity_abilities.index', [$campaign, $entity]) ->withSuccess(__('entities/abilities.reorder.success')); } } ================================================ FILE: app/Http/Controllers/Entity/AbilityController.php ================================================ campaign($campaign)->authEntityView($entity); if (! $campaign->enabled('abilities')) { return redirect()->route('entities.show', [$campaign, $entity])->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } $translations = [ 'all' => __('crud.visibilities.all'), 'members' => __('crud.visibilities.members'), 'admin-self' => __('crud.visibilities.admin-self'), 'admin' => __('crud.visibilities.admin'), 'self' => __('crud.visibilities.self'), 'update' => __('crud.update'), 'remove' => __('crud.remove'), ]; $translations = json_encode($translations); return view('entities.pages.abilities.index', compact( 'campaign', 'entity', 'translations' )); } public function create(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); return view('entities.pages.abilities.create', compact( 'campaign', 'entity' )); } public function store(StoreEntityAbility $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $data = $request->only(['abilities', 'ability_id', 'visibility_id']); $data['entity_id'] = $entity->id; $success = ''; if (is_array($data['abilities'])) { $abilities = []; foreach ($data['abilities'] as $abilityId) { /** @var ?Ability $ability */ $ability = Ability::find($abilityId); if ($ability) { $entityAbility = EntityAbility::create([ 'entity_id' => $entity->id, 'ability_id' => $abilityId, 'visibility_id' => $data['visibility_id'], ]); $abilities[] = $ability->name; } } $success = __('entities/abilities.create.success_multiple', [ 'abilities' => implode(', ', $abilities), 'entity' => $entity->name, ]); } elseif (! empty($data['ability_id'])) { // Allow adding a single ability through the API $entityAbility = new EntityAbility; unset($data['abilities']); $entityAbility = $entityAbility->create($data); $success = trans('entities/abilities.create.success', [ 'ability' => $entityAbility->ability->name, 'entity' => $entity->name, ]); } return redirect() ->route('entities.entity_abilities.index', [$campaign, $entity]) ->with('success', $success); } public function show(Campaign $campaign, Entity $entity, EntityAbility $entityAbility) { return redirect() ->route('entities.entity_abilities.index', [$campaign, $entity->id]); } public function edit(Campaign $campaign, Entity $entity, EntityAbility $entityAbility) { $this->authorize('update', $entity); $ability = $entityAbility; if (empty($ability->ability)) { abort(403); } return view('entities.pages.abilities.update', compact( 'campaign', 'entity', 'ability' )); } public function update(UpdateEntityAbility $request, Campaign $campaign, Entity $entity, EntityAbility $entityAbility) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $data = $request->only(['ability_id', 'visibility_id', 'note']); $entityAbility->update($data); if (request()->ajax()) { return response()->json([ 'success' => true, ]); } return redirect() ->route('entities.entity_abilities.index', [$campaign, $entity->id]) ->with('success', __('entities/abilities.update.success', ['ability' => $entityAbility->ability->name])); } public function destroy(Campaign $campaign, Entity $entity, EntityAbility $entityAbility) { $this->authorize('update', $entity); if (! $entityAbility->delete()) { abort(500); } if (request()->ajax()) { return response()->json([ 'success' => true, ]); } $from = request()->get('from'); if ($from == 'ability') { return redirect()->route('abilities.entities', [$campaign, $entityAbility->ability]); } return redirect() ->route('entities.entity_abilities.index', [$campaign, $entity]); } } ================================================ FILE: app/Http/Controllers/Entity/ArchiveController.php ================================================ middleware('auth'); $this->service = $archiveService; } public function update(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if (request()->ajax()) { return response()->json(); } $this->service->entity($entity)->toggle(); return redirect()->back() ->with( 'success', __('entities/actions.' . ($entity->archived_at ? 'archive' : 'unarchive') . '.success', ['name' => $entity->name]) ); } } ================================================ FILE: app/Http/Controllers/Entity/AssetController.php ================================================ campaign($campaign)->authEntityView($entity); if (! $campaign->enabled('media')) { return redirect()->route('entities.show', [$campaign, $entity])->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } $assets = $entity->assets() ->withoutAliases() ->with('image') ->get(); return view('entities.pages.assets.index', compact( 'campaign', 'entity', 'assets' )); } public function show(Campaign $campaign, Entity $entity, EntityAsset $entityAsset) { return redirect()->route('entities.entity_assets.index', [$campaign, $entity]); } public function create(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); $typeID = (int) request()->get('type'); if ($typeID === EntityAssetType::file->value) { return $this->createFile($campaign, $entity); } elseif ($typeID === EntityAssetType::link->value) { return $this->createLink($campaign, $entity); } abort(404); } public function store(StoreEntityAssets $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $data = []; $type = ''; $typeId = null; if (request()->get('type_id') == EntityAssetType::file->value) { return $this->storeFile($request, $campaign, $entity); } elseif (request()->get('type_id') == EntityAssetType::link->value) { $data = $request->only(['name', 'position', 'visibility_id', 'metadata']); $type = 'links'; $typeId = EntityAssetType::link; } $data['entity_id'] = $entity->id; $data['type_id'] = $typeId; $asset = EntityAsset::create($data); return redirect() ->route('entities.entity_assets.index', [$campaign, $entity]) ->with('success', __( 'entities/' . $type . '.create.success', ['name' => $asset->name, 'entity' => $entity->name] )); } protected function storeFile(StoreEntityAssets $request, Campaign $campaign, Entity $entity) { /** @var EntityFileService $service */ $service = app()->make(EntityFileService::class); try { $files = $service ->entity($entity) ->campaign($campaign) ->upload($request) ->files(); return redirect() ->route('entities.entity_assets.index', [$campaign, $entity]) ->with('success', trans_choice('entities/files.create.success_plural', count($files), ['count' => count($files), 'name' => $files['0']->name])); } catch (TranslatableException $e) { return redirect() ->route('entities.entity_assets.index', [$campaign, $entity]) ->with('error', $e->getTranslatedMessage()); } catch (Exception $e) { return redirect() ->route('entities.entity_assets.index', [$campaign, $entity]) ->with('error', $e->getMessage()); } } public function edit(Campaign $campaign, Entity $entity, EntityAsset $entityAsset) { $this->authorize('update', $entity); $file = 'files'; if ($entityAsset->isLink()) { $file = 'links'; } return view('entities.pages.' . $file . '.update') ->with('campaign', $campaign) ->with('entity', $entity) ->with('entityAsset', $entityAsset); } public function update(StoreEntityAsset $request, Campaign $campaign, Entity $entity, EntityAsset $entityAsset) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $type = 'files'; if ($entityAsset->isLink()) { $data = $request->only(['name', 'metadata.url', 'metadata.icon', 'visibility_id']); $entityAsset->update($data); $type = 'links'; } elseif ($entityAsset->isFile()) { $data = $request->only(['name', 'visibility_id', 'is_pinned']); $entityAsset->update($data); } if (request()->ajax()) { return response()->json([ 'success' => true, ]); } return redirect() ->route('entities.entity_assets.index', [$campaign, $entity]) ->with('success', __('entities/' . $type . '.update.success', ['name' => $entityAsset->name, 'entity' => $entity->name])); } public function destroy(Campaign $campaign, Entity $entity, EntityAsset $entityAsset) { $this->authorize('update', $entity); if (! $entityAsset->delete()) { abort(500); } $type = 'files'; if ($entityAsset->isLink()) { $type = 'links'; } if (request()->ajax()) { return response()->json([ 'success' => true, ]); } return redirect() ->route('entities.entity_assets.index', [$campaign, $entity]) ->with('success', __('entities/' . $type . '.destroy.success', ['name' => $entityAsset->name, 'entity' => $entity->name])); } /** * Create a new file * * @return Application|Factory|\Illuminate\Contracts\View\View */ protected function createFile(Campaign $campaign, Entity $entity) { if (! auth()->user()->can('addFile', [$entity, $campaign])) { return view('entities.pages.files.max') ->with('campaign', $campaign) ->with('max', Limit::campaign($campaign)->entityFiles()); } return view('entities.pages.files.create') ->with('campaign', $campaign) ->with('entity', $entity); } /** * Create a new link * * @return Application|Factory|\Illuminate\Contracts\View\View */ protected function createLink(Campaign $campaign, Entity $entity) { if (! $campaign->boosted()) { return view('components.premium-dialog', [ 'campaign' => $campaign, 'pitch' => 'entities/links.call-to-action', ]); } return view('entities.pages.links.create', compact( 'campaign', 'entity' )); } /** * @return Application|Factory|RedirectResponse|View * * @throws AuthorizationException */ public function go(Campaign $campaign, Entity $entity, EntityAsset $entityAsset) { $this->campaign($campaign)->authEntityView($entity); if ($entityAsset->entity_id !== $entity->id || ! $entityAsset->isLink()) { abort(404); } // If the link goes to the same domain, just go. $url = $entityAsset->metadata['url']; if (Str::startsWith($url, config('app.url')) && ! Str::contains($url, 'entity_links/')) { return redirect()->to($url); } // If the domain is trusted for the user, we don't need the confirmation, just go $trusted = Cookie::get('kanka_trusted_domains'); if ($trusted) { $domains = explode('|', $trusted); if (in_array($entityAsset->urlDomain(), $domains)) { return redirect()->to($url); } } return view('entities.pages.links.go', compact( 'campaign', 'entity', 'entityAsset' )); } } ================================================ FILE: app/Http/Controllers/Entity/AttributeController.php ================================================ enabled('entity_attributes')) { return redirect()->route('entities.show', [$campaign, $entity])->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } $this->campaign($campaign)->authEntityView($entity); $this->authorize('view-attributes', [$entity, $campaign]); $template = null; $marketplaceTemplate = null; $layout = $entity->attributes()->where(['name' => '_layout'])->first(); if (! empty($layout)) { $template = $this->templateService->communityTemplate($layout->value); $marketplaceTemplate = $this->templateService->campaign($campaign)->marketplaceTemplate($layout->value); } return view('entities.pages.attributes.index', compact( 'entity', 'marketplaceTemplate', 'template', 'campaign' )); } public function dashboard(Campaign $campaign, Entity $entity) { if (! $campaign->enabled('entity_attributes')) { return redirect()->route('dashboard', $campaign)->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } $this->campaign($campaign)->authEntityView($entity); $this->authorize('view-attributes', [$entity, $campaign]); $template = null; $marketplaceTemplate = null; $fromDashboard = true; $layout = $entity->attributes()->where(['name' => '_layout'])->first(); if ($layout) { $template = $this->templateService->communityTemplate($layout->value); $marketplaceTemplate = $this->templateService->campaign($campaign)->marketplaceTemplate($layout->value); } return view('entities.pages.attributes.dashboard', compact( 'entity', 'marketplaceTemplate', 'template', 'campaign', 'fromDashboard' )); } public function edit(Campaign $campaign, Entity $entity) { if (! $campaign->enabled('entity_attributes')) { return redirect()->route('dashboard', $campaign)->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } if ($entity->isMissingChild()) { abort(404); } $this->authorize('update', $entity); $this->authorize('attributes', $entity); return view('entities.pages.attributes.edit', compact( 'campaign', 'entity', )); } public function save(SaveAttributes $request, Campaign $campaign, Entity $entity) { if ($entity->isMissingChild()) { abort(404); } $this->authorize('update', $entity); $this->authorize('attributes', $entity); $attributes = $request->get('attribute', []); $this->service ->entity($entity) ->user($request->user()) ->updateVisibility(request()->get('is_attributes_private') === '1') ->save($attributes) ->touch(); return redirect()->route('entities.attributes', [$campaign, $entity->id]) ->with('success', __('entities/attributes.update.success', ['entity' => $entity->name])); } public function liveEdit(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); $id = request()->get('id'); $uid = request()->get('uid'); if (! is_numeric($uid)) { abort(421); } $attribute = $entity->attributes()->where('id', $id)->first(); if (! $id || ! $attribute) { return abort(421); } return response()->view('entities.pages.attributes.live.edit', compact('campaign', 'attribute', 'entity', 'uid')); } } ================================================ FILE: app/Http/Controllers/Entity/AttributeTemplateController.php ================================================ middleware('auth'); $this->service = $service; $this->templateService = $templateService; } public function index(Campaign $campaign, Entity $entity) { if (! $campaign->enabled('entity_attributes')) { return redirect()->route('dashboard', $campaign)->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } $this->authorize('update', $entity); $this->authorize('attributes', $entity); $templates = $this->service->campaign($campaign)->templateList(); return view('entities.pages.attribute-templates.apply', compact( 'campaign', 'entity', 'templates' )); } public function process(ApplyAttributeTemplate $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if (request()->ajax()) { return response()->json(['success' => true]); } $success = $this->templateService ->entity($entity) ->apply($request->get('template_id')); if (! $success) { return redirect()->back()->with('error', __('entities/attributes.template.error')); } $templateName = $this->templateService->templateName(); if ($request->has('submit-story')) { return redirect() ->to($entity->url()) ->with('success', __('entities/attributes.template.success', [ 'name' => $templateName, 'entity' => $entity->child->name, ])); } elseif ($request->has('submit-update')) { return redirect() ->route('entities.attributes.edit', [$campaign, $entity]) ->with('success', __('entities/attributes.template.success', [ 'name' => $templateName, 'entity' => $entity->child->name, ])); } return redirect() ->route('entities.attributes', [$campaign, $entity]) ->with('success', __('entities/attributes.template.success', [ 'name' => $templateName, 'entity' => $entity->child->name, ])); } } ================================================ FILE: app/Http/Controllers/Entity/Attributes/ApiController.php ================================================ apiService = $apiService; } public function index(Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); $this->authorize('view-attributes', [$entity, $campaign]); return response()->json( $this->apiService ->campaign($campaign) ->entity($entity) ->build() ); } } ================================================ FILE: app/Http/Controllers/Entity/Attributes/LiveApiController.php ================================================ campaign($campaign)->authEntityView($entity); $this->authorize('view-attributes', [$entity, $campaign]); return LiveAttributeResource::collection($entity->attributes()->with(['entity', 'entity.campaign', 'entity.attributes'])->get()); } public function store(StoreAttribute $request, Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); $this->authorize('attributes', [$entity, $campaign]); $data = $request->all(); $data['entity_id'] = $entity->id; $attribute = Attribute::create($data); return new LiveAttributeResource($attribute); } public function update(UpdateAttribute $request, Campaign $campaign, Entity $entity, Attribute $attribute) { $this->campaign($campaign)->authEntityView($entity); $this->authorize('attributes', [$entity, $campaign]); $attribute->update($request->all()); return new LiveAttributeResource($attribute); } public function destroy(Campaign $campaign, Entity $entity, Attribute $attribute) { $this->campaign($campaign)->authEntityView($entity); $this->authorize('attributes', [$entity, $campaign]); $attribute->delete(); return response()->json(null, 204); } } ================================================ FILE: app/Http/Controllers/Entity/Attributes/LiveController.php ================================================ authorize('update', $entity); return view('entities.pages.attributes.live.edit') ->with('campaign', $campaign) ->with('entity', $entity) ->with('attribute', $attribute) ->with('uid', request()->get('uid')); } public function save(UpdateEntityAttribute $request, Campaign $campaign, Entity $entity, Attribute $attribute) { $this->authorize('update', $entity); if ($attribute->entity_id !== $entity->id) { abort(404); } $attribute->update([ 'value' => Purify::clean($request->get('value')), ]); // Track that the entity was updated $entity->touch(); if (! request()->ajax()) { return redirect()->route('entities.attributes', [$campaign, $entity]); } $attributeValue = null; $result = $attribute->mappedValue(); $attributeValue = $result; if ($attribute->isText()) { $result = nl2br($result); $attributeValue = $result; } elseif ($attribute->isCheckbox()) { $result = ''; $attributeValue = $attribute->value ? 'true' : 'false'; } return response()->json([ 'value' => $result, 'attribute' => $attributeValue, 'id' => $attribute->id, 'uid' => $request->get('uid'), 'success' => __('entities/attributes.live.success', ['attribute' => $attribute->name()]), ]); } } ================================================ FILE: app/Http/Controllers/Entity/Connections/MapController.php ================================================ campaign($campaign)->authEntityView($entity); $map = $this->mapService ->campaign($campaign) ->entity($entity) ->option(request()->get('option', null)) ->map(); return response()->json( $map ); } } ================================================ FILE: app/Http/Controllers/Entity/Connections/TableController.php ================================================ campaign($campaign)->authEntityView($entity); Datagrid::layout(Relation::class) ->route('entities.relations_table', ['campaign' => $campaign, 'entity' => $entity, 'mode' => 'table']); $this->rows = $entity ->allRelationships() ->sort(request()->only(['o', 'k'])) ->paginate(); // $this->campaign($campaign)->datagrid(); $html = view('layouts.datagrid._table') ->with('rows', $this->rows) ->with('campaign', $campaign) ->render(); $data = [ 'success' => true, 'html' => $html, ]; return response()->json($data); } } ================================================ FILE: app/Http/Controllers/Entity/CopyInventoryController.php ================================================ authorize('update', $entity); return view('entities.pages.inventory.copy', compact( 'campaign', 'entity', )); } public function store(CopyInventory $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $copyFrom = Entity::where('id', $request->only('entity_id'))->first(); $count = 0; foreach ($copyFrom->inventories as $old) { $inventory = $old->replicate(['entity_id']); $inventory->entity_id = $entity->id; $inventory->save(); $count++; } return redirect() ->route('entities.inventory', [$campaign, $entity]) ->with('success_raw', trans_choice('entities/inventories.create.success_bulk', $count, [ 'entity' => $entity->name, 'count' => $count, ])); } } ================================================ FILE: app/Http/Controllers/Entity/EntryController.php ================================================ authorize('update', $entity); return view('entities.pages.entry.edit') ->with('campaign', $campaign) ->with('entity', $entity); } /** * Update the entity's entry */ public function update(UpdateEntityEntry $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $fields = $request->only('entry'); $entity->update($fields); if ($entity->wasChanged()) { EntityLogger::entity($entity); $entity->touch(); } $return = redirect()->to($entity->url()); if (auth()->user()->editor === 'tiptap') { $count = session()->get('tiptap_survey_count', 0); $count++; session()->put('tiptap_survey_count', $count); if ($count % 5 === 0) { session()->flash('tiptap_survey', true); } } return $return; } } ================================================ FILE: app/Http/Controllers/Entity/ExportController.php ================================================ service = $service; $this->markdownExportService = $markdownExportService; } public function json(Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); return $this->service->entity($entity)->json(); } public function markdown(Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); $converter = new HtmlConverter; $converter->getConfig()->setOption('strip_tags', true); $converter->getEnvironment()->addConverter(new TableConverter); if (auth()->check()) { $this->markdownExportService->user(auth()->user()); } $entityData = $this->markdownExportService ->campaign($campaign) ->entity($entity) ->single(true) ->entityData(); return response()->view('entities.markdown.base', ['entity' => $entity, 'entityData' => $entityData, 'converter' => $converter, 'campaign' => $campaign]) ->header('Content-Type', 'application/md') ->header('Content-disposition', 'attachment; filename="' . Str::slug($entity->name) . '.md"'); } public function html(Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); return view('entities.pages.print.print') ->with('campaign', $campaign) ->with('entity', $entity) ->with('model', $entity->entityType->isStandard() ? $entity->child : null) ->with('name', $entity->entityType->pluralCode()) ->with('printing', true); } } ================================================ FILE: app/Http/Controllers/Entity/GenerateInventoryController.php ================================================ authorize('update', $entity); return view('entities.pages.inventory.generate', compact( 'campaign', 'entity', )); } public function store(GenerateInventory $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $count = $this->service ->entity($entity) ->handle($request); return redirect() ->route('entities.inventory', [$campaign, $entity]) ->with('success_raw', trans_choice('entities/inventories.create.success_bulk', $count, [ 'entity' => $entity->name, 'count' => $count, ])); } } ================================================ FILE: app/Http/Controllers/Entity/ImageController.php ================================================ middleware(['auth']); } /** * @throws AuthorizationException */ public function focus(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); return view('entities.pages.image.focus') ->with('campaign', $campaign) ->with('entity', $entity); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function saveFocus(StoreImageFocus $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); // Gallery image? $source = empty($entity->image_path) && $entity->image ? $entity->image : $entity; $source->focus_x = (int) $request->post('focus_x'); $source->focus_y = (int) $request->post('focus_y'); $source->save(); return redirect() ->to($entity->url()) ->with('success', __('entities/image.focus.success')); } /** * @throws AuthorizationException */ public function replace(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); return view('entities.pages.image.replace') ->with('campaign', $campaign) ->with('entity', $entity); } public function update(UpdateEntityImage $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $entity->image_uuid = request()->get('entity_image_uuid'); // New image requires a focus reset if ($entity->isDirty(['image_uuid'])) { $entity->focus_x = null; $entity->focus_y = null; } $entity->save(); return redirect() ->to($entity->url()) ->with('success', __('entities/image.replace.success')); } } ================================================ FILE: app/Http/Controllers/Entity/Inventory/DetailController.php ================================================ campaign($campaign)->authEntityView($entity); if ($inventory->item_id && ! $inventory->item) { abort(403); } return view('entities.pages.inventory.details') ->with('campaign', $campaign) ->with('entity', $entity) ->with('inventory', $inventory); } } ================================================ FILE: app/Http/Controllers/Entity/InventoryController.php ================================================ campaign($campaign)->authEntityView($entity); if (! $campaign->enabled('inventories')) { return redirect()->route('entities.show', [$campaign, $entity])->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } $inventory = $entity->orderedInventory(); return view('entities.pages.inventory.index', compact( 'campaign', 'entity', 'inventory', )); } public function create(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); $positionPreset = request()->get('position'); $positionOptions = ['' => '']; $positions = Inventory::positionList($campaign)->pluck('position')->all(); foreach ($positions as $position) { $positionOptions[$position] = $position; } return view('entities.pages.inventory.create', compact( 'campaign', 'entity', 'positionPreset', 'positionOptions', )); } public function store(StoreInventory $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $count = 0; $itemIds = $request->post('item_id'); if (isset($itemIds) && is_array($itemIds)) { foreach ($itemIds as $id) { $data = $request->only($this->fillable); $data['item_id'] = $id; $inventory = new Inventory; $inventory = $inventory->create($data); $count++; } $success = trans_choice('entities/inventories.create.success_bulk', $count, [ 'entity' => $entity->name, 'count' => $count, ]); } else { $data = $request->only($this->fillable); $inventory = new Inventory; $inventory = $inventory->create($data); $success = __('entities/inventories.create.success', [ 'item' => $inventory->itemName(), 'entity' => $entity->name, ]); } return redirect() ->route('entities.inventory', [$campaign, $entity]) ->with('success_raw', $success); } /** * Unhandled page, redirect */ public function show(Campaign $campaign, Entity $entity, Inventory $inventory) { $this->authorize('update', $entity); return redirect()->route('entities.inventory', [$campaign, $entity]); } public function edit(Campaign $campaign, Entity $entity, Inventory $inventory) { $this->authorize('update', $entity); $positionOptions = ['' => '']; $positions = Inventory::positionList($campaign)->pluck('position')->all(); foreach ($positions as $position) { $positionOptions[$position] = $position; } return view('entities.pages.inventory.update', compact( 'campaign', 'entity', 'inventory', 'positionOptions' )); } public function update(UpdateInventory $request, Campaign $campaign, Entity $entity, Inventory $inventory) { $this->authorize('update', $entity); $data = $request->only($this->fillable); $inventory->update($data); $inventory->refresh(); return redirect() ->route('entities.inventory', [$campaign, $entity]) ->with('success_raw', __('entities/inventories' . '.update.success', [ 'item' => $inventory->itemName(), 'entity' => $entity->name, ])); } public function destroy(Campaign $campaign, Entity $entity, Inventory $inventory) { $this->authorize('update', $entity); $inventory->delete(); return redirect() ->route('entities.inventory', [$campaign, $entity]) ->with('success_raw', __('entities/inventories.destroy.success', [ 'item' => $inventory->itemName(), 'entity' => $entity->name, ])); } } ================================================ FILE: app/Http/Controllers/Entity/InventorySectionController.php ================================================ authorize('update', $entity); if ($inventory->position) { Inventory::where('entity_id', $entity->id)->where('position', $inventory->position)->delete(); } else { Inventory::where('entity_id', $entity->id)->where('position', '')->delete(); } return redirect() ->route('entities.inventory', [$campaign, $entity]) ->with('success_raw', __('entities/inventories.destroy.success_position', [ 'position' => $inventory->position, 'entity' => $entity->name, ])); } } ================================================ FILE: app/Http/Controllers/Entity/LogController.php ================================================ authorize('update', $entity); $this->authorize('history', [$entity, $campaign]); $fields = ['action']; $expanded = false; if ($campaign->superboosted()) { $fields = ['q', 'action']; if ($request->filled('q')) { $expanded = true; } } $postIds = $entity->posts()->pluck('id'); $logs = EntityLog::where(function ($query) use ($entity, $postIds) { $query->where(function ($sub) use ($entity) { $sub->where('parent_type', Entity::class) ->where('parent_id', $entity->id); }) ->orWhere(function ($sub) use ($postIds) { $sub->where('parent_type', Post::class) ->whereIn('parent_id', $postIds); }); }) ->filter($request->only($fields)) ->with([ 'user', 'impersonator', 'parent', ]) ->recent() ->paginate(config('limits.pagination')); $transKey = $entity->entityType->pluralCode(); $q = request()->get('q'); $action = request()->get('action'); $actions = [ '' => __('history.filters.all-actions'), EntityLog::ACTION_CREATE => __('entities/logs.actions.create'), EntityLog::ACTION_UPDATE => __('entities/logs.actions.update'), EntityLog::ACTION_DELETE => __('entities/logs.actions.delete'), EntityLog::ACTION_RESTORE => __('entities/logs.actions.restore'), ]; return view('entities.pages.logs.index', compact( 'campaign', 'entity', 'logs', 'campaign', 'transKey', 'q', 'action', 'actions', 'expanded', )); } } ================================================ FILE: app/Http/Controllers/Entity/MentionController.php ================================================ campaign($campaign)->authEntityView($entity); $options = ['campaign' => $campaign, 'entity' => $entity]; Datagrid::layout(Mention::class) ->route('entities.mentions', $options); $this->rows = $entity ->targetMentions() ->datagridElements(request()->only(['o', 'k'])) ->with([ 'campaign' => function ($sub) { $sub->select('id', 'name', 'slug'); }, 'post' => function ($sub) { $sub->select('id', 'entity_id', 'name', 'visibility_id'); }, 'post.entity' => function ($sub) { $sub->select('id', 'type_id', 'entity_id', 'name', 'is_private'); }, 'entity' => function ($sub) { $sub->select('id', 'type_id', 'entity_id', 'name', 'is_private'); }, 'entity.entityType' => function ($sub) { $sub->select('id', 'code', 'singular', 'plural'); }, 'questElement' => function ($sub) { $sub->select('id', 'name', 'quest_id', 'entity_id', 'visibility_id'); }, 'questElement.entity' => function ($sub) { $sub->select('id', 'type_id', 'entity_id', 'name', 'is_private'); }, /*'questElement.quest' => function ($sub) { $sub->select('id', 'name', 'is_private'); }, 'questElement.quest.entity' => function ($sub) { $sub->select('id', 'type_id', 'entity_id', 'name', 'is_private'); },*/ 'timelineElement' => function ($sub) { $sub->select('id', 'name', 'timeline_id', 'entity_id', 'visibility_id'); }, 'timelineElement.entity' => function ($sub) { $sub->select('id', 'type_id', 'entity_id', 'name', 'is_private'); }, /*'timelineElement.timeline' => function ($sub) { $sub->select('id', 'name', 'is_private'); }, 'timelineElement.timeline.entity' => function ($sub) { $sub->select('id', 'type_id', 'entity_id', 'name', 'is_private'); },*/ ]) ->filterValid() ->paginate(); // Ajax Datagrid if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } $rows = $this->rows; return view('entities.pages.mentions.mentions', compact( 'entity', 'rows', 'campaign', )); } } ================================================ FILE: app/Http/Controllers/Entity/MoveController.php ================================================ middleware(['auth']); } /** * @return Factory|View * * @throws AuthorizationException */ public function index(Campaign $campaign, Entity $entity) { $this->authorize('view', $entity); $campaigns = $this->campaignService ->user(auth()->user()) ->campaign($campaign) ->campaigns(); return view('entities.pages.move.index', compact( 'campaign', 'entity', 'campaigns', )); } /** * @throws AuthorizationException */ public function move(MoveEntityRequest $request, Campaign $campaign, Entity $entity) { $this->authorize('view', $entity); if (request()->ajax()) { return response()->json(['success' => true]); } $copied = $request->filled('copy'); try { $this->service ->entity($entity) ->campaign($campaign) ->user($request->user()) ->to($request->get('campaign')) ->copy($copied) ->validate() ->process(); return redirect() ->route($entity->entityType->hasEntity() ? 'entities.index' : $entity->entityType->pluralCode() . '.index', [$campaign, $entity->entityType]) ->with('success_raw', __('entities/move.success' . ($copied ? '_copy' : null), ['name' => $entity->name, 'campaign' => 'service->target()) . '\' class="text-link">' . $this->service->target()->name . ''])); } catch (TranslatableException $ex) { return redirect() ->to($entity->url()) ->with('error_raw', $ex->getTranslatedMessage()); } } } ================================================ FILE: app/Http/Controllers/Entity/PermissionController.php ================================================ permissionService = $permissionService; } public function view(Campaign $campaign, Entity $entity) { $this->authorize('permissions', $entity); return view('cruds.permissions', compact('entity', 'campaign')); } public function store(StorePermission $request, Campaign $campaign, Entity $entity) { $this->authorize('permissions', $entity); $this->permissionService ->user($request->user()) ->entity($entity) ->save($request->only('role', 'user')); return redirect()->back() ->with('success_raw', __('crud.permissions.success')); } } ================================================ FILE: app/Http/Controllers/Entity/PostController.php ================================================ authorize('browse', [$entity]); return redirect()->to($entity->url()); } public function create(Campaign $campaign, Entity $entity, Post $post) { $this->authorize('post', [$entity, 'add']); $parentRoute = $entity->entityType->pluralCode(); $templates = Post::postTemplates($campaign)->orderBy('name')->get(); $data = []; $template = request()->input('template'); if (! empty($template) && $this->authorize('useTemplates', $campaign)) { $template = Post::template()->with('entity')->has('entity')->where('posts.id', $template)->first(); $data['model'] = $template; $data['model']->name = ''; $data['model']->position = null; } /** @var PostLayout[] $layouts */ $layouts = PostLayout::entity($entity->entityType)->get(); $layoutDefault = ['' => __('fields.description.label')]; $layoutOptions = $disabledLayoutOptions = []; foreach ($layouts as $layout) { $layoutOptions[$layout->id] = $layout->name(); if (! $campaign->superboosted()) { $disabledLayoutOptions[$layout->id] = true; } } $collator = new Collator(app()->getLocale()); $collator->asort($layoutOptions); $layoutOptions = $layoutDefault + $layoutOptions; return view('entities.pages.posts.create', compact( 'campaign', 'post', 'layoutOptions', 'disabledLayoutOptions', 'entity', 'parentRoute', 'templates', 'template', ))->with($data); } public function show(Campaign $campaign, Entity $entity, Post $post) { $this->campaign($campaign)->authEntityView($entity); if (! request()->json()) { return redirect()->to($entity->url()); } return view('entities.pages.posts.show') ->with('campaign', $campaign) ->with('entity', $entity) ->with('post', $post); } public function store(StorePost $request, Campaign $campaign, Entity $entity) { $this->authorize('post', [$entity, 'add']); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $data = $campaign->superboosted() ? $request->all() : $request->except(['layout_id']); $data['entity_id'] = $entity->id; $post = Post::create($data); if (auth()->user()->can('permissions', $entity)) { $this->savePermissionsService->post($post)->request($request)->save(); } if ($request->has('submit-new')) { $route = route('entities.posts.create', [$campaign, $entity]); return response()->redirectTo($route); } elseif ($request->has('submit-update')) { $route = route('entities.posts.edit', [$campaign, $entity, $post]); return response()->redirectTo($route); } return redirect() ->to($entity->url()) ->with('success', __('entities/notes.create.success', [ 'name' => $post->name, 'entity' => $entity->name, ])); } public function edit(Campaign $campaign, Entity $entity, Post $post) { $this->authorize('post', [$entity, 'edit', $post]); $editingUsers = null; /** @var Post $model */ $model = $post; if ($campaign->hasEditingWarning()) { $editingUsers = $this->editingService->model($model)->user(auth()->user())->users(); // If no one is editing the model, we are now editing it if (empty($editingUsers)) { $this->editingService->edit(); } } $parentRoute = $entity->entityType->pluralCode(); $from = request()->get('from'); return view('entities.pages.posts.edit', compact( 'campaign', 'entity', 'model', 'parentRoute', 'from', 'editingUsers' )); } public function update(StorePost $request, Campaign $campaign, Entity $entity, Post $post) { $this->authorize('post', [$entity, 'edit', $post]); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $data = $request->all(); unset($data['entity_id']); if ($request->isNotFilled('position')) { unset($data['position']); } $post->update($data); if (auth()->user()->can('permissions', $entity)) { $this->savePermissionsService ->post($post) ->request($request) ->save(); } $this->editingService->model($post) ->user($request->user()) ->finish(); if ($request->has('submit-new')) { $route = route('entities.posts.create', [$campaign, $entity]); return response()->redirectTo($route); } elseif ($request->has('submit-update')) { $route = route('entities.posts.edit', [$campaign, $entity, $post]); return response()->redirectTo($route); } return redirect()->route('entities.show', [$campaign, $entity, '#post-' . $post->id]) ->with('success', __('entities/notes.edit.success', [ 'name' => $post->name, 'entity' => $entity->name, ])); } public function destroy(Campaign $campaign, Entity $entity, Post $post) { $this->authorize('post', [$entity, 'delete']); $post->delete(); return redirect() ->route('entities.show', [$campaign, $entity]) ->with('success', __('entities/notes.destroy.success', [ 'name' => $post->name, 'entity' => $entity->name, ])); } } ================================================ FILE: app/Http/Controllers/Entity/Posts/LayoutController.php ================================================ authorize('view', [$entity]); return view('entities.pages.posts.layouts.index') ->with('campaign', $campaign) ->with('entity', $entity); } } ================================================ FILE: app/Http/Controllers/Entity/Posts/LogController.php ================================================ authorize('history', [$entity, $campaign]); $this->authorize('post', [$entity, 'edit', $post]); $fields = ['action']; $expanded = false; if ($campaign->superboosted()) { $fields = ['q', 'action']; if ($request->filled('q')) { $expanded = true; } } $logs = $post ->logs() ->filter($request->only($fields)) ->with(['user', 'impersonator']) ->recent() ->paginate(config('limits.pagination')); $transKey = $entity->entityType->pluralCode(); $q = request()->get('q'); $action = request()->get('action'); $actions = [ '' => __('history.filters.all-actions'), EntityLog::ACTION_CREATE => __('entities/logs.actions.create'), EntityLog::ACTION_UPDATE => __('entities/logs.actions.update'), EntityLog::ACTION_DELETE => __('entities/logs.actions.delete'), ]; return view('entities.pages.posts.logs.index', compact( 'post', 'campaign', 'entity', 'logs', 'campaign', 'transKey', 'q', 'action', 'actions', 'expanded' )); } } ================================================ FILE: app/Http/Controllers/Entity/Posts/MoveController.php ================================================ authorize('update', $entity); return view('entities.pages.posts.move.index', compact( 'entity', 'post', 'campaign', )); } public function move(MovePostRequest $request, Campaign $campaign, Entity $entity, Post $post) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } /** @var Entity|null $newEntity */ $newEntity = Entity::where(['id' => $request['entity']])->first(); $this->authorize('update', $newEntity); try { // Check if the post has a layout and if said layout is compatible with the new entity if ($post->layout_id) { if ($post->layout->entity_type_id !== null && $post->layout->entity_type_id != $newEntity->type_id) { throw new TranslatableException(__('errors.post_layout')); } } $newPost = $this->service ->post($post) ->user($request->user()) ->handle($request); $success = 'move_success'; if (isset($request['copy'])) { $success = 'copy_success'; } return redirect() ->route('entities.show', [$campaign, $newEntity, '#post-' . $newPost->id]) ->with('success', __('entities/notes.move.' . $success, ['name' => $newPost->name, 'entity' => $newEntity->name, ])); } catch (TranslatableException $ex) { return redirect() ->route('entities.show', [$campaign, $entity, '#post-' . $post->id]) ->with('error', __($ex->getMessage(), ['name' => $entity->name])); } } } ================================================ FILE: app/Http/Controllers/Entity/Posts/TemplateController.php ================================================ middleware('auth'); $this->service = $templateService; } public function update(Campaign $campaign, Post $post) { $this->authorize('setPostTemplates', $campaign); if (request()->ajax()) { return response()->json(); } $this->service->post($post)->toggle(); return redirect()->back() ->with( 'success', __('posts/actions.templates.success.' . ($post->isTemplate() ? 'set' : 'unset'), ['name' => $post->name]) ); } } ================================================ FILE: app/Http/Controllers/Entity/Posts/VisibilityController.php ================================================ middleware('auth'); } /** * @return View * * @throws AuthorizationException */ public function index(Campaign $campaign, Entity $entity, Post $post) { $this->authorize('post', [$entity, 'edit', $post]); $this->authorize('visibility', $post); return view('entities.pages.posts.dialogs.visibility', [ 'campaign' => $campaign, 'post' => $post, 'entity' => $entity, ]); } /** * @return JsonResponse * * @throws AuthorizationException */ public function update(EditPostVisibility $request, Campaign $campaign, Entity $entity, Post $post) { $this->authorize('post', [$entity, 'edit', $post]); $this->authorize('visibility', $post); $post->update($request->all()); return response()->json(['toast' => __('visibilities.toast'), 'icon' => $post->visibilityIcon('btn-box-tool'), 'post_id' => $post->id, 'visibility_id' => $post->visibility_id]); } } ================================================ FILE: app/Http/Controllers/Entity/PreviewController.php ================================================ service = $service; $this->recentService = $recentService; } public function index(Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); if (auth()->check()) { $this->recentService ->campaign($campaign) ->user(auth()->user()) ->logView($entity); } return response()->json( $this ->service ->entity($entity) ->campaign($campaign) ->preview() ); } } ================================================ FILE: app/Http/Controllers/Entity/PrivacyController.php ================================================ service = $service; } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function index(Campaign $campaign, Entity $entity) { $this->authorize('privacy', $entity); $visibility = $this->service->entity($entity)->visibilities(); return view('entities.pages.privacy.index') ->with('campaign', $campaign) ->with('entity', $entity) ->with('visibility', $visibility); } /** * Toggle an entity's privacy setting */ public function toggle(Campaign $campaign, Entity $entity) { $this->authorize('privacy', $entity); if ($entity->entityType->isCustom()) { $entity->update(['is_private' => ! $entity->is_private]); } else { $misc = $entity->child; $misc->is_private = ! $misc->is_private; $misc->update(['is_private' => $misc->is_private]); $entity->update(['is_private' => $misc->is_private]); } return response()->json([ 'toast' => __('entities/permissions.quick.success.' . ($entity->is_private ? 'private' : 'public'), [ 'entity' => $entity->name, ]), 'success' => true, 'status' => $entity->is_private, ]); } } ================================================ FILE: app/Http/Controllers/Entity/ProfileController.php ================================================ campaign($campaign)->authEntityView($entity); if (! view()->exists('entities.pages.profile._' . $entity->entityType->code)) { return redirect()->to($entity->url()); } return view('entities.pages.profile.index') ->with('campaign', $campaign) ->with('entity', $entity) ->with('model', $entity->child); } } ================================================ FILE: app/Http/Controllers/Entity/RelationController.php ================================================ connectionService = $connectionService; $this->relationService = $relationService; } public function index(Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); $mode = request()->get('mode', null); if (! in_array($mode, ['map', 'table'])) { $mode = null; } $option = request()->get('option', null); if (! in_array($option, ['related', 'mentions', 'only_relations'])) { $option = null; } $order = request()->get('order', null); $rows = $connections = $connectionService = []; // @phpstan-ignore-next-line $defaultToTable = ! $campaign->boosted() || ($campaign->boosted() && $campaign->defaultToConnection()); if ($mode == 'table' || (empty($mode) && $defaultToTable)) { $mode = 'table'; Datagrid::layout(\App\Renderers\Layouts\Entity\Relation::class) ->route('entities.relations_table', ['campaign' => $campaign, 'entity' => $entity, 'mode' => 'table']); $rows = $entity ->allRelationships() ->sort(request()->only(['o', 'k'])) ->with(['owner', 'target', 'target.location', 'target.location.entity']) ->paginate() ->withPath(route('entities.relations_table', ['campaign' => $campaign, 'entity' => $entity, 'mode' => 'table'])); $connections = $this->connectionService ->entity($entity) ->order($order) ->connections(); $connectionService = $this->connectionService; } // @phpstan-ignore-next-line $defaultToMap = ! $campaign->boosted() || ($campaign->boosted() && $campaign->defaultToConnectionMode()); if ($mode != 'table' && empty($option) && $defaultToMap) { if ($campaign->defaultToConnectionMode() == 1) { $option = 'only_relations'; } elseif ($campaign->defaultToConnectionMode() == 2) { $option = 'related'; } elseif ($campaign->defaultToConnectionMode() == 3) { $option = 'mentions'; } } $entityTypeId = 'connection'; return view('entities.pages.relations.index', compact( 'campaign', 'entity', 'entityTypeId', 'rows', 'mode', 'option', 'connections', 'connectionService', 'campaign' )); } /** * @return Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); $mode = $this->getModeOption(); return view('entities.pages.relations.create', compact( 'campaign', 'entity', 'mode' )); } public function store(StoreRelation $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(['success' => true]); } $this->relationService->campaign($campaign)->createRelations($request); $mode = $this->getModeOption(true); $redirect = [$campaign, $entity]; if (! empty($mode)) { $redirect['mode'] = $mode; } return redirect() ->route('entities.relations.index', $redirect) ->with('success', trans_choice('entities/relations.create.success_bulk', $this->relationService->getCount(), [ 'entity' => $entity->name, 'count' => $this->relationService->getCount(), ])); } // This page doesn't exist, but crawlers will try public function show(Campaign $campaign, Entity $entity, Relation $relation) { abort(404); } public function edit(Campaign $campaign, Entity $entity, Relation $relation) { $this->authorize('update', $entity); $from = request()->get('from', 0); if ($from !== 'web') { $from = (int) $from; } $mode = $this->getModeOption(); return view('entities.pages.relations.update', compact( 'campaign', 'entity', 'relation', 'from', 'mode' )); } public function update(StoreRelation $request, Campaign $campaign, Entity $entity, Relation $relation) { $this->authorize('update', $entity); $data = $request->only(['target_id', 'attitude', 'relation', 'colour', 'is_pinned', 'two_way', 'visibility_id']); if ($request->unmirror && $relation->mirror) { $relation->mirror->update(['mirror_id' => null]); $data['mirror_id'] = null; } $relation->update($data); $relation->refresh(); $mode = $this->getModeOption(); if (request()->has('from')) { $from = request()->post('from'); if ($from === 'web') { return response() ->json([ 'updated' => true, 'id' => $relation->id, 'colour' => $relation->colour, 'attitude' => $relation->attitude, 'text' => $relation->relation, 'target' => (new EntityResource($relation->target))->campaign($campaign), ]); } elseif (! empty($from)) { $redirect = [$campaign, (int) $from]; if (! empty($mode)) { $redirect['mode'] = $mode; } if (request()->has('option')) { $redirect['option'] = request()->get('option'); } return redirect() ->route('entities.relations.index', $redirect) ->with('success', trans('entities/relations' . '.update.success', [ 'target' => $relation->target->name, 'entity' => $entity->name, ])); } } $redirect = [$campaign, $entity]; if (! empty($mode)) { $redirect['mode'] = $mode; } if (request()->has('option')) { $redirect['option'] = request()->get('option'); } return redirect() ->route('entities.relations.index', $redirect) ->with('success', __('entities/relations' . '.update.success', [ 'target' => $relation->target->name, 'entity' => $entity->name, ])); } public function destroy(Campaign $campaign, Entity $entity, Relation $relation) { $this->authorize('update', $entity); if (request()->has('mode')) { $mode = request()->get('mode'); } else { $mode = $this->getModeOption(); } $deletedMirror = false; if (request()->get('remove_mirrored') === '1' && $relation->isMirrored()) { $mirror = $relation->mirror; if (! empty($mirror) && auth()->user()->can('relation', [$relation->target, 'delete'])) { $mirror->delete(); $deletedMirror = true; } } // Update the mirror to remove it's mirrored status if ($deletedMirror === false && $relation->isMirrored()) { $mirror = $relation->mirror; $mirror->update([ 'mirror_id' => null, ]); } $relation->delete(); $redirect = [$campaign, $entity]; if (! empty($mode)) { $redirect['mode'] = $mode; } if (request()->has('option')) { $redirect['option'] = request()->get('option'); } if (request()->get('from') === 'web') { return response() ->json(['deleted' => true, 'id' => $relation->id]); } return redirect() ->route('entities.relations.index', $redirect) ->with('success', trans('entities/relations.destroy.success', [ 'target' => $relation->target->name, 'entity' => $entity->name, ])); } protected function getModeOption(bool $post = false) { $mode = request()->get('mode'); if ($post) { $mode = request()->post('mode'); } if (in_array($mode, ['mode', 'table'])) { return $mode; } return null; } } ================================================ FILE: app/Http/Controllers/Entity/ReminderController.php ================================================ calendarService = $calendarService; } public function index(Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); if (! $campaign->enabled('calendars')) { return redirect()->route('entities.show', [$campaign, $entity])->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } $options = ['campaign' => $campaign, 'entity' => $entity]; Datagrid::layout(\App\Renderers\Layouts\Entity\Reminder::class) ->route('entities.reminders.index', $options); $this->rows = $entity ->reminders() ->has('calendar') ->has('calendar.entity') ->with(['calendar', 'calendar.entity', 'remindable']) ->sort(request()->only(['o', 'k'])) ->paginate(); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return view('entities.pages.reminders.index') ->with('campaign', $campaign) ->with('entity', $entity) ->with('rows', $this->rows); } public function show(Campaign $campaign, Entity $entity, Reminder $reminder) { return redirect() ->route('entities.reminders.index', [$campaign, $entity]); } public function create(Campaign $campaign, Entity $entity) { $this->authorize('reminders', $entity); $name = $this->view; $route = $this->route; $parent = explode('.', $this->view)[0]; $next = request()->get('next', null); $calendars = Calendar::get(); return view('calendars.reminders.create_from_entity', compact( 'campaign', 'entity', 'name', 'route', 'parent', 'next', 'entity', 'calendars', )); } public function store(AddCalendarEvent $request, Campaign $campaign, Entity $entity) { $this->authorize('reminders', $entity); if (request()->ajax()) { return response()->json(['success' => true]); } // Since reminders are polymorphic, we need to attach them to the proper model $entity = Entity::findOrFail($request->get('entity_id')); $data = [ 'calendar_id' => $request->get('calendar_id'), 'day' => $request->get('day'), 'month' => $request->get('month'), 'year' => $request->get('year'), 'comment' => $request->get('comment'), 'length' => $request->get('length'), 'colour' => $request->get('colour'), 'recurring_periodicity' => $request->get('recurring_periodicity'), 'recurring_until' => $request->get('recurring_until'), 'visibility_id' => $request->get('visibility_id'), 'type_id' => $request->get('type_id'), ]; $entity->reminders()->create($data); // $reminder = new Reminder($request->all()); // $reminder->entity_id = $entity->id; // $reminder->save(); $next = request()->post('next', '0'); if ($next == 'entity.events') { return redirect() ->route('entities.reminders.index', [$campaign, $entity]) ->with('success', __('calendars.event.create.success')); } return redirect() ->route('entities.reminders.index', [$campaign, $entity]) ->with('success', __('calendars.event.create.success')); } } ================================================ FILE: app/Http/Controllers/Entity/ShareController.php ================================================ authorize('update', $entity); return view('entities.pages.share.setup', compact( 'campaign', 'entity', )); } /** * @throws AuthorizationException */ public function save(StoreShare $request, Campaign $campaign, Entity $entity): JsonResponse { $this->authorize('update', $entity); $visibilityMode = $request->input('visibility_mode'); $campaignVisibility = $request->input('campaign_visibility'); if ($campaignVisibility === 'public') { $this->authorize('update', $campaign); } if ($visibilityMode) { $this->shareService ->campaign($campaign) ->entity($entity); if ($visibilityMode === 'entity') { $this->shareService->shareEntity(); } elseif ($visibilityMode === 'global') { $this->shareService->shareGlobal(); } $entity->refresh(); } elseif ($campaignVisibility === 'public') { $this->campaignShareService ->campaign($campaign) ->makePublic(); } return response()->json([ 'success' => true, 'campaign_public' => $campaign->isPublic(), 'entity_private' => (bool) $entity->is_private, ]); } } ================================================ FILE: app/Http/Controllers/Entity/ShowController.php ================================================ middleware([CachedResponse::class]); } public function index(Request $request, Campaign $campaign, Entity $entity) { $this->campaign($campaign)->authEntityView($entity); /*if ($entity->slug !== $slug) { return redirect()->route('entities.show', [$campaign, $entity, $entity->slug]); }*/ // Perf trick if ($entity->hasChild()) { $entity->child->setRelation('entity', $entity); } $bookmark = null; if ($request->filled('bookmark')) { $bookmark = Bookmark::where('id', $request->get('bookmark')) ->where('campaign_id', $campaign->id) ->first(); } return view('cruds.show') ->with('campaign', $campaign) ->with('entity', $entity) ->with('entityType', $entity->entityType) ->with('bookmark', $bookmark); } } ================================================ FILE: app/Http/Controllers/Entity/StoryController.php ================================================ service = $service; } public function edit(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); return view('entities.pages.story.reorder', compact( 'campaign', 'entity' )); } public function save(ReorderStories $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(); } $this->service ->entity($entity) ->user($request->user()) ->reorder($request); return redirect() ->to($entity->url()) ->with('success', __('entities/story.reorder.success')); } /** * Load more posts to display to the user (partial view) */ public function more(Campaign $campaign, Entity $entity) { $this->authorize('view', $entity); $pagination = app()->isProduction() ? 15 : 6; $posts = $entity->posts()->with(['permissions', 'location', 'layout'])->ordered()->paginate($pagination); return view('entities.components.posts') ->with('entity', $entity) ->with('more', true) ->with('campaign', $campaign) ->with('posts', $posts); } } ================================================ FILE: app/Http/Controllers/Entity/TagController.php ================================================ tagService = $tagService; } /** * @return Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); $formOptions = ['entity.tags-add.save', $campaign, 'entity' => $entity]; return view('entities.pages.tags.create', [ 'campaign' => $campaign, 'entity' => $entity, 'formOptions' => $formOptions, ]); } /** * @throws AuthorizationException */ public function store(UpdateEntityTags $request, Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); if ($request->ajax()) { return response()->json(); } $ids = request()->post('tags', []); if (empty($ids)) { $ids = []; } $this->tagService ->user($request->user()) ->entity($entity) ->withNew() ->sync($ids); $entity->touch(); return redirect()->route('entities.show', [$campaign, $entity]) ->with('success', __('tags.children.create.attach_success_entity', ['name' => $entity->name])); } } ================================================ FILE: app/Http/Controllers/Entity/TemplateController.php ================================================ middleware('auth'); $this->service = $templateService; } public function update(Campaign $campaign, Entity $entity) { $this->authorize('update', $entity); $this->authorize('setTemplates', $campaign); if (request()->ajax()) { return response()->json(); } $this->service->entity($entity)->toggle(); if ($entity->isTemplate()) { session()->flash('success_docs', 'guides/archetypes'); } return redirect()->back() ->with( 'success_raw', __('entries/archetypes.success.' . ($entity->isTemplate() ? 'set' : 'unset'), ['name' => $entity->name]) ); } } ================================================ FILE: app/Http/Controllers/Entity/TooltipController.php ================================================ campaign($campaign)->authEntityView($entity); $tags = $entity->tags; $tagClasses = []; foreach ($tags as $tag) { $tagClasses[] = 'kanka-tag-' . $tag->id; $tagClasses[] = 'kanka-tag-' . $tag->slug; } $render = request()->get('render'); $hasImage = Avatar::entity($entity)->hasImage(); $tooltipText = $this->tooltipService->entity($entity)->campaign($campaign)->tooltip(); $tooltip = view('entities.components.tooltip') ->with('campaign', $campaign) ->with('entity', $entity) ->with('tags', $entity->visibleTags()) ->with('hasImage', $hasImage) ->with('tagClasses', $tagClasses) ->with('render', $render) ->with('tooltip', $tooltipText) ->render(); return response()->json([ $tooltip, ]); } } ================================================ FILE: app/Http/Controllers/Entity/TransformController.php ================================================ authorize('move', $entity); $entities = $this->entityTypeService ->campaign($campaign) ->exclude([$entity->entityType->id, config('entities.ids.bookmark')]) ->prepend(['' => __('entities/transform.fields.select_one')]) ->toSelect(); $confirm = $this->transformService->entity($entity)->confirm(); return view('entities.pages.transform.index', compact( 'campaign', 'entity', 'entities', 'campaign', 'confirm' )); } public function transform(TransformEntityRequest $request, Campaign $campaign, Entity $entity) { $this->authorize('move', $entity); if (request()->ajax()) { return response()->json(['success' => true]); } try { /** @var EntityType $entityType */ $entityType = EntityType::inCampaign($campaign)->find($request->get('target')); $this->transformService ->campaign($campaign) ->entity($entity) ->entityType($entityType) ->transform(); return redirect() ->to($entity->url()) ->with('success', __('entities/transform.success', ['name' => $entity->name])); } catch (TranslatableException $ex) { return redirect() ->route('entities.show', [$campaign, $entity]) ->with('error', __($ex->getMessage(), ['name' => $entity->name])); } } } ================================================ FILE: app/Http/Controllers/EntityCreatorController.php ================================================ middleware('auth'); } /** * @return Factory|View */ public function selection(Request $request, Campaign $campaign) { $this->campaign = $campaign; return $this->renderSelection(null); } public function form(Request $request, Campaign $campaign, EntityType $entityType) { return $this->renderForm($request, $campaign, $entityType); } public function post(Request $request, Campaign $campaign) { return $this->renderForm($request, $campaign); } public function store(StoreEntity $request, Campaign $campaign, EntityType $entityType) { $this->authorize('create', [$entityType, $campaign]); $this->campaign = $campaign; $this->processService ->campaign($campaign) ->user($request->user()) ->entityType($entityType) ->request($request) ->entity(); // Have a target? Return json for the js to handle it instead $first = $this->processService->first(); if ($request->has('_target')) { return response()->json([ '_target' => $request->get('_target'), '_id' => $first->entityType->isCustom() ? $first->id : $first->child->id, '_name' => $first->name, '_multi' => $request->get('_multi'), ]); } // Redirect the user to the edit form if ($request->get('action') == 'edit') { $entity = $first instanceof Post ? $first->entity : $first; $editUrl = route('entities.edit', [$campaign, $entity]); return response()->json([ 'redirect' => $editUrl, ]); } $successKey = 'entities.creator.success_multiple'; $success = trans_choice( $successKey, $this->processService->count(), ['link' => implode(', ', $this->processService->links())] ); // Continue creating more of the same kind if ($request->get('action') == 'more') { return $this->renderForm(new Request, $campaign, $entityType, $success); } return $this->renderSelection($success); } public function storePost(StorePost $request, Campaign $campaign) { // Make sure the user is allowed to create this kind of entity $this->campaign = $campaign; $this->authorize('recover', $this->campaign); $this->processService ->campaign($campaign) ->request($request) ->post(); // Have a target? Return json for the js to handle it instead $first = $this->processService->first(); if ($request->has('_target')) { return response()->json([ '_target' => $request->get('_target'), '_id' => $first->id, '_name' => $first->name, '_multi' => $request->get('_multi'), ]); } // Redirect the user to the edit form if ($request->get('action') == 'edit') { $editUrl = route('entities.posts.edit', [$campaign, $first->entity_id, $first->id]); return response()->json([ 'redirect' => $editUrl, ]); } $successKey = 'entities.creator.success_multiple_posts'; $success = trans_choice( $successKey, $this->processService->count(), ['link' => implode(', ', $this->processService->links())] ); // Continue creating more of the same kind if ($request->get('action') == 'more') { return $this->renderForm(new Request, $campaign, null, $success); } return $this->renderSelection($success); } protected function renderSelection(?string $success) { // Content for the selector $orderedEntityTypes = $this->orderedEntityTypes(); return view('entities.creator.selection', [ 'campaign' => $this->campaign, 'entityTypes' => $orderedEntityTypes, 'new' => $success, 'popular' => $this->popularService->campaign($this->campaign)->user(auth()->user())->get(), ]); } protected function renderForm(Request $request, Campaign $campaign, ?EntityType $entityType = null, ?string $success = null) { $this->campaign = $campaign; // Make sure the user is allowed to create this kind of entity if (! isset($entityType)) { $this->authorize('recover', $campaign); } else { $this->authorize('create', [$entityType, $campaign]); } $origin = $request->get('origin'); $target = $request->get('target'); $multi = $request->get('multi'); $mode = $request->get('mode'); $source = $templates = null; $view = 'form'; if ($mode === 'templates' && isset($entityType)) { $templates = Entity::select('id', 'name', 'entity_id') ->templates($entityType->id) ->get(); $view = 'templates'; } $orderedEntityTypes = $this->orderedEntityTypes(); if (isset($entityType)) { $newLabel = __($entityType->pluralCode() . '.create.title'); $singular = Module::singular($entityType->id); if ($entityType->isCustom()) { $singular = $entityType->name(); } if (! empty($singular)) { $newLabel = __('crud.titles.new', ['module' => $singular]); } } else { $newLabel = __('posts.create.title'); $singular = __('entities.article'); } return view('entities.creator.' . $view, compact( 'campaign', 'entityType', 'entityType', 'origin', 'target', 'multi', 'mode', 'source', 'templates', 'orderedEntityTypes', 'success', 'newLabel', 'singular', )) ->with('campaign', $this->campaign); } /** * Ordered entity types alphabetically to the user's local */ protected function orderedEntityTypes(): Collection { $types = $this->entityTypeService ->campaign($this->campaign) ->user(auth()->user()) ->exclude([config('entities.ids.bookmark'), config('entities.ids.whiteboard')]) ->creatable() ->ordered(); if (auth()->user()->can('recover', $this->campaign)) { $types->add(__('entities.articles')); } return $types; } } ================================================ FILE: app/Http/Controllers/Events/EventController.php ================================================ campaign($campaign)->authEntityView($event->entity); return redirect()->route('entities.children', [$campaign, $event->entity]); } } ================================================ FILE: app/Http/Controllers/Facebook/DeletionController.php ================================================ input('signed_request'); if (! $signedRequest) { return response()->json(['error' => 'missing signed_request'], 400); } $data = $this->parseSignedRequest($signedRequest, config('services.facebook.client_secret')); if (! $data || ! isset($data['user_id'])) { return response()->json(['error' => 'invalid signed_request'], 400); } $userId = $data['user_id']; $confirmation = 'fbdel_' . $userId . '_' . time(); $user = User::where(['provider' => 'facebook', 'provider_id' => $userId])->first(); if ($user) { Log::info('Facebook Deletion', ['user' => $user->id]); DeleteUser::dispatch($user); JobLog::create([ 'name' => 'facebook:user-deletion', 'result' => $user->id, ]); } return response()->json([ 'url' => url('/facebook/data-deletion/status?code=' . $confirmation), 'confirmation_code' => $confirmation, ]); } public function status(Request $request) { $code = $request->query('code'); return "Data deletion processed. Confirmation code: {$code}"; } public function generate(Request $request) { if (app()->isProduction()) { abort(404); } $secret = config('services.facebook.client_secret'); $payload = json_encode([ 'user_id' => $request->query('user_id', '123456789'), 'issued_at' => time(), ]); $payloadB64 = rtrim(strtr(base64_encode($payload), '+/', '-_'), '='); $sig = hash_hmac('sha256', $payloadB64, $secret, true); $sigB64 = rtrim(strtr(base64_encode($sig), '+/', '-_'), '='); return $sigB64 . '.' . $payloadB64; } private function parseSignedRequest($signedRequest, $secret) { [$encodedSig, $payload] = explode('.', $signedRequest, 2); $sig = $this->base64UrlDecode($encodedSig); $data = json_decode($this->base64UrlDecode($payload), true); $expectedSig = hash_hmac('sha256', $payload, $secret, true); if (! hash_equals($expectedSig, $sig)) { return null; } return $data; } private function base64UrlDecode($input) { return base64_decode(strtr($input, '-_', '+/')); } } ================================================ FILE: app/Http/Controllers/Families/FamilyController.php ================================================ campaign($campaign)->authEntityView($family->entity); return redirect()->route('entities.children', [$campaign, $family->entity]); } } ================================================ FILE: app/Http/Controllers/Families/MemberController.php ================================================ campaign($campaign)->authEntityView($family->entity); $options = ['campaign' => $campaign, 'family' => $family, 'm' => $this->descendantsMode()]; $relation = 'allMembers'; if ($this->filterToDirect()) { $relation = 'members'; } Datagrid::layout(Character::class) ->route('families.members', $options); $this->rows = $family ->{$relation}() ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->with([ 'characterFamilies', 'entity', 'entity.tags', 'entity.image', 'entity.entityType', 'entity.entityLocations', ]) ->has('entity') ->paginate(); // Ajax Datagrid if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('families.members', $family); } public function create(Campaign $campaign, Family $family) { $this->authorize('update', $family->entity); return view('families.members.create', [ 'campaign' => $campaign, 'model' => $family, ]); } public function store(StoreCharacterFamily $request, Campaign $campaign, Family $family) { $this->authorize('update', $family->entity); if ($request->ajax()) { return response()->json(['success' => true]); } $newMembers = $family->members()->syncWithoutDetaching($request->characters); return redirect()->route('entities.show', [$campaign, $family->entity]) ->with('success', trans_choice('families.members.create.success', count($newMembers['attached']), ['count' => count($newMembers['attached'])])); } } ================================================ FILE: app/Http/Controllers/Families/TreeController.php ================================================ campaign($campaign)->authEntityView($family->entity); return view('families.trees.index') ->with('family', $family) ->with('campaign', $campaign) ->with('mode', 'vue'); } } ================================================ FILE: app/Http/Controllers/Families/Trees/ApiController.php ================================================ service = $service; } /** * Provide the family tree info as a json */ public function index(Campaign $campaign, Family $family): JsonResponse { $this->campaign($campaign)->authEntityView($family->entity); if (auth()->check()) { $this->service->user(auth()->user()); } return response()->json( $this ->service ->campaign($campaign) ->family($family) ->api() ); } /** * Provide the entity info as a json */ public function entity(Campaign $campaign, Entity $entity): JsonResponse { if (empty($entity->child)) { abort(404); } $this->authorize('view', $entity); return response()->json( $this ->service ->entity($entity) ); } /** * Save the new config */ public function save(Request $request, Campaign $campaign, Family $family): JsonResponse { $this->authorize('update', $family->entity); if (! $campaign->premium()) { return response()->json('You need to activate premium functions on the campaign to use this feature', 204); } return response()->json( $this ->service ->family($family) ->campaign($campaign) ->save($request->get('data')) ->api() ); } } ================================================ FILE: app/Http/Controllers/Filters/FormController.php ================================================ middleware(['auth']); } public function index(Request $request, Campaign $campaign, EntityType $entityType) { $this->authorize('access', $campaign); $plural = Str::plural(Str::remove('-', $entityType->code)); $route = $entityType->hasEntity() ? 'entities.index' : $plural . '.index'; $this->entityType($entityType); if ($entityType->isCustom()) { $this->filterService ->request($request) ->entityType($entityType) ->campaign($campaign) ->build(); /** @var CustomEntityFilter $filters */ $filters = app()->make(CustomEntityFilter::class); $filters->campaign($campaign)->entityType($entityType)->build(); return view('filters.form') ->with('filters', $filters->filters()) ->with('campaign', $campaign) ->with('entityType', $entityType) ->with('filterService', $this->filterService); } $model = $entityType->getClass(); try { return $this->campaign($campaign)->render($model, $plural, $route); } catch (Exception $e) { if (app()->hasDebugModeEnabled()) { throw $e; } return redirect()->route('dashboard', $campaign); } } public function connection(Campaign $campaign) { $this->authorize('access', $campaign); $route = 'relations.index'; $model = new Relation; $plural = 'relations'; try { return $this->campaign($campaign)->render($model, $plural, $route, 'entities/relations'); } catch (Exception $e) { return redirect()->route('dashboard', $campaign); } } protected function render(mixed $model, string $plural, string $route, ?string $langKey = null) { if (isset($this->entityType)) { $this->filterService ->entityType($this->entityType) ->campaign($this->campaign) ->build(); } else { $this->filterService ->campaign($this->campaign) ->model($model) ->make($plural); } $mode = request()->get('m'); $reflect = new ReflectionClass($model); $filtersClass = 'App\Datagrids\Filters\\' . $reflect->getShortName() . 'Filter'; /** @var CharacterFilter $filters */ $filters = app()->make($filtersClass); if ($filters) { $filters->campaign($this->campaign)->build(); } return view('filters.form') ->with('campaign', $this->campaign) ->with('filters', $filters->filters()) ->with('filterService', $this->filterService) ->with('route', $route) ->with('entityModel', $model) ->with('entityType', $this->entityType ?? null) ->with('count', 0) ->with('langKey', $langKey ?? $plural) ->with('hasAttributeFilters', false) ->with('activeFilters', $this->filterService->activeFiltersCount()) ->with('clipboardFilters', $this->filterService->clipboardFilters()) ->with('mode', $mode); } } ================================================ FILE: app/Http/Controllers/Filters/SaveController.php ================================================ middleware(['auth']); } protected function render(Campaign $campaign, EntityType $entityType) { $mode = request()->get('m'); $parents = $this->sidebarService->campaign($campaign)->availableParents(); return view('filters.save_form') ->with('campaign', $campaign) ->with('entityType', $entityType) ->with('parents', $parents) ->with('mode', $mode); } public function save(Campaign $campaign, EntityType $entityType) { $this->authorize('create', Bookmark::class); if (request()->ajax()) { return response()->json(['success' => true]); } // Check limit if (Bookmark::count() >= config('limits.campaigns.bookmarks') && ! $campaign->boosted()) { return redirect()->back()->withErrors(__('filters.bookmark.premium')); } $this->filterService ->entityType($entityType) ->campaign($campaign) ->build(); $filters = $entityType->isCustom() ? $this->filterService->clipboardFilters() : 'm=' . request()->get('m') . '&' . $this->filterService->clipboardFilters(); $bookmark = new Bookmark; $bookmark->campaign_id = $campaign->id; $bookmark->name = request()->get('name', __('filters.bookmark.name', ['module' => $entityType->plural()])); $bookmark->icon = request()->get('icon', null); $bookmark->entity_type_id = $entityType->id; $bookmark->filters = $filters; $bookmark->parent = $entityType->isCustom() ? null : $entityType->pluralCode(); $bookmark->save(); $route = $this->routingService->campaign($campaign)->bookmark($bookmark)->url(); return redirect()->to($route)->withSuccess(__('filters.bookmark.success')); } } ================================================ FILE: app/Http/Controllers/Front/HelperController.php ================================================ get('type'); if (empty($type)) { return redirect()->route('home'); } // Try creating the object try { $className = "\App\Models\\" . Str::camel($type); $misc = new \ReflectionClass($className); if (! $misc->isInstantiable()) { abort(404); } /** @var MiscModel $misc */ $misc = new $className; if (! $misc instanceof MiscModel) { abort(404); } // @phpstan-ignore-next-line $filters = $misc->getFilterableColumns(); return view('helpers.api-filters', compact( 'filters', 'type' )); } catch (Exception $e) { abort(404); } } } ================================================ FILE: app/Http/Controllers/FrontendPrepareController.php ================================================ json(); } } ================================================ FILE: app/Http/Controllers/Gallery/BrowseController.php ================================================ service = $browseService; } public function index(Request $request, Campaign $campaign) { $this->authorize('galleryBrowse', $campaign); return response()->json( $this->service ->campaign($campaign) ->user($request->user()) ->folder($request->get('folder')) ->term($request->get('term')) ->images() ); } } ================================================ FILE: app/Http/Controllers/Gallery/CreateController.php ================================================ middleware('auth'); $this->service = $service; } public function index(Campaign $campaign, CreateFolder $request) { $this->authorize('gallery', $campaign); $folder = $this->service ->campaign($campaign) ->create($request); return response()->json(['folder' => $folder]); } } ================================================ FILE: app/Http/Controllers/Gallery/DeleteController.php ================================================ middleware('auth'); $this->service = $service; $this->storage = $storageService; } public function destroy(Campaign $campaign, DeleteImages $request) { $this->authorize('gallery', $campaign); $count = $this->service ->campaign($campaign) ->delete($request->get('images')); $this->storage->campaign($campaign)->clearCache(); return response()->json([ 'toast' => trans_choice('gallery.delete.success', $count, ['count' => $count]), 'used' => $this->storage->uncachedUsedSpace(), ]); } public function file(Campaign $campaign, Image $image) { $this->authorize('gallery', $campaign); $image->delete(); $this->storage->campaign($campaign)->clearCache(); return response()->json([ 'used' => $this->storage->uncachedUsedSpace(), ]); } } ================================================ FILE: app/Http/Controllers/Gallery/ImageController.php ================================================ middleware('auth'); $this->service = $service; } public function show(Campaign $campaign, Image $image) { $this->authorize('gallery', $campaign); if ($image->isFolder()) { return response()->json( $this->service ->user(auth()->user()) ->campaign($campaign) ->image($image) ->filters(request()->only('unused')) ->sort(request()->only('sort')) ->open() ); } } } ================================================ FILE: app/Http/Controllers/Gallery/SearchController.php ================================================ middleware('auth'); $this->service = $service; } public function index(Request $request, Campaign $campaign) { $this->authorize('gallery', $campaign); $term = mb_trim($request->get('term') ?? ''); return response()->json( $this->service ->user($request->user()) ->campaign($campaign) ->term($term) ->filters($request->only('unused')) ->sort($request->only('sort')) ->search() ); } } ================================================ FILE: app/Http/Controllers/Gallery/SetupController.php ================================================ middleware('auth'); $this->service = $service; } public function index(Campaign $campaign) { $this->authorize('gallery', $campaign); return response()->json( $this->service ->user(auth()->user()) ->campaign($campaign) ->setup() ); } } ================================================ FILE: app/Http/Controllers/Gallery/ShowController.php ================================================ middleware('auth'); } public function show(Campaign $campaign, Image $image) { $this->authorize('gallery', $campaign); return new GalleryFileFull($image); } } ================================================ FILE: app/Http/Controllers/Gallery/TiptapController.php ================================================ authorize('galleryBrowse', $campaign); return ImageResource::collection( $this->service ->campaign($campaign) ->user($request->user()) ->request($request) ->images() ); } } ================================================ FILE: app/Http/Controllers/Gallery/UpdateController.php ================================================ middleware('auth'); $this->service = $service; } public function bulk(UpdateFiles $request, Campaign $campaign) { $this->authorize('gallery', $campaign); $count = $this->service ->campaign($campaign) ->files($request->get('files')) ->update($request->only('visibility_id', 'folder_id', 'folder_home')); return response()->json(['toast' => trans_choice('gallery.update.success', $count, ['count' => $count])]); } public function process(UpdateFile $request, Campaign $campaign, Image $image) { $this->authorize('gallery', $campaign); $image->update( $request->only(['name', 'visibility_id']) ); return (new GalleryFile($image))->campaign($campaign); } public function focus(UpdateFocus $request, Campaign $campaign, Image $image) { $this->authorize('gallery', $campaign); $image->update( $request->only(['focus_x', 'focus_y']) ); return (new GalleryFile($image))->campaign($campaign); } } ================================================ FILE: app/Http/Controllers/Gallery/UploadController.php ================================================ service = $uploadService; $this->storage = $storageService; } public function file(UploadFile $request, Campaign $campaign) { $this->authorize('galleryUpload', $campaign); try { return response()->json( $this->service ->campaign($campaign) ->user($request->user()) ->file($request->file('file')) ); } catch (TranslatableException $e) { return response()->json( ['error' => $e->getTranslatedMessage()], 421 ); } } public function files(UploadFiles $request, Campaign $campaign) { $this->authorize('galleryUpload', $campaign); try { $files = $this->service ->campaign($campaign) ->user($request->user()) ->folder($request->get('folder_id', '')) ->files((array) $request->file('files')); $this->storage->campaign($campaign)->clearCache(); return response()->json([ 'files' => $files, 'used' => $this->storage->uncachedUsedSpace(), ]); } catch (TranslatableException $e) { return response()->json( ['error' => $e->getTranslatedMessage()], 421 ); } } public function url(UploadUrl $request, Campaign $campaign) { $this->authorize('galleryUpload', $campaign); try { return response()->json( $this->service ->campaign($campaign) ->user($request->user()) ->request($request) ->url($request->get('url')) ); } catch (TranslatableException $e) { return response()->json( ['error' => $e->getTranslatedMessage()], 421 ); } catch (DecoderException) { return response()->json( ['error' => __('gallery.download.errors.invalid_url')], 422 ); } } } ================================================ FILE: app/Http/Controllers/Gallery/VisibilityController.php ================================================ authorize('gallery', $campaign); return view('gallery.file.visibility.edit') ->with('campaign', $campaign) ->with('image', $image); } public function save(EditPostVisibility $request, Campaign $campaign, Image $image) { $this->authorize('gallery', $campaign); if (request()->ajax()) { return response()->json(); } $image->visibility_id = $request->visibility_id; $image->save(); return redirect()->back()->withSuccess(__('entities/image.visibility.updated')); } } ================================================ FILE: app/Http/Controllers/GalleryController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { $this->authorize('gallery', $campaign); $folder = null; $folderId = request()->get('folder_id'); if (! empty($folderId)) { $folder = Image::where('is_folder', '1')->where('id', $folderId)->firstOrFail(); } return view('gallery.index', compact('campaign', 'folder')); } } ================================================ FILE: app/Http/Controllers/HealthController.php ================================================ hasDebugModeEnabled()) { throw $e; } report($e); $exception = $e->getMessage(); } return response()->view('health', [ 'exception' => $exception, ], status: $exception ? 500 : 200); } } ================================================ FILE: app/Http/Controllers/HistoryController.php ================================================ middleware('auth'); } public function index(HistoryRequest $request, Campaign $campaign) { $this->authorize('recover', $campaign); $pagnation = $campaign->superboosted() ? 25 : 10; $models = EntityLog::where(function ($query) use ($campaign) { $query->where(function ($sub) use ($campaign) { $sub->where('parent_type', Entity::class) ->whereIn('parent_id', function ($entities) use ($campaign) { $entities ->select('id') ->from('entities') ->where('entities.campaign_id', $campaign->id); }); })->orWhere(function ($query) use ($campaign) { $query->where('parent_type', Post::class) ->whereIn('parent_id', Post::has('entity') ->join('entities', 'entities.id', '=', 'posts.entity_id') ->where('entities.campaign_id', $campaign->id) ->select('posts.id')); }); }) ->with([ 'user', 'impersonator', 'parent' => function ($morphTo) { $morphTo->withTrashed()->morphWith([ Entity::class => ['entityType'], Post::class => ['entity' => fn ($q) => $q->withTrashed(), 'entity.entityType'], ]); }, ]) ->filter($request->only('action', 'user')) ->orderBy('entity_logs.created_at', 'desc') // ->where('parent.campaign_id', '=', $campaign->id) ->paginate($pagnation); $previous = null; $superboosted = $campaign->superboosted(); $users = $campaign ->members() ->leftJoin('users as u', 'u.id', 'campaign_user.user_id') ->with(['user']) ->orderBy('u.name') ->get(); $user = $request->get('user'); $action = $request->get('action'); $actions = [ '' => __('history.filters.all-actions'), EntityLog::ACTION_CREATE => __('entities/logs.actions.create'), EntityLog::ACTION_UPDATE => __('entities/logs.actions.update'), EntityLog::ACTION_DELETE => __('entities/logs.actions.delete'), EntityLog::ACTION_RESTORE => __('entities/logs.actions.restore'), ]; $filters = []; if (! empty($user)) { $filters['user'] = (int) $user; } if (! empty($action)) { $filters['action'] = (int) $action; } return view('history.index', compact( 'models', 'previous', 'campaign', 'superboosted', 'users', 'user', 'action', 'actions', 'filters', )); } } ================================================ FILE: app/Http/Controllers/HomeController.php ================================================ guest()) { return redirect()->route('login'); } return $this->back(); } /** * When a user hits /, or logs in, figure out where to take them. * Last campaign? Any campaign? Create a new campaign? */ protected function back() { // If we have a success message, save it for the redirect (for example when deleting a campaign) $with = Session::get('success'); // Go to the user's last campaign, if any $last = auth()->user()->last_campaign_id; if (! empty($last)) { /** @var ?Campaign $lastCampaign */ $lastCampaign = Campaign::acl($last)->first(); if ($lastCampaign) { return redirect()->route('dashboard', $last)->with('success', $with); } } // No valid last campaign? Let's redirect to the last campaign the user had $campaigns = auth()->user()->campaigns; foreach ($campaigns as $campaign) { return redirect()->route('dashboard', $campaign)->with('success', $with); } // No campaign? Ask the user to create one return redirect()->route('start')->with('success', $with); } } ================================================ FILE: app/Http/Controllers/InvitationController.php ================================================ inviteService = $inviteService; $this->campaignService = $campaignService; } /** * @param string $token * @return RedirectResponse */ public function join($token) { try { if (auth()->check()) { $this->inviteService ->user(auth()->user()); } $campaign = $this->inviteService ->useToken($token) ->campaign(); if (auth()->check()) { $this->campaignService ->user(auth()->user()) ->campaign($campaign) ->set(); } return redirect()->to('/'); } catch (RequireLoginException $e) { return redirect()->route('login')->with('info', $e->getMessage()); } catch (Exception $e) { if (auth()->guest()) { return redirect()->route('login')->withErrors($e->getMessage()); } // Let's redirect the user to their first campaign, to handle the error message, or on start otherwise $campaign = auth()->user()->campaigns->first(); if (! $campaign) { return redirect()->route('start')->withError($e->getMessage()); } return redirect() ->route('dashboard', $campaign) ->withErrors($e->getMessage()); } } } ================================================ FILE: app/Http/Controllers/Items/EntityController.php ================================================ campaign($campaign)->authEntityView($item->entity); $options = ['campaign' => $campaign, 'item' => $item]; Datagrid::layout(Entity::class) ->route('items.inventories', $options); $this->rows = $item ->entities() ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->with(['image', 'entityType', 'tags']) ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('items.entities', $item); } } ================================================ FILE: app/Http/Controllers/Items/ItemController.php ================================================ campaign($campaign)->authEntityView($item->entity); return redirect()->route('entities.children', [$campaign, $item->entity]); } } ================================================ FILE: app/Http/Controllers/Journals/JournalController.php ================================================ campaign($campaign)->authEntityView($journal->entity); return redirect()->route('entities.children', [$campaign, $journal->entity]); } } ================================================ FILE: app/Http/Controllers/Layout/NavigationController.php ================================================ middleware('auth'); } public function index() { $data = $this->navigationService ->user(auth()->user()) ->data(); return response()->json( $data ); } } ================================================ FILE: app/Http/Controllers/Locations/CharacterController.php ================================================ campaign($campaign)->authEntityView($location->entity); $options = ['campaign' => $campaign, 'location' => $location, 'm' => $this->descendantsMode()]; $filters = []; if ($this->filterToDirect()) { $filters['location_id'] = $location->id; } Datagrid::layout(Character::class) ->route('locations.characters', $options); $this->rows = $location ->allCharacters($this->filterToDirect()) ->filter($filters) ->filteredCharacters() ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('locations.characters', $location); } } ================================================ FILE: app/Http/Controllers/Locations/EventController.php ================================================ campaign($campaign)->authEntityView($location->entity); $options = ['campaign' => $campaign, 'location' => $location, 'm' => $this->descendantsMode()]; $filters = []; if ($this->filterToDirect()) { $filters['locations'] = [$location->id]; } Datagrid::layout(Event::class) ->route('locations.events', $options); $this->rows = $location ->allEvents() ->filter($filters) ->filteredEvents() ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return redirect()->to($location->getLink()); } } ================================================ FILE: app/Http/Controllers/Locations/LocationController.php ================================================ campaign($campaign)->authEntityView($location->entity); return redirect()->route('entities.children', [$campaign, $location->entity]); } } ================================================ FILE: app/Http/Controllers/Locations/QuestController.php ================================================ campaign($campaign)->authEntityView($location->entity); $options = ['campaign' => $campaign, 'location' => $location, 'm' => $this->descendantsMode()]; $filters = []; if ($this->filterToDirect()) { $filters['location_id'] = $location->id; } Datagrid::layout(Quest::class) ->route('locations.quests', $options); $this->rows = $location ->allQuests() ->filter($filters) ->filteredQuests() ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return redirect()->to($location->getLink()); } } ================================================ FILE: app/Http/Controllers/Maps/Bulks/GroupController.php ================================================ authorize('update', $map->entity); $action = $request->get('action'); $models = $request->get('model'); if (! in_array($action, $this->validBulkActions()) || empty($models)) { return redirect()->back(); } if ($action === 'edit') { return $this->bulkBatch(route('maps.groups.bulk', [$campaign, 'map' => $map]), '_map-group', $models); } if (request()->ajax()) { return response()->json(['success' => true]); } $count = $this->bulkProcess($request, MapGroup::class); return redirect() ->route('maps.map_groups.index', [$campaign, 'map' => $map]) ->with('success', trans_choice('maps/groups.bulks.' . $action, $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Maps/Bulks/LayerController.php ================================================ authorize('update', $map->entity); $this->campaign = $campaign; $action = $request->get('action'); $models = $request->get('model'); if (! in_array($action, $this->validBulkActions()) || empty($models)) { return redirect()->back(); } if ($action === 'edit') { return $this->campaign($campaign)->bulkBatch(route('maps.layers.bulk', [$campaign, 'map' => $map]), '_map-layer', $models); } if (request()->ajax()) { return response()->json(['success' => true]); } $count = $this->bulkProcess($request, MapLayer::class); return redirect() ->route('maps.map_layers.index', [$campaign, $map]) ->with('success', trans_choice('maps/layers.bulks.' . $action, $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Maps/Bulks/MarkerController.php ================================================ authorize('update', $map->entity); $action = $request->get('action'); $models = $request->get('model'); if (! in_array($action, $this->validBulkActions()) || empty($models)) { return redirect()->back(); } if ($action === 'edit') { return $this->campaign($campaign)->bulkBatch(route('maps.markers.bulk', [ 'campaign' => $campaign, 'map' => $map]), '_map-marker', $models, $map); } if (request()->ajax()) { return response()->json(['success' => true]); } $count = $this->campaign($campaign)->bulkProcess($request, MapMarker::class); return redirect() ->route('maps.map_markers.index', [$campaign, 'map' => $map]) ->with('success', trans_choice('maps/markers.bulks.' . $action, $count, ['count' => $count])); } } ================================================ FILE: app/Http/Controllers/Maps/ExploreController.php ================================================ middleware('adless'); } /** * Exploration view for a map */ public function index(Campaign $campaign, Map $map) { if (empty($map->entity)) { abort(404); } $this->campaign($campaign)->authEntityView($map->entity); if (! $map->explorable()) { return redirect() ->route('entities.show', [$campaign, $map->entity]) ->withError(__('maps.errors.explore.missing')); } if ($map->isChunked()) { if ($map->chunkingError()) { return redirect() ->route('entities.show', [$campaign, $map->entity]); } elseif (! $map->chunkingReady()) { return redirect() ->route('entities.show', [$campaign, $map->entity]); } } // Error handling try { $map->bounds(); } catch (Exception $e) { return redirect()->route('entities.show', [$campaign, $map->entity]) ->with('error_raw', __('Error getting bounds from the map. This sometimes happens with animated WebP files, which aren\'t supported. Please contact us on :discord or at at :email.', [ 'discord' => 'Discord', 'email' => '' . config('app.email') . '', ])); } return view('maps.explore') ->with('map', $map) ->with('campaign', $campaign); } /** * Map ticker for updates to pointers */ public function ticker(Campaign $campaign, Map $map) { $this->campaign($campaign)->authEntityView($map->entity); $timestamp = request()->get('ts', time()); /** @var MapMarker[] $markers */ $markers = $map->markers()->where('updated_at', '>=', $timestamp)->get(); $data = []; foreach ($markers as $marker) { $data[] = [ 'id' => $marker->id, 'longitude' => $marker->longitude, 'latitude' => $marker->latitude, ]; } return response()->json([ 'ts' => Carbon::now(), 'markers' => $data, ]); } /** * Load only a chunk of the map and cache it for the user */ public function chunks(Campaign $campaign, Map $map) { $headers = ['Expires', Carbon::now()->addDays(1)->toDateTimeString()]; if (! request()->has(['z', 'x', 'y'])) { return response() ->file(public_path('/images/map_chunks/transparent.png'), $headers); } $path = 'maps/' . $map->id . '/chunks/' . request()->get('z') . '/' . request()->get('x') . '_' . request()->get('y') . '.png'; if (! Storage::exists($path)) { return response() ->file(public_path('/images/map_chunks/transparent.png'), $headers); } return redirect()->to(Storage::url($path)); // return response() // ->file(Storage::path($path), $headers); } } ================================================ FILE: app/Http/Controllers/Maps/GroupController.php ================================================ authorize('update', $map->entity); $options = ['map' => $map->id]; Datagrid::layout(Group::class) ->route('maps.map_groups.index', $options); $this->rows = $map ->groups() ->sort(request()->only(['o', 'k']), ['position' => 'asc']) ->with(['map', 'parent']) ->paginate(config('limits.pagination')); $groups = $map ->groups() ->orderBy('position', 'asc') ->with(['map']) ->get(); $this->campaign($campaign); if (request()->ajax()) { return $this->datagridAjax(); } return view('cruds.subview') ->with([ 'fullview' => 'maps.groups.index', 'model' => $map, 'entity' => $map->entity, 'campaign' => $this->campaign, 'rows' => $this->rows, 'groups' => $groups, 'mode' => $this->descendantsMode(), ]); } public function show(Campaign $campaign, Map $map) { return redirect()->route('entities.show', [$campaign, $map->entity]); } public function create(Campaign $campaign, Map $map) { $this->authorize('update', $map->entity); if (! auth()->user()->can('addGroup', [$map, $campaign])) { return view('maps.form._groups_max') ->with('campaign', $campaign) ->with('map', $map) ->with('max', config('limits.campaigns.maps.groups.premium')); } return view( 'maps.groups.create', compact('map', 'campaign') ); } public function store(Campaign $campaign, Map $map, StoreMapGroup $request) { $this->authorize('update', $map->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } if (! auth()->user()->can('addGroup', [$map, $campaign])) { return view('maps.form._groups_max') ->with('campaign', $campaign) ->with('map', $map) ->with('max', config('limits.campaigns.maps.groups.premium')); } $model = new MapGroup; $data = $request->only('name', 'position', 'entry', 'visibility_id', 'is_shown', 'parent_id'); if (Arr::exists($data, 'position')) { $map->groups()->where('position', '>', $data['position'] - 1)->increment('position'); } $data['map_id'] = $map->id; $new = $model->create($data); if ($request->has('submit-update')) { return redirect() ->route('maps.map_groups.edit', [$campaign, 'map' => $map, $new]) ->withSuccess(__('maps/groups.create.success', ['name' => $new->name])); } elseif ($request->has('submit-new')) { return redirect() ->route('maps.map_groups.create', [$campaign, 'map' => $map]) ->withSuccess(__('maps/groups.create.success', ['name' => $new->name])); } elseif ($request->has('submit-explore')) { return redirect() ->route('maps.explore', [$campaign, $map]) ->withSuccess(__('maps/groups.create.success', ['name' => $new->name])); } return redirect() ->route('maps.map_groups.index', [$campaign, $map]) ->withSuccess(__('maps/groups.create.success', ['name' => $new->name])); } public function edit(Campaign $campaign, Map $map, MapGroup $mapGroup) { $this->authorize('update', $map->entity); $model = $mapGroup; return view( 'maps.groups.edit', compact('map', 'model', 'campaign') ); } public function update(StoreMapGroup $request, Campaign $campaign, Map $map, MapGroup $mapGroup) { $this->authorize('update', $map->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $mapGroup->update($request->only('name', 'position', 'entry', 'visibility_id', 'is_shown', 'parent_id')); if ($request->has('submit-update')) { return redirect() ->route('maps.map_groups.edit', [$campaign, 'map' => $map, $mapGroup]) ->withSuccess(__('maps/groups.edit.success', ['name' => $mapGroup->name])); } elseif ($request->has('submit-new')) { return redirect() ->route('maps.map_groups.create', [$campaign, 'map' => $map]) ->withSuccess(__('maps/groups.edit.success', ['name' => $mapGroup->name])); } elseif ($request->has('submit-explore')) { return redirect() ->route('maps.explore', [$campaign, $map]) ->withSuccess(__('maps/groups.edit.success', ['name' => $mapGroup->name])); } return redirect() ->route('maps.map_groups.index', [$campaign, $map]) ->withSuccess(__('maps/groups.edit.success', ['name' => $mapGroup->name])); } public function destroy(Campaign $campaign, Map $map, MapGroup $mapGroup) { $this->authorize('update', $map->entity); $mapGroup->delete(); return redirect() ->route('maps.map_groups.index', [$campaign, $map]) ->withSuccess(__('maps/groups.delete.success', ['name' => $mapGroup->name])); } } ================================================ FILE: app/Http/Controllers/Maps/LayerController.php ================================================ authorize('update', $map->entity); $options = ['campaign' => $campaign, 'map' => $map->id]; Datagrid::layout(Layer::class) ->route('maps.map_layers.index', $options); $this->rows = $map ->layers() ->sort(request()->only(['o', 'k']), ['position' => 'asc']) ->with(['map', 'image']) ->paginate(config('limits.pagination')); $layers = $map ->layers() ->orderBy('position', 'asc') ->with(['image']) ->get(); $this->campaign($campaign); if (request()->ajax()) { return $this->datagridAjax(); } return view('cruds.subview') ->with([ 'fullview' => 'maps.layers.index', 'model' => $map, 'entity' => $map->entity, 'campaign' => $this->campaign, 'rows' => $this->rows, 'layers' => $layers, 'mode' => $this->descendantsMode(), ]); } public function show(Campaign $campaign, Map $map) { return redirect() ->route('entities.show', [$campaign, $map->entity]); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign, Map $map) { $this->authorize('update', $map->entity); if (! auth()->user()->can('addLayer', [$map, $campaign])) { return view('maps.form._layers_max') ->with('campaign', $campaign) ->with('map', $map) ->with('max', config('limits.campaigns.maps.layers.premium')); } return view( 'maps.layers.create', compact('map', 'campaign') ); } /** * @throws AuthorizationException */ public function store(Campaign $campaign, Map $map, StoreMapLayer $request) { $this->authorize('update', $map->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } if (! auth()->user()->can('addLayer', [$map, $campaign])) { return view('maps.form._groups_max') ->with('campaign', $campaign) ->with('map', $map) ->with('max', config('limits.campaigns.maps.layers.premium')); } $model = new MapLayer; $data = $request->only('name', 'position', 'entry', 'visibility_id', 'type_id', 'image_uuid'); if (Arr::exists($data, 'position')) { $map->layers()->where('position', '>', $data['position'] - 1)->increment('position'); } $data['map_id'] = $map->id; $new = $model->create($data); if ($request->has('submit-update')) { return redirect() ->route('maps.map_layers.edit', [$campaign, 'map' => $map, $new]) ->withSuccess(__('maps/layers.create.success', ['name' => $new->name])); } elseif ($request->haS('submit-new')) { return redirect() ->route('maps.map_layers.create', [$campaign, 'map' => $map]) ->withSuccess(__('maps/layers.create.success', ['name' => $new->name])); } elseif ($request->has('submit-explore')) { return redirect() ->route('maps.explore', [$campaign, $map]) ->withSuccess(__('maps/layers.create.success', ['name' => $new->name])); } return redirect() ->route('maps.map_layers.index', [$campaign, $map]) ->withSuccess(__('maps/layers.create.success', ['name' => $new->name])); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function edit(Campaign $campaign, Map $map, MapLayer $mapLayer) { $this->authorize('update', $map->entity); // Migrate to gallery // if (!empty($mapLayer->image_path)) { // return view('maps.layers.migrate') // ->with('campaign', $campaign) // ->with('map', $map) // ->with('layer', $mapLayer) // ; // } $model = $mapLayer; return view( 'maps.layers.edit', compact('campaign', 'map', 'model') ); } /** * @throws AuthorizationException */ public function update(StoreMapLayer $request, Campaign $campaign, Map $map, MapLayer $mapLayer) { $this->authorize('update', $map->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $mapLayer->update($request->only('name', 'position', 'entry', 'visibility_id', 'type_id', 'image_uuid')); if ($request->has('submit-update')) { return redirect() ->route('maps.map_layers.edit', [$campaign, $map, $mapLayer]) ->withSuccess(__('maps/layers.create.success', ['name' => $mapLayer->name])); } elseif ($request->haS('submit-new')) { return redirect() ->route('maps.map_layers.create', [$campaign, $map]) ->withSuccess(__('maps/layers.create.success', ['name' => $mapLayer->name])); } elseif ($request->has('submit-explore')) { return redirect() ->route('maps.explore', [$campaign, $map]) ->withSuccess(__('maps/layers.create.success', ['name' => $mapLayer->name])); } return redirect() ->route('maps.map_layers.index', [$campaign, $map]) ->withSuccess(__('maps/layers.edit.success', ['name' => $mapLayer->name])); } /** * @throws AuthorizationException */ public function destroy(Campaign $campaign, Map $map, MapLayer $mapLayer) { $this->authorize('update', $map->entity); $mapLayer->delete(); return redirect() ->route('maps.map_layers.index', [$campaign, $map]) ->withSuccess(__('maps/layers.delete.success', ['name' => $mapLayer->name])); } } ================================================ FILE: app/Http/Controllers/Maps/Layers/MigrateController.php ================================================ service = $migrateLayerService; } public function index(Campaign $campaign, Map $map, MapLayer $mapLayer) { $this->authorize('update', $map->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } try { $this->service->layer($mapLayer)->migrate(); } catch (TranslatableException $e) { return redirect() ->route('maps.map_layers.edit', [$campaign, $map, $mapLayer]) ->with('error', $e->getTranslatedMessage()); } catch (\Exception $e) { } return redirect() ->route('maps.map_layers.edit', [$campaign, $map, $mapLayer]) ->withSuccess('Map layer image migrated.'); } } ================================================ FILE: app/Http/Controllers/Maps/MapController.php ================================================ campaign($campaign)->authEntityView($map->entity); return redirect()->route('entities.children', [$campaign, $map->entity]); } } ================================================ FILE: app/Http/Controllers/Maps/MarkerController.php ================================================ authorize('update', $map->entity); $options = ['campaign' => $campaign, 'map' => $map->id]; Datagrid::layout(Marker::class) ->route('maps.map_markers.index', $options); $this->rows = $map ->markers() ->sort(request()->only(['o', 'k']), ['id' => 'desc']) ->with(['map']) ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('maps.markers.index', $map); } public function show(Campaign $campaign, Map $map) { return redirect() ->route('entities.show', [$campaign, $map->entity]); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function create(Request $request, Campaign $campaign, Map $map) { $this->authorize('update', $map->entity); $source = null; if (request()->has('source')) { $source = MapMarker::findOrFail(request()->get('source')); FormCopy::request($request)->source($source); } $activeTab = 1; if ($source) { $activeTab = $source->shape_id; } return view( 'maps.markers.create', compact('campaign', 'map', 'source', 'activeTab') ); } /** * @throws AuthorizationException */ public function store(Campaign $campaign, Map $map, StoreMapMarker $request) { $this->authorize('update', $map->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $model = new MapMarker; $data = $request->only($this->fields); $data['map_id'] = $map->id; $new = $model->create($data); if ($request->has('submit-explore')) { return redirect() ->route('maps.explore', [$campaign, $map, 'focus' => $new->id]) ->withSuccess(__('maps/markers.create.success', ['name' => $new->name])); } elseif ($request->has('submit-update')) { return redirect() ->route('maps.map_markers.edit', [$campaign, $map, $new]) ->withSuccess(__('maps/markers.create.success', ['name' => $new->name])); } elseif ($request->get('from') == 'explore') { return redirect() ->route('maps.explore', [$campaign, $map, 'focus' => $new->id]); } return redirect() ->route('maps.map_markers.index', [$campaign, $map, 'focus' => $new->id]) ->withSuccess(__('maps/markers.create.success', ['name' => $new->name])); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function edit(Campaign $campaign, Map $map, MapMarker $mapMarker, string $from = '') { $this->authorize('update', $map->entity); if ($mapMarker->map_id !== $map->id) { abort(503); } $from = request()->get('from'); $model = $mapMarker; $includeMap = true; $activeTab = $mapMarker->shape_id; return view( 'maps.markers.edit', compact('map', 'campaign', 'model', 'includeMap', 'activeTab', 'from') ); } /** * @throws AuthorizationException */ public function update(StoreMapMarker $request, Campaign $campaign, Map $map, MapMarker $mapMarker) { $this->authorize('update', $map->entity); if ($mapMarker->map_id !== $map->id) { abort(503); } // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $data = $request->only($this->fields); if (! request()->has('entity_id') && ! isset($data['entity_id'])) { $data['entity_id'] = null; } $mapMarker->update($data); if ($request->has('submit-explore')) { return redirect() ->route('maps.explore', [$campaign, $map, 'focus' => $mapMarker->id]) ->withSuccess(__('maps/markers.edit.success', ['name' => $mapMarker->name])); } elseif ($request->has('submit-update')) { return redirect() ->route('maps.map_markers.edit', [$campaign, $map, $mapMarker]) ->withSuccess(__('maps/markers.edit.success', ['name' => $mapMarker->name])); } elseif ($request->get('from') == 'explore') { return redirect() ->route('maps.explore', [$campaign, $map, 'focus' => $mapMarker->id]); } return redirect() ->route('maps.map_markers.index', [$campaign, $map, '#tab_form-markers']) ->withSuccess(__('maps/markers.edit.success', ['name' => $mapMarker->name])); } /** * @return RedirectResponse * * @throws AuthorizationException */ public function destroy(Campaign $campaign, Map $map, MapMarker $mapMarker) { $this->authorize('update', $map->entity); if ($mapMarker->map_id !== $map->id) { abort(503); } $mapMarker->delete(); if (request()->get('from') == 'explore') { return redirect() ->route('maps.explore', [$campaign, $map]); } return redirect() ->route('maps.map_markers.index', [$campaign, $map]) ->withSuccess(__('maps/markers.delete.success', ['name' => $mapMarker->name])); } } ================================================ FILE: app/Http/Controllers/Maps/Markers/DetailController.php ================================================ campaign($campaign)->authEntityView($map->entity); if (! empty($mapMarker->entity_id)) { $this->campaign($campaign)->authEntityView($mapMarker->entity); } $name = $mapMarker->name; if ($mapMarker->entity) { $name = ! empty($mapMarker->name) ? $mapMarker->name : $mapMarker->entity->name; $name = '' . $name . ''; } if (request()->has('mobile')) { return response()->view('maps.markers.dialog_details', [ 'marker' => $mapMarker, 'campaign' => $campaign, 'name' => $name, ]); } return response()->json([ 'body' => view('maps.markers.details', [ 'marker' => $mapMarker, 'campaign' => $campaign, ])->render(), 'name' => $name, ]); } } ================================================ FILE: app/Http/Controllers/Maps/Markers/MoveController.php ================================================ authorize('update', $map->entity); $mapMarker->update($request->only('latitude', 'longitude')); return response()->json([ 'success' => true, 'marker_id' => $mapMarker->id, ]); } } ================================================ FILE: app/Http/Controllers/Maps/PreviewController.php ================================================ enabled('maps')) { return redirect()->route('dashboard', $campaign)->with( 'error_raw', __('campaigns.settings.errors.module-disabled', [ 'fix' => '' . __('crud.fix-this-issue') . '', ]) ); } $this->campaign($campaign)->authEntityView($map->entity); return view('maps.preview') ->with('campaign', $campaign) ->with('entity', $map->entity); } } ================================================ FILE: app/Http/Controllers/Maps/Reorders/GroupController.php ================================================ authorize('update', $map->entity); $order = 1; $ids = $request->get('group'); foreach ($ids as $id) { $group = MapGroup::where('id', $id)->where('map_id', $map->id)->first(); if (empty($group)) { continue; } $group->position = $order; $group->updateQuietly(); $order++; } $order--; return redirect() ->route('maps.map_groups.index', [$campaign, 'map' => $map]) ->with('success', trans_choice('maps/groups.reorder.success', $order, ['count' => $order])); } } ================================================ FILE: app/Http/Controllers/Maps/Reorders/LayerController.php ================================================ authorize('update', $map->entity); $order = 1; $ids = $request->get('layer'); foreach ($ids as $id) { $layer = MapLayer::where('id', $id)->where('map_id', $map->id)->first(); if (empty($layer)) { continue; } $layer->position = $order; $layer->updateQuietly(); $order++; } $order--; return redirect() ->route('maps.map_layers.index', [$campaign, $map]) ->with('success', trans_choice('maps/layers.reorder.success', $order, ['count' => $order])); } } ================================================ FILE: app/Http/Controllers/Notes/NoteController.php ================================================ campaign($campaign)->authEntityView($note->entity); return redirect()->route('entities.children', [$campaign, $note->entity]); } } ================================================ FILE: app/Http/Controllers/NotificationController.php ================================================ middleware(['auth', 'identity']); $this->navigationService = $navigationService; } /** * @return Factory|View */ public function index() { // Set all notifications as read /** @var User $user */ $user = auth()->user(); $notifications = $user->notifications()->paginate(); // @phpstan-ignore-next-line $user->unreadNotifications->markAsRead(); return view('notifications.index', compact('notifications')); } public function read($id) { $notification = auth()->user()->notifications()->where('id', $id)->first(); if (empty($notification)) { abort(403); } $notification->markAsRead(); return response()->json(['success' => true]); } public function refresh() { /** @var User $user */ $user = auth()->user(); return response()->json( $this->navigationService->user($user)->pull() ); } public function clearAll() { auth()->user()->notifications()->delete(); return redirect() ->route('notifications') ->with('success', __('notifications.clear.success')); } } ================================================ FILE: app/Http/Controllers/Onboarding/InitialController.php ================================================ authorize('update', $campaign); $this->initialService ->campaign($campaign) ->user(auth()->user()) ->request($request) ->save(); return response()->json([ 'success' => true, 'redirect' => route('home', $campaign), ]); } public function skip(Request $request, Campaign $campaign) { $this->authorize('update', $campaign); $this->initialService ->campaign($campaign) ->user(auth()->user()) ->skip($request->get('reason', 'skip')); return response()->json([ 'success' => true, ]); } } ================================================ FILE: app/Http/Controllers/Organisation/MemberController.php ================================================ campaign($campaign)->authEntityView($organisation->entity); $options = ['campaign' => $campaign, 'organisation' => $organisation]; $base = 'members'; if ($this->filterToAll()) { $options['all'] = true; $base = 'allMembers'; } Datagrid::layout(Member::class) ->route('organisations.members', $options) ->actionParams(['from' => 'org']); $this->rows = $organisation ->{$base}() ->select('organisation_member.*') ->with([ 'organisation', 'organisation.entity', 'organisation.entity.entityType' => function ($sub) { $sub->select('id', 'code'); }, 'parent', 'parent.character', 'parent.character.entity', 'character', 'character.entity', 'character.entity.image', 'character.entity.entityType' => function ($sub) { $sub->select('id', 'code'); }, 'character.entity.locations']) ->has('character') ->has('character.entity') ->leftJoin('characters as c', 'c.id', 'organisation_member.character_id') ->sort(request()->only(['o', 'k']), ['c.name' => 'asc']) ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('organisations.members', $organisation); } /** * Show the form for creating a new resource. */ public function create(Campaign $campaign, Organisation $organisation) { $this->authorize('member', $organisation); return view($this->view . '.create', [ 'model' => $organisation, 'campaign' => $campaign, ]); } /** * Store a newly created resource in storage. */ public function store(StoreOrganisationMembers $request, Campaign $campaign, Organisation $organisation) { $this->authorize('member', $organisation); if ($request->ajax()) { return response()->json(['success' => true]); } $count = 0; foreach ($request->get('characters', []) as $character) { $relation = OrganisationMember::create(['character_id' => $character, 'organisation_id' => $organisation->id] + $request->except('characters')); $count++; } return redirect()->route('entities.show', [$campaign, $organisation->entity]) ->with('success', trans_choice('organisations.members.create.success_multiple', $count, ['name' => $organisation->name, 'count' => $count])); } /** * Display the specified resource. */ public function show(Campaign $campaign, Organisation $organisation, OrganisationMember $organisationMember) { $this->authorize('member', $organisation); return view($this->view . '.show', [ 'campaign' => $campaign, 'model' => $organisation, 'member' => $organisationMember, ]); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Organisation $organisation, OrganisationMember $organisationMember) { $this->authorize('member', $organisation); return view($this->view . '.edit', [ 'model' => $organisation, 'member' => $organisationMember, 'campaign' => $campaign, ]); } /** * Update the specified resource in storage. */ public function update( StoreOrganisationMember $request, Campaign $campaign, Organisation $organisation, OrganisationMember $organisationMember ) { $this->authorize('member', $organisation); if ($request->ajax()) { return response()->json(['success' => true]); } $organisationMember->update($request->all()); return redirect()->route('entities.show', [$campaign, $organisation->entity]) ->with('success', trans($this->view . '.edit.success')); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Organisation $organisation, OrganisationMember $organisationMember) { $this->authorize('member', $organisation); $organisationMember->delete(); return redirect()->route('entities.show', [$campaign, $organisationMember->organisation->entity]) ->with('success', trans($this->view . '.destroy.success')); } } ================================================ FILE: app/Http/Controllers/Organisation/OrganisationController.php ================================================ campaign($campaign)->authEntityView($organisation->entity); return redirect()->route('entities.children', [$campaign, $organisation->entity]); } } ================================================ FILE: app/Http/Controllers/Passport/ClientController.php ================================================ validation->make($request->all(), [ 'name' => ['required', 'string', 'max:255'], 'redirect' => ['required', $this->redirectRule], 'confidential' => 'boolean', ])->validate(); $client = $this->clients->createAuthorizationCodeGrantClient( $request->name, explode(',', $request->redirect), (bool) $request->input('confidential', true), $request->user(), ); return response()->json([ 'secret' => $client->plainSecret, ]); } } ================================================ FILE: app/Http/Controllers/PasswordSecurityController.php ================================================ middleware('2fa'); } /* * Generates secret code for 2fa */ public function generate2faSecretCode(Request $request) { /** @var User $user */ $user = $request->user(); $otp = new Google2FA; // Generate a new Google2FA code for User PasswordSecurity::create([ 'user_id' => $user->id, 'google2fa_enable' => 0, 'google2fa_secret' => $otp->generateSecretKey(), ]); return redirect()->route('settings.account')->with('success', __('settings.account.2fa.success_key')); } /* * Aborts login of user with 2fa enabled. */ public function cancel2FA(Request $request) { return redirect()->route('login'); } } ================================================ FILE: app/Http/Controllers/PayPalController.php ================================================ middleware(['auth', 'identity']); $this->service = $service; } /** * process transaction. */ public function processTransaction(ValidatePledge $request, Tier $tier) { if ($tier->isFree()) { abort(401); } if (request()->ajax()) { return response()->json(); } $response = $this->service ->user($request->user()) ->tier($tier) ->process(); if (isset($response['id'])) { foreach ($response['links'] as $links) { if ($links['rel'] == 'approve') { return redirect()->away($links['href']); } } return redirect() ->route('settings.subscription') ->with( 'error', __('subscriptions/paypal.errors.rejected') . ' ' . __('subscriptions/paypal.errors.contact', ['email' => config('app.email')]) ); } else { Log::error('Subscription PayPal error', $response); return redirect() ->route('settings.subscription') ->with( 'error', __('subscriptions/paypal.errors.failed') . ' ' . __('subscriptions/paypal.errors.contact', ['email' => config('app.email')]) ); } } /** * Process a successful transaction */ public function successTransaction(Request $request) { $provider = new PayPalClient; $provider->setApiCredentials(config('paypal')); $provider->getAccessToken(); $response = $provider->capturePaymentOrder($request['token']); if (isset($response['status']) && $response['status'] == 'COMPLETED') { $pledge = $response['purchase_units']['0']['reference_id']; Log::info('Paypal', $response); $this->service ->user($request->user()) ->subscribe($pledge); $routeOptions = ['success' => 1]; $flash = 'subscribed'; /** @var ?Tier $tier */ $tier = Tier::where('name', $pledge)->first(); /** @var ?TierPrice $tierPrice */ $tierPrice = $tier->prices() ->where('currency', $request->user()->currency()) ->where('period', PricingPeriod::Yearly) ->first(); return redirect() ->route('settings.subscription.finish', $routeOptions) ->with('success', __('settings.subscription.success.subscribed')) ->with('sub_tracking', $flash) ->with('sub_id', $tierPrice?->id) ->with('sub_value', $response['purchase_units']['0']['payments']['captures'][0]['amount']['value']); } else { Log::error('Subscription PayPal error 2', $response); return redirect() ->route('settings.subscription') ->with( 'error', __('subscriptions/paypal.errors.incomplete') . ' ' . __('subscriptions/paypal.errors.contact', ['email' => config('app.email')]) ); } } /** * cancel transaction */ public function cancelTransaction(Request $request) { return redirect() ->route('settings.subscription') ->with('error', __('settings.subscription.errors.callback')); } } ================================================ FILE: app/Http/Controllers/PresetController.php ================================================ middleware('auth'); } public function index(Campaign $campaign, PresetType $presetType) { $presets = Preset::inType($presetType->id)->orderBy('name')->get(); return view('presets.list') ->with('campaign', $campaign) ->with('presets', $presets) ->with('presetType', $presetType) ->with('from', request()->get('from')); } public function show(Campaign $campaign, PresetType $presetType, Preset $preset) { return response() ->json(['preset' => $preset->config]); } public function create(Campaign $campaign, PresetType $presetType) { $this->authorize('mapPresets', $campaign); $from = request()->get('from', 'dashboard'); return view('presets.forms.create') ->with('campaign', $campaign) ->with('presetType', $presetType) ->with('from', $from); } public function store(StorePreset $request, Campaign $campaign, PresetType $presetType) { $this->authorize('mapPresets', $campaign); if ($request->ajax()) { return response()->json(['success' => true]); } $data = $request->only('name', 'config', 'visibility_id'); $data['type_id'] = $presetType->id; $data['campaign_id'] = $campaign->id; $preset = new Preset; $preset = $preset->create($data); [$route, $params] = $this->parseFrom($request); if (! is_array($params)) { $params = [$params]; } $params['campaign'] = $campaign; return redirect() ->route($route, $params) ->with('success', __('presets.create.success', ['name' => $preset->name])); } public function edit(Campaign $campaign, PresetType $presetType, Preset $preset) { $this->authorize('mapPresets', $campaign); $from = request()->get('from', 'dashboard'); return view('presets.forms.edit') ->with('campaign', $campaign) ->with('presetType', $presetType) ->with('preset', $preset) ->with('from', $from); } public function update(StorePreset $request, Campaign $campaign, PresetType $presetType, Preset $preset) { $this->authorize('mapPresets', $campaign); if ($request->ajax()) { return response()->json(['success' => true]); } $data = $request->only('name', 'config', 'visibility_id'); $preset->update($data); [$route, $params] = $this->parseFrom($request); if (! is_array($params)) { $params = [$params]; } $params['campaign'] = $campaign; return redirect() ->route($route, $params) ->with('success', __('presets.edit.success', ['name' => $preset->name])); } public function destroy(Request $request, Campaign $campaign, PresetType $presetType, Preset $preset) { $this->authorize('mapPresets', $campaign); if ($request->ajax()) { return response()->json(['success' => true]); } $preset->delete(); [$route, $params] = $this->parseFrom($request); if (! is_array($params)) { $params = [$params]; } $params['campaign'] = $campaign; return redirect() ->route($route, $params) ->with('success', __('presets.destroy.success', ['name' => $preset->name])); } protected function parseFrom(Request $request) { $from = $request->get('from'); if (empty($from) || $from === 'dashboard') { return ['dashboard', null]; } $from = base64_decode($from); $from = explode(':', $from); if (count($from) !== 2) { return ['dashboard', null]; } return [$from[0], $from[1]]; } } ================================================ FILE: app/Http/Controllers/Quests/ElementController.php ================================================ entity)) { abort(404); } // Policies will always fail if they can't resolve the user. $this->campaign($campaign)->authEntityView($quest->entity); $datagridSorter = new QuestElementSorter; $datagridSorter->request(request()->all()); $model = $quest; $entity = $model->entity; $elements = $quest ->elements() ->with(['entity', 'entity.image', 'entity.entityType']) ->simpleSort($datagridSorter) ->paginate(); return view('quests.elements.index', compact( 'campaign', 'model', 'entity', 'elements', 'datagridSorter' )); } public function create(Campaign $campaign, Quest $quest) { $this->authorize('update', $quest->entity); return view('quests.elements.create', compact( 'campaign', 'quest' )); } public function store(StoreQuestElement $request, Campaign $campaign, Quest $quest) { $this->authorize('update', $quest->entity); if ($request->ajax()) { return response()->json(['success' => true]); } $data = $request->only([ 'entity_id', 'name', 'role', 'entry', 'colour', 'visibility_id', 'copy_entity_entry', ]); $data['quest_id'] = $quest->id; $element = new QuestElement; $element = $element->create($data); if ($request->has('submit-update')) { return redirect() ->route('quests.quest_elements.edit', [$campaign, 'quest_element' => $element, 'quest' => $quest]) ->with('success', __('quests.elements.create.success', [ 'entity' => $element->name(), ])); } elseif ($request->has('submit-new')) { return redirect() ->route('quests.quest_elements.create', [$campaign, $quest]) ->with('success', __('quests.elements.create.success', [ 'entity' => $element->name(), ])); } return redirect() ->route('quests.quest_elements.index', [$campaign, $quest]) ->with('success', __('quests.elements.create.success', [ 'entity' => $element->name(), ])); } public function show(Campaign $campaign, Quest $quest, QuestElement $questElement) { abort(404); } public function edit(Campaign $campaign, Quest $quest, QuestElement $questElement) { $this->authorize('update', $quest->entity); $model = $questElement; $editingUsers = null; if ($campaign->hasEditingWarning()) { /** @var MultiEditingService $editingService */ $editingService = app()->make(MultiEditingService::class); $editingUsers = $editingService->model($questElement)->user(auth()->user())->users(); // If no one is editing the quest element, we are now editing it if (empty($editingUsers)) { $editingService->edit(); } } return view('quests.elements.update', compact( 'campaign', 'quest', 'model', 'editingUsers' )); } public function update(StoreQuestElement $request, Campaign $campaign, Quest $quest, QuestElement $questElement) { $this->authorize('update', $quest->entity); if ($request->ajax()) { return response()->json(['success' => true]); } $data = $request->only(['entity_id', 'name', 'role', 'entry', 'colour', 'visibility_id', 'copy_entity_entry']); $questElement->update($data); $questElement->refresh(); /** @var MultiEditingService $editingService */ $editingService = app()->make(MultiEditingService::class); $editingService->model($questElement) ->user($request->user()) ->finish(); if ($request->has('submit-update')) { return redirect() ->route('quests.quest_elements.edit', [$campaign, 'quest_element' => $questElement, 'quest' => $quest]) ->with('success', __('quests.elements.edit.success', [ 'entity' => $questElement->name(), ])); } elseif ($request->has('submit-new')) { return redirect() ->route('quests.quest_elements.create', [$campaign, $quest]) ->with('success', __('quests.elements.create.success', [ 'entity' => $questElement->name(), ])); } return redirect() ->route('quests.quest_elements.index', [$campaign, $quest]) ->with('success', __('quests.elements.edit.success', [ 'entity' => $questElement->name(), ])); } public function destroy(Campaign $campaign, Quest $quest, QuestElement $questElement) { $this->authorize('update', $quest->entity); $questElement->delete(); return redirect() ->route('quests.quest_elements.index', [$campaign, $quest]) ->with('success', __('quests.elements.destroy.success', [ 'entity' => $questElement->name(), ])); } } ================================================ FILE: app/Http/Controllers/Quests/QuestController.php ================================================ campaign($campaign)->authEntityView($quest->entity); return redirect()->route('entities.children', [$campaign, $quest->entity]); } } ================================================ FILE: app/Http/Controllers/Races/MemberController.php ================================================ campaign($campaign)->authEntityView($race->entity); $options = ['campaign' => $campaign, 'race' => $race, 'm' => $this->descendantsMode()]; $relation = 'allCharacters'; if ($this->filterToDirect()) { $relation = 'characters'; } Datagrid::layout(Character::class) ->route('races.characters', $options); $this->rows = $race ->{$relation}() ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->with([ 'entity.locations', 'characterRaces', 'entity', 'entity.tags', 'entity.image', 'entity.tags', 'entity.entityType', ]) ->has('entity') ->paginate(config('limits.pagination')); return $this->campaign($campaign)->datagridAjax(); } /** * Show the form for creating a new resource. */ public function create(Campaign $campaign, Race $race) { $this->authorize('update', $race->entity); return view('races.members.create', [ 'campaign' => $campaign, 'model' => $race, ]); } /** * Store a newly created resource in storage. */ public function store(StoreCharacterRace $request, Campaign $campaign, Race $race) { $this->authorize('update', $race->entity); $newMembers = $race->characters()->syncWithoutDetaching($request->members); return redirect()->route('entities.show', [$campaign, $race->entity]) ->with('success', trans_choice('races.members.create.success', count($newMembers['attached']), ['count' => count($newMembers['attached'])])); } } ================================================ FILE: app/Http/Controllers/Races/RaceController.php ================================================ campaign($campaign)->authEntityView($race->entity); return redirect()->route('entities.children', [$campaign, $race->entity]); } } ================================================ FILE: app/Http/Controllers/ReferralController.php ================================================ guest()) { if ($this->service->referral($referral)->expired()) { abort(404); } $this->service->flag(); return redirect()->route('register'); } // Already logged in? Just redirect to their dashboard return redirect()->route('home'); } } ================================================ FILE: app/Http/Controllers/RelationController.php ================================================ filterService = $filterService; $this->datagrid = $datagridRenderer; $this->attributeService = $attributeService; $this->relationService = $relationService; } public function titleKey(): string { return __('sidebar.relations'); } /** * @return Application|Factory|\Illuminate\Contracts\View\View|Response * * @throws AuthorizationException */ public function create(Campaign $campaign) { $this->authorize('relations', $campaign); $model = new $this->model; $params['campaign'] = $campaign; $params['entityAttributeTemplates'] = []; $params['source'] = null; $params['langKey'] = $this->langKey; return view('entities.pages.relations.full-form.create', array_merge(['name' => $this->view], $params)); } /** * @return JsonResponse|RedirectResponse * * @throws AuthorizationException */ public function store(StoreRelation $request, Campaign $campaign) { $this->authorize('relations', $campaign); if ($request->get('from') === 'web') { $this->relationService->campaign($campaign)->createRelations($request); $new = $this->relationService->getNew(); $new->load('target', 'owner'); return response()->json([ 'created' => true, 'id' => $new->id, 'source' => $new->owner_id, 'target' => (new EntityResource($new->target))->campaign($campaign), 'text' => $new->relation, 'colour' => $new->colour, 'attitude' => $new->attitude, 'shape' => $new->isMirrored() ? 'none' : 'triangle', 'url' => route('entities.relations.edit', [ 'campaign' => $campaign, 'entity' => $new->owner_id, 'relation' => $new, 'from' => 'web', ]), ]); } // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } try { $this->relationService->campaign($campaign)->createRelations($request); $new = $this->relationService->getNew(); $count = $this->relationService->getCount(); $success = trans_choice($this->langKey . '.create.success_bulk', $count, [ 'entity' => '' . $new->owner->name . '', 'count' => $count, ]); session()->flash('success_raw', $success); if ($request->has('submit-new')) { $route = route($this->route . '.create', $campaign); return response()->redirectTo($route); } elseif ($request->has('submit-update')) { $route = route($this->route . '.edit', [$campaign, $new]); return response()->redirectTo($route); } elseif ($request->has('submit-view')) { $route = route($this->route . '.show', [$campaign, $new]); return response()->redirectTo($route); } elseif ($request->has('submit-copy')) { $route = route($this->route . '.create', [$campaign, 'copy' => $new->id]); return response()->redirectTo($route); } elseif (auth()->user()->new_entity_workflow == 'created') { $route = route($this->route . '.show', [$campaign, $new]); return response()->redirectTo($route); } $route = route($this->route . '.index', $campaign); return response()->redirectTo($route); } catch (\LogicException $exception) { $error = str_replace(' ', '_', mb_strtolower($exception->getMessage())); return redirect() ->back() ->withInput() ->with('error', __('crud.errors.' . $error)); } } /** * @return RedirectResponse */ public function show(Campaign $campaign, Relation $relation) { return redirect() ->route('relations.index', $campaign); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function edit(Campaign $campaign, Relation $relation) { $this->authorize('update', $relation->owner); $params = [ 'campaign' => $campaign, 'model' => $relation, 'relation' => $relation, 'name' => $this->view, 'source' => null, 'langKey' => $this->langKey, ]; return view('entities.pages.relations.full-form.update', $params); } /** * @return JsonResponse|RedirectResponse * * @throws AuthorizationException */ public function update(StoreRelation $request, Campaign $campaign, Relation $relation) { $this->authorize('update', $relation->owner); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $data = $request->only(['owner_id', 'target_id', 'attitude', 'relation', 'colour', 'is_pinned', 'two_way', 'visibility_id']); $relation->update($data); $relation->refresh(); return redirect() ->route('relations.index', $campaign) ->with('success', __('entities/relations' . '.update.success', [ 'target' => $relation->target->name, 'entity' => $relation->owner->name, ])); } public function destroy(Campaign $campaign, Relation $relation) { $this->authorize('update', $relation->owner); $relation->delete(); return redirect() ->route('relations.index', $campaign) ->with('success', __('entities/relations.destroy.success', [ 'target' => $relation->target->name, 'entity' => $relation->owner->name, ])); } } ================================================ FILE: app/Http/Controllers/ReminderUpdateController.php ================================================ calendarService = $calendarService; } public function edit(Campaign $campaign, Reminder $reminder) { $this->checkPermissions($reminder); $entity = $this->entity; $post = $this->post; $name = $this->view; $route = $this->route; $parent = explode('.', $this->view)[0]; $calendar = $reminder->calendar; $next = request()->get('next', null); $from = request()->get('from', null); if (! empty($from)) { if ($from !== 'calendar') { $from = Calendar::find($from); } } return view('calendars.reminders.edit', compact( 'campaign', 'entity', 'post', 'reminder', 'calendar', 'name', 'route', 'parent', 'next', 'from' )); } public function update(UpdateCalendarEvent $request, Campaign $campaign, Reminder $reminder) { $this->checkPermissions($reminder); if (request()->ajax()) { return response()->json(['success' => true]); } if (request()->has('entity_id') && request()->get('entity_id') != $this->entity->id && is_null($this->post)) { $newEntity = Entity::findOrFail(request()->get('entity_id')); $this->authorize('reminders', $newEntity); $request->merge(['type_id' => null]); } if (! is_null($this->post)) { $request->merge(['type_id' => EntityEventTypes::calendarDate->value]); } $routeOptions = ['campaign' => $campaign, 'entity' => $reminder->calendar->entity, 'year' => request()->post('year')]; $reminder->update($request->all()); if (request()->has('layout')) { $routeOptions['layout'] = request()->get('layout'); } if (request()->get('layout', $reminder->calendar->defaultLayout()) !== 'year') { $routeOptions['month'] = request()->post('month'); } $next = request()->post('next', '0'); if ($next == 'calendars.events') { return redirect() ->route('calendars.events', [$campaign, $reminder->calendar]) ->with('success', __('calendars.event.edit.success')); } elseif ($next == 'entity.reminders') { return redirect() ->route('entities.reminders.index', [$campaign, $this->entity]) ->with('success', __('calendars.event.edit.success')); } elseif (Str::startsWith($next, 'calendar.')) { $id = Str::after($next, 'calendar.'); $routeOptions['calendar'] = $id; } return redirect()->route('entities.show', $routeOptions) ->with('success', __('calendars.event.edit.success')); } public function destroy(Campaign $campaign, Reminder $reminder) { $this->checkPermissions($reminder); $reminder->delete(); $success = __('calendars.event.destroy', ['name' => $reminder->calendar->name]); $next = request()->post('next', '0'); if ($next == 'calendars.events') { return redirect() ->route('calendars.events', [$campaign, $reminder->calendar]) ->with('success', $success); } elseif ($next == 'entity.reminders') { return redirect() ->route('entities.reminders.index', [$campaign, $this->entity]) ->with('success', $success); } elseif (Str::startsWith($next, 'calendar.')) { return redirect() ->route('entities.show', [$campaign, $reminder->calendar->entity]) ->with('success', $success); } return redirect() ->route('entities.reminders.index', [$campaign, $this->entity]) ->with('success', $success); } private function checkPermissions(Reminder $reminder) { if ($reminder->remindable instanceof Post) { $this->authorize('reminders', $reminder->remindable->entity); $this->entity = $reminder->remindable->entity; $this->post = $reminder->remindable; } else { $this->authorize('reminders', $reminder->remindable); $this->entity = $reminder->remindable; $this->post = null; } } } ================================================ FILE: app/Http/Controllers/Roadmap/FeatureController.php ================================================ with('feature', $feature); } public function store(StoreFeature $request) { return redirect()->route('roadmap'); } public function upvote(Request $request, Feature $feature) { $this->middleware('auth'); $this->authorize('vote', $feature); /** @var FeatureVote $vote */ $vote = FeatureVote::where('user_id', auth()->user()->id) ->where('feature_id', $feature->id) ->firstOrNew(); if ($vote->exists) { $vote->delete(); $feature->upvote_count--; $feature->updateQuietly(); } else { $vote->feature_id = $feature->id; $vote->user_id = auth()->user()->id; $vote->save(); $feature->upvote_count++; $feature->updateQuietly(); } return view('roadmap.feature._upvote') ->with('feature', $feature); } } ================================================ FILE: app/Http/Controllers/Roadmap/RoadmapController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign, Entity $entity) { return response()->json( $this ->searchService ->entity($entity) ->request($request) ->find() ); } } ================================================ FILE: app/Http/Controllers/Search/CalendarController.php ================================================ get('q') ?? ''); return response()->json( $this->searchService ->term($term) ->campaign($campaign) ->only(config('entities.ids.calendar')) ->find() ); } /** * Live Search */ public function months(Request $request, Campaign $campaign) { $term = mb_trim($request->get('q') ?? ''); return response()->json( $this->searchService ->term($term) ->campaign($campaign) ->monthList() ); } } ================================================ FILE: app/Http/Controllers/Search/CampaignController.php ================================================ authorize('search', $campaign); return response()->json( $this->searchService ->campaign($campaign) ->members( $request->get('q') ) ); } public function roles(Request $request, Campaign $campaign) { $this->authorize('search', $campaign); return response()->json( $this->searchService ->campaign($campaign) ->roles( $request->get('q') ) ); } } ================================================ FILE: app/Http/Controllers/Search/FullTextController.php ================================================ get('term')); $term2 = null; $data = [ 'campaign' => $campaign, 'models' => [], 'term' => $term, 'route' => 'search.fulltext', ]; if (empty($term)) { return view('search.fulltext')->with($data); } /** @var ?Entity $entity */ $entity = Entity::with('entityType')->where('name', $term)->first(); if ($entity) { $term2 = $entity->entityType->code . ':' . $entity->id; } // Get entity ids from meilisearch $results = $this->service ->campaign($campaign) ->limit(100) ->search($term, $term2); $results = array_column($results, 'id'); // Then get the actual entities from the campaign $models = Entity::whereIn('id', $results) ->orderBy('name') ->paginate(); // If the current page is higher than the max amount of pages, redirect the user if ((int) $request->get('page', 1) > $models->lastPage()) { return redirect()->route('search.fulltext', [ $campaign, 'page' => $models->lastPage(), 'order' => $request->get('order'), ]); } $data['models'] = $models; return view('search.fulltext')->with($data); } } ================================================ FILE: app/Http/Controllers/Search/GameSystemSearchController.php ================================================ get('q') . '%') ->withCount('campaignSystem') ->orderBy('campaign_system_count', 'desc') ->orderBy('name', 'asc') ->limit(20) ->get(); foreach ($systems as $system) { $format = [ 'id' => $system->id, 'text' => $system->name, ]; $formatted[] = $format; } // Add Other if it's empty if (empty($formatted)) { $other = GameSystem::where('name', 'Other')->first(); $formatted[] = [ 'id' => $other->id, 'text' => __('sidebar.other'), ]; } return response()->json( $formatted ); } } ================================================ FILE: app/Http/Controllers/Search/ImageController.php ================================================ middleware('auth'); } public function index(Campaign $campaign) { /** @var Image[] $images */ $images = Image::where('is_default', false) ->where('is_folder', false) ->where('name', 'like', '%' . request()->get('q') . '%') ->limit(10) ->get(); $formatted = []; foreach ($images as $image) { $format = [ 'id' => $image->id, 'text' => $image->name, 'image' => $image->getUrl(40), ]; $formatted[] = $format; } return response()->json($formatted); } } ================================================ FILE: app/Http/Controllers/Search/ListController.php ================================================ json( $this->service ->campaign($campaign) ->request($request) ->entityType($entityType) ->search() ); } } ================================================ FILE: app/Http/Controllers/Search/LiveController.php ================================================ get('q') ?? ''); $exclude = mb_trim($request->get('exclude') ?? ''); if ($exclude === 'undefined') { $exclude = null; } if (auth()->check()) { $this->searchService->user(auth()->user()); } if (request()->has('new')) { $this->searchService->new(true); } if ($request->get('v2') === 'true') { $this->searchService->v2(); } if ($request->has('posts')) { $this->searchService->posts()->new(); } if ($request->has('thumb')) { $size = min(max($request->get('thumb'), 64), 512); $this->searchService->thumb($size); } $this->searchService ->term($term) ->campaign($campaign) ->full() ->excludeIds($exclude); return response()->json( $this->searchService->find() ); } /** * Filter on entities which have reminders */ public function reminderEntities(Request $request, Campaign $campaign) { $term = mb_trim($request->get('q') ?? ''); return response()->json( $this->searchService ->term($term) ->campaign($campaign) ->exclude([ config('entities.ids.calendar'), config('entities.ids.tag'), config('entities.ids.map'), config('entities.ids.timeline'), ]) ->find() ); } /** * Filter on entities which have relations */ public function relationEntities(Request $request, Campaign $campaign) { $term = mb_trim($request->get('q') ?? ''); $exclude = []; if ($request->has('exclude')) { $exclude = $request['exclude']; } return response()->json( $this->searchService ->term($term) ->campaign($campaign) ->exclude([config('entities.ids.bookmark')]) ->excludeIds($exclude) ->only($request->get('only')) ->find() ); } /** * Filter on entities which have multiple tags */ public function tagChildren(Request $request, Campaign $campaign) { $term = mb_trim($request->get('q') ?? ''); $exclude = []; if ($request->has('exclude-entity')) { /** @var Tag $tag */ $tag = Tag::findOrFail($request->get('exclude-entity')); $exclude = $tag->entities->pluck('id')->toArray(); $exclude[] = $tag->entity->id; } return response()->json( $this->searchService ->term($term) ->campaign($campaign) ->excludeIds($exclude) ->find() ); } /** * Filter on entities which aren't part of an ability */ public function abilityEntities(Request $request, Campaign $campaign) { $term = mb_trim($request->get('q') ?? ''); $exclude = []; if ($request->has('exclude')) { /** @var Ability $ability */ $ability = Ability::findOrFail($request->get('exclude')); $exclude = $ability->entities->pluck('id')->toArray(); } return response()->json( $this->searchService ->term($term) ->campaign($campaign) ->exclude([config('entities.ids.tag')]) ->excludeIds($exclude) ->find() ); } /** * Only find calendar entities */ public function calendars(Request $request, Campaign $campaign) { $term = mb_trim($request->get('q') ?? ''); return response()->json( $this->searchService ->term($term) ->campaign($campaign) ->only([config('entities.ids.calendar')]) ->find() ); } /** * Filter on org members */ public function organisationMembers(Request $request, Campaign $campaign) { $term = mb_trim($request->get('q') ?? ''); $exclude = []; $data = []; if ($request->has('exclude')) { /** @var Organisation $org */ $org = Organisation::findOrFail($request->get('exclude')); $members = $org ->members() ->select('organisation_member.*') ->with('character') ->has('character') ->leftJoin('characters as c', 'c.id', 'organisation_member.character_id') ->orderBy('c.name'); if (! empty($term)) { $members ->whereLike('c.name', '%' . $term . '%'); } /** @var OrganisationMember $member */ foreach ($members->get() as $member) { $data[] = [ 'id' => $member->id, 'text' => $member->character->name . (! empty($member->role) ? ' (' . $member->role . ')' : null), ]; } } return response()->json( $data ); } } ================================================ FILE: app/Http/Controllers/Search/MapGroupController.php ================================================ campaign($campaign)->authEntityView($map->entity); return response()->json( $this->service ->campaign($campaign) ->map($map) ->request($request) ->search() ); } } ================================================ FILE: app/Http/Controllers/Search/MarkerController.php ================================================ middleware('auth'); } public function index(Request $request, Campaign $campaign) { $term = mb_trim($request->get('q') ?? ''); // parent map_id allowed for the marker (limits search to the markers of the map only) $include = $request->has('include') ? [$request->get('include')] : []; // marker must be in given map $modelClass = MapMarker::whereIn('map_id', $include); // Search text if (! empty($term)) { if (Str::startsWith($term, '=')) { $modelClass->where('name', mb_ltrim($term, '=')); } else { $modelClass->where('name', 'like', "%{$term}%"); } } else { $modelClass->orderBy('updated_at', 'desc'); } // execute query $models = $modelClass->limit(10) ->get(); // format results for frontend select $formatted = []; /** @var MapMarker $model */ foreach ($models as $model) { $format = [ 'id' => $model->id, 'text' => $model->markerTitle(), ]; $formatted[] = $format; } return response()->json($formatted); } } ================================================ FILE: app/Http/Controllers/Search/MentionController.php ================================================ authorize('member', $campaign); $this->service ->user(auth()->user()) ->request($request) ->campaign($campaign); return response()->json( $this->service->search() ); } public function load(MentionRequest $request, Campaign $campaign) { $this->authorize('member', $campaign); $this->service ->user(auth()->user()) ->request($request) ->campaign($campaign); return response()->json( $this->service->load() ); } } ================================================ FILE: app/Http/Controllers/Search/RecentController.php ================================================ recentService->campaign($campaign); if (auth()->check()) { $recent = $this->recentService ->user(auth()->user()) ->recent(); } return response()->json([ 'recent' => $recent, 'bookmarks' => $this->recentService->bookmarks(), 'indexes' => $this->recentService->indexes(), 'fulltext_route' => route('search.fulltext', [$campaign]), 'texts' => [ 'recents' => __('search.lookup.recents'), 'results' => __('search.lookup.results'), 'bookmarks' => __('entities.bookmarks'), 'index' => __('search.lookup.lists'), 'hint' => __('search.lookup.hint'), 'fulltext' => __('search.fulltext'), 'keyboard' => __('search.lookup.keyboard', [ 'k' => 'k', 'esc' => 'esc', ]), 'empty_results' => __('search.lookup.empty'), ], ]); } } ================================================ FILE: app/Http/Controllers/Search/TemplateController.php ================================================ json( $this->searchService ->request($request) ->campaign($campaign) ->entityType($entityType) ->search() ); } } ================================================ FILE: app/Http/Controllers/SearchController.php ================================================ get('q'); if (empty($term) || ! is_string($term)) { return redirect()->route('search.fulltext', [$campaign]); } $term = mb_trim($term); return redirect()->route('search.fulltext', [$campaign, 'term' => $term]); } } ================================================ FILE: app/Http/Controllers/Settings/AccountController.php ================================================ middleware(['auth', 'identity', 'password.confirm']); } public function index() { $user = auth()->user(); return view('settings.account') ->with(compact('user')); } } ================================================ FILE: app/Http/Controllers/Settings/ApiController.php ================================================ middleware(['auth', 'identity']); } /** * @return Factory|View */ public function index(Request $request) { $tokens = $request->user()->tokens() ->with('client') ->where('revoked', false) ->where('expires_at', '>', Date::now()) ->orderByDesc('created_at') ->get(); // Retrieving all the user's connections to third-party OAuth app clients. $applications = $tokens ->reject(fn (Token $token): bool => $token->client->revoked || $token->client->firstParty()) ->values(); $clients = Client::where('user_id', auth()->user()->id) ->where('revoked', false) ->orderByDesc('created_at') ->paginate(config('limits.pagination'), ['*'], 'clientsPage'); $tokens = $request->user()->tokens() ->with('client') ->where('revoked', false) ->where('expires_at', '>', Date::now()) ->orderByDesc('created_at') ->paginate(config('limits.pagination'), ['*'], 'tokensPage'); return view('settings.api', compact('tokens', 'clients', 'applications')); } /** * Create a new api token form * * @return Application|Factory|\Illuminate\Contracts\View\View * * @throws AuthorizationException */ public function create() { return view('settings.api.create'); } public function store(StoreApiToken $request) { if (request()->ajax()) { return response()->json(['success' => true]); } $accessToken = auth()->user()->createToken($request['name'])->accessToken; return redirect()->route('settings.api')->with('new_token', $accessToken); } public function revoke(Request $request, Token $token) { $this->authorize('update', $token); $revokedToken = Token::where('user_id', auth()->user()->id) ->where('id', $token->id) ->first(); $revokedToken->revoke(); return redirect()->route('settings.api')->with('success', 'Token deleted successfully.'); } } ================================================ FILE: app/Http/Controllers/Settings/AppearanceController.php ================================================ middleware(['auth', 'identity']); } public function index() { $highlight = request()->get('highlight'); $from = request()->get('from'); $editorOptions = [ '' => __('settings/appearance.editors.default', ['name' => 'Summernote']), ]; if (auth()->user()->created_at->isBefore('2023-01-09 12:00:00')) { $editorOptions['legacy'] = __('settings/appearance.editors.legacy', ['name' => 'TinyMCE 4']); } $editorOptions['tiptap'] = __('settings/appearance.editors.tiptap'); return view('settings.appearance') ->with('highlight', $highlight) ->with('from', $from) ->with('editorOptions', $editorOptions); } public function update(StoreSettingsLayout $request) { if ($request->ajax()) { return response()->json(); } /** @var User $user */ $user = $request->user(); $settingFields = $request->only([ 'editor', 'advanced_mentions', 'new_entity_workflow', 'campaign_switcher_order_by', 'date_format', ]); $user ->saveSettings($settingFields) ->update($request->only(['theme'])); // refresh user campaigns in cache if order by has changed if ($request->has('campaign_switcher_order_by')) { UserCache::clear(); } if ($request->filled('from')) { $from = base64_decode($request->get('from')); return redirect() ->to($from) ->with('success', __('settings/appearance.success')); } return redirect() ->route('settings.appearance') ->with('success', __('settings/appearance.success')); } } ================================================ FILE: app/Http/Controllers/Settings/Apps/DiscordController.php ================================================ middleware(['auth', 'identity']); $this->discord = $discord; } public function callback(Request $request) { try { $this->discord ->user($request->user()) ->validate($request->get('code', '')) ->addServer() ->addRoles(); return response()->redirectToRoute('settings.apps')->withSuccess( __('settings.apps.discord.success.add') ); } catch (Exception $e) { Log::error('Discord sync error for ' . $request->user()->id . ': ' . $e->getMessage()); return response()->redirectToRoute('settings.apps')->withError( __('settings.apps.discord.errors.add') ); } } public function destroy(Request $request) { try { $this->discord ->user($request->user()) ->remove(); return response()->redirectToRoute('settings.apps')->withSuccess( __('settings.apps.discord.success.remove') ); } catch (Exception $e) { return response()->redirectToRoute('settings.apps')->withError( __('settings.apps.discord.errors.remove') ); } } } ================================================ FILE: app/Http/Controllers/Settings/AppsController.php ================================================ middleware(['auth', 'identity']); } /** * @return Factory|View */ public function index() { return view('settings.apps'); } } ================================================ FILE: app/Http/Controllers/Settings/BoostController.php ================================================ middleware(['auth', 'identity']); } /** * @return Application|Factory|View|RedirectResponse * * @throws AuthorizationException * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function index() { if (! auth()->user()->hasBoosterNomenclature()) { return redirect()->route('settings.premium'); } // If a campaign was provided, make sure we have access to it $campaignId = request()->get('campaign'); $campaign = null; $superboost = false; /** @var User $user */ $user = auth()->user(); $boosts = $user->boosts()->with('campaign')->groupBy('campaign_id')->get(); $userCampaigns = $user->campaigns()->unboosted()->whereNotIn('campaigns.id', $boosts->pluck('campaign_id'))->get(); if (! empty($campaignId)) { /** @var Campaign $campaign */ $campaign = Campaign::where(['id' => (int) $campaignId])->firstOrFail(); CampaignCache::campaign($campaign); $this->authorize('access', $campaign); if ($campaign->superboosted()) { return redirect() ->route('settings.boost'); } $userCampaigns = $userCampaigns->where('id', '!=', $campaignId); $superboost = request()->get('superboost') === '1'; } return view('settings.boosters.index') ->with('campaigns', $userCampaigns) ->with('boosts', $boosts) ->with('focus', $campaign) ->with('superboost', $superboost); } } ================================================ FILE: app/Http/Controllers/Settings/BragiController.php ================================================ middleware(['auth', 'identity']); } /** * @return Factory|View */ public function index() { if (! auth()->user()->hasTokens()) { abort(401); } $logs = auth()->user()->bragiLogs()->orderByDesc('created_at')->paginate(); $isAdmin = auth()->user()->hasRole('admin'); return view('settings.bragi') ->with('logs', $logs) ->with('isAdmin', $isAdmin); } } ================================================ FILE: app/Http/Controllers/Settings/ClientController.php ================================================ middleware(['auth', 'identity']); } /** * Create a new api client form * * @return Application|Factory|View * * @throws AuthorizationException */ public function create() { return view('settings.client.create'); } public function edit(Client $client) { $this->authorize('update', $client); $client = Client::where('user_id', auth()->user()->id) ->where('id', $client->id) ->first(); return view('settings.client.update', ['client' => $client]); } public function update(StoreClient $request, Client $client) { $this->authorize('update', $client); $updatedClient = Client::where('user_id', auth()->user()->id) ->where('id', $client->id) ->first(); $updatedClient->forceFill($request->only(['name', 'redirect']))->save(); return redirect()->route('settings.api', ['clients' => 1])->with('success', 'Client updated successfully.'); } public function store(StoreClient $request) { if (request()->ajax()) { return response()->json(['success' => true]); } // Creating an OAuth app client that belongs to the given user. $client = app(ClientRepository::class)->createAuthorizationCodeGrantClient( user: auth()->user(), name: $request['name'], redirectUris: [$request['redirect']], confidential: true, enableDeviceFlow: true ); return redirect()->route('settings.api', ['clients' => 1])->with('new_token', $client->plainSecret); } public function revoke(Request $request, Client $client) { $this->authorize('update', $client); $client = Client::where('user_id', auth()->user()->id) ->where('id', $client->id) ->first(); $client->forceFill(['revoked' => true])->save(); return redirect()->route('settings.api')->with('success', 'Client deleted successfully.'); } } ================================================ FILE: app/Http/Controllers/Settings/NewsletterApiController.php ================================================ middleware(['auth', 'identity']); } /** * @return JsonResponse */ public function update(NewsletterStore $request) { $request->user() ->updateSettings($request->only(['mail_release'])) ->save(); MailSettingsChangeJob::dispatch($request->user()); return response()->json(['success' => true, 'message' => __('profiles.newsletter.updated')]); } } ================================================ FILE: app/Http/Controllers/Settings/NewsletterController.php ================================================ middleware(['auth', 'identity']); } /** * @return Factory|View */ public function index(Request $request) { $user = $request->user(); return view('settings.notifications', compact('user')); } } ================================================ FILE: app/Http/Controllers/Settings/PatreonController.php ================================================ middleware(['auth', 'identity']); $this->service = $service; } /** * @return Factory|View */ public function index(Request $request) { return view('settings.patreon'); } /** * @return RedirectResponse */ public function unlink(Request $request) { $this->service->user($request->user())->unlink(); return redirect()->route('settings.patreon') ->with('success', __('settings.patreon.remove.success')); } } ================================================ FILE: app/Http/Controllers/Settings/PremiumController.php ================================================ middleware(['auth', 'identity']); $this->service = $campaignBoostService; } public function index() { if (auth()->user()->hasBoosterNomenclature()) { return redirect()->route('settings.boost'); } // If a campaign was provided, make sure we have access to it $campaignId = request()->get('campaign'); $campaign = null; /** @var User $user */ $user = auth()->user(); $premiums = $user->boosts() ->with(['campaign', 'campaign.boosts', 'campaign.boosts.user']) ->has('campaign') ->groupBy('campaign_id') ->get(); $userCampaigns = $user->campaigns()->with(['boosts', 'boosts.user'])->unboosted()->whereNotIn('campaigns.id', $premiums->pluck('campaign_id'))->get(); if (! empty($campaignId)) { /** @var Campaign $campaign */ $campaign = Campaign::where(['id' => (int) $campaignId])->firstOrFail(); CampaignCache::campaign($campaign); $this->authorize('access', $campaign); if ($campaign->premium()) { return redirect() ->route('settings.premium'); } $userCampaigns = $userCampaigns->where('id', '!=', $campaignId); } return view('settings.premium.index') ->with('campaigns', $userCampaigns) ->with('premiums', $premiums) ->with('focus', $campaign); } /** * Migrate a user to the new system * * @return RedirectResponse */ public function migrate() { try { $this->service ->user(auth()->user()) ->migrate(); return redirect()->route('settings.premium') ->with('success', __('Thanks for switching to our premium campaigns!')); } catch (TranslatableException $ex) { return redirect() ->route('settings.boost') ->with('error', __($ex->getMessage())); } } /** * For local debugging * * @return RedirectResponse|void */ public function back() { if (! config('app.debug')) { return redirect()->route('settings.premium'); } $this->service ->user(auth()->user()) ->back(); return redirect()->route('settings.boost') ->with('success', __('Migrated back')); } } ================================================ FILE: app/Http/Controllers/Settings/ProfileController.php ================================================ middleware(['auth', 'identity']); } /** * @return Application|Factory|View */ public function index(Request $request) { $user = $request->user(); return view('settings.profile', compact('user')); } public function update(StoreSettingsProfile $request) { if ($request->ajax()) { return response()->json(); } /** @var User $user */ $user = $request->user(); $user->saveSettings($request->only(['settings.hide_subscription', 'settings.marketplace_name', 'settings.pronouns', 'settings.link'])) ->update($request->only('name', 'has_last_login_sharing', 'avatar', 'profile')); return redirect() ->route('settings.profile') ->with('success', trans('settings.profile.success')); } } ================================================ FILE: app/Http/Controllers/Settings/ReferralController.php ================================================ middleware('auth'); } public function index() { $this->service->user(auth()->user()); return view('settings.referrals.index') ->with('referral', $this->service->referral()) ->with('users', $this->service->users()) ->with('subscribers', $this->service->subscribers()) ->with('badge', $this->service->badge()); } } ================================================ FILE: app/Http/Controllers/Settings/ReleaseController.php ================================================ middleware(['auth', 'identity']); $this->tutorialService = $tutorialService; } /** * Update the user's last viewed release * * @return JsonResponse */ public function read(AppRelease $appRelease) { $user = auth()->user(); // Track it using the system already set up for tutorials. $this->tutorialService->user($user)->track('releases_' . $appRelease->category_id->value . '_' . $appRelease->id); return response()->json([ 'success' => true, ]); } } ================================================ FILE: app/Http/Controllers/Settings/Subscription/CancellationController.php ================================================ middleware(['auth', 'identity', 'subscriptions']); } public function index(Request $request) { $user = $request->user(); if ($user->hasPayPal() || $user->subscription('kanka')?->onGracePeriod()) { return view('settings.subscription.cancellation.grace', compact( 'user', )); } // Loss data $premiumCampaign = null; /** @var Collection $members */ $members = new Collection; $plugins = 0; $premiumCampaigns = $user->boosts() ->with([ 'campaign' => function ($sub) { return $sub->select('campaigns.id', 'campaigns.name'); }, 'campaign.members', 'campaign.plugins', ]) ->groupBy('campaign_id')->get(); foreach ($premiumCampaigns as $campaign) { if (! $premiumCampaign) { $premiumCampaign = $campaign->campaign; foreach ($campaign->campaign->members as $member) { $members->push($member->user_id); } $plugins += $campaign->campaign->plugins->count(); } } $members = $members->unique(); $players = $members->reject(fn ($userId) => $userId == $user->id)->count(); $discord = auth()->user()->discord(); return view('settings.subscription.cancellation.form', compact( 'user', 'premiumCampaigns', 'premiumCampaign', 'players', 'discord', 'plugins', )); } public function save(SubscriptionCancel $request) { if ($request->ajax()) { return response()->json(['success' => true]); } $this->service ->user($request->user()) ->request($request) ->cancel(); return redirect() ->route('settings.subscription.cancelled', ['cancelled' => 1]) ->with('sub_tracking', 'cancel') ->with('sub_value', 0); } } ================================================ FILE: app/Http/Controllers/Settings/Subscription/CancelledController.php ================================================ middleware(['auth', 'identity', 'subscriptions']); } public function index(Request $request) { $user = $request->user(); // If the user doesn't have an ending sub, send them back to the subscription page if (! $user->subscription('kanka')?->ends_at) { return redirect() ->route('settings.subscription'); } $tracking = session()->get('sub_tracking'); $gaTrackingEvent = null; if (! empty($tracking)) { $gaTrackingEvent = 'TJhYCMDErpYDEOaOq7oC'; if ($tracking === 'cancel') { DataLayer::newCancelledSubscriber(); } } $endDate = $user->subscription('kanka')->ends_at->isoFormat('MMMM D, Y'); return view('settings.subscription.cancelled') ->with('user', $user) ->with('endDate', $endDate) ->with('gaTrackingEvent', $gaTrackingEvent); } } ================================================ FILE: app/Http/Controllers/Settings/Subscription/FinishController.php ================================================ middleware(['auth', 'identity', 'subscriptions']); } public function index(Request $request) { /** @var User $user */ $user = auth()->user(); $stripeApiToken = config('cashier.key', null); $status = $this->subscriptionService->user($user)->status(); $current = $this->subscriptionService->currentPlan(); $currency = $user->currencySymbol(); $tracking = session()->get('sub_tracking'); $newSubPricingId = session()->get('sub_id'); $isPayPal = $user->hasPayPal(); $gaTrackingEvent = $gaPurchase = null; if (! empty($tracking)) { $gaTrackingEvent = 'TJhYCMDErpYDEOaOq7oC'; DataLayer::newSubscriber(); DataLayer::add('userSubValue', session('sub_value')); } if (! empty($newSubPricingId)) { /** @var TierPrice $pricing */ $pricing = TierPrice::find($newSubPricingId); $gaPurchase = [ 'value' => $pricing->cost, 'currency' => $pricing->currency, 'coupon' => session()->get('sub_coupon'), 'item_id' => $pricing->tier->id, 'item_name' => $pricing->tier->name . ($pricing->isYearly() ? ' Yearly' : null), ]; } $availableCampaigns = $user->campaigns()->unboosted()->get(); $isTrial = $request->get('trial') == 1; return view('settings.subscription.finish', compact( 'stripeApiToken', 'status', 'availableCampaigns', 'user', 'currency', 'current', 'tracking', 'gaTrackingEvent', 'gaPurchase', 'isPayPal', 'isTrial', )); } } ================================================ FILE: app/Http/Controllers/Settings/Subscription/FreeTrialController.php ================================================ middleware(['auth', 'identity', 'subscriptions']); } public function index(Request $request) { /** @var User $user */ $user = $request->user(); $this->authorize('free-trial', $user); return view('settings.subscription.free-trial', compact( 'user', )); } public function accept(Request $request) { /** @var User $user */ $user = $request->user(); $this->authorize('free-trial', $user); if ($request->ajax()) { return response()->json(['success' => true]); } $this->trialService->user($user)->accept(); return redirect()->route('settings.subscription.finish', ['trial' => 1]); } } ================================================ FILE: app/Http/Controllers/Settings/SubscriptionApiController.php ================================================ middleware(['auth', 'identity']); $this->service = $service; $this->couponService = $couponService; } /** * Creates an intent for payment so we can capture the payment * method for the user. * * @param Request $request The request data from the user. */ public function setupIntent(Request $request) { return $request->user()->createSetupIntent(); } /** * Adds a payment method to the current user. * * @return JsonResponse * * @throws CustomerAlreadyCreated */ public function paymentMethods(Request $request) { /** @var User $user */ $user = $request->user(); $paymentMethodID = $request->get('payment_method'); if ($user->stripe_id == null) { $user->createAsStripeCustomer(); } if ($user->isFrauding()) { return response() ->json(null, 429); } $user->addPaymentMethod($paymentMethodID); $user->updateDefaultPaymentMethod($paymentMethodID); // Save the expiration date on the user for alerts about expiring cards $payment = $user->defaultPaymentMethod(); if ($payment && $payment instanceof PaymentMethod) { /** @var Card $card */ $card = $payment->asStripePaymentMethod()->card; $expiresAt = Carbon::createFromDate($card->exp_year, $card->exp_month)->endOfMonth(); $user->card_expires_at = $expiresAt; $user->save(); } return response()->json(null, 204); } /** * Returns the payment methods the user has saved * * @param Request $request The request data from the user. */ public function getPaymentMethods(Request $request) { $user = $request->user(); $methods = []; if ($user->hasPaymentMethod()) { foreach ($user->paymentMethods() as $method) { array_push($methods, [ 'id' => $method->id, // @phpstan-ignore-line 'brand' => $method->card->brand, // @phpstan-ignore-line 'last_four' => $method->card->last4, // @phpstan-ignore-line 'exp_month' => $method->card->exp_month, // @phpstan-ignore-line 'exp_year' => $method->card->exp_year, // @phpstan-ignore-line ]); } } return response()->json($methods); } /** * Removes a payment method for the current user. * * @param Request $request The request data from the user. */ public function removePaymentMethod(Request $request) { /** @var User $user */ $user = $request->user(); $paymentMethodID = $request->get('id'); $paymentMethods = $user->paymentMethods(); foreach ($paymentMethods as $method) { // @phpstan-ignore-next-line if ($method->id == $paymentMethodID) { $method->delete(); break; } } $user->card_expires_at = null; $user->saveQuietly(); return response()->json(null, 204); } public function checkCoupon(ValidateCoupon $request, Tier $tier) { /** @var User $user */ $user = $request->user(); $coupon = $request->get('coupon'); return response()->json( $this->couponService ->user($user) ->code((string) $coupon) ->tier($tier) ->check() ); } } ================================================ FILE: app/Http/Controllers/Settings/SubscriptionController.php ================================================ middleware(['auth', 'identity', 'subscriptions']); $this->subscription = $service; $this->subscriptionUpgrade = $subscriptionUpgradeService; $this->emailValidation = $validationService; $this->currencyService = $currencyService; } public function index() { /** @var User $user */ $user = auth()->user(); $stripeApiToken = config('cashier.key'); $this->currencyService->user($user)->setDefaultCurrency(); $status = $this->subscription->user($user)->status(); $current = $this->subscription->currentPlan(); $currency = $user->currencySymbol(); $tiers = Tier::with('prices')->ordered()->get(); $isPayPal = $user->hasPayPal(); $hasManual = $user->hasManualSubscription(); $tempTiers = []; $downgrades = []; $upgrades = []; if (isset($current)) { foreach ($tiers as $tier) { if ($tier->id == $current->tier->id) { $downgrades = $tempTiers; } else { $tempTiers[] = $tier->id; } } $upgrades = array_diff($tempTiers, $downgrades); } return view('settings.subscription.index', compact( 'stripeApiToken', 'status', 'user', 'currency', 'current', 'tiers', 'isPayPal', 'hasManual', 'upgrades', 'downgrades', )); } public function change(Request $request, Tier $tier) { if ($tier->isFree()) { dd('Cancel instead'); } /** @var User $user */ $user = $request->user(); $period = $request->get('period') === 'yearly' ? PricingPeriod::Yearly : PricingPeriod::Monthly; // If the user has a cancelled sub still ending if ($user->subscribed('kanka') && $user->subscription('kanka')->onGracePeriod() && ! $user->hasPayPal()) { if ($tier->isCurrent($user)) { return view('settings.subscription.renew') ->with('user', $user); } return view('settings.subscription.change_blocked') ->with('user', $user); } $amount = $this->subscription ->user($request->user()) ->tier($tier) ->period($period) ->amount(); $card = $user->hasPaymentMethod() ? Arr::first($user->paymentMethods()) : null; if (empty($user->stripe_id)) { $user->createAsStripeCustomer(); } $intent = $user->createSetupIntent(); $isDowngrading = $this->subscription->downgrading(); $isYearly = $period->isYearly(); $hasPromo = true; // \Carbon\Carbon::create(2023, 11, 28)->isFuture(); $limited = $this->subscription->isLimited(); if ($user->hasPayPal() || $user->hasManualSubscription()) { $limited = true; } $upgrade = $this->subscriptionUpgrade ->user($user) ->tier($tier) ->period($period) ->upgradePrice(); $currency = $user->currencySymbol(); if ($user->isFrauding()) { $this->emailValidation->user($user)->requiresEmail(); } if ($user->hasNewsletter()) { $delay = app()->isProduction() ? 30 : 1; AbandonedCart::dispatch(auth()->user(), $tier)->delay(now()->addMinutes($delay)); } $nextBillingDate = Carbon::now(); if ($period === PricingPeriod::Yearly) { $nextBillingDate->addYear(); } else { $nextBillingDate->addMonth(); } return view('settings.subscription.change', compact( 'tier', 'period', 'amount', 'card', 'intent', 'currency', 'user', 'upgrade', 'isDowngrading', 'hasPromo', 'limited', 'isYearly', 'nextBillingDate' )); } public function renew(Request $request) { if ($request->ajax()) { return response()->json([]); } try { $this->subscription ->user($request->user()) ->renew(); $request->user()->log(UserAction::subRenew); $routeOptions = []; return redirect() ->route('settings.subscription', $routeOptions) ->withSuccess(__('subscriptions.renew.success')); } catch (IncompletePayment $exception) { session()->put('subscription_callback', $request->get('payment_id')); return redirect()->route( 'cashier.payment', // @phpstan-ignore-next-line [$exception->payment->id, 'redirect' => route('settings.subscription.callback')] ); } catch (TranslatableException $e) { return redirect() ->route('settings.subscription') ->with('error_raw', $e->getTranslatedMessage()); } catch (Exception $e) { // Error? json return response()->json([ 'error' => true, 'message' => $e->getMessage(), ]); } } /** * Subscribe */ public function subscribe(UserSubscribeStore $request, Tier $tier) { if ($request->user()->isFrauding()) { return redirect() ->route('settings.subscription') ->withError(__('settings.subscription.errors.failed', ['email' => config('app.email')])); } if ($request->ajax()) { return response()->json(['success' => true]); } try { $period = $request->get('period') === 'yearly' ? PricingPeriod::Yearly : PricingPeriod::Monthly; $this->subscription->user($request->user()) ->tier($tier) ->period($period) ->coupon($request->get('coupon')) ->request($request->all()) ->change() ->finish(); return redirect() ->route('settings.subscription.finish') ->with('sub_tracking', 'subscribed') ->with('sub_value', $this->subscription->subscriptionValue()) ->with('sub_coupon', $request->get('coupon')) ->with('sub_id', $this->subscription->tierPrice()->id); } catch (IncompletePayment $exception) { session()->put('subscription_callback', $request->get('payment_id')); return redirect()->route( 'cashier.payment', // @phpstan-ignore-next-line [$exception->payment->id, 'redirect' => route('settings.subscription.callback')] ); } catch (TranslatableException $e) { return redirect() ->route('settings.subscription') ->with('error_raw', $e->getTranslatedMessage()); } catch (Exception $e) { // If they are trying to sub in another currency, let's help them understand that issue $error = $e->getMessage(); if (Str::contains($error, 'expected currency')) { preg_match_all('/`(\w{3})`/', $error, $currencies); return redirect() ->route('settings.subscription') ->with('error_raw', __('subscription.errors.invalid_currency', [ 'old' => mb_strtoupper($currencies[1][0]), 'new' => mb_strtoupper($currencies[1][1]), 'email' => '' . config('app.email') . '', ])); } // Error? json return response()->json([ 'error' => true, 'message' => $e->getMessage(), ]); } } /** * Stripe secure 3d callback page handler */ public function callback(Request $request) { // Not expecting a callback if (! session()->has('subscription_callback')) { return redirect() ->route('settings.subscription'); } // This contains our original request session()->remove('subscription_callback'); if ($request->get('success')) { return redirect() ->route('settings.subscription') ->withSuccess(__('settings.subscription.success.callback')); } return redirect() ->route('settings.subscription') ->withError(__('settings.subscription.errors.callback')); } } ================================================ FILE: app/Http/Controllers/Settings/TutorialController.php ================================================ middleware(['auth', 'identity']); $this->service = $service; } public function reset() { if (app()->isProduction()) { abort(404); } $this->service ->user(auth()->user()) ->reset(); return redirect()->route('settings.profile') ->with('success', __('Tutorials reset')); } public function dismiss(string $code) { $this->service ->user(auth()->user()) ->track($code); return response()->json(['success', true]); } } ================================================ FILE: app/Http/Controllers/SetupController.php ================================================ middleware(['auth', 'identity']); } public function index() { $campaigns = auth()->user()->campaigns->where('visibility_id', CampaignVisibility::public); if (request()->has('campaign')) { $campaign = $campaigns->where('slug', request()->get('campaign'))->firstOrFail(); } return view('spotlights.index') ->with('campaigns', $campaigns) ->with('campaign', $campaign ?? null); } public function form(Campaign $campaign) { $this->authorize('member', $campaign); $content = $this->service ->campaign($campaign) ->content(); return view('spotlights.form') ->with('campaign', $campaign) ->with('content', $content); } public function save(ApplyRequest $request, Campaign $campaign) { $this->authorize('member', $campaign); $this->service ->campaign($campaign) ->user(auth()->user()) ->request($request); if ($request->get('action') == 'apply') { try { $this->service->apply(); return redirect() ->route('spotlights.form', [$campaign]) ->with('success', __('spotlights.apply.success')); } catch (TranslatableException $e) { return redirect() ->route('spotlights.form', [$campaign]) ->with('error', $e->getTranslatedMessage()); } } $this->service->save(); return redirect() ->route('spotlights.form', [$campaign]) ->with('success', __('spotlights.form.success')); } public function retract(Request $request, Campaign $campaign) { $this->authorize('member', $campaign); $this->service ->campaign($campaign) ->user(auth()->user()) ->retract(); return redirect() ->route('spotlights.form', [$campaign]) ->with('success', __('spotlights.retract.success')); } } ================================================ FILE: app/Http/Controllers/Subscription/PayPal/RenewalController.php ================================================ middleware(['auth', 'identity']); } public function index(Request $request) { $user = $request->user(); if (! $user->can('renewPaypalSubscription', $user)) { return redirect() ->route('settings.subscription') ->with('error', __('subscriptions/paypal-renew.errors.permission')); } $tiers = Tier::ordered()->get()->reject(fn (Tier $tier) => $tier->isFree()); return view('settings.subscription.paypal-renew', compact('user', 'tiers')); } public function process(ValidatePledge $request, Tier $tier) { $this->authorize('renewPaypalSubscription', $request->user()); if ($tier->isFree()) { abort(401); } $response = $this->service ->user($request->user()) ->tier($tier) ->process(); if (isset($response['id'])) { foreach ($response['links'] as $link) { if ($link['rel'] === 'approve') { return redirect()->away($link['href']); } } } Log::error('PayPal renewal process error', $response); return redirect() ->route('settings.subscription') ->with('error', __('subscriptions/paypal.errors.failed') . ' ' . __('subscriptions/paypal.errors.contact', ['email' => config('app.email')])); } public function success(Request $request) { $provider = new PayPalClient; $provider->setApiCredentials(config('paypal')); $provider->getAccessToken(); $response = $provider->capturePaymentOrder($request['token']); if (isset($response['status']) && $response['status'] === 'COMPLETED') { $tierName = $response['purchase_units']['0']['reference_id']; $tier = Tier::where('name', $tierName)->firstOrFail(); Log::info('PayPal renewal', $response); $this->service ->user($request->user()) ->renew($tier); return redirect() ->route('settings.subscription') ->with('success', __('subscriptions.renew.success', ['date' => auth()->user()->subscription('kanka')->ends_at->isoFormat('MMMM D, Y')])); } Log::error('PayPal renewal capture error', $response); return redirect() ->route('settings.subscription') ->with('error', __('subscriptions/paypal.errors.incomplete') . ' ' . __('subscriptions/paypal.errors.contact', ['email' => config('app.email')])); } public function cancel() { return redirect() ->route('settings.subscription') ->with('error', __('settings.subscription.errors.callback')); } } ================================================ FILE: app/Http/Controllers/Summernote/GalleryController.php ================================================ middleware('auth'); $this->service = $service; } public function index(Campaign $campaign) { $this->authorize('access', $campaign); $start = request()->get('page', 0); $name = request()->get('name'); $perPage = 20; $offset = $start * $perPage; $response = [ 'data' => [], 'links' => [], ]; // Has folder? Go back option $folderId = request()->get('folder_id'); if (! empty($folderId) && ! request()->has('page')) { $image = Image::where('is_folder', true)->where('id', $folderId)->firstOrFail(); $response['data'][] = [ 'title' => __('crud.actions.back'), 'folder' => $image->is_folder, 'id' => $image->id, 'icon' => 'fa-regular fa-arrow-left', 'url' => route('campaign.gallery.summernote', $image->folder_id ? [$campaign, 'folder_id' => $image->folder_id] : [$campaign]), ]; } $canBrowse = auth()->user()->can('browse', [Image::class, $campaign]); $images = Image::acl($canBrowse) ->where('is_default', false) ->orderBy('is_folder', 'desc') ->orderBy('updated_at', 'desc') ->offset($offset) ->take(20); if ($name) { $images->where('name', 'like', "%{$name}%"); } else { $images->imageFolder($folderId); } $images = $images->get(); /** @var Image $image */ foreach ($images as $image) { $response['data'][] = [ 'src' => $image->url(), 'title' => $image->name, 'folder' => $image->is_folder, 'icon' => 'fa-regular fa-folder', 'id' => $image->id, 'url' => $image->is_folder ? route('campaign.gallery.summernote', [$campaign, 'folder_id' => $image->id]) : [$campaign], 'thumb' => $image->getImagePath(128, 128), ]; } // Next page $total = Image::count(); if ($offset + $perPage < $total) { $params = ['page' => $start + 1]; $params[] = $campaign; if (! empty($folderId)) { $params['folder_id'] = $folderId; } $response['links']['next'] = route('campaign.gallery.summernote', $params); } return response()->json($response); } /** * Called when adding an image from the text editor */ public function upload(GalleryImageStore $request, Campaign $campaign): JsonResponse { $this->authorize('create', [Image::class, $campaign]); $images = $this->service ->campaign($campaign) ->user(auth()->user()) ->store($request); /** @var Image $image */ $image = Arr::first($images); return response()->json([ 'url' => $image->url(), 'id' => $image->id, ]); } } ================================================ FILE: app/Http/Controllers/Tags/ChildController.php ================================================ campaign($campaign)->authEntityView($tag->entity); $options = ['campaign' => $campaign, 'tag' => $tag, 'm' => $this->descendantsMode()]; $base = 'allChildren'; if ($this->filterToDirect()) { $base = 'entities'; } Datagrid::layout(Entity::class) ->route('tags.children', $options); $this->rows = $tag ->{$base}() ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->with(['image', 'tags', 'entityType']) ->paginate(config('limits.pagination')); // Ajax Datagrid if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('tags.children', $tag); } /** * @return Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign, Tag $tag) { $this->authorize('update', $tag->entity); $formOptions = ['tags.entity-add.save', $campaign, 'tag' => $tag]; if (request()->has('from-children')) { $formOptions['from-children'] = true; } return view('tags.entities.create', [ 'campaign' => $campaign, 'model' => $tag, 'formOptions' => $formOptions, ]); } /** * @throws AuthorizationException */ public function store(StoreTagEntity $request, Campaign $campaign, Tag $tag) { $this->authorize('update', $tag->entity); if (request()->ajax()) { return response()->json(['success' => true]); } $redirectUrlOptions = ['campaign' => $campaign, 'entity' => $tag->entity]; if (request()->has('from-children')) { $redirectUrlOptions['tag_id'] = $tag->id; } $count = $tag->attachEntities($request->get('entities')); return redirect()->route('entities.show', $redirectUrlOptions) ->with('success', trans_choice('tags.children.create.attach_success', $count, ['name' => $tag->name, 'count' => $count])); } } ================================================ FILE: app/Http/Controllers/Tags/PostController.php ================================================ campaign($campaign)->authEntityView($tag->entity); $options = ['campaign' => $campaign, 'tag' => $tag]; Datagrid::layout(Post::class) ->route('tags.posts', $options); $this->rows = $tag ->posts() ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->with(['entity', 'entity.image', 'tags']) ->has('entity') ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return $this ->campaign($campaign) ->subview('tags.children.posts', $tag); } } ================================================ FILE: app/Http/Controllers/Tags/TagController.php ================================================ campaign($campaign)->authEntityView($tag->entity); return redirect()->route('entities.children', [$campaign, $tag->entity]); } } ================================================ FILE: app/Http/Controllers/Tags/TransferController.php ================================================ service = $service; } public function index(Campaign $campaign, Tag $tag) { $this->authorize('update', $tag->entity); return view('tags.transfer.entities.transfer', compact('campaign', 'tag')); } public function postIndex(Campaign $campaign, Tag $tag) { $this->authorize('update', $tag->entity); return view('tags.transfer.posts.transfer', compact('campaign', 'tag')); } public function process(TransferTag $request, Campaign $campaign, Tag $tag) { $this->authorize('update', $tag->entity); $newTag = Tag::where('id', $request->tag_id)->first(); try { $this->service->transfer($tag, $newTag); return redirect() ->route('entities.show', [$campaign, $tag->entity]) ->with('success_raw', __('tags.transfer.success', ['tag' => $tag->name, 'newTag' => $newTag->name])); } catch (TranslatableException $ex) { return redirect() ->route('entities.show', [$campaign, $tag->entity]) ->with('error', __('tags.transfer.fail', ['tag' => $tag->name, 'newTag' => $newTag->name])); } } public function processPosts(TransferTag $request, Campaign $campaign, Tag $tag) { $this->authorize('update', $tag->entity); $newTag = Tag::where('id', $request->tag_id)->first(); try { $this->service->transferPosts($tag, $newTag); return redirect() ->route('entities.show', [$campaign, $tag->entity]) ->with('success_raw', __('tags.transfer.success_post', ['tag' => $tag->name, 'newTag' => $newTag->name])); } catch (TranslatableException $ex) { return redirect() ->route('entities.show', [$campaign, $tag->entity]) ->with('error', __('tags.transfer.fail_post', ['tag' => $tag->name, 'newTag' => $newTag->name])); } } } ================================================ FILE: app/Http/Controllers/Templates/LoadController.php ================================================ json( $this ->templateService ->campaign($campaign) ->api($request->get('template')) ); } } ================================================ FILE: app/Http/Controllers/Timelines/TimelineController.php ================================================ campaign($campaign)->authEntityView($timeline->entity); return redirect()->route('entities.children', [$campaign, $timeline->entity]); } } ================================================ FILE: app/Http/Controllers/Timelines/TimelineElementController.php ================================================ service = $timelineService; } public function show(Campaign $campaign, Timeline $timeline, TimelineElement $timelineElement) { return redirect()->route('entities.show', [$campaign, $timeline->entity]); } public function index(Campaign $campaign, Timeline $timeline) { return redirect()->route('entities.show', [$campaign, $timeline->entity]); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function create(Request $request, Campaign $campaign, Timeline $timeline) { $this->authorize('update', $timeline->entity); $eraId = $request->get('era_id'); $position = $request->get('position', 1); $era = TimelineEra::findOrFail($eraId); return view( 'timelines.elements.create', compact('campaign', 'timeline', 'eraId', 'position', 'era') ); } /** * @throws AuthorizationException */ public function store(Campaign $campaign, Timeline $timeline, StoreTimelineElement $request) { $this->authorize('update', $timeline->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $model = new TimelineElement; $data = $request->only($this->fields); $data['timeline_id'] = $timeline->id; $new = $model->create($data); $this->service->reorderElements($new); return redirect() ->route('entities.show', [$campaign, $timeline->entity, '#timeline-element-' . $new->id]) ->withSuccess(__('timelines/elements.create.success', ['name' => $new->name])); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function edit(Campaign $campaign, Timeline $timeline, TimelineElement $timelineElement) { $this->authorize('update', $timeline->entity); $editingUsers = null; $model = $timelineElement; if ($campaign->hasEditingWarning()) { /** @var MultiEditingService $editingService */ $editingService = app()->make(MultiEditingService::class); $editingUsers = $editingService->model($model)->user(auth()->user())->users(); // If no one is editing the model, we are now editing it if (empty($editingUsers)) { $editingService->edit(); } } return view( 'timelines.elements.edit', compact('timeline', 'campaign', 'model', 'editingUsers') ); } /** * @throws AuthorizationException */ public function update(StoreTimelineElement $request, Campaign $campaign, Timeline $timeline, TimelineElement $timelineElement) { $this->authorize('update', $timeline->entity); /** @var MultiEditingService $editingService */ $editingService = app()->make(MultiEditingService::class); $editingService->model($timelineElement) ->user($request->user()) ->finish(); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $data = $request->only($this->fields); if (! $request->has('entity_id')) { $data['entity_id'] = null; } if ($request->position == null) { unset($data['position']); } $timelineElement->update($data); if ($request->position) { $this->service->reorderElements($timelineElement); } return redirect() ->route('entities.show', [$campaign, $timeline->entity, '#timeline-element-' . $timelineElement->id]) ->withSuccess(__('timelines/elements.edit.success', ['name' => $timelineElement->name])); } /** * @throws AuthorizationException */ public function destroy(Campaign $campaign, Timeline $timeline, TimelineElement $timelineElement) { $this->authorize('update', $timeline->entity); $timelineElement->delete(); $this->service->reorderElements($timelineElement, true); return redirect() ->route('entities.show', [$campaign, $timeline->entity]) ->withSuccess(__('timelines/elements.delete.success', ['name' => $timelineElement->name])); } } ================================================ FILE: app/Http/Controllers/Timelines/TimelineEraController.php ================================================ authorize('update', $timeline->entity); $options = ['campaign' => $campaign, 'timeline' => $timeline->id]; Datagrid::layout(Era::class) ->route('timelines.timeline_eras.index', $options); $this->rows = $timeline ->eras() ->sort(request()->only(['o', 'k']), ['position' => 'asc']) ->with(['timeline']) ->paginate(config('limits.pagination')); if (request()->ajax()) { return $this->campaign($campaign)->datagridAjax(); } return view('timelines.eras.index') ->with('rows', $this->rows) ->with('campaign', $campaign) ->with('model', $timeline) ->with('entity', $timeline->entity); } public function show(Campaign $campaign, Timeline $timeline, TimelineEra $timelineEra) { return redirect()->to($timeline->getLink()); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function create(Campaign $campaign, Timeline $timeline) { $this->authorize('update', $timeline->entity); $from = request()->get('from') == 'view' ? 'view' : null; return view( 'timelines.eras.create', compact('campaign', 'timeline', 'from') ); } /** * @throws AuthorizationException */ public function store(Campaign $campaign, Timeline $timeline, StoreTimelineEra $request) { $this->authorize('update', $timeline->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $model = new TimelineEra; $data = $request->only($this->fields); $data['timeline_id'] = $timeline->id; $new = $model->create($data); if (request()->post('from') == 'view') { return redirect() ->route('entities.show', [$campaign, $timeline->entity, '#era' . $new->id]) ->withSuccess(__('timelines/eras.create.success', ['name' => $new->name])); } return redirect() ->route('timelines.timeline_eras.index', [$campaign, $timeline]) ->withSuccess(__('timelines/eras.create.success', ['name' => $new->name])); } /** * @return Application|Factory|View * * @throws AuthorizationException */ public function edit(Campaign $campaign, Timeline $timeline, TimelineEra $timelineEra) { $this->authorize('update', $timeline->entity); $model = $timelineEra; $from = request()->get('from') == 'view' ? 'view' : null; return view( 'timelines.eras.edit', compact('campaign', 'timeline', 'model', 'from') ); } /** * @throws AuthorizationException */ public function update(StoreTimelineEra $request, Campaign $campaign, Timeline $timeline, TimelineEra $timelineEra) { $this->authorize('update', $timeline->entity); // For ajax requests, send back that the validation succeeded, so we can really send the form to be saved. if (request()->ajax()) { return response()->json(['success' => true]); } $timelineEra->update($request->only($this->fields)); if (request()->post('from') == 'view') { return redirect() ->route('entities.show', [$campaign, $timeline->entity, '#era' . $timelineEra->id]) ->withSuccess(__('timelines/eras.edit.success', ['name' => $timelineEra->name])); } return redirect() ->route('timelines.timeline_eras.index', [$campaign, $timeline]) ->withSuccess(__('timelines/eras.edit.success', ['name' => $timelineEra->name])); } public function destroy(Campaign $campaign, Timeline $timeline, TimelineEra $timelineEra) { $this->authorize('update', $timeline->entity); $timelineEra->delete(); if (request()->get('from') == 'view') { return redirect() ->route('entities.show', [$campaign, $timeline->entity]) ->withSuccess(__('timelines/eras.delete.success', ['name' => $timelineEra->name])); } return redirect() ->route('timelines.timeline_eras.index', [$campaign, $timeline]) ->withSuccess(__('timelines/eras.delete.success', ['name' => $timelineEra->name])); } public function bulk(Request $request, Campaign $campaign, Timeline $timeline) { $this->authorize('update', $timeline->entity); $action = $request->get('action'); $models = $request->get('model'); if (! in_array($action, $this->validBulkActions()) || empty($models)) { return redirect()->back(); } /*if ($action === 'edit') { return $this->bulkBatch(route('timelines.eras.bulk', [$campaign, 'timeline' => $timeline]), '_timeline-era', $models); }*/ if (request()->ajax()) { return response()->json(['success' => true]); } $count = $this->bulkProcess($request, TimelineEra::class); return redirect() ->route('timelines.timeline_eras.index', [$campaign, 'timeline' => $timeline]) ->with('success', trans_choice('timelines/eras.bulks.' . $action, $count, ['count' => $count])); } public function positionList(Campaign $campaign, Timeline $timeline, TimelineEra $timelineEra) { $this->authorize('view', $timeline->entity); if ($timeline->id != $timelineEra->timeline_id) { abort(404); } $new = (bool) request()->get('new'); return response()->json([ 'positions' => $timelineEra->positionOptions(null, $new), ]); } } ================================================ FILE: app/Http/Controllers/Timelines/TimelineReorderController.php ================================================ service = $timelineService; } /** * @return Factory|View * * @throws AuthorizationException */ public function index(Campaign $campaign, Timeline $timeline) { $this->authorize('update', $timeline->entity); $eras = $timeline ->eras() ->with(['orderedElements', 'orderedElements.entity']) ->ordered() ->get(); return view('timelines.reorder.index', compact( 'campaign', 'eras', 'timeline', )); } /** * @throws AuthorizationException */ public function save(Campaign $campaign, Timeline $timeline, ReorderTimeline $request) { $this->authorize('update', $timeline->entity); $this->service ->timeline($timeline) ->reorder($request); return redirect() ->route('entities.show', [$campaign, $timeline->entity]) ->withSuccess(__('timelines.reorder.success', ['name' => $timeline->name])); } } ================================================ FILE: app/Http/Controllers/TroubleshootingController.php ================================================ middleware(['auth', 'identity']); } public function index() { $campaigns = $this->service ->user(auth()->user()) ->campaigns(); $token = session()->get('token'); return view('helpers.troubleshooting.index') ->with(compact('campaigns', 'token')); } public function store(SaveUserHelp $request) { if (request()->ajax()) { return response()->json(); } try { $campaign = Campaign::findOrFail($request->get('campaign')); $invite = $this->service ->user($request->user()) ->campaign($campaign) ->generate(); return redirect() ->route('troubleshooting') ->with('token', $invite->token); } catch (TranslatableException $e) { return redirect() ->route('troubleshooting') ->with('error_raw', $e->getTranslatedMessage()); } } } ================================================ FILE: app/Http/Controllers/User/EmailValidationController.php ================================================ middleware('auth'); } public function validateEmail(Request $request, UserValidation $userValidation) { if (auth()->user()->id != $userValidation->user_id) { return response()->redirectTo(route('settings.subscription'))->withError(__('emails/validation.error')); } $userValidation->is_valid = true; $userValidation->saveQuietly(); return response()->redirectTo(route('settings.subscription'))->withSuccess(__('emails/validation.success')); } } ================================================ FILE: app/Http/Controllers/User/ProfileController.php ================================================ campaigns()->public()->front()->paginate(); return view('users.profile') ->with('user', $user) ->with('campaigns', $campaigns); } } ================================================ FILE: app/Http/Controllers/WebhookController.php ================================================ getUserByStripeId($payload['data']['object']['customer'])) { /** @var User $user */ /** @var SubscriptionService $service */ $service = app()->make('App\Services\SubscriptionService'); $data = $payload['data']['object']; $status = Arr::get($data, 'status', false); Log::debug('Customer Sub Updated Status ' . $status); // If the status is past_due, we need to remind the user to update their credit card info. // Also if the user is cancelling, we've already handled that in Kanka, we don't need to handle it here, but // stripe will still tell us about it. if ($status != 'past_due' && ! $this->isCancelling($payload)) { $service->user($user)->webhook() ->plan($payload['data']['object']['plan']['id']) ->finish(); } } return $response; } /** * Handle a deleted subscription */ public function handleCustomerSubscriptionDeleted(array $payload) { // Call parent handler method $response = parent::handleCustomerSubscriptionDeleted($payload); // User notification. Maybe even an email if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) { /** @var User $user */ // Notify admin SubscriptionDeletedEmailJob::dispatch($user); // Cleanup the user "now". This used to have a delay, but if Stripe is calling this endpoint, // it's that the user's sub has ended. SubscriptionEndJob::dispatch($user); MailSettingsChangeJob::dispatch($user); } return $response; } /** * Handle an upcoming invoice (yearly renewal warning). */ public function handleInvoiceUpcoming(array $payload): Response { $data = $payload['data']['object']; $user = $this->getUserByStripeId($data['customer'] ?? null); if (! $user) { return $this->successMethod(); } /** @var User $user */ $yearlyPlans = array_filter(array_merge( config('subscription.owlbear.yearly'), config('subscription.wyvern.yearly'), config('subscription.elemental.yearly'), )); $lines = $data['lines']['data'] ?? []; $isYearly = collect($lines)->contains( fn ($line) => in_array($line['price']['id'] ?? null, $yearlyPlans) ); if (! $isYearly) { return $this->successMethod(); } UpcomingYearlyAlert::dispatch($user); return $this->successMethod(); } /** * Check if a request is to cancel a user */ protected function isCancelling(array $data): bool { // Log::debug('data', $data); $cancel = Arr::get($data, 'object.canceled_at', null); $previousCancel = Arr::get($data, 'previous_attributes.canceled_at', null); return ! empty($cancel) && empty($previousCancel); } /** * Handle payment method automatically updated by vendor. */ protected function handlePaymentMethodAutomaticallyUpdated(array $payload) { if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) { /** @var User $user */ $user->updateDefaultPaymentMethodFromStripe(); /** @var PaymentMethodService $paymentService */ $paymentService = app()->make(PaymentMethodService::class); $paymentService->updateExpiry($user, UserAction::paymentAuto); } return $this->successMethod(); } /** * Handle payment method updated by vendor. */ protected function handlePaymentMethodUpdated(array $payload) { if ($user = $this->getUserByStripeId($payload['data']['object']['customer'])) { /** @var User $user */ $user->updateDefaultPaymentMethodFromStripe(); /** @var PaymentMethodService $paymentService */ $paymentService = app()->make(PaymentMethodService::class); $paymentService->updateExpiry($user, UserAction::paymentEdit); } return $this->successMethod(); } } ================================================ FILE: app/Http/Controllers/Whiteboards/ApiController.php ================================================ campaign($campaign)->authEntityView($whiteboard->entity); if (auth()->check()) { $this->apiService->user(auth()->user()); } return response()->json( $this->apiService ->campaign($campaign) ->whiteboard($whiteboard) ->load() ); } public function store(StoreShapeRequest $request, Campaign $campaign, Whiteboard $whiteboard) { $this->authorize('view', $campaign); $this->authorize('update', $whiteboard->entity); $shape = $this->persistanceService ->whiteboard($whiteboard) ->request($request) ->create(); $shape->setRelation('whiteboard', $whiteboard); $whiteboard->setRelation('campaign', $campaign); $entity = null; if ($request->has('entity_id')) { $entity = Entity::find($request->get('entity_id')); if ($entity) { $entity = new EntityResource($entity)->campaign($campaign)->toArray($request); } else { $entity = null; } } broadcast(new Updated( $whiteboard, 'created', $shape, $shape->image(), $entity ))->toOthers(); return response()->json([ 'success' => true, 'id' => $shape->id, 'urls' => [ 'edit' => route('whiteboards.shapes.update', [$campaign, $whiteboard, $shape]), 'delete' => route('whiteboards.shapes.delete', [$campaign, $whiteboard, $shape]), 'stroke' => route('whiteboards.shapes.stroke', [$campaign, $whiteboard, $shape]), ], ]); } public function update(UpdateShapeRequest $request, Campaign $campaign, Whiteboard $whiteboard, WhiteboardShape $whiteboardShape) { $this->authorize('view', $campaign); $this->authorize('update', $whiteboard->entity); $this->persistanceService ->whiteboard($whiteboard) ->request($request) ->shape($whiteboardShape) ->save(); $whiteboardShape->setRelation('whiteboard', $whiteboard); $whiteboard->setRelation('campaign', $campaign); broadcast(new Updated($whiteboard, 'updated', $whiteboardShape))->toOthers(); return response()->json([ 'success' => true, ]); } public function destroy(Campaign $campaign, Whiteboard $whiteboard, WhiteboardShape $whiteboardShape) { $this->authorize('view', $campaign); $this->authorize('update', $whiteboard->entity); $whiteboardShape->delete(); $whiteboardShape->setRelation('whiteboard', $whiteboard); $whiteboard->setRelation('campaign', $campaign); broadcast(new Updated($whiteboard, 'deleted', $whiteboardShape))->toOthers(); return response(null, 204); } public function stroke(CreateStrokeRequest $request, Campaign $campaign, Whiteboard $whiteboard, WhiteboardShape $whiteboardShape) { $this->authorize('view', $campaign); $this->authorize('update', $whiteboard->entity); $stroke = $this->persistanceService ->whiteboard($whiteboard) ->request($request) ->shape($whiteboardShape) ->addStroke(); return response()->json([ 'success' => true, 'id' => $stroke->id, ]); } } ================================================ FILE: app/Http/Controllers/Whiteboards/CrudController.php ================================================ authorize('create', [$this->getEntityType(), $campaign]); if (! auth()->user()->can('whiteboards', $campaign)) { // @phpstan-ignore-next-line return view('whiteboards.cta') ->with('campaign', $campaign); } return $this->campaign($campaign)->crudCreate(); } public function store(StoreWhiteboard $request, Campaign $campaign) { $this->authorize('create', [$this->getEntityType(), $campaign]); if (! auth()->user()->can('whiteboards', $campaign)) { return view('whiteboards.cta') ->with('campaign', $campaign); } return $this->campaign($campaign)->crudStore($request); } /** * Display the specified resource. */ public function show(Campaign $campaign, Whiteboard $whiteboard) { return $this->campaign($campaign)->crudShow($whiteboard); } /** * Show the form for editing the specified resource. */ public function edit(Campaign $campaign, Whiteboard $whiteboard) { return $this->campaign($campaign)->crudEdit($whiteboard); } /** * Update the specified resource in storage. */ public function update(StoreWhiteboard $request, Campaign $campaign, Whiteboard $whiteboard) { return $this->campaign($campaign)->crudUpdate($request, $whiteboard); } /** * Remove the specified resource from storage. */ public function destroy(Campaign $campaign, Whiteboard $whiteboard) { return $this->campaign($campaign)->crudDestroy($whiteboard); } protected function getEntityType(): EntityType { return EntityType::where('id', config('entities.ids.whiteboard'))->first(); } } ================================================ FILE: app/Http/Controllers/Whiteboards/DrawController.php ================================================ campaign($campaign)->authEntityView($whiteboard->entity); return view('whiteboards.draw', compact('campaign', 'whiteboard')); } } ================================================ FILE: app/Http/Controllers/Widgets/CalendarWidgetController.php ================================================ service = $advancerService; $this->reminderService = $reminderService; } public function add(Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { if ($campaignDashboardWidget->widget != Widget::Calendar) { return response()->json([ 'success' => false, ]); } /** @var Calendar $calendar */ $calendar = $campaignDashboardWidget->entity->child; $this->service->calendar($calendar)->advance(); return view('dashboard.widgets.calendar.body') ->with('widget', $campaignDashboardWidget) ->with('calendar', $calendar) ->with('campaign', $campaign); } public function sub(Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { if ($campaignDashboardWidget->widget != Widget::Calendar) { return response()->json([ 'success' => false, ]); } /** @var Calendar $calendar */ $calendar = $campaignDashboardWidget->entity->child; $this->service->calendar($calendar)->retreat(); return view('dashboard.widgets.calendar.body') ->with('widget', $campaignDashboardWidget) ->with('calendar', $calendar) ->with('campaign', $campaign); } public function render(Campaign $campaign, CampaignDashboardWidget $campaignDashboardWidget) { if ($campaignDashboardWidget->widget != Widget::Calendar) { return response()->json([ 'success' => false, ]); } return view('dashboard.widgets.calendar.body') ->with('widget', $campaignDashboardWidget) ->with('campaign', $campaign); } } ================================================ FILE: app/Http/Kernel.php ================================================ [ Middleware\EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, AuthenticateSession::class, ShareErrorsFromSession::class, Middleware\VerifyCsrfToken::class, SubstituteBindings::class, // Middleware\HttpsProtocol::class, // Force https in prod Middleware\LocaleChange::class, // Save language changing Middleware\LocalizeDatetime::class, Tracking::class, Middleware\CheckIfUserBanned::class, Middleware\OTP::class, ReplicationSwitcher::class, ], 'api' => [ // Do this in the routes 'throttle:rate_limit,1', 'bindings', Middleware\ApiLogMiddleware::class, ], // Used for locale-less routes like our sitemaps, go/, auth/callbacks, webhooks 'minimum' => [ Middleware\EncryptCookies::class, AddQueuedCookiesToResponse::class, StartSession::class, ShareErrorsFromSession::class, Middleware\VerifyCsrfToken::class, SubstituteBindings::class, Middleware\HttpsProtocol::class, ReplicationSwitcher::class, ], 'webhooks' => [ SubstituteBindings::class, Middleware\HttpsProtocol::class, ], ]; /** * The application's route middleware. * * These middleware may be assigned to groups or used individually. */ protected $middlewareAliases = [ 'localize' => LaravelLocalizationRoutes::class, 'localizationRedirect' => LaravelLocalizationRedirectFilter::class, 'localeSessionRedirect' => LocaleSessionRedirect::class, 'localeViewPath' => LaravelLocalizationViewPath::class, 'localizeDatetime' => Middleware\LocalizeDatetime::class, 'client' => ValidateToken::class, // Laravel Passport 'auth' => Authenticate::class, 'auth.basic' => AuthenticateWithBasicAuth::class, 'bindings' => SubstituteBindings::class, 'can' => Authorize::class, 'guest' => Middleware\RedirectIfAuthenticated::class, 'throttle' => ThrottleRequests::class, 'login.redirect' => Middleware\LoginRedirect::class, 'translator' => Middleware\Translator::class, 'identity' => Middleware\Identity::class, 'password.confirm' => PasswordConfirm::class, 'subscriptions' => Middleware\Subscriptions::class, '2fa' => Middleware\OTP::class, 'adless' => Middleware\Adless::class, ]; } ================================================ FILE: app/Http/Middleware/Adless.php ================================================ route('campaign'); // @phpstan-ignore-next-line $log = ApiLog::request($request) ->response($response) ->duration($duration); if ($campaign !== null && $campaign instanceof Campaign) { $log->campaign($campaign); } $log->log(); } } ================================================ FILE: app/Http/Middleware/CachedResponse.php ================================================ guest()) { $response->headers->set('Cache-Control', 'public, max-age=600, s-maxage=1200'); } return $response; } } ================================================ FILE: app/Http/Middleware/Campaigns/Boosted.php ================================================ route('campaign'); if (empty($campaign)) { return redirect()->route('login') ->withErrors(__('You\'ve been banned')); } if (! $campaign->boosted()) { if ($request->is('api/*') || Domain::isApi()) { return response()->json([ 'error' => 'This feature is reserved to boosted campaigns.', ]); } return redirect()->route('dashboard', $campaign)->withErrors(__('crud.errors.boosted_campaigns', ['boosted' => __('concept.premium-campaigns')])); } return $next($request); } } ================================================ FILE: app/Http/Middleware/Campaigns/Premium.php ================================================ route('campaign'); if (! $campaign instanceof Campaign) { return $next($request); } if ($campaign->premium()) { return $next($request); } if ($request->is('api/*') || Domain::isApi()) { return response()->json([ 'error' => __('Required premium features to be enabled.'), ], 401); } } } ================================================ FILE: app/Http/Middleware/Campaigns/Superboosted.php ================================================ route('campaign'); if (empty($campaign)) { return redirect()->route('home'); } if (! $campaign->superboosted()) { if ($request->is('api/*') || Domain::isApi()) { return response()->json([ 'error' => 'This feature is reserved to premium campaign.', ]); } return redirect()->route('dashboard', $campaign)->withErrors(__('campaigns.errors.premium')); } return $next($request); } } ================================================ FILE: app/Http/Middleware/CheckIfUserBanned.php ================================================ guest() || ! auth()->user()->isBanned()) { return $next($request); } // If banned for less than 7 days, tell the user as much if (auth()->user()->banned_until < Carbon::now()->addDays(7)) { $days = auth()->user()->banned_until->diffInDays(Carbon::now()); auth()->logout(); return redirect()->route('login')->with( 'error', trans_choice('auth.banned.temporary', $days, ['days' => $days]) ); } auth()->logout(); return redirect()->route('login')->with('error', __('auth.banned.permanent')); } } ================================================ FILE: app/Http/Middleware/EncryptCookies.php ================================================ secure() && config('app.force_https') === true) { return redirect()->secure($request->getRequestUri()); } return $next($request); } } ================================================ FILE: app/Http/Middleware/Identity.php ================================================ route('home'); } return $next($request); } } ================================================ FILE: app/Http/Middleware/LastCampaign.php ================================================ guest()) { return $next($request); } // When a user looks at another campaign, track it for when they come back later to Kanka and log in /** @var Campaign $campaign */ $campaign = $request->route('campaign'); if ($request->user()->last_campaign_id !== $campaign->id) { $request->user()->last_campaign_id = $campaign->id; $request->user()->saveQuietly(); } return $next($request); } } ================================================ FILE: app/Http/Middleware/LocaleChange.php ================================================ is( 'subscription-api/*', 'oauth/*' ) ) { return $next($request); } // If it's not a get request, don't touch it either if (! $request->isMethod('get')) { $locale = $this->currentLocale(); LaravelLocalization::setLocale($locale); return $next($request); } // If we are impersonating someone, you guessed it, don't touch it either if (IdentityFacade::isImpersonating()) { return $next($request); } $locale = $this->currentLocale(); LaravelLocalization::setLocale($locale); $new = $request->get('lang'); if (! empty($new) && $this->valid($new)) { return $this->update($request, $new); } return $next($request); } protected function currentLocale(): string { if (auth()->check()) { return auth()->user()->locale; } // Unlogged users can change language, we keep it in a cookie $locale = Cookie::get('kanka_locale'); return ! empty($locale) && $this->valid($locale) ? $locale : 'en-US'; } protected function update(Request $request, string $locale): RedirectResponse { if (auth()->check()) { /** @var User $user */ $user = auth()->user(); $user->log(UserAction::lang, ['from' => $user->locale, 'to' => $locale]); $user->locale = $locale; $user->saveQuietly(); return redirect()->to($request->path()); } else { return redirect()->to($request->path())->withCookie( Cookie::make('kanka_locale', $locale) ); } } protected function valid(string $locale): bool { $locales = LaravelLocalization::getSupportedLocales(); if (! in_array($locale, array_keys($locales))) { return false; } // Remove old return ! in_array($locale, $this->disabledLangs); } protected function fallbackUrl(): string { $targetUrl = LaravelLocalization::getLocalizedURL('en'); // Prod is behind a reverse proxy that doesn't know about https if (config('app.force_https') && ! Str::startsWith('https', $targetUrl)) { $targetUrl = Str::replaceFirst('http://', 'https://', $targetUrl); } return $targetUrl; } } ================================================ FILE: app/Http/Middleware/LocalizeDatetime.php ================================================ shouldIgnore($request)) { return $next($request); } $locale = app('laravellocalization')->getCurrentLocale(); Carbon::setLocale($locale); Number::useLocale($locale); return $next($request); } } ================================================ FILE: app/Http/Middleware/LoginRedirect.php ================================================ has('next')) { [$routeName, $campaignSlug] = array_pad(explode('.', $request->get('next'), 2), 2, null); if (in_array($routeName, $whitelist)) { try { $params = $campaignSlug ? ['campaign' => $campaignSlug] : $request->except('next'); session(['login_redirect' => route($routeName, $params)]); } catch (\Exception) { // Route generation failed, skip the redirect. } } } return $next($request); } } ================================================ FILE: app/Http/Middleware/OTP.php ================================================ is('settings/security/cancel2fa')) { auth()->logout(); return $next($request); } // If the user is impersonating someone that has 2FA, don't ask for the user's OTP if ($request->user() && Identity::isImpersonating()) { return $next($request); } // Send requested logging User to OTP Authentication Support /** @var OTPAuthentication $authentication */ $authentication = app(OTPAuthentication::class)->boot($request); if ($authentication->isAuthenticated()) { $redirectTo = session()->get('2fa_redirect'); if ($redirectTo) { session()->remove('2fa_redirect'); return redirect($redirectTo); } return $next($request); } return $authentication->makeRequestOneTimePasswordResponse(); } } ================================================ FILE: app/Http/Middleware/PasswordConfirm.php ================================================ responseFactory = $responseFactory; $this->urlGenerator = $urlGenerator; $this->passwordTimeout = $passwordTimeout ?: 10800; } /** * Handle an incoming request. * * @param Request $request * @param string|null $redirectToRoute */ public function handle($request, Closure $next, $redirectToRoute = null) { if ($request->user()->isSocialLogin()) { return $next($request); } if ($this->shouldConfirmPassword($request)) { if ($request->expectsJson()) { return $this->responseFactory->json([ 'message' => __('Password confirmation required.'), ], 423); } return $this->responseFactory->redirectGuest( $this->urlGenerator->route($redirectToRoute ?? 'password.confirm') ); } return $next($request); } /** * Determine if the confirmation timeout has expired. * * @param Request $request * @return bool */ protected function shouldConfirmPassword($request) { if (config('app.debug')) { return false; } $confirmedAt = time() - $request->session()->get('auth.password_confirmed_at', 0); return $confirmedAt > $this->passwordTimeout; } } ================================================ FILE: app/Http/Middleware/RedirectIfAuthenticated.php ================================================ check()) { return redirect('/'); } return $next($request); } } ================================================ FILE: app/Http/Middleware/ReplicationSwitcher.php ================================================ is('login', 'register', 'auth.*', 'auth/*', 'password*', 'settings/security/*')) { DB::setDefaultConnection('replica'); } return $next($request); } } ================================================ FILE: app/Http/Middleware/Subscriptions.php ================================================ route('home'); } return $next($request); } } ================================================ FILE: app/Http/Middleware/Tracking.php ================================================ all()); // if (session()->has('tracking')) { // dd(session()->get('tracking')); // } if ($request->hasAny(['utm_campaign', 'utm_id', 'utm_source', 'utm_medium'])) { $data = []; if ($request->has('utm_id')) { $data['id'] = $request->get('utm_id'); } if ($request->has('utm_campaign')) { $data['campaign'] = $request->get('utm_campaign'); } if ($request->has('utm_source')) { $data['source'] = $request->get('utm_source'); } if ($request->has('utm_medium')) { $data['medium'] = $request->get('utm_medium'); } session()->put('tracking', $data); } // dump(session()->all()); return $next($request); } } ================================================ FILE: app/Http/Middleware/Translator.php ================================================ user()->hasRole('translator')) { return redirect()->route('home'); } return $next($request); } } ================================================ FILE: app/Http/Middleware/TrimStrings.php ================================================ 'nullable|string|max:191', 'entry' => 'nullable|string', 'tooltip' => 'nullable|string', 'image_uuid' => 'nullable|exists:images,id', 'header_uuid' => 'nullable|exists:images,id', 'type' => 'nullable|string|max:191', ]; // Editing an special entity type? Don't allow selecting oneself. /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } return $rules; } } ================================================ FILE: app/Http/Requests/API/StoreEntities.php ================================================ 'array|max:20', 'entities.*.name' => 'required|string|max:191', 'entities.*.entry' => 'nullable|string', 'entities.*.type' => 'nullable|string|max:191', 'entities.*.tags' => 'array', 'entities.*.tags.*' => 'distinct|exists:tags,id', ]; } } ================================================ FILE: app/Http/Requests/API/StoreReminder.php ================================================ clean([ 'calendar_id' => 'required|integer|exists:calendars,id', 'day' => 'required|integer|min:1', 'month' => 'required|integer|min:1', 'year' => 'required|integer', 'length' => 'integer|min:1', 'is_recurring' => 'nullable', 'recurring_until' => 'nullable', 'recurring_periodicity' => 'nullable|max:5', 'colour' => 'nullable|string|max:7', 'comment' => 'nullable|max:191', 'type_id' => 'nullable|integer|exists:entity_event_types,id', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]); } } ================================================ FILE: app/Http/Requests/API/UpdateUserRole.php ================================================ 'required|integer|exists:users,id', 'role_id' => 'required|integer|exists:campaign_roles,id', ]; } } ================================================ FILE: app/Http/Requests/API/UploadEntityImage.php ================================================ 'required|mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), ]; } } ================================================ FILE: app/Http/Requests/AddCalendarEvent.php ================================================ 'required_without:name|integer|exists:entities,id', 'name' => 'required_without:entity_id|nullable', 'day' => 'required', 'month' => 'required', 'year' => 'required', 'length' => 'required|integer|min:1', 'is_recurring' => 'nullable', 'recurring_until' => 'nullable', 'recurring_periodicity' => 'nullable|max:5', 'colour' => 'nullable|string|max:7', 'comment' => 'nullable|max:191', 'type_id' => 'nullable|integer|exists:entity_event_types,id', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]; } public function prepareForValidation() { if ($this->entity_id && ! is_numeric($this->entity_id)) { $this->merge([ 'name' => $this->entity_id, ]); $this->offsetUnset('entity_id'); } } } ================================================ FILE: app/Http/Requests/AddCalendarWeather.php ================================================ 'required', 'temperature' => 'nullable', 'precipitation' => 'nullable', 'wind' => 'nullable', 'effect' => 'nullable', 'day' => 'required', 'month' => 'required', 'year' => 'required', 'name' => 'nullable|string|max:40', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]; } } ================================================ FILE: app/Http/Requests/ApplyAttributeTemplate.php ================================================ 'required', ]; } } ================================================ FILE: app/Http/Requests/BragiRequest.php ================================================ */ public function rules() { return [ 'prompt' => 'required|string|min:10|max:' . config('bragi.limit.prompt'), ]; } } ================================================ FILE: app/Http/Requests/BulkRequest.php ================================================ 'required', 'model' => 'required_without:models', 'models' => 'required_without:model', 'entity_type' => 'integer|exists:entity_types,id', ]; } } ================================================ FILE: app/Http/Requests/Bulks/Copy.php ================================================ |string> */ public function rules(): array { return [ 'models' => 'required_without:entities|string', 'entities' => 'required_without:models|array', 'entities.*' => 'integer', 'campaign' => 'required|integer|exists:campaigns,id', ]; } } ================================================ FILE: app/Http/Requests/Bulks/Template.php ================================================ |string> */ public function rules(): array { return [ 'template_id' => 'required|integer|exists:attribute_templates,id', ]; } } ================================================ FILE: app/Http/Requests/Bulks/Transform.php ================================================ 'required|integer|exists:entity_types,id', ]; } } ================================================ FILE: app/Http/Requests/Campaigns/ApproveApplication.php ================================================ 'required|integer|exists:campaign_roles,id', 'reason' => 'nullable|string|max:191', ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/DefaultImageDestroy.php ================================================ 'required|integer|exists:entity_types,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/DefaultImageStore.php ================================================ 'required|integer|exists:entity_types,id', 'default_entity_image' => 'required|mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/DestroyDefaultThumbnail.php ================================================ 'required|integer|exists:entity_types,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/GalleryImageFolderStore.php ================================================ 'required|max:100', ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/GalleryImageStore.php ================================================ 'required|array', 'file.*' => [ File::types(['jpeg', 'jpg', 'gif', 'png', 'webp', 'woff2', 'svg']), 'max:' . Limit::upload(), new GallerySize, ], 'folder_id' => [ 'nullable', Rule::exists('images', 'id')->where(function ($query) { return $query->where('is_folder', 1); }), ], ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/GalleryImageUpdate.php ================================================ 'required|max:45', 'folder_id' => [ 'nullable', Rule::exists('images', 'id')->where(function ($query) { return $query->where('is_folder', 1); }), ], ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/PatchCampaignApplication.php ================================================ 'required_if:action,approve:rejection|exists:campaign_roles,id', 'reason' => 'nullable|string|max:191', 'action' => 'required', ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/RejectApplication.php ================================================ 'nullable|string|max:191', ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/StoreCampaignApplication.php ================================================ 'nullable|string|min:10', 'experience' => 'required|integer|in:0,1,2', 'availability_days' => 'nullable|array', 'availability_days.*' => 'string|in:mon,tue,wed,thu,fri,sat,sun', 'time_start' => 'nullable|date_format:H:i', 'time_end' => 'nullable|date_format:H:i', 'timezone' => 'nullable|string|max:100', 'pref_rp_combat' => 'required|integer|between:0,2', 'pref_tone' => 'required|integer|between:0,2', 'external_link' => 'nullable|url|max:255', 'additional_notes' => 'nullable|string|max:255', ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/StoreCampaignApplicationStatus.php ================================================ 'required', ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/StoreCampaignSetup.php ================================================ 'nullable|string|max:10', 'systems' => 'nullable|array', 'systems.*' => 'exists:game_systems,id', 'campaign_genre' => 'nullable|integer', 'genres' => 'nullable|array', 'genres.*' => 'exists:genres,id', 'intro' => 'nullable|string|max:2000', 'timezone' => 'nullable|string|max:45', 'schedule' => 'nullable|string|max:45', 'players' => 'nullable|string|max:45', 'playstyles' => 'array', 'playstyles.*' => 'exists:playstyles,id', 'is_prioritised' => 'nullable|boolean', ]; } } ================================================ FILE: app/Http/Requests/Campaigns/StoreCampaignVisibility.php ================================================ ['required', new Enum(CampaignVisibility::class)], ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/StoreDefaultThumbnail.php ================================================ 'required|integer|exists:entity_types,id', 'default_entity_image' => 'required|mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), ]; return $rules; } } ================================================ FILE: app/Http/Requests/Campaigns/StoreDefaults.php ================================================ |string> */ public function rules(): array { return [ 'entity_visibility' => 'boolean', 'entity_personality_visibility' => 'boolean', 'settings' => 'array', 'settings.default_visibility' => 'nullable|string|in:admin,members,self,admin-self', 'settings.private_mention_visibility' => 'boolean', 'ui_settings' => 'array', 'ui_settings.connections' => 'boolean', 'ui_settings.connection_mode' => 'boolean', 'ui_settings.descendants' => 'boolean', ]; } } ================================================ FILE: app/Http/Requests/Campaigns/StoreImage.php ================================================ |string> */ public function rules(): array { return [ 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), ]; } } ================================================ FILE: app/Http/Requests/Campaigns/StoreTheme.php ================================================ */ public function rules(): array { return [ 'config' => 'required|nullable|string', ]; } } ================================================ FILE: app/Http/Requests/Campaigns/Vanity.php ================================================ */ public function rules(): array { return [ 'vanity' => [ 'string', 'min:4', 'max:45', 'unique:campaigns,slug', new \App\Rules\Vanity, ], ]; } } ================================================ FILE: app/Http/Requests/CopyEntityToCampaignRequest.php ================================================ 'required|integer|exists:campaigns,id', ]; } } ================================================ FILE: app/Http/Requests/CopyInventory.php ================================================ clean([ 'entity_id' => 'required:exists:entities,id', ]); } } ================================================ FILE: app/Http/Requests/DeleteCampaign.php ================================================ ['required', 'string', new CampaignDelete], ]; return $rules; } } ================================================ FILE: app/Http/Requests/DeleteEntityType.php ================================================ |string> */ public function rules(): array { return [ 'delete' => ['required', 'string', new Confirm], ]; } } ================================================ FILE: app/Http/Requests/DeleteSettingsAccount.php ================================================ ['string', new GoodBye], ]; return $rules; } } ================================================ FILE: app/Http/Requests/EditPostVisibility.php ================================================ 'nullable|integer|exists:visibilities,id', ]; } } ================================================ FILE: app/Http/Requests/Entities/UpdateListingPreferenceRequest.php ================================================ check()) { return false; } if ((int) $this->input('per_page') === 100 && ! auth()->user()->isSubscriber()) { return false; } return true; } public function rules(): array { return [ 'columns' => ['sometimes', 'array'], 'columns.*' => ['string'], 'layout' => ['sometimes', 'nullable', 'in:grid,table'], 'nested' => ['sometimes', 'nullable', 'boolean'], 'per_page' => ['sometimes', 'nullable', 'integer', 'in:15,25,45,100'], ]; } } ================================================ FILE: app/Http/Requests/FilterPublicCampaignRequest.php ================================================ 'integer|max:2', 'is_boosted' => 'nullable|boolean', 'is_open' => 'nullable|boolean', 'system' => 'nullable|string|max:25', 'language' => 'nullable|string|max:5', 'genre' => 'nullable|integer', ]; } } ================================================ FILE: app/Http/Requests/Front/StoreCommunityEventEntry.php ================================================ ['required', 'url', new EntityLink], 'comment' => 'nullable|string', ]; } protected function getRedirectUrl() { $url = $this->redirector->getUrlGenerator(); return $url->previous() . '#event-form'; } } ================================================ FILE: app/Http/Requests/Gallery/CreateFolder.php ================================================ |string> */ public function rules(): array { return [ 'name' => 'required|string|max:191', 'visibility_id' => 'integer|exists:visibilities,id', 'folder_id' => 'nullable|exists:images,id', ]; } } ================================================ FILE: app/Http/Requests/Gallery/DeleteImages.php ================================================ |string> */ public function rules(): array { return [ 'images' => 'required|array', 'images.*' => 'distinct|exists:images,id', ]; } } ================================================ FILE: app/Http/Requests/Gallery/MoveFiles.php ================================================ |string> */ public function rules(): array { return [ 'folder_id' => 'nullable|string|exists:images,id', 'images' => 'required|array', 'images.*' => 'distinct|exists:images,id', ]; } } ================================================ FILE: app/Http/Requests/Gallery/UpdateFile.php ================================================ |string> */ public function rules(): array { return [ 'name' => 'required|string|max:191', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]; } } ================================================ FILE: app/Http/Requests/Gallery/UpdateFiles.php ================================================ |string> */ public function rules(): array { return [ 'files' => 'required|array', 'images.*' => 'distinct|exists:images,id', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'folder_id' => 'nullable|string|exists:images,id', 'folder_home' => 'nullable', ]; } } ================================================ FILE: app/Http/Requests/Gallery/UpdateFocus.php ================================================ |string> */ public function rules(): array { return [ 'focus_x' => 'nullable|integer', 'focus_y' => 'nullable|integer', ]; } } ================================================ FILE: app/Http/Requests/Gallery/UploadFile.php ================================================ |string> */ public function rules(): array { $types = ['jpeg', 'jpg', 'gif', 'png', 'webp']; if (request()->has('map')) { Limit::map(); $types[] = 'svg'; } return [ 'file' => [ 'required', File::types($types), 'max:' . Limit::upload(), new GallerySize, ], ]; } } ================================================ FILE: app/Http/Requests/Gallery/UploadFiles.php ================================================ 'required|array', 'files.*' => [ 'required', File::types($types), 'max:' . Limit::upload(), new GallerySize, ], ]; } } ================================================ FILE: app/Http/Requests/Gallery/UploadUrl.php ================================================ |string> */ public function rules(): array { return [ 'url' => 'url|active_url', ]; } } ================================================ FILE: app/Http/Requests/GalleryBulkDelete.php ================================================ */ public function rules(): array { return [ 'file.*' => 'exists:images,id', ]; } } ================================================ FILE: app/Http/Requests/GenerateInventory.php ================================================ clean([ 'tags' => 'array', 'tags.*' => 'distinct|exists:tags,id', 'item_amount' => ['required', 'integer', 'between:1,100'], ]); } } ================================================ FILE: app/Http/Requests/HistoryRequest.php ================================================ */ public function rules() { return [ 'user' => 'integer|nullable|exists:users,id', 'action' => 'integer|nullable', ]; } } ================================================ FILE: app/Http/Requests/ManageFamilies.php ================================================ 'array', 'character_family.*' => 'integer|exists:families,id', 'family_privates' => 'array', 'family_privates.*' => 'boolean', ]; } } ================================================ FILE: app/Http/Requests/ManageRaces.php ================================================ 'array', 'character_race.*' => 'integer|exists:races,id', 'race_privates' => 'array', 'race_privates.*' => 'boolean', ]; } } ================================================ FILE: app/Http/Requests/MoveEntity.php ================================================ 'array|required', 'entities.*' => 'distinct|integer|exists:entities,id', 'campaign_id' => 'required|integer|exists:campaigns,id', 'copy' => 'boolean', ]; return $rules; } } ================================================ FILE: app/Http/Requests/MoveEntityRequest.php ================================================ 'required|integer|exists:campaigns,id', 'copy' => 'nullable', ]; } } ================================================ FILE: app/Http/Requests/MovePostRequest.php ================================================ 'required|integer|exists:entities,id', 'copy' => 'nullable', ]; } } ================================================ FILE: app/Http/Requests/Onboarding/InitialRequest.php ================================================ |string> */ public function rules(): array { return [ 'name' => 'nullable|min:4|max:191', 'type' => 'nullable|in:worldbuilding,campaign,story,skip', ]; } } ================================================ FILE: app/Http/Requests/PermissionTestRequest.php ================================================ 'required_without:*.entity_type_id|integer|exists:entities,id', '*.entity_type_id' => 'required_without:*.entity_id|integer|exists:entity_types,id', '*.action' => 'required|integer|exists:campaign_permissions,action', // '*.user_id' => 'required|integer|exists:campaign_user,user_id', '*.user_id' => [ 'required', 'integer', Rule::exists('campaign_user')->where(function ($query) { $query->where('user_id', $this->input('*.user_id'))->where('campaign_id', CampaignLocalization::getCampaign()->id); }), ], ]; } } ================================================ FILE: app/Http/Requests/QuickCreator/StoreEntity.php ================================================ |string> */ public function rules(): array { return [ 'name' => 'required|string', 'template_id' => [ 'nullable', 'integer', Rule::exists('entities', 'id')->where(function ($query) { return $query->where('is_template', true); }), ], ]; } } ================================================ FILE: app/Http/Requests/QuickCreator/StorePost.php ================================================ |string> */ public function rules(): array { return [ 'name' => 'required|string|max:191', 'entity_id' => 'required|integer|exists:entities,id', ]; } } ================================================ FILE: app/Http/Requests/ReadBanner.php ================================================ 'required|string|max:20', 'type' => 'nullable|string|max:10', ]; } } ================================================ FILE: app/Http/Requests/RecoverEntity.php ================================================ 'required|array', 'entities.*' => 'distinct|exists:entities,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/RecoverPost.php ================================================ 'required|array', 'posts.*' => 'distinct|exists:posts,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/RenameEntityFile.php ================================================ 'sometimes|required|min:3', 'visibility' => 'sometimes|required|string|in:all,admin,self,members,admin-self', ]; } } ================================================ FILE: app/Http/Requests/ReorderAbility.php ================================================ 'array', 'entity_ability.*' => 'integer|exists:entity_abilities,id', ]; } } ================================================ FILE: app/Http/Requests/ReorderBookmarks.php ================================================ 'array', 'bookmark.*' => 'integer|exists:bookmarks,id', ]; } } ================================================ FILE: app/Http/Requests/ReorderGroups.php ================================================ 'array', 'groups.*' => 'integer|exists:map_groups,id', ]; } } ================================================ FILE: app/Http/Requests/ReorderLayers.php ================================================ 'array', 'layers.*' => 'integer|exists:map_layers,id', ]; } } ================================================ FILE: app/Http/Requests/ReorderStories.php ================================================ [ '*' => [ 'id' => 'integer|exists:posts,id', 'visibility_id' => 'integer|exists:visibilities,id', ], ], ]; } } ================================================ FILE: app/Http/Requests/ReorderStyles.php ================================================ 'array', 'styles.*' => 'integer|exists:campaign_styles,id', ]; } } ================================================ FILE: app/Http/Requests/ReorderTimeline.php ================================================ 'array', 'timeline_era.*' => 'integer|exists:timeline_eras,id', ]; } } ================================================ FILE: app/Http/Requests/SaveAttributes.php ================================================ clean([ 'attribute' => ['array', new UniqueAttributeNames], ]); } } ================================================ FILE: app/Http/Requests/SaveAttributesApi.php ================================================ clean([ 'attribute' => ['array', new ApiUniqueAttributeNames], 'attribute.*' => ['array'], 'attribute.*.name' => ['nullable', 'string'], 'attribute.*.id' => ['nullable', 'integer', 'exists:attributes,id'], 'attribute.*.type_id' => [ 'required_without:attribute.*.id', new Enum(AttributeType::class), ], ]); } } ================================================ FILE: app/Http/Requests/SaveUserHelp.php ================================================ 'required|integer|exists:campaigns,id', ]; } } ================================================ FILE: app/Http/Requests/Search/MentionRequest.php ================================================ |string> */ public function rules(): array { return [ 'entities' => 'array', 'entities.*' => 'integer', 'posts' => 'array', 'posts.*' => 'integer', ]; } } ================================================ FILE: app/Http/Requests/Settings/NewsletterStore.php ================================================ 'nullable', ]; } } ================================================ FILE: app/Http/Requests/Settings/StoreApiToken.php ================================================ ['required', 'string', 'max:90'], ]; } } ================================================ FILE: app/Http/Requests/Settings/StoreClient.php ================================================ ['required', 'string', 'max:90'], 'redirect' => ['required', 'string', 'max:120', 'url', 'active_url'], ]; } } ================================================ FILE: app/Http/Requests/Settings/UserAltSubscribeStore.php ================================================ 'required|in:giropay,sofort,ideal', 'period' => 'required|in:yearly', 'accountholder-name' => 'required_if:method,giropay', 'sofort-country' => 'required_if:method,sofort', ]; } } ================================================ FILE: app/Http/Requests/Settings/UserBillingStore.php ================================================ [ 'nullable', Rule::in(['usd', 'eur', 'brl']), ], ]; } } ================================================ FILE: app/Http/Requests/Settings/UserSubscribeStore.php ================================================ 'required_without:is_downgrade', 'reason' => 'nullable', ]; } } ================================================ FILE: app/Http/Requests/Spotlights/ApplyRequest.php ================================================ |string> */ public function rules(): array { $isApply = $this->string('action')->toString() === 'apply'; $requiredOrNullable = $isApply ? 'required|string|min:15' : 'nullable|string'; return [ 'action' => 'nullable|in:save,apply', 'time' => $requiredOrNullable, 'world' => $requiredOrNullable, 'proud' => $requiredOrNullable, 'inspiration' => $requiredOrNullable, 'stories' => $requiredOrNullable, 'kanka' => 'nullable|string', ]; } } ================================================ FILE: app/Http/Requests/StoreAbility.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'charges' => 'nullable|max:120', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreAbilityEntity.php ================================================ 'required|integer|exists:entities,id', 'visibility_id' => 'required|integer|exists:visibilities,id', 'entities' => 'array|required', 'entities.*' => ['different:ability_id|integer|exists:entities,id'], ]; } } ================================================ FILE: app/Http/Requests/StoreAttribute.php ================================================ clean([ 'name' => 'required|max:191', 'value' => 'nullable|string', 'type' => 'nullable|string', 'api_key' => 'nullable|string|max:20', ]); } } ================================================ FILE: app/Http/Requests/StoreAttributeTemplate.php ================================================ 'required|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'entity_type_id' => 'nullable|integer|exists:entity_types,id', ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } return $rules; } } ================================================ FILE: app/Http/Requests/StoreBillingSettings.php ================================================ 'max:1024', ]; return $rules; } } ================================================ FILE: app/Http/Requests/StoreBookmark.php ================================================ clean([ 'name' => 'required|max:191', 'entity_id' => 'required_without_all:entity_type_id,random_entity_type,dashboard_id|nullable|exists:entities,id', 'entity_type_id' => 'required_without_all:entity_id,random_entity_type,dashboard_id|nullable|exists:entity_types,id', 'random_entity_type' => 'required_without_all:entity_id,entity_type_id,dashboard_id', 'dashboard_id' => 'required_without_all:entity_id,entity_type_id,random_entity_type', 'icon' => ['nullable', new FontAwesomeIcon], 'tab' => 'nullable', 'parent' => 'nullable|string|max:25', 'css' => 'nullable|string|max:45', 'filters' => 'nullable|string|max:191', 'menu' => 'nullable|string|max:45', 'position' => 'nullable|integer|min:1|max:99', ]); } } ================================================ FILE: app/Http/Requests/StoreCalendar.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'month_name' => 'required|array|min:1', 'month_length' => 'required|array|min:1', 'weekday' => 'required|array|min:2', 'start_offset' => 'nullable|integer|min:0|max:99', 'year_name' => 'nullable|array', 'moon_name' => 'nullable|array', 'moon_fullmoon' => 'nullable|array', 'moon_offset' => 'required_with:moon_name|array', 'moon_colour' => 'required_with:moon_name|array', 'moon_id' => 'required_with:moon_name|nullable|array', 'epoch_name' => 'nullable|array', 'season_name' => 'nullable|array', 'show_birthdays' => 'boolean', 'template_id' => 'nullable', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', 'format' => ['nullable', new CalendarFormat, 'string', 'max:20'], // 'moon_offset' => [ // '*' => new CalendarMoonOffset() // ], ]; if (request()->has('quick-creator')) { $rules = [ 'name' => 'required|max:191', 'type' => 'nullable|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', ]; } $rules['has_leap_year'] = 'boolean'; $rules['leap_year_amount'] = 'exclude_if:has_leap_year,0|numeric|min:-128|max:128'; $rules['leap_year_offset'] = 'exclude_if:has_leap_year,0|numeric|min:1|max:255'; $rules['leap_year_start'] = 'exclude_if:has_leap_year,0|numeric|min:1|max:255'; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreCampaign.php ================================================ 'required|string|min:4|max:191', 'description' => 'nullable|string', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'header_image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'locale' => 'nullable|string', 'systems' => 'array', 'systems.*' => 'distinct|exists:game_systems,id', 'entity_visibility' => 'nullable', 'entity_personality_visibility' => 'nullable', 'css' => 'nullable|string', 'theme_id' => 'nullable|exists:themes,id', ]; if ((Domain::isApi()) && ! request()->isMethod('POST')) { $rules['name'] = 'string|min:4'; } return $rules; } } ================================================ FILE: app/Http/Requests/StoreCampaignDashboard.php ================================================ 'required|max:100', 'roles' => 'required', 'source' => 'nullable|exists:campaign_dashboards,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/StoreCampaignDashboardWidget.php ================================================ ['required', new Enum(Widget::class)], 'entity_id' => 'nullable|exists:entities,id', 'dashboard_id' => 'nullable|exists:campaign_dashboards,id', 'config.order' => 'nullable|in:name_asc,name_desc,oldest', 'entity_type_id' => 'nullable|exists:entity_types,id', 'config.folder_id' => ['required_if:widget,gallery', Rule::exists('images', 'id')->where('is_folder', true)], ]; } } ================================================ FILE: app/Http/Requests/StoreCampaignInvite.php ================================================ 'required|integer|exists:campaign_roles,id', 'validity' => 'nullable|integer', ]; return $rules; } } ================================================ FILE: app/Http/Requests/StoreCampaignRole.php ================================================ 'required', 'role_id' => 'integer|exists:campaign_roles,id', 'duplicate' => 'boolean', ]; } } ================================================ FILE: app/Http/Requests/StoreCampaignRoleUser.php ================================================ 'required|integer|exists:users,id|unique:campaign_role_users,user_id,NULL,NULL,campaign_role_id,' . request()->get('campaign_role_id'), ]; } } ================================================ FILE: app/Http/Requests/StoreCampaignStyle.php ================================================ clean([ 'name' => 'required|string|max:45', 'content' => ['required', 'max:' . self::MAX], 'is_enabled' => 'nullable', ]); } } ================================================ FILE: app/Http/Requests/StoreCampaignTheme.php ================================================ clean([ 'theme_id' => 'nullable|exists:themes,id', ]); } } ================================================ FILE: app/Http/Requests/StoreCampaignUser.php ================================================ 'required', ]; } } ================================================ FILE: app/Http/Requests/StoreCharacter.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'locations' => [ 'nullable', 'array', new EntityField( config('entities.ids.location'), Location::class, ), ], 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'age' => 'nullable|max:25', 'sex' => 'nullable|max:45', 'pronouns' => 'nullable|max:45', 'title' => 'nullable|max:191', 'template_id' => 'nullable', 'families' => [ 'nullable', 'array', new EntityField(config('entities.ids.family'), Family::class), ], 'races' => [ 'nullable', 'array', new EntityField(config('entities.ids.race'), Race::class), ], 'personality_name' => 'nullable|array', 'personality_entry' => 'nullable|array', 'appearance_name' => 'nullable|array', 'appearance_entry' => 'nullable|array', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', 'status_id' => ['nullable', 'exists:category_statuses,id'], 'tags' => 'nullable|array', 'tags.*' => 'distinct|exists:tags,id', ]; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreCharacterFamily.php ================================================ clean([ 'characters' => 'required|array|min:1', 'characters.*' => 'distinct|required|distinct|exists:characters,id', ]); } } ================================================ FILE: app/Http/Requests/StoreCharacterOrganisation.php ================================================ |string> */ public function rules(): array { return $this->clean([ 'organisation_id' => 'required|integer|exists:organisations,id', 'role' => 'nullable|string|max:191', 'is_private' => 'nullable|boolean', 'parent_id' => 'nullable|integer|exists:organisation_member,id', ]); } } ================================================ FILE: app/Http/Requests/StoreCharacterRace.php ================================================ clean([ 'race_id' => 'required|integer|exists:races,id', 'members' => 'array', 'members.*' => 'integer|exists:characters,id', ]); } } ================================================ FILE: app/Http/Requests/StoreConversation.php ================================================ 'required|max:191', 'type' => 'nullable|string|max:45', 'target_id' => ['required', new Enum(ConversationTarget::class)], 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreConversationMessage.php ================================================ 'required|string', 'character_id' => 'nullable|integer|exists:characters,id', 'user_id' => 'nullable|integer|exists:users,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/StoreConversationParticipant.php ================================================ 'nullable|integer|exists:characters,id|required_without_all:user_id', 'user_id' => 'nullable|integer|exists:users,id|required_without_all:character_id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/StoreCreature.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'locations' => ['nullable', 'array', new EntityField(config('entities.ids.location'), Location::class)], 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', 'status_id' => ['nullable', 'exists:category_statuses,id'], ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreCustomEntity.php ================================================ |string> */ public function rules() { $rules = [ 'name' => 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'image_uuid' => 'nullable|integer|exists:images,id', 'parent_id' => 'nullable|integer|exists:entities,id', 'attribute' => ['array', new UniqueAttributeNames], ]; return $this->clean($rules); } /** * Return the resolved parent_id so EditController can sync it back into the original request. * * @return array */ public function resolvedFields(): array { if (! $this->parentIdResolved) { return []; } return ['parent_id' => $this->input('parent_id')]; } protected function prepareForValidation(): void { $value = $this->input('parent_id'); if (empty($value) || is_numeric($value)) { return; } // AJAX calls are validation-only pre-flight requests; replace the string with null // so the 'nullable' rule passes without creating an entity. if (request()->ajax()) { $this->merge(['parent_id' => null]); return; } // Resolve entity type from route: creation uses {entity_type}, editing uses {entity}. $entityType = request()->route('entity_type') ?? request()->route('entity')?->entityType; if (! $entityType instanceof EntityType) { $this->merge(['parent_id' => null]); return; } $name = Str::startsWith($value, 'new:') ? Str::substr($value, 4) : $value; $campaign = CampaignLocalization::getCampaign(); $id = $this->createEntityFromName($name, $entityType, $campaign); $this->parentIdResolved = true; $this->merge(['parent_id' => $id]); } } ================================================ FILE: app/Http/Requests/StoreDiceRoll.php ================================================ 'required|max:191', 'parameters' => 'required|max:191', 'character_id' => 'nullable|integer|exists:characters,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; if (request()->has('quick-creator')) { unset($rules['parameters']); } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreEntityAbility.php ================================================ 'required_without:abilities|exists:abilities,id', 'abilities' => 'required:ability_id|array|min:1', 'abilities.*' => 'distinct|exists:abilities,id', 'position' => 'nullable|integer|min:0|max:100', 'note' => 'nullable|string', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]; } } ================================================ FILE: app/Http/Requests/StoreEntityAlias.php ================================================ clean([ 'name' => 'required|string|max:45', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]); } } ================================================ FILE: app/Http/Requests/StoreEntityAsset.php ================================================ clean([ 'name' => 'required_unless:type_id,' . EntityAssetType::file->value . '|max:45', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'file' => [ 'required_if:type_id,' . EntityAssetType::file->value, 'file', 'max:' . Limit::upload(), new EntityFile, ], 'metadata.url' => 'required_if:type_id,' . EntityAssetType::link->value . '|string|url', 'metadata.icon' => ['max:45', new FontAwesomeIcon], ]); } } ================================================ FILE: app/Http/Requests/StoreEntityAssets.php ================================================ clean([ 'name' => 'required_unless:type_id,' . EntityAssetType::file->value . '|max:45', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'files' => ['required_if:type_id,' . EntityAssetType::file->value], 'files.*' => [ 'file', 'max:' . Limit::upload(), new EntityFile, ], 'metadata.url' => 'required_if:type_id,' . EntityAssetType::link->value . '|string|url', 'metadata.icon' => ['max:45', new FontAwesomeIcon], ]); } } ================================================ FILE: app/Http/Requests/StoreEntityFile.php ================================================ [ 'required', 'file', 'max:' . Limit::upload(), new EntityFile, ], 'name' => 'nullable|string|max:45', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]; } } ================================================ FILE: app/Http/Requests/StoreEntityLink.php ================================================ clean([ 'name' => 'required|string|max:45', 'url' => 'required|string|url', 'icon' => 'nullable|string|max:45', 'position' => 'nullable|integer', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]); } } ================================================ FILE: app/Http/Requests/StoreEntityPermission.php ================================================ ['required_without:*.user_id', 'integer', 'exists:campaign_roles,id'], '*.user_id' => ['required_without:*.campaign_role_id', 'integer', 'exists:users,id'], '*.access' => ['required', 'boolean'], '*.action' => ['required', 'numeric'], ]; } } ================================================ FILE: app/Http/Requests/StoreEntityTag.php ================================================ 'required|integer|exists:tags,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/StoreEntityType.php ================================================ |string> */ public function rules(): array { return [ 'singular' => ['required', 'string', 'max:45'], 'plural' => ['required', 'string', 'max:45'], 'icon' => ['required', 'string', 'max:100'], 'roles' => 'array', 'roles.*' => 'integer|exists:campaign_roles,id', 'default_entity_image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), ]; } } ================================================ FILE: app/Http/Requests/StoreEvent.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'date' => 'nullable|max:191', 'locations' => ['nullable', 'array', new EntityField(config('entities.ids.location'), Location::class)], 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreFamily.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'location_id' => 'nullable|integer|exists:locations,id', 'parent_id' => 'nullable|integer|exists:entities,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', 'status_id' => ['nullable', 'exists:category_statuses,id'], ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreFamilyTree.php ================================================ check(); } /** * Get the validation rules that apply to the request. * * @return array|string> */ public function rules(): array { return [ ]; } } ================================================ FILE: app/Http/Requests/StoreImageFocus.php ================================================ 'nullable|integer', 'focus_y' => 'nullable|integer', ]; } } ================================================ FILE: app/Http/Requests/StoreInventory.php ================================================ 'required|integer|exists:entities,id', 'name' => 'nullable|string|required_without:item_id', 'item_id' => 'nullable|array|required_without:name', 'item_id.*' => 'integer|exists:items,id', 'amount' => 'required|numeric', 'position' => 'nullable|string|max:191', 'description' => 'nullable|string|max:191', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'image_uuid' => 'nullable|exists:images,id', 'is_equipped' => 'boolean', ]; } } ================================================ FILE: app/Http/Requests/StoreItem.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'location_id' => 'nullable|integer|exists:locations,id', 'creators' => 'nullable|array', 'creators.*' => 'integer|exists:entities,id', 'parent_id' => 'nullable|integer|exists:entities,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'price' => 'nullable|string|max:191', 'size' => 'nullable|string|max:191', 'weight' => 'nullable|string|max:191', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreJournal.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'date' => 'nullable|date', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'location_id' => 'nullable|exists:locations,id', 'author_id' => 'nullable|exists:entities,id', 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'parent_id' => 'nullable|integer|exists:entities,id', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; if (request()->has('calendar_id') && request()->post('calendar_id') !== null && ! request()->has('calendar_skip')) { $rules['calendar_day'] = 'required_with:calendar_id|min:1'; $rules['calendar_year'] = 'required_with:calendar_id'; if (request()->has('length')) { $rules['length'] = 'required_with:calendar_id|min:1'; } } /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreLocation.php ================================================ 'required|max:191', 'title' => 'nullable|string|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', 'status_id' => ['nullable', 'exists:category_statuses,id'], ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreMap.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'location_id' => 'nullable|integer|exists:locations,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp,svg|max:' . Limit::map()->upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'center_x' => 'nullable|numeric', 'center_y' => 'nullable|numeric', 'max_zoom' => 'nullable|numeric|min:1|max:' . Map::MAX_ZOOM, 'min_zoom' => 'nullable|numeric|min:' . Map::MIN_ZOOM . '|max:' . Map::MAX_ZOOM_REAL, 'initial_zoom' => 'nullable|numeric|min:' . Map::MIN_ZOOM . '|max:' . Map::MAX_ZOOM_REAL, 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreMapGroup.php ================================================ 'required|max:191', 'position' => 'nullable|string|max:3', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'parent_id' => 'nullable|integer|exists:map_groups,id', ]; /** @var MapGroup $self */ $self = request()->route('map_group'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:map_groups,id', ]; } return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreMapLayer.php ================================================ 'required|max:191', 'entry' => 'nullable', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'image_uuid' => 'required|exists:images,id', 'position' => 'nullable|string|max:3', 'type_id' => 'nullable|integer', ]; // If editing, don't need a new image /** @var MapLayer $self */ $self = request()->route('map_layer'); if ($self && ! empty($self->image_path)) { unset($rules['image_uuid']); } return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreMapMarker.php ================================================ 'nullable|string|required_without:entity_id', 'entry' => 'nullable|string', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'entity_id' => 'nullable|integer|exists:entities,id|required_without:name', 'group_id' => 'nullable|integer|exists:map_groups,id', 'longitude' => 'required|numeric', 'latitude' => 'required|numeric', 'colour' => 'max:7', 'size_id' => 'nullable|integer', 'shape_id' => 'required|integer', 'custom_shape' => 'nullable|string', 'is_draggable' => 'boolean', 'is_popupless' => 'boolean', 'css' => 'nullable|string|max:45', 'icon' => 'required|integer', 'custom_icon' => 'nullable|string', 'circle_radius' => 'nullable|integer', 'opacity' => 'nullable|min:0|max:100|integer', 'marker_size' => 'nullable|integer|min:10', ]; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreMarketplaceProfile.php ================================================ 'nullable|string|min:4', ]; } } ================================================ FILE: app/Http/Requests/StoreNote.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'parent_id' => 'nullable|integer|exists:entities,id', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreOrganisation.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'parent_id' => 'nullable|integer|exists:entities,id', 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'locations' => ['nullable', 'array', new EntityField(config('entities.ids.location'), Location::class)], 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', 'status_id' => ['nullable', 'exists:category_statuses,id'], ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreOrganisationMember.php ================================================ clean([ 'organisation_id' => 'required|integer|exists:organisations,id', 'character_id' => 'required|integer|exists:characters,id', 'role' => 'nullable', 'is_private' => 'nullable', 'parent_id' => 'nullable|integer|exists:organisation_member,id', ]); } } ================================================ FILE: app/Http/Requests/StoreOrganisationMembers.php ================================================ clean([ 'characters' => 'required|array|min:1', 'characters.*' => 'distinct|required|distinct|exists:characters,id', 'role' => 'nullable', 'is_private' => 'nullable', 'parent_id' => 'nullable|exists:organisation_member,id', ]); } } ================================================ FILE: app/Http/Requests/StorePermission.php ================================================ 'required|integer|exists:entities,id', ]; } } ================================================ FILE: app/Http/Requests/StorePost.php ================================================ ['required', 'string', 'max:191', new Lessless], 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'location_id' => 'nullable|exists:locations,id', 'is_pinned' => 'boolean', 'position' => 'nullable|integer|min:-128|max:128', 'entry' => 'nullable|string', 'layout_id' => 'nullable|integer|exists:post_layouts,id', ]; if (request()->has('calendar_id') && request()->post('calendar_id') !== null && ! request()->has('calendar_skip')) { $rules['calendar_day'] = 'required_with:calendar_id|min:1'; $rules['calendar_year'] = 'required_with:calendar_id'; if (request()->has('length')) { $rules['length'] = 'required_with:calendar_id|min:1'; } } return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StorePreset.php ================================================ */ public function rules() { return [ 'name' => 'required|string', ]; } } ================================================ FILE: app/Http/Requests/StoreQuest.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'status_id' => ['nullable', 'exists:category_statuses,id'], 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'parent_id' => 'nullable|integer|exists:entities,id', 'character_id' => 'nullable|integer|exists:characters,id', 'location_id' => 'nullable|integer|exists:locations,id', 'template_id' => 'nullable', 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; // If the calendar is present and not null, but we aren't "skipping" it (editing but without permission) if (request()->has('calendar_id') && request()->post('calendar_id') !== null && ! request()->has('calendar_skip')) { $rules['calendar_day'] = 'required_with:calendar_id|min:1'; $rules['calendar_year'] = 'required_with:calendar_id'; if (request()->has('length')) { $rules['length'] = 'required_with:calendar_id|min:1'; } } /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreQuestElement.php ================================================ input('copy_entity_entry') && empty($this->input('entity_id'))) { $this->merge(['copy_entity_entry' => false]); } } /** * Get the validation rules that apply to the request. * * @return array */ public function rules() { $rules = [ 'entity_id' => 'nullable|required_without:name|exists:entities,id', 'name' => 'nullable|string|required_without:entity_id', 'entry' => 'string|nullable', 'role' => 'nullable|string|max:191', 'colour' => 'nullable|string|max:10', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'copy_entity_entry' => 'nullable|boolean', ]; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreRace.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'locations' => ['nullable', 'array', new EntityField(config('entities.ids.location'), Location::class)], 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', 'status_id' => ['nullable', 'exists:category_statuses,id'], ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreRelation.php ================================================ clean([ 'owner_id' => 'required|integer|exists:entities,id', 'targets' => 'required_without:target_id', 'targets.*' => 'integer|exists:entities,id', 'target_id' => 'required_without:targets|integer|exists:entities,id|different:owner_id', 'relation' => 'required|max:255', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'attitude' => 'min:-100|max:100', 'colour' => 'nullable|max:7', 'is_pinned' => 'boolean', 'two_way' => 'boolean', ]); } } ================================================ FILE: app/Http/Requests/StoreSettingsAccount.php ================================================ 'required|email|unique:users,email,' . $user->id, ]; return $rules; } } ================================================ FILE: app/Http/Requests/StoreSettingsLayout.php ================================================ 'nullable|string|max:5', 'theme' => 'nullable', // 'editor' => 'in:,summernote,markdown', ]; } } ================================================ FILE: app/Http/Requests/StoreSettingsProfile.php ================================================ 'required|string|min:2', 'pronouns' => 'string|max:45', 'link' => 'url|max:90', 'newsletter' => 'boolean', 'has_last_login_sharing' => 'boolean', 'avatar' => 'mimes:jpeg,png,jpg,gif,webp|max:8192', ]; return $rules; } } ================================================ FILE: app/Http/Requests/StoreShare.php ================================================ ['nullable', 'string', 'in:entity,global'], 'campaign_visibility' => ['nullable', 'string', 'in:public'], ]; } } ================================================ FILE: app/Http/Requests/StoreTag.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'icon' => ['nullable', 'string', 'max:100', 'regex:/^(fa-|ra )/'], 'colour' => [ 'nullable', 'max:7', ], 'attribute' => ['array', new UniqueAttributeNames], 'is_private' => 'nullable|boolean', ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreTagEntity.php ================================================ 'required|integer|exists:entities,id', 'entities' => 'required|min:1', 'entities.*' => 'different:tag_id|integer|exists:entities,id', ]; } } ================================================ FILE: app/Http/Requests/StoreTimeline.php ================================================ 'required|max:191', 'entry' => 'nullable|string', 'type' => 'nullable|string|max:191', 'parent_id' => 'nullable|integer|exists:entities,id', 'calendar_id' => 'nullable|integer|exists:calendars,id', 'image' => 'mimes:jpeg,png,jpg,gif,webp,svg|max:' . Limit::upload(), 'image_url' => 'nullable|url|active_url', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', 'revert_order' => 'nullable', 'attribute' => ['array', new UniqueAttributeNames], ]; /** @var Entity $self */ $self = request()->route('entity'); if (! empty($self)) { $rules['parent_id'] = [ 'nullable', 'integer', 'exists:entities,id', new Nested($self), ]; } $rules['tags'] = 'nullable|array'; $rules['tags.*'] = 'distinct|exists:tags,id'; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreTimelineElement.php ================================================ 'prohibited', 'entity_id' => 'nullable|required_without:name|exists:entities,id', 'name' => 'nullable|string|max:191|required_without:entity_id', 'era_id' => 'required|integer|exists:timeline_eras,id', 'entry' => 'nullable|string', 'position' => 'nullable|integer', 'colour' => 'nullable|string|max:12', 'date' => 'nullable|string|max:45', 'visibility_id' => 'nullable|integer|exists:visibilities,id', 'icon' => ['nullable', 'string', new FontAwesomeIcon], 'use_event_date' => 'boolean', ]; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreTimelineEra.php ================================================ 'required|string', 'entry' => 'nullable|string', 'abbreviation' => 'nullable|string', 'start_date' => 'nullable|integer', 'end_date' => 'nullable|integer', ]; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/StoreWebhook.php ================================================ 'integer|required', 'tags' => 'array', 'tags.*' => 'integer|exists:tags,id', 'url' => 'string|required|active_url|max:191', 'type' => 'required|integer', 'message' => 'required_if:type_id,1', 'status' => 'nullable|boolean', ]; } } ================================================ FILE: app/Http/Requests/StoreWhiteboard.php ================================================ 'required|max:191', 'type' => 'nullable|string|max:45', 'entity_image_uuid' => 'nullable|exists:images,id', 'entity_header_uuid' => 'nullable|exists:images,id', 'template_id' => 'nullable', ]; return $this->clean($rules); } } ================================================ FILE: app/Http/Requests/SubscriptionCancel.php ================================================ |string> */ public function rules(): array { return [ 'reason' => 'nullable', 'reason_secondary' => 'nullable', 'reason_custom' => 'nullable', ]; } } ================================================ FILE: app/Http/Requests/TransferTag.php ================================================ 'required|integer|exists:tags,id', ]; } } ================================================ FILE: app/Http/Requests/TransformEntity.php ================================================ 'array|required', 'entities.*' => 'distinct|integer|exists:entities,id', 'entity_type' => 'required|integer|exists:entity_types,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/TransformEntityRequest.php ================================================ 'required|integer|exists:entity_types,id', 'confirm' => 'required|boolean', ]; } } ================================================ FILE: app/Http/Requests/Translation/StoreFaqTranslationRequest.php ================================================ 'required|integer|exists:faq_categories,id', 'locale' => 'required', ]; } } ================================================ FILE: app/Http/Requests/UpdateAttribute.php ================================================ clean([ 'name' => 'nullable|string|max:191', 'value' => 'nullable|string', ]); } } ================================================ FILE: app/Http/Requests/UpdateCalendarEvent.php ================================================ 'integer|exists:entities,id', 'day' => 'required', 'month' => 'required', 'year' => 'required', 'length' => 'required|integer|min:1', 'is_recurring' => 'nullable', 'recurring_until' => 'nullable', 'recurring_periodicity' => 'nullable|max:5', 'colour' => 'nullable|string|max:7', 'comment' => 'nullable|max:191', 'type_id' => 'nullable|integer|exists:entity_event_types,id', 'visibility' => 'nullable|string|in:all,admin,self,members,admin-self', ]; } } ================================================ FILE: app/Http/Requests/UpdateCampaign.php ================================================ 'string|min:4', 'vanity' => ['nullable', 'string', 'min:4', 'max:45', 'unique:campaigns,slug', new Vanity], 'description' => 'nullable|string', 'image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'header_image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), 'locale' => 'nullable|string', 'systems' => 'array', 'systems.*' => 'distinct|exists:game_systems,id', 'entity_visibility' => 'nullable', 'entity_personality_visibility' => 'nullable', 'is_public' => 'nullable', 'css' => 'nullable|string', 'theme_id' => 'nullable|exists:themes,id', 'genres' => 'array', 'genres.*' => 'distinct|exists:genres,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/UpdateCampaignHeader.php ================================================ 'nullable', 'header_image' => 'nullable|mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), ]; } } ================================================ FILE: app/Http/Requests/UpdateEntityAbility.php ================================================ 'nullable|string', 'visibility_id' => 'nullable|integer|exists:visibilities,id', ]; } } ================================================ FILE: app/Http/Requests/UpdateEntityAttribute.php ================================================ 'nullable', 'uid' => 'required|numeric', ]; } } ================================================ FILE: app/Http/Requests/UpdateEntityEntry.php ================================================ 'required|string', ]; } } ================================================ FILE: app/Http/Requests/UpdateEntityFile.php ================================================ 'required|string|max:45', 'visibility' => 'nullable|string|max:10', ]; } } ================================================ FILE: app/Http/Requests/UpdateEntityImage.php ================================================ 'nullable|exists:images,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/UpdateEntityTags.php ================================================ 'array', ]; } } ================================================ FILE: app/Http/Requests/UpdateInventory.php ================================================ clean($rules); } } ================================================ FILE: app/Http/Requests/UpdateModuleName.php ================================================ */ public function rules(): array { return [ 'enabled' => 'nullable|boolean', 'singular' => 'nullable|string|max:45', 'plural' => 'nullable|string|max:45', 'icon' => 'nullable|string|max:60', 'default_entity_image' => 'mimes:jpeg,png,jpg,gif,webp|max:' . Limit::upload(), ]; } } ================================================ FILE: app/Http/Requests/UpdateRelation.php ================================================ clean([ 'target_id' => 'integer|exists:entities,id', 'relation' => 'max:255', 'visibility_id' => 'integer|exists:visibilities,id', 'attitude' => 'min:-100|max:100', 'colour' => 'max:7', 'is_pinned' => 'boolean', ]); } } ================================================ FILE: app/Http/Requests/UpdateUserRoles.php ================================================ 'array', 'roles.*' => 'required|integer|exists:campaign_roles,id', ]; return $rules; } } ================================================ FILE: app/Http/Requests/ValidateCoupon.php ================================================ */ public function rules() { return [ 'coupon' => 'required|min:4|max:25', ]; } } ================================================ FILE: app/Http/Requests/ValidatePledge.php ================================================ */ public function rules() { return [ ]; } } ================================================ FILE: app/Http/Requests/ValidateReminderLength.php ================================================ 'integer|nullable', 'month' => 'required|integer', 'year' => 'required|integer', 'length' => 'required|integer', ]; } } ================================================ FILE: app/Http/Requests/Whiteboards/CreateRequest.php ================================================ user()->can('create', Whiteboard::class); } /** * Get the validation rules that apply to the request. * * @return array|string> */ public function rules(): array { return [ 'name' => 'required|string|max:255', 'data' => 'required', ]; } } ================================================ FILE: app/Http/Requests/Whiteboards/CreateStrokeRequest.php ================================================ |string> */ public function rules(): array { return [ 'points' => 'required|array', 'fill' => 'string', 'width' => 'integer|min:1', ]; } } ================================================ FILE: app/Http/Requests/Whiteboards/StoreShapeRequest.php ================================================ user()->can('update', $this->whiteboard->entity); } /** * Get the validation rules that apply to the request. * * @return array|string> */ public function rules(): array { return [ 'type' => 'required|string', 'x' => 'required|numeric', 'y' => 'required|numeric', 'scale_x' => 'numeric', 'scale_y' => 'numeric', 'rotation' => 'nullable|numeric', 'width' => 'required|numeric', 'height' => 'required|numeric', 'is_locked' => 'boolean', 'z_index' => 'integer|integer', ]; } } ================================================ FILE: app/Http/Requests/Whiteboards/UpdateRequest.php ================================================ user()->can('update', $this->whiteboard->entity); } /** * Get the validation rules that apply to the request. * * @return array|string> */ public function rules(): array { return [ 'type' => 'required|string', 'shape' => 'required', 'x' => 'required|integer', 'y' => 'required|integer', 'scale_x' => 'required|integer', 'scale_y' => 'required|integer', 'rotation' => 'integer', 'width' => 'required|integer', 'height' => 'required|integer', 'is_locked' => 'boolean', 'z_index' => 'integer', ]; } } ================================================ FILE: app/Http/Requests/Whiteboards/UpdateShapeRequest.php ================================================ user()->can('update', $this->whiteboard->entity); } /** * Get the validation rules that apply to the request. * * @return array|string> */ public function rules(): array { return [ 'group_id' => 'nullable|integer|exists:whiteboard_shapes,id', 'x' => 'numeric', 'y' => 'numeric', 'scale_x' => 'numeric', 'scale_y' => 'numeric', 'rotation' => 'numeric', 'width' => 'numeric', 'height' => 'numeric', 'is_locked' => 'boolean', 'z_index' => 'integer', ]; } } ================================================ FILE: app/Http/Resources/AbilityResource.php ================================================ resource; return $this->entity([ 'charges' => $ability->charges, 'abilities' => $ability->entity->descendants()->pluck('id')->toArray(), ]); } } ================================================ FILE: app/Http/Resources/Api/CategoryStatusResource.php ================================================ */ public function toArray(Request $request): array { /** @var CategoryStatus $model */ $model = $this->resource; return [ 'id' => $model->id, 'key' => $model->key, 'is_default' => $model->is_default, 'category_id' => $model->category_id, ]; } } ================================================ FILE: app/Http/Resources/Api/DefaultEntityTypeResource.php ================================================ resource; return [ 'id' => $entityType->id, 'code' => $entityType->code, 'singular' => __('entities.' . $entityType->code), 'plural' => __('entities.' . $entityType->pluralCode()), 'icon' => $entityType->icon, 'is_special' => false, 'is_enabled' => true, 'is_nested' => $entityType->isNested(), 'has_table' => $entityType->hasTable(), ]; } } ================================================ FILE: app/Http/Resources/Api/EntityImagesResource.php ================================================ */ public function toArray(Request $request): array { /** @var Entity $entity */ $entity = $this->resource; return [ 'image' => [ 'uuid' => $entity->image_uuid, 'full' => Avatar::entity($entity)->original(), 'thumbnail' => Avatar::entity($entity)->size(40)->thumbnail(), ], 'header' => [ 'uuid' => $entity->header_uuid, 'full' => $entity->header?->getUrl(), 'thumbnail' => $entity->hasHeaderImage() ? $entity->getHeaderUrl() : null, ], ]; } } ================================================ FILE: app/Http/Resources/Api/EntityTagResource.php ================================================ */ public function toArray(Request $request): array { /** @var EntityTag $model */ $model = $this->resource; return [ 'tag_id' => $model->tag_id, ]; } } ================================================ FILE: app/Http/Resources/ApiEntityTagResource.php ================================================ */ public function toArray(Request $request): array { return parent::toArray($request); } } ================================================ FILE: app/Http/Resources/ApiExclusion.php ================================================ has($field)) { unset($rules[$field]); } } return $rules; } } ================================================ FILE: app/Http/Resources/ApiSync.php ================================================ Carbon::now(), ]; if (config('app.debug')) { $additional['queries'] = new ApiService; } // Make sure we have the app's url for pagination, otherwise on prod it will skip the https scheme try { if (app()->isProduction() && $resource instanceof LengthAwarePaginator) { /** @var LengthAwarePaginator $resource */ $path = $resource->path(); $path = Str::replaceFirst('http://', 'https://', $path); $resource->setPath($path); } } catch (Exception $e) { // Do nothing, this can happen for sub resources // being called (ex character::characterOrgsCollection) // throw $e; } return parent::collection($resource) ->additional($additional); } } ================================================ FILE: app/Http/Resources/ApplicationResource.php ================================================ resource; return [ 'id' => $model->id, 'user_id' => $model->user_id, 'text' => $model->text, 'created_at' => $model->created_at, 'updated_at' => $model->updated_at, ]; } } ================================================ FILE: app/Http/Resources/AttributeResource.php ================================================ resource; return $this->onEntity([ 'name' => $attribute->name, 'value' => $attribute->isCheckbox() ? (bool) $attribute->value : $attribute->value, 'parsed' => $attribute->mappedValue(), 'default_order' => $attribute->default_order, 'is_star' => (bool) $attribute->isPinned(), 'is_pinned' => (bool) $attribute->isPinned(), 'api_key' => $attribute->api_key, 'type_id' => $attribute->type_id, ]); } } ================================================ FILE: app/Http/Resources/AttributeTemplateResource.php ================================================ resource; return $this->entity([ 'entity_type_id' => $model->entity_type_id, ]); } } ================================================ FILE: app/Http/Resources/Attributes/LiveAttributeResource.php ================================================ resource; $formatted = [ 'id' => $attribute->id, 'name' => $attribute->mappedName(), 'name_raw' => $attribute->name, 'value' => $attribute->isCheckbox() ? (bool) $attribute->value : $attribute->mappedValue(), 'value_raw' => $attribute->value, 'type_id' => $attribute->type_id, 'is_section' => $attribute->isSection(), 'is_number' => $attribute->isNumber(), 'is_multiline' => $attribute->isText(), 'is_checkbox' => $attribute->isCheckbox(), 'is_random' => $attribute->isRandom(), 'is_private' => (bool) $attribute->is_private, 'is_pinned' => $attribute->isPinned(), 'is_hidden' => (bool) $attribute->is_hidden, ]; if ($attribute->isList()) { $formatted['values'] = $attribute->listRange(); } // Routes $formatted['apis'] = [ 'update' => [ 'method' => 'POST', 'url' => route('entities.attributes.live-api.update', [$attribute->entity->campaign, $attribute->entity, $attribute]), ], 'delete' => [ 'method' => 'POST', 'url' => route('entities.attributes.live-api.delete', [$attribute->entity->campaign, $attribute->entity, $attribute]), ], ]; return $formatted; } } ================================================ FILE: app/Http/Resources/BookmarkResource.php ================================================ resource; return [ 'id' => $model->id, 'name' => $model->name, 'entity_id' => $model->entity_id, 'filters' => $model->filters, 'icon' => $model->icon, 'is_private' => $model->is_private, 'is_active' => $model->is_active, 'menu' => $model->menu, 'random_entity_type' => $model->random_entity_type, 'entity_type_id' => $model->entity_type_id, 'tab' => $model->tab, 'target' => $model->target, 'dashboard_id' => $model->dashboard_id, 'parent' => $model->parent, 'css' => $model->css, 'created_at' => $model->created_at, 'updated_at' => $model->updated_at, 'created_by' => $model->created_by, 'updated_by' => $model->updated_by, 'options' => $model->options, ]; } } ================================================ FILE: app/Http/Resources/CalendarResource.php ================================================ resource; return $this->entity([ 'date' => $calendar->date, 'parameters' => $calendar->parameters, 'months' => json_decode($calendar->months), 'weekdays' => json_decode($calendar->weekdays), 'years' => json_decode($calendar->years), 'seasons' => json_decode($calendar->seasons), 'moons' => json_decode($calendar->moons), 'start_offset' => $calendar->start_offset, // X year is a leap year 'suffix' => $calendar->suffix, 'format' => $calendar->format, 'has_leap_year' => $calendar->has_leap_year, 'skip_year_zero' => $calendar->skip_year_zero, 'leap_year_amount' => $calendar->leap_year_amount, // Add X number of days 'leap_year_month' => $calendar->leap_year_month, // At the end of month X 'leap_year_offset' => $calendar->leap_year_offset, // every X years 'leap_year_start' => $calendar->leap_year_start, // X year is a leap year ]); } } ================================================ FILE: app/Http/Resources/CalendarWeatherResource.php ================================================ resource; return [ 'id' => $model->id, 'campaign_id' => $model->campaign_id, 'entity_id' => $model->entity_id, 'widget' => $model->widget, 'config' => $model->config, 'width' => (int) $model->width, 'position' => (int) $model->position, 'tags' => $model->tags()->pluck('id')->toArray(), 'created_at' => $model->created_at, 'updated_at' => $model->updated_at, 'created_by' => $model->created_by, 'updated_by' => $model->updated_by, ]; } } ================================================ FILE: app/Http/Resources/CampaignResource.php ================================================ withMentions = true; return $this; } /** * Transform the resource into an array. * * @param Request $request */ public function toArray($request): array { /** @var Campaign $campaign */ $campaign = $this->resource; $url = route('dashboard', ['campaign' => $campaign]); $apiViewUrl = 'campaigns.show'; $data = [ 'id' => $campaign->id, 'slug' => $campaign->slug, 'name' => $campaign->name, 'locale' => $campaign->locale, 'description_raw' => $campaign->description?->description, 'image' => $campaign->image, 'image_full' => $campaign->thumbnail(0), 'image_thumb' => $campaign->thumbnail(), 'visibility' => $campaign->isUnlisted() ? 'discreet' : ($campaign->isPublic() ? 'public' : 'private'), 'visibility_id' => $campaign->visibility_id->value, 'created_at' => $campaign->created_at, 'updated_at' => $campaign->updated_at, 'settings' => $campaign->settings, 'ui_settings' => $campaign->ui_settings, 'default_images' => $campaign->default_images, 'follower' => $campaign->follower(), 'boosted' => $campaign->boosted(), 'superboosted' => $campaign->superboosted(), 'premium' => $campaign->premium(), 'is_hidden' => (bool) $campaign->is_hidden, 'is_prioritised' => (bool) $campaign->is_prioritised, 'urls' => [ 'view' => $url, 'api' => Route::has($apiViewUrl) ? route($apiViewUrl, [$campaign]) : null, ], ]; CampaignCache::campaign($campaign)->user(auth()->user()); UserCache::campaign($campaign)->user(auth()->user()); if (auth()->user()->can('member', $campaign) && auth()->user()->can('members', $campaign)) { $data['members'] = CampaignUserResource::collection($campaign->members); } if ($this->withMentions) { $data['description'] = $campaign->parsedEntry(); } // Hide stuff like sidebar unset($data['ui_settings']['sidebar']); return $data; } } ================================================ FILE: app/Http/Resources/CampaignStyleResource.php ================================================ resource; return [ 'id' => $model->id, 'campaign_id' => $model->campaign_id, 'name' => $model->name, 'content' => $model->content, 'is_enabled' => $model->is_enabled, 'is_theme' => $model->isTheme(), 'created_at' => $model->created_at, 'updated_at' => $model->updated_at, 'created_by' => $model->created_by, ]; } } ================================================ FILE: app/Http/Resources/CampaignUserResource.php ================================================ resource; return [ 'id' => $resource->id, 'user' => new UserResource($resource->user), ]; } } ================================================ FILE: app/Http/Resources/CampaignUserRoleResource.php ================================================ resource; return [ 'id' => $model->id, 'name' => $model->name, 'is_admin' => $model->isAdmin(), ]; } } ================================================ FILE: app/Http/Resources/CategoryStatusResource.php ================================================ */ public function toArray(Request $request): array { /** @var CategoryStatus $model */ $model = $this->resource; return [ 'id' => $model->id, 'key' => $model->key, 'is_custom' => $model->isCustom(), ]; } } ================================================ FILE: app/Http/Resources/CharacterOrganisationResource.php ================================================ resource; $raceIDs = $model->characterRaces->pluck('race.id'); $privateRaceIDs = $model->characterRaces->where('is_private', true)->pluck('race.id'); $familyIDs = $model->characterFamilies->pluck('family.id'); $privateFamilyIDs = $model->characterFamilies->where('is_private', true)->pluck('family.id'); $locationIds = $model->entity->locations->pluck('id'); $character = [ 'title' => $model->title, 'age' => $model->age, 'sex' => $model->sex, 'pronouns' => $model->pronouns, 'races' => $raceIDs, 'private_races' => $privateRaceIDs, 'families' => $familyIDs, 'private_families' => $privateFamilyIDs, 'locations' => $locationIds, 'traits' => CharacterTraitResource::collection($model->characterTraits), 'is_personality_visible' => (bool) $model->is_personality_visible, 'is_personality_pinned' => (bool) $model->is_personality_pinned, 'is_appearance_pinned' => (bool) $model->is_appearance_pinned, ]; if (request()->get('related', false)) { $character['organisations'] = new CharacterOrganisationResource($model->organisationMemberships()->has('organisation')->get()); } return $this->entity($character); } } ================================================ FILE: app/Http/Resources/CharacterTraitResource.php ================================================ resource; return [ 'id' => $resource->id, 'name' => $resource->name, 'entry' => $resource->entry, 'entry_parsed' => $resource->entry ? Mentions::mapAny($resource) : null, 'section_id' => $resource->section_id, 'section' => $resource->section_id == CharacterTrait::SECTION_APPEARANCE ? 'appearance' : 'personality', // 'is_private' => (bool) $this->is_private, 'default_order' => $resource->default_order, ]; } } ================================================ FILE: app/Http/Resources/Conversation/ConversationMessageResource.php ================================================ resource; return [ 'id' => $resource->id, 'from_id' => $resource->user_id ?: $resource->character_id, 'user' => $resource->user?->name, 'character' => $resource->character?->name, 'message' => $resource->message, 'created_at' => $resource->created_at->diffForHumans(), 'updated_at' => $resource->updated_at->diffForHumans(), 'created_by' => $resource->created_by, 'can_delete' => auth()->check() && auth()->user()->can('delete', $resource), 'can_edit' => auth()->check() && auth()->user()->can('edit', $resource), 'delete_url' => route('conversations.conversation_messages.destroy', [$campaign, $resource->conversation_id, $resource->id]), 'is_updated' => $resource->updated_at != $resource->created_at, 'group' => $resource->isGroup(), ]; } } ================================================ FILE: app/Http/Resources/Conversation/ConversationResource.php ================================================ get('oldest', null); $newest = $request->get('newest', null); /** @var Conversation $resource */ $resource = $this->resource; $messages = new Collection($resource->messages()->default($oldest, $newest)->get()); $messages = $messages->reverse(); $data = []; $previous = null; /** @var ConversationMessage $message */ foreach ($messages as $message) { $message->grouppedWith($previous); $data[] = new ConversationMessageResource($message); // [ // 'id' => $message->id, // 'user' => $message->user ? $message->user->name : null, // 'character' => $message->character ? $message->character->name : null, // 'message' => $message->message, // 'created_at' => $message->created_at->diffForHumans(), // 'can_delete' => Auth::user()->can('delete', $message), // 'can_edit' => Auth::user()->can('edit', $message), // 'delete_url' => route('conversations.conversation_messages.destroy', [$this, $message]), // 'is_updated' => $message->updated_at !== $message->created_at // ]; $previous = $message; } // Check if there are previous messages available $first = $messages->first(); $previous = false; if ($first) { $previous = $resource->messages()->where('id', '<', $first->id)->count() > 0; } return [ 'messages' => $data, 'previous' => $previous, ]; } } ================================================ FILE: app/Http/Resources/ConversationMessageResource.php ================================================ resource; return [ 'conversation_id' => $resource->conversation_id, 'created_by' => $resource->created_by, 'character_id' => $resource->character_id, 'user_id' => $resource->user_id, 'message' => $resource->message, ]; } } ================================================ FILE: app/Http/Resources/ConversationParticipantResource.php ================================================ resource; return [ 'conversation_id' => $resource->conversation_id, 'character_id' => $resource->character_id, 'user_id' => $resource->user_id, ]; } } ================================================ FILE: app/Http/Resources/ConversationResource.php ================================================ resource; return $this->entity([ 'target' => $model->forCharacters() ? 'characters' : 'members', 'target_id' => $model->target_id, 'is_closed' => $model->is_closed, 'participants' => $model->participants()->count(), 'messages' => $model->messages()->count(), ]); } } ================================================ FILE: app/Http/Resources/CreatureResource.php ================================================ resource; $locationIds = $model->entity->locations->pluck('id'); return $this->entity([ 'locations' => $locationIds, ]); } } ================================================ FILE: app/Http/Resources/DiceRollResource.php ================================================ resource; return $this->entity([ 'system' => $model->system, 'parameters' => $model->parameters, 'rolls' => $model->diceRollResults()->pluck('results')->toArray(), ]); } } ================================================ FILE: app/Http/Resources/Entities/ExploreResource.php ================================================ */ public function toArray(Request $request): array { /** @var Entity $entity */ $entity = $this->resource; $campaign = CampaignLocalization::getCampaign(); $attributes = []; if ($entity->is_private) { $attributes[] = 'private'; } if ($entity->status) { $attributes[] = $entity->status->key; } // Use the eager-loaded relation directly (not $entity->child which goes through EntityCache and loses withCount) $child = $entity->entityType->isStandard() ? $entity->getRelationValue(Str::camel($entity->entityType->code)) : null; $parentEntity = $entity->parent; $routeParams = [$campaign, $entity->entityType]; $links = ['back' => __('crud.actions.back')]; if ($parentEntity) { $routeParams['parent_id'] = $parentEntity; $links['back'] = __('datagrids.actions.back_to', ['name' => $parentEntity->name]); } $routeBack = route('entities.index', $routeParams); $showParams = [$campaign, $entity]; if ($request->filled('bookmark')) { $showParams['bookmark'] = $request->get('bookmark'); } $data = [ 'id' => $entity->id, 'name' => $entity->name, 'type' => $entity->type, 'type_slug' => Str::slug($entity->type ?? ''), 'attributes' => $attributes, 'selected' => false, 'children' => $entity->children_count ?? 0, 'images' => [ 'thumb' => Avatar::entity($entity)->fallback()->size(192, 144)->thumbnail(), 'full' => Avatar::entity($entity)->original(), ], 'is_private' => $entity->is_private, 'parent_id' => $parentEntity?->id, 'entityType' => new EntityTypeResource($entity->entityType), 'urls' => [ 'tooltip' => route('entities.tooltip', [$campaign, $entity]), 'show' => route('entities.show', $showParams), 'children' => route('entities.index', [$campaign, $entity->entityType, 'parent_id' => $entity->id]), 'children_api' => route('entities.index-api', [$campaign, $entity->entityType, 'parent_id' => $entity->id, 'children' => true]), 'parent' => $routeBack, 'parent_api' => $parentEntity ? route('entities.index-api', [$campaign, $entity->entityType, 'parent_id' => $parentEntity->id]) : route('entities.index-api', [$campaign, $entity->entityType]), 'edit' => route('entities.edit', [$campaign, $entity]), 'relations' => route('entities.relations.index', [$campaign, $entity]), 'inventory' => route('entities.inventory', [$campaign, $entity]), ], 'can_edit' => auth()->check() && auth()->user()->can('update', $entity), 'tags' => $this->tags(), 'links' => $links, ]; // Column-driven entity-type-specific data (standard types only — custom types have no child model) if ($child) { if ($this->hasColumn('title') && isset($child->title)) { $data['title'] = $child->title; } if ($this->hasColumn('date') && isset($child->date)) { $data['date'] = $child->date; } if ($this->hasColumn('price') && isset($child->price)) { $data['price'] = $child->price; } if ($this->hasColumn('size') && isset($child->size)) { $data['size'] = $child->size; } if ($this->hasColumn('weight') && isset($child->weight)) { $data['weight'] = $child->weight; } if ($this->hasColumn('colour') && isset($child->colour)) { $data['colour'] = $child->colour; } if ($this->hasColumn('sex') && isset($child->sex)) { $data['sex'] = $child->sex; } if ($this->hasColumn('pronouns') && isset($child->pronouns)) { $data['pronouns'] = $child->pronouns; } if ($this->hasColumn('is_auto_applied') && isset($child->is_auto_applied)) { $data['is_auto_applied'] = (bool) $child->is_auto_applied; } if ($this->hasColumn('is_hidden') && isset($child->is_hidden)) { $data['is_hidden'] = (bool) $child->is_hidden; } if ($this->hasColumn('is_enabled') && isset($child->is_enabled)) { $data['is_enabled'] = (bool) $child->is_enabled; } if ($this->hasColumn('entity_type_name')) { $data['entity_type_name'] = $child->entityType?->name() ?? null; } if ($this->hasColumn('location')) { $data['location'] = $this->formatSingleEntity($child->location ?? null); } if ($this->hasColumn('character')) { $data['character'] = $this->formatSingleEntity($child->character ?? null); } if ($this->hasColumn('author') && method_exists($child, 'author')) { $data['author'] = $this->formatSingleEntity($child->author ?? null); } if ($this->hasColumn('instigator') && method_exists($child, 'instigator')) { $data['instigator'] = $this->formatSingleEntity($child->instigator ?? null); } if ($this->hasColumn('families') && method_exists($child, 'characterFamilies')) { $data['families'] = $this->formatRelatedEntities($child, 'characterFamilies', 'family'); } if ($this->hasColumn('races') && method_exists($child, 'characterRaces')) { $data['races'] = $this->formatRelatedEntities($child, 'characterRaces', 'race'); } if ($this->hasColumn('creators') && method_exists($child, 'itemCreators')) { $data['creators'] = $this->formatPivotEntities($child, 'itemCreators', 'creator'); } // Count columns if ($this->hasColumn('members_count') && isset($child->members_count)) { $data['members_count'] = $child->members_count ?? 0; } if ($this->hasColumn('elements_count') && isset($child->elements_count)) { $data['elements_count'] = $child->elements_count ?? 0; } if ($this->hasColumn('eras_count') && isset($child->eras_count)) { $data['eras_count'] = $child->eras_count ?? 0; } if ($this->hasColumn('entities_count') && isset($child->entities_count)) { $data['entities_count'] = $child->entities_count ?? 0; } if ($this->hasColumn('attributes_count')) { $data['attributes_count'] = $entity->attributes_count ?? 0; } // Map explore link if ($this->hasColumn('explore')) { if ($child->isReal()) { $data['explore'] = ['url' => route('maps.explore', [$campaign, $child->id])]; } elseif (! $entity->hasImage()) { $data['explore'] = null; } elseif ($child->isChunked() && $child->chunkingError()) { $data['explore'] = ['url' => null, 'status' => 'error']; } elseif ($child->isChunked() && $child->chunkingRunning()) { $data['explore'] = ['url' => null, 'status' => 'running']; } else { $data['explore'] = ['url' => route('maps.explore', [$campaign, $child->id])]; } } // Whiteboard draw link if ($this->hasColumn('draw')) { $data['draw'] = ['url' => route('whiteboards.draw', [$campaign, $child->id])]; } } // Calendar date (lives on entity, not child) if ($this->hasColumn('calendar_date')) { $data['calendar_date'] = $this->formatCalendarDate($entity); } // Status (lives on entity, not child) if ($this->hasColumn('status')) { $status = $entity->status; if ($status) { $status->setRelation('entityType', $entity->entityType); } $data['status'] = ($status && $status->icon) ? [ 'icon' => $status->icon(), 'tooltip' => $status->name(), ] : null; } // Parent entity link if ($this->hasColumn('parent') && $parentEntity) { $data['parent_entity'] = [ 'id' => $parentEntity->id, 'name' => $parentEntity->name, 'url' => route('entities.show', [$campaign, $parentEntity]), ]; } // Entity locations (many-to-many) if ($this->hasColumn('locations') && $entity->relationLoaded('locations')) { $data['locations'] = $this->formatEntityLocations($entity); } // Children preview for grid avatar bubbles if ($entity->relationLoaded('children')) { $data['children_preview'] = $entity->children->take(3)->map(fn ($childEntity) => [ 'id' => $childEntity->id, 'name' => $childEntity->name, 'image' => Avatar::entity($childEntity)->fallback()->size(40, 40)->thumbnail(), ])->values()->toArray(); } return $data; } protected function tags(): array { /** @var Entity $entity */ $entity = $this->resource; $tags = []; $campaign = CampaignLocalization::getCampaign(); foreach ($entity->visibleTags() as $tag) { $tags[] = [ 'id' => $tag->id, 'urls' => [ 'show' => route('entities.show', [$campaign, $tag->entity]), ], 'name' => $tag->name, 'colour' => $tag->colourClass(), 'colour_style' => $tag->colourStyle(), 'shortname' => $tag->hasIcon() ? '' : $tag->shortname(), ]; } return $tags; } protected function formatRelatedEntities(mixed $child, string $pivotRelation, string $entityRelation): array { if (! method_exists($child, $pivotRelation)) { return []; } $items = []; $campaign = CampaignLocalization::getCampaign(); foreach ($child->{$pivotRelation} as $pivot) { $related = $pivot->{$entityRelation}; if ($related && $related->entity) { $items[] = [ 'id' => $related->entity->id, 'name' => $related->entity->name, 'url' => route('entities.show', [$campaign, $related->entity]), ]; } } return $items; } /** * Format pivot relations where the related model is an Entity directly (e.g. ItemCreator->creator) */ protected function formatPivotEntities(mixed $child, string $pivotRelation, string $entityRelation): array { if (! method_exists($child, $pivotRelation)) { return []; } $items = []; $campaign = CampaignLocalization::getCampaign(); foreach ($child->{$pivotRelation} as $pivot) { $entity = $pivot->{$entityRelation}; if ($entity) { $items[] = [ 'id' => $entity->id, 'name' => $entity->name, 'url' => route('entities.show', [$campaign, $entity]), ]; } } return $items; } protected function formatSingleEntity(mixed $model): ?array { if (! $model) { return null; } $campaign = CampaignLocalization::getCampaign(); // Some relations (e.g. Journal::author, Quest::instigator) already return an Entity directly if ($model instanceof Entity) { return [ 'id' => $model->id, 'name' => $model->name, 'url' => route('entities.show', [$campaign, $model]), ]; } if (! $model->entity) { return null; } return [ 'id' => $model->entity->id, 'name' => $model->entity->name, 'url' => route('entities.show', [$campaign, $model->entity]), ]; } protected function formatCalendarDate(Entity $entity): ?array { if (! $entity->relationLoaded('calendarDate') || ! $entity->calendarDate) { return null; } $reminder = $entity->calendarDate; if (! $reminder->calendar || ! $reminder->calendar->entity) { return null; } $campaign = CampaignLocalization::getCampaign(); return [ 'date' => $reminder->readableDate(), 'url' => route('entities.show', [ $campaign, $reminder->calendar->entity, 'month' => $reminder->month, 'year' => $reminder->year, ]), ]; } protected function formatEntityLocations(Entity $entity): array { $items = []; $campaign = CampaignLocalization::getCampaign(); foreach ($entity->locations as $location) { if ($location->entity) { $items[] = [ 'id' => $location->entity->id, 'name' => $location->entity->name, 'url' => route('entities.show', [$campaign, $location->entity]), ]; } } return $items; } } ================================================ FILE: app/Http/Resources/Entities/TemplateResource.php ================================================ */ public function toArray(Request $request): array { /** @var Entity $entity */ $entity = $this->resource; $campaign = CampaignLocalization::getCampaign(); return [ 'id' => $entity->id, 'name' => $entity->name, 'url' => route('entities.create', [$campaign, $entity->entityType, 'copy' => $entity, 'template' => true]), ]; } } ================================================ FILE: app/Http/Resources/Entity.php ================================================ resource; if ($model->isMissingChild()) { return ['error' => 'KA7: Entity #' . $model->id . ' missing child.']; } $url = $model->url(); $apiViewUrl = 'campaigns.' . $model->entityType->pluralCode() . '.show'; return [ 'id' => $model->id, 'child_id' => $model->hasChild() ? $model->id : null, 'child_type' => $model->hasChild() ? $model->entityType->code : null, 'name' => $model->name, 'image' => Avatar::entity($model)->original(), 'image_thumb' => Avatar::entity($model)->size(40)->thumbnail(), 'has_custom_image' => ! empty($model->image_path) && ! empty($model->image), 'type' => $model->type, 'type_id' => $model->type_id, 'entity_type' => $model->entityType->code, 'tooltip' => $model->tooltip, 'url' => $model->url(), 'is_attributes_private' => $model->is_attributes_private, 'is_private' => (bool) $model->is_private, 'created_at' => $model->created_at, 'created_by' => $model->created_by, 'updated_at' => $model->updated_at, 'updated_by' => $model->updated_by, 'urls' => [ 'view' => $url, 'api' => Route::has($apiViewUrl) ? route($apiViewUrl, [$model->campaign_id, $model->entity_id]) : null, ], ]; } } ================================================ FILE: app/Http/Resources/EntityAbilityResource.php ================================================ resource; return [ 'id' => $model->id, 'visibility_id' => $model->visibility_id, 'charges' => $model->charges, 'ability_id' => $model->ability_id, 'position' => $model->position, 'note' => $model->note, 'created_at' => $model->created_at, 'created_by' => $model->created_by, 'updated_at' => $model->updated_at, 'updated_by' => $model->updated_by, ]; } } ================================================ FILE: app/Http/Resources/EntityAssetResource.php ================================================ resource; $data = $this->onEntity([ 'type_id' => $asset->type_id, '_file' => $asset->isFile(), '_link' => $asset->isLink(), '_alias' => $asset->isAlias(), 'name' => $asset->name, 'metadata' => $asset->metadata, 'visibility_id' => $asset->visibility_id, 'is_pinned' => (bool) $asset->isPinned(), ]); if ($asset->isFile()) { $data['_url'] = $asset->url(); } return $data; } } ================================================ FILE: app/Http/Resources/EntityChild.php ================================================ resource; $merged = [ 'id' => $model->id, 'is_private' => (bool) $model->is_private, 'entity_id' => $model->entity->id, 'created_at' => $model->created_at, 'created_by' => $model->entity->created_by, 'updated_at' => $model->updated_at, 'updated_by' => $model->entity->updated_by, ]; $final = array_merge($prepared, $merged); ksort($final); return $final; } /** * Transform the resource into an array. * * @return array */ public function onEntity(array $prepared = []) { /** @var Model|Attribute|mixed $model */ $model = $this->resource; $merged = [ 'id' => $model->id, 'is_private' => (bool) $model->is_private, 'entity_id' => $model->entity_id, 'created_at' => $model->created_at, 'created_by' => $model->created_by, 'updated_at' => $model->updated_at, 'updated_by' => $model->updated_by, ]; if ($model instanceof Post) { unset($merged['is_private']); } $final = array_merge($prepared, $merged); ksort($final); return $final; } } ================================================ FILE: app/Http/Resources/EntityDefaultThumbnailResource.php ================================================ resource; return [ 'entity_type' => $resource['type'], 'url' => Img::url($resource['path']), ]; } } ================================================ FILE: app/Http/Resources/EntityMentionResource.php ================================================ resource; return [ 'entity_id' => $model->entity_id, 'post_id' => $model->post_id, 'campaign_id' => $model->campaign_id, 'target_id' => $model->target_id, ]; } } ================================================ FILE: app/Http/Resources/EntityPermissionResource.php ================================================ resource; return [ 'id' => $model->id, 'campaign_role_id' => $model->campaign_role_id, 'user_id' => $model->user_id, 'action' => $model->action, 'access' => (bool) $model->access, 'created_at' => $model->created_at, 'updated_at' => $model->updated_at, ]; } } ================================================ FILE: app/Http/Resources/EntityResource.php ================================================ withRelated = true; return $this; } public function withMisc(): self { $this->withMisc = true; return $this; } /** * @param Request $request * @return array */ public function toArray($request) { /** @var Entity $entity */ $entity = $this->resource; $url = $entity->url(); if ($entity->entityType->isCustom()) { $apiViewUrl = 'campaigns.entities.show'; } else { $apiViewUrl = 'campaigns.' . $entity->entityType->pluralCode() . '.show'; } $data = [ 'id' => $entity->id, 'name' => $entity->name, 'type' => $entity->entityType->code, 'type_field' => $entity->type, 'type_id' => $entity->type_id, 'module' => [ 'id' => $entity->entityType->id, 'code' => $entity->entityType->code, 'singular' => $entity->entityType->name(), 'plural' => $entity->entityType->plural(), ], 'tags' => $entity->tags->pluck('id')->toArray(), 'is_private' => (bool) $entity->is_private, 'is_template' => $entity->isTemplate(), 'campaign_id' => $entity->campaign_id, 'is_attributes_private' => (bool) $entity->is_attributes_private, 'status_id' => $entity->status_id, 'tooltip' => $entity->tooltip, 'header_image' => $entity->header_image, 'header_uuid' => $entity->header_uuid, 'image_uuid' => $entity->image_uuid, 'created_at' => $entity->created_at, 'created_by' => $entity->created_by, 'updated_at' => $entity->updated_at, 'updated_by' => $entity->updated_by, 'archived_at' => $entity->archived_at, 'urls' => [ 'view' => $url, 'api' => Route::has($apiViewUrl) ? route($apiViewUrl, [ $entity->campaign_id, $entity->entity_id, ]) : null, ], ]; $data['parent_id'] = $entity->parent_id; if ($entity->entityType->isCustom()) { $data['entry'] = $entity->entry; $data['entry_parsed'] = $entity->parsedEntry(); $data['locations'] = []; foreach ($entity->locations as $loc) { $data['locations'][] = $loc->id; } } else { $data['child_id'] = $entity->entity_id; } if (request()->get('related', false)) { $data['attributes'] = AttributeResource::collection( $entity->attributes, ); $data['posts'] = PostResource::collection($entity->posts); $data['entity_events'] = ReminderResource::collection( $entity->reminders, ); $data['reminders'] = ReminderResource::collection( $entity->reminders, ); $data['relations'] = RelationResource::collection( $entity->relationships, ); $data['inventory'] = InventoryResource::collection( $entity->inventories, ); $data['entity_abilities'] = EntityAbilityResource::collection( $entity->abilities, ); // Children and Parents if ($entity->ancestors) { $ancestors = []; foreach ($entity->ancestors as $ancestor) { $ancestors[] = $ancestor->id; } $data['parents'] = $ancestors; } if ($entity->children) { $descendants = []; foreach ($entity->children as $descendant) { $descendants[] = $descendant->id; } $data['children'] = $descendants; } } if ( request()->get('related', false) || request()->get('image', false) ) { if ($entity->isMissingChild()) { $data['child'] = 'Invalid child, please contact us on Discord with the following: EntityResource for #' . $entity->id; } else { $image = ! empty($entity->image); $data['child'] = [ 'image' => $image ? $entity->image->path : $entity->image_path, 'image_full' => Avatar::entity($entity)->original(), 'image_thumb' => Avatar::entity($entity) ->size(40) ->thumbnail(), 'has_custom_image' => $image || ! empty($entity->image_path), ]; } } // Get the actual model if ($this->withMisc && $entity->entityType->isStandard()) { $className = "App\Http\Resources\\" . ucfirst($entity->entityType->code) . 'Resource'; if (class_exists($className)) { $entity->child->setRelation('entity', $entity); $obj = new $className($entity->child); $data['child'] = $obj; } else { $data['child'] = 'unknown child class ' . $className; } } // Check for ?fields $fields = request()->query('fields'); if ($fields) { $fields = array_map('trim', explode(',', $fields)); $data = array_intersect_key($data, array_flip($fields)); } return $data; } /** * Transform the resource into an array. * * @return array|string */ public function entity(array $prepared = []) { /** @var mixed|MiscModel|Item $misc */ $misc = $this->resource; if (! $misc->entity) { return 'permission issue'; } $galleryImage = $misc->entity->image; $url = $misc->getLink(); $apiViewUrl = 'campaigns.' . $misc->entity->entityType->pluralCode() . '.show'; $merged = [ 'id' => $misc->id, 'name' => $misc->entity->name, 'entry' => $misc->entity->hasEntry() ? $misc->entity->entry : null, 'entry_parsed' => $misc->entity->hasEntry() ? $misc->entity->parsedEntry() : null, 'tooltip' => $misc->entity->tooltip ?: null, 'type' => $misc->entity->type ?: null, 'image' => $misc->entity->image_path, 'focus_x' => $misc->entity->focus_x, 'focus_y' => $misc->entity->focus_y, // Image 'image_full' => Avatar::entity($misc->entity)->original(), 'image_thumb' => Avatar::size(40)->fallback()->thumbnail(), 'has_custom_image' => ! empty($misc->entity->image_path) || ! empty($galleryImage), 'image_uuid' => $misc->entity->image ? $misc->entity->image->id : null, // Header 'header_full' => $misc->entity->getHeaderUrl(), 'header_uuid' => $misc->entity->header ? $misc->entity->header->id : null, 'has_custom_header' => $misc->entity->hasHeaderImage(), 'is_private' => (bool) $misc->entity->is_private, 'is_template' => (bool) $misc->entity->isTemplate(), 'is_attributes_private' => (bool) $misc->entity->is_attributes_private, 'status_id' => $misc->entity->status_id, 'status' => CategoryStatusResource::make($misc->entity->status), 'entity_id' => $misc->entity->id, // 'module' => [ // 'id' => $misc->entity->entityType->id, // 'code' => $misc->entity->entityType->code, // 'singular' => $misc->entity->entityType->name(), // 'plural' => $misc->entity->entityType->plural(), // ], 'tags' => $misc->entity->tags()->pluck('tags.id')->toArray(), 'created_at' => $misc->entity->created_at, 'created_by' => $misc->entity->created_by, 'updated_at' => $misc->entity->updated_at, 'updated_by' => $misc->entity->updated_by, 'urls' => [ 'view' => $url, 'api' => Route::has($apiViewUrl) ? route($apiViewUrl, [$misc->campaign_id, $misc->id]) : null, ], ]; // Foreign elements $attributes = $misc->getAttributes(); if (array_key_exists('location_id', $attributes)) { $merged['location_id'] = $misc->location_id; } if (array_key_exists('character_id', $attributes)) { $merged['character_id'] = $misc->character_id; } if (request()->get('related', false) || $this->withRelated) { $merged['attributes'] = AttributeResource::collection( $misc->entity->attributes, ); $merged['posts'] = PostResource::collection($misc->entity->posts); $merged['entity_events'] = ReminderResource::collection( $misc->entity->reminders, ); $merged['reminders'] = ReminderResource::collection( $misc->entity->reminders, ); $merged['relations'] = RelationResource::collection( $misc->entity->relationships, ); $merged['inventory'] = InventoryResource::collection( $misc->entity->inventories, ); $merged['entity_abilities'] = EntityAbilityResource::collection( $misc->entity->abilities, ); $merged['entity_assets'] = EntityAssetResource::collection( $misc->entity->assets, ); if ($misc->entity->ancestors) { $ancestors = []; foreach ($misc->entity->ancestors as $ancestor) { $ancestors[] = $ancestor->id; } $merged['parents'] = $ancestors; } if ($misc->entity->children) { $descendants = []; foreach ($misc->entity->children as $descendant) { $descendants[] = $descendant->entity_id; } $merged['children'] = $descendants; } } $final = array_merge($merged, $prepared); // Check for ?fields $fields = request()->query('fields'); if ($fields) { $fields = array_map('trim', explode(',', $fields)); $final = array_intersect_key($final, array_flip($fields)); } // ksort($final); return $final; } } ================================================ FILE: app/Http/Resources/EntityTagResource.php ================================================ resource; return [ 'id' => $model->pivot->id, // @phpstan-ignore-line 'tag_id' => $model->id, ]; } } ================================================ FILE: app/Http/Resources/EntityTypeResource.php ================================================ resource; return [ 'id' => $entityType->id, 'code' => $entityType->code, 'singular' => $entityType->name(), 'plural' => $entityType->plural(), 'icon' => $entityType->icon, 'is_custom' => $entityType->isCustom(), 'is_enabled' => $entityType->isEnabled(), 'is_nested' => $entityType->isNested(), 'has_table' => $entityType->hasTable(), ]; } } ================================================ FILE: app/Http/Resources/EventResource.php ================================================ resource; return $this->entity([ 'date' => $model->date, 'locations' => $model->entity->locations->pluck('id'), 'calendar_id' => $model->entity->calendarDate?->calendar_id, 'calendar_year' => $model->entity->calendarDate?->year, 'calendar_month' => $model->entity->calendarDate?->month, 'calendar_day' => $model->entity->calendarDate?->day, ]); } } ================================================ FILE: app/Http/Resources/FamilyResource.php ================================================ resource; return $this->entity([ 'members' => $model->members()->pluck('character_id')->toArray(), ]); } } ================================================ FILE: app/Http/Resources/FamilyTreeResource.php ================================================ resource; return $model->config ?? []; } } ================================================ FILE: app/Http/Resources/Gallery/Tiptap/ImageResource.php ================================================ */ public function toArray(Request $request): array { /** @var Image $image */ $image = $this->resource; return [ 'src' => $image->url(), 'name' => $image->name, 'folder' => $image->isFolder(), 'uuid' => $image->id, 'icon' => 'fa-regular fa-folder', 'url' => $image->isFolder() ? route('gallery.tiptap', [$image->campaign, 'folder' => $image->id]) : null, 'thumbnail' => $image->getUrl(192, 144), ]; } } ================================================ FILE: app/Http/Resources/GalleryFile.php ================================================ */ public function toArray(Request $request): array { /** @var Image $file */ $file = $this->resource; return [ 'id' => $file->id, 'is_folder' => $file->isFolder(), 'name' => $file->name, 'thumbnail' => $file->hasThumbnail() ? $file->getUrl(192, 144) : null, 'original' => $file->hasThumbnail() ? $file->url() : null, 'thumbnails' => $file->isFolder() ? $this->thumbnails($file) : null, 'visibility_id' => $file->visibility_id, 'focus_x' => $file->focus_x, 'focus_y' => $file->focus_y, 'is_selected' => false, 'is_deleted' => false, 'open' => route('gallery.show', [$this->campaign, $file]), 'link' => $file->url(), 'ext' => $file->ext, 'size' => $file->niceSize(), 'creator' => $file->creator->name ?? __('crud.unknown'), 'api' => [ 'show' => route('gallery.file.show', [$this->campaign, $file]), 'update' => route('gallery.file.update', [ $this->campaign, $file, ]), 'delete' => route('gallery.file.delete', [ $this->campaign, $file, ]), 'focus' => route('gallery.file.update-focus', [ $this->campaign, $file, ]), ], ]; } protected function thumbnails(Image $file): array { $thumbnails = []; foreach ($file->images as $subFile) { if (! $subFile->hasThumbnail()) { continue; } if (count($thumbnails) >= 3) { return $thumbnails; } if ($subFile->isFolder()) { continue; } $thumbnails[] = $subFile->getUrl(192, 144); } return $thumbnails; } } ================================================ FILE: app/Http/Resources/GalleryFileFull.php ================================================ */ public function toArray(Request $request): array { /** @var Image $file */ $file = $this->resource; $mentions = []; foreach ($file->inEntities() as $entity) { $mentions[] = [ 'url' => $entity->url(), 'name' => $entity->name, 'type' => 'image', ]; } foreach ($file->mentions as $mention) { if ($mention->isPost() && $mention->post) { $mentions[] = [ 'url' => $mention->entity->url() . '?#post-' . $mention->post_id, 'name' => $mention->post->name, 'type' => 'post', ]; } else { $mentions[] = [ 'url' => $mention->entity->url(), 'name' => $mention->entity->name, 'type' => 'entity', ]; } } return [ 'mentions' => $mentions, ]; } } ================================================ FILE: app/Http/Resources/ImageResource.php ================================================ resource; return [ 'id' => $image->id, 'name' => $image->name, 'is_folder' => (bool) $image->is_folder, 'folder_id' => $image->folder_id, 'path' => $image->is_folder ? null : Img::crop(300, 300)->url($image->path), 'ext' => $image->ext, 'size' => $image->size, 'created_at' => $image->created_at, 'created_by' => $image->created_by, 'updated_at' => $image->updated_at, 'visibility_id' => $image->visibility_id, 'focus_x' => $image->focus_x, 'focus_y' => $image->focus_y, ]; } } ================================================ FILE: app/Http/Resources/InventoryResource.php ================================================ resource; return $this->onEntity([ 'item_id' => $model->item_id, 'name' => $model->name, 'position' => $model->position, 'amount' => $model->amount, 'visibility_id' => $model->visibility_id, 'is_equipped' => (bool) $model->is_equipped, 'copy_item_entry' => (bool) $model->copy_item_entry, 'description' => $model->description, ]); } } ================================================ FILE: app/Http/Resources/ItemResource.php ================================================ resource; return $this->entity([ 'price' => $model->price, 'size' => $model->size, 'weight' => $model->weight, 'creators' => $model->itemCreators->pluck('creator_id'), ]); } } ================================================ FILE: app/Http/Resources/JournalResource.php ================================================ resource; return $this->entity([ 'date' => $model->date, 'author' => $model->author, 'author_id' => $model->author_id, 'calendar_id' => $model->entity->calendarDate?->calendar_id, 'calendar_year' => $model->entity->calendarDate?->year, 'calendar_month' => $model->entity->calendarDate?->month, 'calendar_day' => $model->entity->calendarDate?->day, 'calendar_reminder_length' => $model->entity->calendarDate?->length, ]); } } ================================================ FILE: app/Http/Resources/KankaCollection.php ================================================ $this->collection, // Add the sync object to the result for caching when the api was last called 'sync' => Carbon::now(), ]; } } ================================================ FILE: app/Http/Resources/LocationResource.php ================================================ resource; return $this->entity([ 'title' => $model->title, ]); } } ================================================ FILE: app/Http/Resources/MapGroupResource.php ================================================ resource; return $this->entity([ 'map_id' => $model->map_id, 'name' => $model->name, 'parent_id' => $model->parent_id, 'position' => (int) $model->position, 'visibility_id' => $model->visibility_id, 'is_shown' => (bool) $model->is_shown, ]); } } ================================================ FILE: app/Http/Resources/MapLayerResource.php ================================================ resource; return $this->entity([ 'map_id' => (int) $model->map_id, 'name' => $model->name, 'position' => (int) $model->position, 'width' => (int) $model->width, 'height' => (int) $model->height, 'visibility_id' => $model->visibility_id, 'type_id' => (bool) $model->type_id, 'type' => (string) $model->typeName(), ]); } } ================================================ FILE: app/Http/Resources/MapMarkerResource.php ================================================ resource; return $this->entity([ 'map_id' => $model->map_id, 'name' => $model->name, 'entity_id' => $model->entity_id, 'longitude' => $model->longitude, 'latitude' => $model->latitude, 'colour' => $model->colour, 'font_colour' => $model->font_colour, 'shape_id' => $model->shape_id, 'size_id' => $model->size_id, 'pin_size' => $model->pin_size, 'icon' => $model->icon, 'custom_icon' => $model->custom_icon, 'custom_shape' => $model->custom_shape, 'is_draggable' => (bool) $model->is_draggable, 'is_popupless' => (bool) $model->is_popupless, 'opacity' => $model->opacity, 'circle_radius' => $model->circle_radius, 'polygon_style' => $model->polygon_style, 'visibility_id' => $model->visibility_id, 'css' => $model->css, ]); } } ================================================ FILE: app/Http/Resources/MapResource.php ================================================ resource; return $this->entity([ 'height' => $model->height, 'width' => $model->width, 'grid' => $model->grid, 'min_zoom' => $model->minZoom(), 'max_zoom' => $model->maxZoom(), 'initial_zoom' => $model->initialZoom(), 'center_marker_id' => $model->center_marker_id, 'center_x' => $model->center_x, 'center_y' => $model->center_y, 'layers' => MapLayerResource::collection($model->layers), 'groups' => MapGroupResource::collection($model->groups), 'is_real' => (bool) $model->is_real, 'config' => $model->config, ]); } } ================================================ FILE: app/Http/Resources/ModelResource.php ================================================ resource; $merged = [ 'id' => $model->id, 'is_private' => (bool) $model->is_private, 'created_at' => $model->created_at, 'created_by' => $model->created_by, 'updated_at' => $model->updated_at, 'updated_by' => $model->updated_by, ]; $final = array_merge($prepared, $merged); ksort($final); return $final; } } ================================================ FILE: app/Http/Resources/NoteResource.php ================================================ resource; return $this->entity([ ]); } } ================================================ FILE: app/Http/Resources/OrganisationMemberResource.php ================================================ resource; return $this->entity([ 'role' => $model->role, 'organisation_id' => $model->organisation_id, 'character_id' => $model->character_id, 'pin_id' => $model->pin_id, 'status_id' => $model->status_id, 'parent_id' => $model->parent_id, ]); } } ================================================ FILE: app/Http/Resources/OrganisationResource.php ================================================ resource; $locationIds = $model->entity->locations->pluck('id'); return $this->entity([ 'members' => OrganisationMemberResource::collection($model->members()->has('character')->with('character')->get()), 'locations' => $locationIds, ]); } } ================================================ FILE: app/Http/Resources/PostLayoutResource.php ================================================ resource; return [ 'id' => $model->id, 'code' => $model->code, 'entity_type_id' => $model->entity_type_id, 'config' => $model->config, ]; } } ================================================ FILE: app/Http/Resources/PostPermissionResource.php ================================================ */ public function toArray(Request $request): array { /** @var PostPermission $model */ $model = $this->resource; return [ 'user_id' => $model->user_id, 'role_id' => $model->role_id, 'permission' => $model->permission, 'permission-text' => $model->permText(), ]; } } ================================================ FILE: app/Http/Resources/PostResource.php ================================================ resource; return $this->onEntity([ 'name' => $model->name, 'visibility_id' => (int) $model->visibility_id->value, 'entry' => $model->entry, 'entry_parsed' => $model->parsedEntry(), // 'is_pinned' => (bool) $this->is_pinned, 'position' => $model->position, 'settings' => $model->settings, 'permissions' => PostPermissionResource::collection($model->permissions), 'layout_id' => $model->layout_id, 'is_template' => $model->isTemplate(), 'tags' => $model->tags()->pluck('tags.id')->toArray(), 'calendar_id' => $model->calendarDate?->calendar_id, 'calendar_year' => $model->calendarDate?->year, 'calendar_month' => $model->calendarDate?->month, 'calendar_day' => $model->calendarDate?->day, 'calendar_reminder_length' => $model->calendarDate?->length, ]); } } ================================================ FILE: app/Http/Resources/ProfileResource.php ================================================ resource; return [ 'id' => $model->id, 'name' => $model->name, 'avatar' => $model->hasAvatar() ? Img::resetCrop()->url($model->avatar) : null, 'avatar_thumb' => $model->hasAvatar() ? $model->getAvatarUrl() : null, 'locale' => $model->locale, 'timezone' => $model->timezone, 'date_format' => $model->dateformat, 'default_pagination' => $model->pagination, 'last_campaign_id' => $model->last_campaign_id, 'is_patreon' => $model->isSubscriber(), 'is_subscriber' => $model->isSubscriber(), 'rate_limit' => $model->rate_limit, ]; } } ================================================ FILE: app/Http/Resources/Public/CampaignResource.php ================================================ resource; return [ 'id' => $campaign->id, 'thumb' => $campaign->image ? $campaign->thumbnail(320, 240) : 'https://th.kanka.io/zzKcBpijSBvm4rPWdzRpI82pTNQ=/320x240/smart/src/app/backgrounds/mountain-background-medium.jpg', 'name' => $campaign->name, 'justify' => $campaign->spotlight?->url, 'link' => route('dashboard', $campaign), 'followers' => Number::format($campaign->follower()), 'entities' => Number::format($campaign->visible_entity_count), 'locale' => $campaign->locale, 'system' => $campaign->getSystems(), 'is_open' => $campaign->isOpen(), 'is_prioritised' => (bool) $campaign->is_prioritised, ]; } } ================================================ FILE: app/Http/Resources/QuestElementResource.php ================================================ resource; return $this->entity([ 'entity_id' => $model->entity_id, 'name' => $model->name, 'entry' => $model->entry, 'entry_parsed' => ! empty($model->entry) ? $model->parsedEntry() : null, 'copy_entity_entry' => (bool) $model->copy_entity_entry, 'colour' => $model->colour, 'role' => $model->role, 'visibility_id' => $model->visibility_id, ]); } } ================================================ FILE: app/Http/Resources/QuestResource.php ================================================ resource; return $this->entity([ 'date' => $model->date, 'instigator_id' => $model->instigator_id, 'location_id' => $model->location_id, 'calendar_id' => $model->entity->calendarDate?->calendar_id, 'calendar_year' => $model->entity->calendarDate?->year, 'calendar_month' => $model->entity->calendarDate?->month, 'calendar_day' => $model->entity->calendarDate?->day, 'calendar_reminder_length' => $model->entity->calendarDate?->length, 'elements_count' => $model->elements->count(), 'elements' => QuestElementResource::collection($model->elements), ]); } } ================================================ FILE: app/Http/Resources/RaceResource.php ================================================ resource; $locationIds = $model->entity->locations->pluck('id'); return $this->entity([ 'locations' => $locationIds, ]); } } ================================================ FILE: app/Http/Resources/RelationResource.php ================================================ resource; return [ 'id' => $model->id, 'owner_id' => $model->owner_id, 'target_id' => $model->target_id, 'relation' => $model->relation, 'attitude' => $model->attitude, 'colour' => $model->colour, // 'is_private' => (bool) $this->is_private, 'visibility_id' => $model->visibility_id, 'is_star' => (bool) $model->isPinned(), 'is_pinned' => (bool) $model->isPinned(), 'mirror_id' => $model->mirror_id, 'created_at' => $model->created_at, 'created_by' => $model->created_by, 'updated_at' => $model->updated_at, ]; } } ================================================ FILE: app/Http/Resources/ReminderResource.php ================================================ resource; return [ 'id' => $model->id, 'remindable_id' => $model->remindable_id, 'remindable_type' => $model->remindable_type, 'calendar_id' => $model->calendar_id, 'date' => $model->date(), 'day' => $model->day, 'month' => $model->month, 'year' => $model->year, 'length' => $model->length, 'comment' => $model->comment, 'is_recurring' => (bool) $model->is_recurring, 'recurring_until' => $model->recurring_until, 'recurring_periodicity' => $model->recurring_periodicity, 'colour' => $model->colour, 'type_id' => $model->type_id, 'visibility_id' => $model->visibility_id, 'created_at' => $model->created_at, 'created_by' => $model->created_by, 'updated_at' => $model->updated_at, 'updated_by' => $model->updated_by, ]; } } ================================================ FILE: app/Http/Resources/TagResource.php ================================================ resource; return $this->entity([ 'colour' => $model->colour, 'icon' => $model->icon, 'entities' => $model->entities()->distinct()->pluck('entities.id')->toArray(), 'is_auto_applied' => (bool) $model->is_auto_applied, 'is_hidden' => (bool) $model->is_hidden, ]); } } ================================================ FILE: app/Http/Resources/TimelineElementResource.php ================================================ resource; return [ 'id' => $model->id, 'era_id' => $model->era_id, 'timeline_id' => $model->timeline_id, 'entity_id' => $model->entity_id, 'name' => $model->name, 'entry' => $model->entry, 'entry_parsed' => $model->parsedEntry(), 'date' => $model->date, 'colour' => $model->colour, 'position' => $model->position, 'visibility_id' => $model->visibility_id, 'icon' => $model->icon, 'is_collapsed' => $model->collapsed(), 'use_entity_entry' => $model->use_entity_entry, ]; } } ================================================ FILE: app/Http/Resources/TimelineEraResource.php ================================================ resource; return [ 'id' => $era->id, 'name' => $era->name, 'abbreviation' => $era->abbreviation, 'start_year' => $era->start_year, 'entry' => $era->entry, 'entry_parsed' => $era->parsedEntry(), 'end_year' => $era->end_year, 'elements' => $era->elements ? TimelineElementResource::collection($era->elements) : [], 'is_collapsed' => $era->collapsed(), 'position' => $era->position, ]; } } ================================================ FILE: app/Http/Resources/TimelineResource.php ================================================ resource; return $this->entity([ 'eras' => TimelineEraResource::collection($model->eras()->with('elements')->get()), 'revert_order' => $model->revert_order, ]); } } ================================================ FILE: app/Http/Resources/UserResource.php ================================================ resource; $data = [ 'id' => $user->id, 'name' => $user->name, 'avatar' => $user->hasAvatar() ? $user->getAvatarUrl() : null, 'password' => 'hihi', ]; $campaign = CampaignLocalization::getCampaign(); if ($campaign) { $roles = $user->campaignRoles->where('campaign_id', $campaign->id); $data['role'] = CampaignUserRoleResource::collection($roles); } return $data; } } ================================================ FILE: app/Http/Resources/VisibilityResource.php ================================================ resource; return [ 'id' => $model->id, 'code' => $model->code, ]; } } ================================================ FILE: app/Http/Resources/VoteResource.php ================================================ */ public function toArray(Request $request): array { /** @var CommunityVote $model */ $model = $this->resource; $data = [ 'id' => $model->id, 'name' => $model->name, 'excerpt' => $model->excerpt, 'content' => $model->content, 'options' => $model->options(), 'voting' => $model->isVoting(), 'slug' => $model->getSlug(), 'published' => $model->visible_at->isoFormat('MMMM D, Y'), 'until' => $model->published_at->isoFormat('MMMM D, Y'), 'valid_ballots' => $model->ballots->count(), ]; $ballots = []; foreach ($model->options() as $key => $text) { $ballots[$key] = $model->ballotWidth($key); } $data['ballots'] = $ballots; return $data; } } ================================================ FILE: app/Http/Resources/Web/EntityResource.php ================================================ */ public function toArray(Request $request): array { /** @var Entity $entity */ $entity = $this->resource; if (! isset($this->campaign)) { dd($this); } return [ 'id' => $entity->id, 'name' => $entity->name, 'image' => Avatar::entity($entity)->size(192)->fallback()->thumbnail(), 'link' => route('entities.show', [$this->campaign, $entity]), 'tooltip' => route('entities.tooltip', [$this->campaign, $entity]), ]; } } ================================================ FILE: app/Http/Resources/Whiteboards/EntityResource.php ================================================ */ public function toArray(Request $request): array { /** @var Entity $entity */ $entity = $this->resource; return [ 'id' => $entity->id, 'name' => $entity->name, 'link' => $entity->url(), 'preview' => route('entities.tooltip', [$this->campaign, $entity]), ]; } } ================================================ FILE: app/Http/Resources/Whiteboards/ShapeResource.php ================================================ */ public function toArray(Request $request): array { /** @var WhiteboardShape $shape */ $shape = $this->resource; $campaign = $shape->whiteboard->campaign; $whiteboard = $shape->whiteboard; $data = [ 'id' => $shape->id, 'type' => $shape->type, 'x' => $shape->x / self::SCALE, 'y' => $shape->y / self::SCALE, 'width' => $shape->width / self::SCALE, 'height' => $shape->height / self::SCALE, 'rotation' => $shape->rotation / self::ROT_SCALE, 'is_locked' => (bool) $shape->is_locked, 'z_index' => $shape->z_index, 'fill' => Arr::get($shape->shape, 'fill'), 'urls' => [ 'edit' => route('whiteboards.shapes.update', [$campaign, $whiteboard, $shape]), 'delete' => route('whiteboards.shapes.delete', [$campaign, $whiteboard, $shape]), 'stroke' => route('whiteboards.shapes.stroke', [$campaign, $whiteboard, $shape]), ], ]; if ($shape->isCircle()) { $data['radius'] = $shape->width / self::SCALE / 2; } if ($shape->isText()) { $data['text'] = Arr::get($shape->shape, 'text'); $data['fontSize'] = Arr::get($shape->shape, 'fontSize'); } if ($shape->isImage()) { $data['uuid'] = Arr::get($shape->shape, 'uuid'); } if ($shape->isEntity()) { $data['entity'] = Arr::get($shape->shape, 'entity_id'); } if ($shape->isDrawing()) { $data['children'] = StrokeResource::collection($shape->strokes); } return $data; } } ================================================ FILE: app/Http/Resources/Whiteboards/StrokeResource.php ================================================ */ public function toArray(Request $request): array { /** @var WhiteboardStroke $stroke */ $stroke = $this->resource; return [ 'id' => $stroke->id, 'fill' => $stroke->color, 'points' => $stroke->unpack(), 'strokeWidth' => $stroke->width, ]; } } ================================================ FILE: app/Http/Validators/HashValidator.php ================================================ calendar = $calendar; } /** * Execute the job. */ public function handle(): void { $model = new Reminder; DB::update( 'UPDATE ' . $model->getTable() . ' SET elapsed = NULL ' . 'WHERE calendar_id = \'' . $this->calendar->id . '\'' ); } } ================================================ FILE: app/Jobs/CampaignRoleUserJob.php ================================================ campaignRoleUser) || empty($this->campaignRoleUser->campaignRole)) { Log::info('no role found', [$this->campaignRoleUser]); return; } $notification = new Header( 'campaign.role.' . ($this->new ? 'add' : 'remove'), 'user', 'success', [ 'role' => e($this->campaignRoleUser->campaignRole->name), 'campaign' => e($this->campaignRoleUser->campaignRole->campaign->name), 'link' => route('dashboard', ['campaign' => $this->campaignRoleUser->campaignRole->campaign->id]), ] ); $this->campaignRoleUser->user->notify($notification); } } ================================================ FILE: app/Jobs/Campaigns/Delete.php ================================================ campaign = $campaign->id; } /** * Execute the job. * * @return void */ public function handle() { /** @var Campaign|null $campaign */ $campaign = Campaign::find($this->campaign); if (! $campaign) { return; } $campaign->forceDelete(); } } ================================================ FILE: app/Jobs/Campaigns/Export.php ================================================ campaignId = $campaign->id; $this->userId = $user->id; $this->campaignExportId = $campaignExport->id; } /** * Execute the job * * @throws Exception */ public function handle() { Log::info('Campaign export', ['init', 'id' => $this->campaignExportId]); $campaignExport = CampaignExport::find($this->campaignExportId); if (! $campaignExport) { Log::info('Campaign export', ['empty', 'id' => $this->campaignExportId]); return 0; } Log::info('Campaign export', ['running', 'id' => $this->campaignExportId]); $campaignExport->update(['status' => CampaignExportStatus::running]); /** @var Campaign|null $campaign */ $campaign = Campaign::find($this->campaignId); if (! $campaign) { return 0; } /** @var User|null $user */ $user = User::find($this->userId); if (! $user) { return 0; } $isMarkdown = false; if ($campaignExport->type == 2) { $isMarkdown = true; } /** @var ExportService $service */ $service = app()->make(ExportService::class); $service ->markdown($isMarkdown) ->user($user) ->campaign($campaign) ->log($campaignExport) ->export(); // Don't delete in "sync" mode as there is no delay. $queue = config('queue.default'); if ($queue !== 'sync') { FileCleanup::dispatch($service->exportPath()) ->delay(now()->addHours(config('limits.campaigns.export'))); } return 1; } public function failed(Throwable $exception) { $campaignExport = CampaignExport::find($this->campaignExportId); if (! $campaignExport) { return; } $campaignExport->update(['status' => CampaignExportStatus::failed]); // Set the campaign export date to null so that the user can try again. // If it failed once, trying again won't help, but this might motivate // them to report the error. /** @var Campaign|null $campaign */ $campaign = Campaign::find($this->campaignId); if (! $campaign) { return; } /** @var User|null $user */ $user = User::find($this->userId); if (! $user) { return; } /** @var ExportService $service */ $service = app()->make(ExportService::class); $service ->user($user) ->campaign($campaign) ->fail(); // Sentry will handle the rest } } ================================================ FILE: app/Jobs/Campaigns/Hide.php ================================================ campaign = $campaign->id; } /** * Execute the job. * * @return void */ public function handle() { /** @var HideService $service */ $service = app()->make(HideService::class); $campaign = Campaign::find($this->campaign); if (! $campaign) { // Campaign wasn't found Log::warning('Hide Campaign: unknown #' . $this->campaign . '.'); } $service->campaign($campaign)->notify(); Log::info('Campaign #' . $this->campaign . ' hidden (job)'); } } ================================================ FILE: app/Jobs/Campaigns/Import.php ================================================ jobID = $campaignImport->id; } /** * Execute the job * * @throws Exception */ public function handle() { Log::info('Campaign import', ['init', 'id' => $this->jobID]); /** @var CampaignImport $job */ $job = CampaignImport::find($this->jobID); if (! $job) { Log::info('Campaign import', ['empty', 'id' => $this->jobID]); return 0; } if (! $job->campaign || ! $job->user) { Log::info('Campaign import', ['empty_campaign_or_user', 'id' => $this->jobID]); return 0; } Log::info('Campaign import', ['running', 'id' => $this->jobID]); $job->update(['status_id' => CampaignImportStatus::RUNNING]); if ($job->isCsv()) { Log::info('Campaign import', ['csv', 'id' => $this->jobID]); $service = app()->make(CsvValidatorService::class) ->job($job) ->run(); } else { Log::info('Campaign import', ['backup import', 'id' => $this->jobID]); /** @var ImportService $service */ $service = app()->make(ImportService::class); $service ->job($job) ->run(); } return 1; } public function failed(Throwable $exception) { $job = CampaignImport::find($this->jobID); if (! $job) { Log::info('Campaign import', ['empty', 'id' => $this->jobID]); return 0; } /** @var ImportService $service */ $service = app()->make(ImportService::class); $service ->job($job) ->fail($exception); } } ================================================ FILE: app/Jobs/Campaigns/ImportCsv.php ================================================ jobID = $campaignImport->id; $this->userId = $userId; $this->columnMap = $columnMap; $this->tagIds = $tagIds; $this->entityTypeId = $entityTypeId; $this->appearances = $appearances; $this->personalities = $personalities; } /** * Execute the job * * @throws Exception */ public function handle() { Log::info('CSV campaign import', ['init', 'id' => $this->jobID]); /** @var CampaignImport $job */ $job = CampaignImport::find($this->jobID); $entityType = EntityType::inCampaign($job->campaign)->where('id', $this->entityTypeId)->first(); $logs = $job->logs; if (! $job) { Log::info('CSV campaign import', ['empty', 'id' => $this->jobID]); return 0; } if (! $job->campaign || ! $job->user || ! $entityType) { $logs[] = 'Missing campaign, user or entity type'; Log::info('Campaign import', ['empty_campaign_or_user', 'id' => $this->jobID]); $job->update(['status_id' => CampaignImportStatus::FAILED, 'logs' => $logs]); return 0; } $logs[] = 'Running csv import'; Log::info('CSV campaign import', ['running', 'id' => $this->jobID]); $job->update(['status_id' => CampaignImportStatus::RUNNING, 'logs' => $logs]); $service = app()->make(CsvImportService::class) ->job($job) ->campaign($job->campaign) ->user($job->user) ->entityType($entityType) ->fieldMap($this->columnMap) ->traits($this->appearances, $this->personalities) ->tags($this->tagIds) ->run(); return 1; } public function failed(Throwable $exception) { $job = CampaignImport::find($this->jobID); if (! $job) { Log::info('Campaign import', ['empty', 'id' => $this->jobID]); return 0; } /** @var CsvImportService $service */ $service = app()->make(CsvImportService::class); $service ->job($job) ->fail($exception); } } ================================================ FILE: app/Jobs/Campaigns/NotifyAdmins.php ================================================ campaign = $campaign->id; $this->key = $key; $this->icon = $icon; $this->colour = $colour; $this->params = $params; } public function handle() { /** @var NotificationService $service */ $service = app()->make(NotificationService::class); /** @var Campaign|null $campaign */ $campaign = Campaign::find($this->campaign); if (! $campaign) { Log::warning('Notify campaign: unknown #' . $this->campaign . '.'); } $service ->campaign($campaign) ->notify($this->key, $this->icon, $this->colour, $this->params); } } ================================================ FILE: app/Jobs/Campaigns/Populate.php ================================================ campaign = $campaign->id; } /** * Execute the job. */ public function handle(): void { /** @var Campaign|null $campaign */ $campaign = Campaign::find($this->campaign); if (! $campaign) { return; } /** @var StarterService $service */ $service = app()->make(StarterService::class); $service ->campaign($campaign) ->bind() ->create(); } } ================================================ FILE: app/Jobs/ChunkMapJob.php ================================================ mapID = $mapID; $this->onConnection('heavy'); } /** * Execute the job. * * @return void */ public function handle() { /** @var ChunkingService $service */ $service = app()->make(ChunkingService::class); /** @var ?Map $map */ $map = Map::find($this->mapID); if (empty($map)) { Log::error('Chunking map: unknown map #' . $this->mapID); return; } $now = Carbon::now(); Log::info('Chunking map #' . $this->mapID); try { $service ->map($map) ->chunk(); $elapsed = Carbon::now()->diffInMinutes($now); Log::info('Chunked map #' . $this->mapID . ' in ' . $elapsed . ' minutes.'); } catch (Exception $e) { throw $e; } } public function failed(Exception $exception) { /** @var ?Map $map */ $map = Map::find($this->mapID); if (empty($map)) { Log::error('No map #' . $this->mapID); return; } if ($map->chunkingError()) { Log::error('Already error #' . $this->mapID); return; } $map->chunking_status = Map::CHUNKING_ERROR; $map->saveQuietly(); Log::error('Saved error for #' . $this->mapID); throw $exception; } } ================================================ FILE: app/Jobs/Copyright/DeleteCampaignImage.php ================================================ deleteImage(); Log::info('Removed image from campaign #' . $this->campaignId . ' for copyright reasons'); } private function deleteImage() { $campaign = Campaign::find($this->campaignId); if (empty($campaign) || ! (Storage::exists($campaign->image))) { // Image was deleted return; } Storage::delete($campaign->image); $campaign->image = null; $campaign->saveQuietly(); } } ================================================ FILE: app/Jobs/Copyright/DeleteEntityImage.php ================================================ entityId = $data['entity']; $this->removeHeader = $data['remove_header']; $this->removeImage = $data['remove_image']; } /** * Execute the job. * * @return void */ public function handle() { // Most basic setup, the child has an image, otherwise, might have a gallery image if ($this->removeImage) { $this->deleteImage('image'); } if ($this->removeHeader) { $this->deleteImage('header_image'); } Log::info('Removed image or header from entity #' . $this->entityId . ' for copyright reasons'); } private function deleteImage(string $field) { /** @var Entity $entity */ $entity = Entity::find($this->entityId); if (empty($entity) || empty($entity->child)) { // Entity was deleted return; } /** @var ImageRemoveService $service */ $service = app()->make(ImageRemoveService::class); $campaign = Campaign::find($entity->campaign_id); $service->campaign($campaign)->entity($entity)->notify(); if ($campaign->superboosted() && $entity->image && $field == 'image') { $entity->image->delete(); } elseif (! empty($entity->image_path) && $field == 'image') { Images::model($entity)->field($field)->cleanup(); $entity->updateQuietly(['image_path' => '']); } if ($campaign->superboosted() && $entity->header && $field == 'header_image') { $entity->header->delete(); } elseif (! empty($entity->header_image) && $field == 'header_image') { Images::model($entity)->field($field)->cleanup(); $entity->update(['header_image' => $entity->header_image]); } // Whenever an entity is updated, we always want to re-calculate the cached image. Avatar::entity($entity)->forget(); } } ================================================ FILE: app/Jobs/Copyright/DeleteUserImage.php ================================================ deleteImage(); Log::info('Removed image from user #' . $this->userId . ' for copyright reasons'); } private function deleteImage() { $user = User::where('id', $this->userId)->first(); if (empty($user) || ! (Storage::exists($user->avatar))) { // Image was deleted return; } Storage::delete($user->avatar); $user->avatar = null; $user->saveQuietly(); } } ================================================ FILE: app/Jobs/DeletedCampaignCleanupJob.php ================================================ campaignId = $campaign->id; } /** * Execute the job. * * @return void */ public function handle() { if (empty($this->campaignId)) { // We dont want to delete everything. return; } // Delete leftover images if (Storage::has('/w/' . $this->campaignId)) { Storage::deleteDirectory('/w/' . $this->campaignId); } if (Storage::has('/campaigns/' . $this->campaignId)) { Storage::deleteDirectory('/campaigns/' . $this->campaignId); } // Cleanup deleted campaign entries from meilisearch $client = new Client(config('scout.meilisearch.host'), config('scout.meilisearch.key')); $client->getKeys(); $client->index('entities')->deleteDocuments(['filter' => 'campaign_id = ' . $this->campaignId]); } } ================================================ FILE: app/Jobs/Discord/AdminInviteJob.php ================================================ make(NotificationService::class); $service ->webhook($webhook) ->title('New support request') ->content('A new admin invite has been created') ->user($this->adminInvite->user) ->description('One of our users is in need of help and has generated an admin invite') ->url('https://admin.kanka.io/invites/' . $this->adminInvite->id) ->send(); } } ================================================ FILE: app/Jobs/Discord/ReportJob.php ================================================ name = $name; $this->content = $content; } /** * Execute the job. */ public function handle(): void { $webhook = config('discord.webhooks.admin'); if (empty($webhook)) { return; } Log::info('Jobs/Discord/ReportJob', ['start', 'name' => $this->name]); /** @var NotificationService $service */ $service = app()->make(NotificationService::class); $messageData = $service ->webhook(config('discord.webhooks.admin')) ->content('') ->description($this->content) ->title($this->name) ->send() ->json(); } } ================================================ FILE: app/Jobs/Discord/SendNewFeature.php ================================================ feature = $feature; } /** * Execute the job. */ public function handle(): void { /** @var Feature|null $feature */ $feature = Feature::find($this->feature); if (empty($feature)) { // Feature wasn't found Log::warning('Jobs/Discord/SendNewFeature', ['unknown feature', 'feature' => $this->feature]); return; } $webhook = config('discord.webhooks.features'); if (empty($webhook)) { Log::warning('Jobs/Discord/SendNewFeature', ['no webhook defined']); return; } Log::info('Jobs/Discord/SendNewFeature', ['start', 'feature' => $feature->id]); /** @var NotificationService $service */ $service = app()->make(NotificationService::class); $messageData = $service ->webhook(config('discord.webhooks.features')) ->title($feature->name) ->content('A new idea has been approved and can be voted on!') ->user($feature->user) ->description($feature->description) ->url(route('roadmap', ['status' => 'ideas', 'idea' => $feature->id])) ->send() ->json(); $feature->message_id = $messageData['id']; $feature->saveQuietly(); } } ================================================ FILE: app/Jobs/Discord/UpdateFeatureUpvotes.php ================================================ feature = $feature; } /** * Execute the job. */ public function handle(): void { /** @var Feature|null $feature */ $feature = Feature::find($this->feature); if (empty($feature) || empty($feature->message_id)) { // Feature wasn't found Log::warning('Jobs/Discord/UpdateFeatureUpvotes', ['unknown feature or no message_id', 'feature' => $this->feature]); return; } $webhook = config('discord.webhooks.features'); if (empty($webhook)) { Log::warning('Jobs/Discord/UpdateFeatureUpvotes', ['no webhook defined']); return; } Log::info('Jobs/Discord/UpdateFeatureUpvotes', ['start', 'feature' => $feature->id]); $content = 'A new idea has been approved and can be voted on! :arrow_up_small: ' . $feature->upvote_count . '.'; // Construct the message edit URL $editUrl = config('discord.webhooks.features') . "/messages/{$feature->message_id}"; Http::patch($editUrl, [ 'content' => $content, ]); } } ================================================ FILE: app/Jobs/DiscordRoleJob.php ================================================ user = $user; $this->add = $add; } /** * Execute the job * * @throws Exception */ public function handle() { $this->discord = app()->make(DiscordService::class); try { if ($this->add) { $this->discord->user($this->user)->addRoles(); } else { $this->discord->user($this->user)->removeRoles(); } } catch (Exception $e) { Log::error('DiscordRoleJob:: ' . $e->getMessage()); // Silence errors and ignore } } } ================================================ FILE: app/Jobs/Emails/EmailChangeJob.php ================================================ user = $user->id; $this->email = $email; } /** * @throws BindingResolutionException */ public function handle() { // User deleted their account already? Sure thing /** @var User|null $user */ $user = User::find($this->user); if (empty($user)) { return; } // Send an email to the user Mail::to($this->email) ->locale($user->locale) ->send( new EmailChangeMail($user) ); } } ================================================ FILE: app/Jobs/Emails/MailSettingsChangeJob.php ================================================ userId = $user->id; $this->new = $new; } /** * @throws BindingResolutionException */ public function handle() { // User deleted their account already? Sure thing /** @var ?User $user */ $user = User::find($this->userId); if (empty($user)) { return; } /** @var NewsletterService $newsletter */ $newsletter = app()->make(NewsletterService::class); // If the user was subscribed and no longer desires anything, unsub them $wantsSomething = $user->hasNewsletter(); if ($newsletter->user($user)->isSubscribed() && ! $wantsSomething) { $newsletter->remove(); } elseif ($wantsSomething) { $options = [ 'releases' => (bool) $user->mail_release, 'new' => $this->new, ]; $newsletter->update($options); } } } ================================================ FILE: app/Jobs/Emails/Purge/FirstWarningJob.php ================================================ userId = $userId; } /** * Execute the job. */ public function handle(): void { /** @var User $user */ $user = User::find($this->userId); if (empty($user)) { return; } Log::info('PurgeFirstWarning', ['user' => $this->userId]); /** @var CampaignService $service */ $service = app()->make(CampaignService::class); $campaigns = $service->user($user)->flaggedCampaigns(); $user->log(UserAction::purgeWarningFirst); $target = app()->isProduction() ? $user->email : config('mail.from.address'); if (empty($target)) { return; } try { Mail::to($target) ->locale($user->locale ?? 'en-US') ->send( new FirstWarning($user, $campaigns) ); } catch (ServerException $e) { // Silence } catch (Exception $e) { // Something went wrong with mailgun, or the email is invalid. Silence these errors // to avoid spamming sentry. throw $e; } } } ================================================ FILE: app/Jobs/Emails/Purge/SecondWarningJob.php ================================================ userId = $userId; } /** * Execute the job. */ public function handle(): void { $user = User::find($this->userId); if (empty($user)) { return; } Log::info('PurgeFirstWarning', ['user' => $this->userId]); /** @var CampaignService $service */ $service = app()->make(CampaignService::class); $campaigns = $service->user($user)->flaggedCampaigns(); $user->log(UserAction::purgeWarningSecond); $target = app()->isProduction() ? $user->email : config('mail.from.address'); if (empty($target)) { return; } try { Mail::to($target) ->locale($user->locale ?? 'en-US') ->send( new SecondWarning($user, $campaigns) ); } catch (ServerException $e) { // Silence } catch (Exception $e) { // Something went wrong with mailgun, or the email is invalid. Silence these errors // to avoid spamming sentry. throw $e; } } } ================================================ FILE: app/Jobs/Emails/SubscriptionCancelEmailJob.php ================================================ cancellationId = $cancellation->id; } public function handle(): void { $cancellation = SubscriptionCancellation::find($this->cancellationId); if (empty($cancellation)) { return; } $user = $cancellation->user; if (empty($user)) { return; } // Send an email to the admins Mail::to('hello@kanka.io') ->send(new CancelledSubscriptionMail($cancellation)); // Send an email to the user Mail::to($user->email) ->send(new CancelledUserSubscriptionMail($cancellation)); $user->log(UserAction::subCancelManual); } } ================================================ FILE: app/Jobs/Emails/SubscriptionCreatedEmailJob.php ================================================ userId = $user->id; $this->new = $new; $this->period = $period; } public function handle() { // User deleted their account already? Sure thing $user = User::find($this->userId); if (empty($user)) { return; } if ($this->new) { // Send an email to the admins Mail::to('hello@kanka.io') ->send( new NewSubscriptionMail($user, $this->period) ); } } } ================================================ FILE: app/Jobs/Emails/SubscriptionDeletedEmailJob.php ================================================ userId = $user->id; } public function handle() { // User deleted their account already? Sure thing /** @var ?User $user */ $user = User::find($this->userId); if (empty($user)) { Log::warning('Subscription Deleted Email Job: unknown user id', ['userId' => $this->userId]); return; } // Don't notify if the user was banned if ($user->isBanned()) { Log::warning('Subscription Deleted Email Job: banned user id', ['userId' => $this->userId]); return; } $user->notify(new Header( 'subscriptions.deleted', 'far fa-credit-card', 'red' )); $user->log(UserAction::subCancelAuto); } } ================================================ FILE: app/Jobs/Emails/SubscriptionDowngradedEmailJob.php ================================================ userId = $user->id; $this->reason = $reason; $this->custom = $custom; } public function handle() { // User deleted their account already? Sure thing $user = User::find($this->userId); if (empty($user)) { return; } $reason = $this->reason; if ($reason == 'custom') { $reason = 'other'; } // Send an email to the admins Mail::to('hello@kanka.io') ->send( new DowngradedSubscriptionMail($user, $reason, $this->custom) ); } } ================================================ FILE: app/Jobs/Emails/SubscriptionFailedEmailJob.php ================================================ userId = $user->id; } public function handle() { // User deleted their account already? Sure thing $user = User::find($this->userId); if (empty($user)) { Log::warning('Subscription Failed Email Job: unknown user id', ['userId' => $this->userId]); return; } $user->notify(new Header( 'subscriptions.failed', 'far fa-credit-card', 'red' )); // Send an email to the admins /*Mail::to('hello@kanka.io') ->send( new FailedSubscriptionMail($user) );*/ // Send an email to the user Mail::to($user->email) // ->bcc('hello@kanka.io') ->send( new FailedUserSubscriptionMail($user) ); $user->log(UserAction::failedChargeEmail); } } ================================================ FILE: app/Jobs/Emails/Subscriptions/Admin/PaypalRenewedJob.php ================================================ user); if (! $user) { return; } Mail::to('hello@kanka.io') ->send( new PaypalRenewedMail($user) ); } } ================================================ FILE: app/Jobs/Emails/Subscriptions/Converted.php ================================================ userId = $user->id; } /** * Execute the job. */ public function handle(): void { $user = User::find($this->userId); if (empty($user)) { return; } // Guess the period based on the sub's end date $price = $user->subscription('kanka')->stripe_price; /** @var ?TierPrice $price */ $price = TierPrice::where('stripe_id', $price)->first(); Mail::to('hello@kanka.io') ->send( new ConvertedMail($user, $price->period) ); } } ================================================ FILE: app/Jobs/Emails/Subscriptions/EmailValidationJob.php ================================================ user = $user->id; $this->token = $token->id; } /** * @throws BindingResolutionException */ public function handle() { // Small check in case the user deleted their account before the queue could get to them /** @var User|null $user */ $user = User::find($this->user); if (empty($user)) { return; } $userValidation = UserValidation::find($this->token); $url = route('validation.email', ['userValidation' => $userValidation]); Mail::to($user->email) ->locale($user->locale) ->send( new ValidationEmail($user, $url) ); } } ================================================ FILE: app/Jobs/Emails/Subscriptions/ExpiringCardAlert.php ================================================ user = $user->id; } /** * @throws BindingResolutionException */ public function handle() { // User deleted their account already? Sure thing /** @var User|null $user */ $user = User::find($this->user); if (empty($user)) { return; } // Send an email to the user Mail::to($user->email) ->locale($user->locale) ->send( new ExpiringCardEmail($user) ); } } ================================================ FILE: app/Jobs/Emails/Subscriptions/PaypalExpiringAlert.php ================================================ userId = $user->id; } public function handle(): void { /** @var User|null $user */ $user = User::find($this->userId); if (empty($user)) { return; } Mail::to($user->email) ->locale($user->locale) ->send(new PaypalExpiringMail($user)); $user->log(UserAction::subPaypalExpiringWarning); } } ================================================ FILE: app/Jobs/Emails/Subscriptions/UpcomingYearlyAlert.php ================================================ user = $user->id; } /** * @throws BindingResolutionException */ public function handle() { // User deleted their account already? Sure thing /** @var User|null $user */ $user = User::find($this->user); if (empty($user)) { return; } // Send an email to the user Mail::to($user->email) ->locale($user->locale) ->send( new UpcomingYearlyEmail($user) ); $user->log(UserAction::yearlyRenewWarning); } } ================================================ FILE: app/Jobs/Emails/Subscriptions/WelcomeSubscriptionEmailJob.php ================================================ userId = $user->id; $this->tierId = $tier->id; } public function handle() { // User deleted their account already? Sure thing /** @var User|null $user */ $user = User::find($this->userId); if (empty($user)) { return; } $tier = Tier::find($this->tierId); Mail::to($user->email) ->send( new NewSubscriberMail($user, $tier) ); } } ================================================ FILE: app/Jobs/Emails/TrialAcceptedEmailJob.php ================================================ userId = $user->id; } public function handle() { // User deleted their account already? Sure thing $user = User::find($this->userId); if (empty($user)) { return; } // Send an email to the admins Mail::to('hello@kanka.io') ->send( new NewTrialAcceptedMail($user) ); } } ================================================ FILE: app/Jobs/Emails/WelcomeEmailJob.php ================================================ userId = $user->id; $this->language = $language; } /** * Handle the job */ public function handle() { $user = User::find($this->userId); // In a dev environment, it's possible that the queue wasn't running and // the user was deleted before we even get to send the welcome email. if (empty($user)) { return; } Log::info('WelcomeEmailJob', ['user' => $this->userId]); // try { Mail::to($user->email) ->locale($this->language) ->send( new WelcomeEmail($user) ); /*} catch (\GuzzleHttp\Exception\ServerException $e) { // Silence } catch (Exception $e) { // Something went wrong with mailgun, or the email is invalid. Silence these errors // to avoid spamming sentry. throw $e; }*/ } } ================================================ FILE: app/Jobs/EntityMappingJob.php ================================================ modelId = $model->id; $this->class = get_class($model); } /** * Execute the job. * * @return void */ public function handle() { // Get the model $model = $this->class::find($this->modelId); if (! $model) { return; } /** @var EntityMappingService $entityMappingService. */ $entityMappingService = app()->make(EntityMappingService::class); $entityMappingService->with($model)->silent()->map(); } public function failure() { // Sentry will handle this } } ================================================ FILE: app/Jobs/EntityUpdatedJob.php ================================================ entity = $entity; $this->dirty = $entity->getDirty(); } /** * Execute the job. * * @return void */ public function handle() { Log::info('EntityUpdateJob for entity #' . $this->entity->id); // Whenever the image is updated, clear the avatar cache if ($this->isDirty(['image_uuid', 'image_path'])) { Avatar::entity($this->entity)->forget(); } } public function failure() { // Sentry will handle this } /** * Determine if a specific field was changed on the entity when saving */ protected function isDirty(array $keys): bool { foreach ($this->dirty as $key => $val) { if (in_array($key, $keys)) { return true; } } return false; } } ================================================ FILE: app/Jobs/EntityWebhookJob.php ================================================ campaign = $entity->campaign; $this->user = $user; $this->action = $action; $this->entity = $entity; } /** * Execute the job. * * @return void */ public function handle() { Log::info('EntityWebhookJob for entity #' . $this->entity->id); if (! $this->campaign->premium()) { return; } /** @var WebhookService $webhookService */ $webhookService = app()->make(WebhookService::class); $webhookService->campaign($this->campaign)->user($this->user)->entity($this->entity)->process($this->action); } public function failure() { // Sentry will handle this } } ================================================ FILE: app/Jobs/FileCleanup.php ================================================ path = $path; } /** * Execute the job. */ public function handle(): void { if (Storage::exists($this->path)) { Storage::delete($this->path); } } } ================================================ FILE: app/Jobs/ResetCssCache.php ================================================ campaign = $campaign->id; } /** * Execute the job. * * @return void */ public function handle() { $campaign = Campaign::find($this->campaign); if (empty($campaign)) { return; } Cache::forget('campaign_' . $campaign->id . '_theme'); $campaign->touch(); Log::info('Campaign #' . $campaign->id . ' cleared css cache (job)'); } } ================================================ FILE: app/Jobs/Roadmap/DiscordFeatureJob.php ================================================ make(NotificationService::class); $isCancellation = ($this->feature->meta['from'] ?? null) === 'cancellation'; $content = $isCancellation ? '❗ Cancellation - A new idea has been submitted and needs approval.' : 'A new idea has been submitted and needs approval.'; $service ->webhook($webhook) ->title($this->feature->name) ->content($content) ->user($this->feature->user) ->description($this->feature->description) ->url('https://admin.kanka.io/features/' . $this->feature->id) ->send(); } } ================================================ FILE: app/Jobs/Spotlight/DiscordSpotlightJob.php ================================================ make(NotificationService::class); $service ->webhook($webhook) ->title('Spotlight application for ' . $this->spotlightContent->campaign->name) ->content('A campaign applied for a spotlight.') ->user($this->spotlightContent->creator) ->description(Str::limit(Arr::get($this->spotlightContent->content_json, 'time'), 250)) ->url('https://admin.kanka.io/spotlight-contents/' . $this->spotlightContent->id) ->send(); } } ================================================ FILE: app/Jobs/SubscriptionEndJob.php ================================================ userId = $user->id; $this->cancelled = $cancelled; $this->trial = $trial; } /** * A user has ended their subscription, either by cancelling or automatically by the script. * Let's send them a notification, update their discord roles, send an email, and update their user status */ public function handle() { /** @var ?User $user */ $user = User::find($this->userId); if (empty($user) || $this->userId == 27078 || $user->subscribed('kanka')) { // User deleted their account already or renewed their subscription. return; } // Cleanup the user $user->pledge = null; $settings = $user->settings; unset($settings['grandfathered_boost']); $user->settings = $settings; $user->saveQuietly(); // Cleanup the campaign boosts $boostService = app()->make('App\Services\Campaign\BoostService'); $unboostedCampaigns = []; /** @var CampaignBoost $boost */ foreach ($user->boosts()->with(['campaign'])->groupBy('campaign_id')->get() as $boost) { $boostService ->campaign($boost->campaign) ->user($user) ->unboost($boost); if (! in_array($boost->campaign_id, $unboostedCampaigns)) { AutoRemove::dispatch($boost->campaign, $user); $unboostedCampaigns[] = $boost->campaign_id; } } // Cleanup the subscriber role /** @var Role $role */ $role = Role::where('name', Pledge::ROLE)->first(); $user->roles()->detach($role->id); // Notify the user in app about the change $key = 'subscriptions.' . ($this->trial ? 'trial' : ($this->cancelled ? 'failed' : 'ended')); $user->notify( new Header( $key, 'fa-regular fa-gem', 'orange' ) ); // Lastly, cleanup any discord stuff /** @var DiscordService $discord */ $discord = app()->make('App\Services\DiscordService'); try { $discord->user($user)->removeRoles(); } catch (Exception $e) { Log::error('DiscordRoleJob:: ' . $e->getMessage()); // Silence errors and ignore } } } ================================================ FILE: app/Jobs/TestWebhookJob.php ================================================ campaignId = $campaign->id; $this->user = $user; $this->webhook = $webhook; } /** * Execute the job. * * @return void */ public function handle() { /** @var Campaign|null $campaign */ $campaign = Campaign::find($this->campaignId); /** @var WebhookService $webhookService */ $webhookService = app()->make(WebhookService::class); $webhookService->user($this->user)->campaign($campaign)->test($this->webhook); } public function failure() { // Sentry will handle this } } ================================================ FILE: app/Jobs/Users/AbandonedCart.php ================================================ user = $user->id; $this->tier = $tier->name; } /** * Execute the job. */ public function handle(): void { /** @var ?User $user */ $user = User::find($this->user); if (empty($user)) { Log::warning('Jobs/Users/AbandonedCart', ['unknown user', 'user' => $this->user]); return; } Log::info('Jobs/Users/AbandonedCart', ['start', 'user' => $this->user]); // If the user subbed, we don't do anything if (! $user->hasNewsletter() || $user->subscribed('kanka')) { return; } $fields = [ 'abandoned_cart' => now()->format('Y-m-d'), 'abandoned_package' => $this->tier, ]; /** @var NewsletterService $service */ $service = app()->make(NewsletterService::class); $options = [ 'releases' => true, 'new' => false, ]; // Log::warning('Jobs/Users/AbandonnedCard', ['fields' => $fields]); $service->user($user)->fields($fields)->update($options); } } ================================================ FILE: app/Jobs/Users/DeleteUser.php ================================================ user = $user->id; } /** * Execute the job. * * @return void */ public function handle() { /** @var User|null $user */ $user = User::find($this->user); if (empty($user)) { // User wasn't found Log::warning('Jobs/Users/DeleteUser', ['unknown user', 'user' => $this->user]); return; } Log::info('Jobs/Users/DeleteUser', ['start', 'user' => $user->id]); /** @var CleanupService $service */ // We don't use the model's observer, because in laravel 10, it's adding the observer for each loop in local dev, slowing everything down $service = app()->make(CleanupService::class); $service->user($user)->delete(); $user->delete(); } } ================================================ FILE: app/Jobs/Users/NewPassword.php ================================================ userId = $user->id; } /** * Execute the job. */ public function handle(): void { /** @var User|null $user */ $user = User::find($this->userId); if (empty($user)) { Log::warning('Jobs/Users/NewPassword', ['unknown user', 'user' => $this->userId]); return; } $target = app()->isProduction() ? $user->email : config('mail.from.address'); try { Mail::to($target) ->locale($user->locale ?? 'en-US') ->send( new \App\Mail\Users\NewPassword($user) ); } catch (ServerException $e) { // Silence } catch (Exception $e) { // Something went wrong with mailgun, or the email is invalid. Silence these errors // to avoid spamming sentry. throw $e; } } } ================================================ FILE: app/Jobs/Users/UnsubscribeUser.php ================================================ email = $email; } /** * Execute the job. * * @return void */ public function handle() { /** @var NewsletterService $service */ $service = app()->make(NewsletterService::class); $service->email($this->email)->delete(); Log::info('Newsletter', ['action' => 'delete', 'email' => $this->email]); } } ================================================ FILE: app/Jobs/Users/UnsyncDiscord.php ================================================ id = $app->id; } /** * Execute the job. */ public function handle(): void { $app = UserApp::find($this->id); if (empty($app)) { return; } $app->delete(); $app->user->notify( new Header( 'apps.discord.invalid', 'brands fa-discord', 'warning', ['route' => 'settings.apps'] ) ); } } ================================================ FILE: app/Jobs/Users/UpdateEmail.php ================================================ userId = $user->id; } /** * Execute the job. * * @return void */ public function handle() { /** @var User|null $user */ $user = User::find($this->userId); /** @var NewsletterService $newsletter */ $newsletter = app()->make(NewsletterService::class); $newsletter->user($user)->update(['email' => $user->email]); Log::info('Newsletter', ['action' => 'update', 'user' => $user->id]); } } ================================================ FILE: app/Listeners/Campaigns/Admins/Notify.php ================================================ $event->user->name, 'campaign' => $event->campaign->name, 'link' => route('dashboard', $event->campaign), ]; } elseif ($event instanceof UserJoined) { $params = [ 'user' => $event->user->name, 'campaign' => $event->campaign->name, 'link' => route('dashboard', $event->campaign), ]; } $this->service ->campaign($event->campaign) ->notify($key, $icon, $colour, $params); } } ================================================ FILE: app/Listeners/Campaigns/Applications/LogApplication.php ================================================ user->campaignLog( $event->campaign->id, 'applications', $action, [ 'user' => $event->application->user->name, 'id' => $event->application->user_id, ] ); } } ================================================ FILE: app/Listeners/Campaigns/Campaigns/LogCampaign.php ================================================ campaign->wasChanged('is_open')) { $event->user->campaignLog( $event->campaign->id, 'applications', 'switch', ['new' => $event->campaign->isOpen() ? 'open' : 'closed'] ); } if ($event->campaign->wasChanged('visibility_id')) { $event->user->campaignLog( $event->campaign->id, 'visibility', 'switch', ['new' => $event->campaign->isPublic() ? 'public' : 'private'] ); } } } ================================================ FILE: app/Listeners/Campaigns/ClearCampaignCache.php ================================================ campaignRoleUser->campaignRole->campaign ?? $event->campaignRole->campaign ?? $event->campaign ?? $event->campaignDashboard->campaign ?? null; CampaignCache::campaign($campaign)->clear(); } } ================================================ FILE: app/Listeners/Campaigns/ClearCampaignThemeCache.php ================================================ campaignStyle->campaign ?? $event->campaign ?? null; CampaignCache::campaign($campaign)->clearTheme(); } } ================================================ FILE: app/Listeners/Campaigns/ClearCampaignUsersSaved.php ================================================ campaign->wasChanged(['visibility_id', 'name', 'image'])) { foreach ($event->campaign->users as $member) { UserCache::user($member)->clear(); } foreach ($event->campaign->followers as $follower) { UserCache::user($follower)->clear(); } } } } ================================================ FILE: app/Listeners/Campaigns/Dashboards/LogDashboard.php ================================================ 'created', $event instanceof DashboardUpdated => 'updated', $event instanceof DashboardDeleted => 'deleted', }; $event->user->campaignLog( $event->campaignDashboard->campaign_id, 'dashboards', $action, [ 'name' => $event->campaignDashboard->name, 'id' => $event->campaignDashboard->id, ] ); } } ================================================ FILE: app/Listeners/Campaigns/EntityTypes/LogEntityType.php ================================================ entityType->campaign_id) && ! isset($event->campaign)) { return; } $action = match (true) { $event instanceof EntityTypeCreated => 'created', $event instanceof EntityTypeUpdated => 'updated', $event instanceof EntityTypeDeleted => 'deleted', $event instanceof EntityTypeToggled => 'toggled', }; if ($event instanceof EntityTypeUpdated && $event->entityType->wasChanged('is_enabled')) { $action = $event->entityType->is_enabled ? 'enabled' : 'disabled'; } elseif ($event instanceof EntityTypeToggled) { $action = $event->campaign->setting->{$event->entityType->pluralCode()} ? 'enabled' : 'disabled'; } $event->user->campaignLog( $event->entityType->campaign_id ?? $event->campaign->id, 'modules', $action, [ 'id' => $event->entityType->id, 'code' => $event->entityType->code, ] ); } } ================================================ FILE: app/Listeners/Campaigns/Exports/LogExport.php ================================================ user->campaignLog( $event->campaignExport->campaign_id, 'export', 'queued' ); } } ================================================ FILE: app/Listeners/Campaigns/Invites/LogInvite.php ================================================ user->campaignLog( $event->campaignInvite->campaign_id, 'invites', $action, [ 'id' => $event->campaignInvite->id, ] ); } } ================================================ FILE: app/Listeners/Campaigns/Members/LogMember.php ================================================ $event->campaignUser->user->name]; } elseif ($event instanceof UserJoined) { $params = ['invite' => $event->campaignInvite->id]; } $event->user->campaignLog( $event->campaign->id, 'members', $action, $params, ); } } ================================================ FILE: app/Listeners/Campaigns/Members/LogUserRoleChanged.php ================================================ campaignRoleUser->campaignRole)) { return; } $event->user->campaignLog( $event->campaignRoleUser->campaignRole->campaign_id, 'user-role', $action, [ 'user' => $event->campaignRoleUser->user->name, 'user_id' => $event->campaignRoleUser->user_id, 'role' => $event->campaignRoleUser->campaignRole->name, 'role_id' => $event->campaignRoleUser->campaign_role_id, ] ); } } ================================================ FILE: app/Listeners/Campaigns/Members/RunRoleUserJob.php ================================================ campaignRoleUser, $event instanceof RoleUserAdded); } } ================================================ FILE: app/Listeners/Campaigns/Plugins/ClearThemeCache.php ================================================ campaignPlugin->plugin->isTheme()) { CampaignCache::campaign($event->campaignPlugin->campaign)->clearTheme(); } } } ================================================ FILE: app/Listeners/Campaigns/Plugins/LogPlugin.php ================================================ campaignPlugin->wasChanged('is_active')) { $action = $event->campaignPlugin->is_active ? 'enabled' : 'disabled'; } if ($event->campaignPlugin->wasChanged('plugin_version_id')) { $action = 'updated'; } } if ($event instanceof PluginImported) { $action = 'imported'; } $event->user->campaignLog( $event->campaignPlugin->campaign_id, 'plugins', $action, [ 'name' => $event->campaignPlugin->plugin->name, 'id' => $event->campaignPlugin->id, 'plugin' => $event->campaignPlugin->plugin_id, ]); } } ================================================ FILE: app/Listeners/Campaigns/Roles/LogRole.php ================================================ 'created', $event instanceof RoleUpdated => 'updated', $event instanceof RoleDeleted => 'deleted', }; $event->user->campaignLog( $event->campaignRole->campaign_id, 'roles', $action, [ 'name' => $event->campaignRole->name, 'id' => $event->campaignRole->id, ] ); } } ================================================ FILE: app/Listeners/Campaigns/Sidebar/LogSidebar.php ================================================ campaign->wasChanged('ui_settings')) { return; } $action = match (true) { $event instanceof SidebarReset => 'reset', $event instanceof SidebarSaved => 'updated', }; $event->user->campaignLog( $event->campaign->id, 'sidebar', $action, ); } } ================================================ FILE: app/Listeners/Campaigns/Styles/ClearStylesCache.php ================================================ campaign)->clearStyles(); } } ================================================ FILE: app/Listeners/Campaigns/Styles/LogStyle.php ================================================ 'created', $event instanceof StyleUpdated => 'updated', $event instanceof StyleDeleted => 'deleted', }; if ($event instanceof StyleUpdated && $event->campaignStyle->wasChanged('is_enabled')) { $action = $event->campaignStyle->is_enabled ? 'enabled' : 'disabled'; } $event->user->campaignLog( $event->campaignStyle->campaign_id, 'styles', $action, [ 'name' => $event->campaignStyle->name, 'id' => $event->campaignStyle->id, ] ); } } ================================================ FILE: app/Listeners/Campaigns/Thumbnails/LogThumbnail.php ================================================ 'created', $event instanceof ThumbnailDeleted => 'deleted', }; $event->user->campaignLog( $event->campaign->id, 'thumbnails', $action, [ 'type' => $event->entityType->code, ] ); } } ================================================ FILE: app/Listeners/Campaigns/Thumbnails/LogThumbnails.php ================================================ user->campaignLog( $event->campaign->id, 'thumbnails', 'deleted', ); } } ================================================ FILE: app/Listeners/Campaigns/Webhooks/LogWebhook.php ================================================ 'created', $event instanceof WebhookUpdated => 'updated', $event instanceof WebhookDeleted => 'deleted', $event instanceof WebhookTested => 'tested', }; if ($event instanceof WebhookUpdated && $event->webhook->wasChanged('status')) { $action = $event->webhook->status ? 'enabled' : 'disabled'; } $event->user->campaignLog( $event->webhook->campaign_id, 'webhooks', $action, [ 'id' => $event->webhook->id, ] ); } } ================================================ FILE: app/Listeners/Entities/LogEntity.php ================================================ user?->campaignLog( $event->entity->campaign_id, 'recovery', 'entity', [ 'type' => $event->entity->entityType->code, 'name' => $event->entity->name, 'id' => $event->entity->id, ] ); } } ================================================ FILE: app/Listeners/Posts/LogPost.php ================================================ 'created', $event instanceof PostUpdated => 'updated', $event instanceof PostDeleted => 'deleted', $event instanceof PostRestored => 'restored', }; $actionId = match (true) { $event instanceof PostCreated => EntityLog::ACTION_CREATE_POST, $event instanceof PostUpdated => EntityLog::ACTION_UPDATE_POST, $event instanceof PostDeleted => EntityLog::ACTION_DELETE_POST, $event instanceof PostRestored => EntityLog::ACTION_RESTORE, }; $log = new EntityLog; $log->created_by = $event->user->id; $log->parent_id = $event->post->id; $log->parent_type = Post::class; $log->impersonated_by = Identity::getImpersonatorId(); $log->action = $actionId; $changes = $this->postLoggerService->post($event->post)->dirty(); if ($event instanceof PostUpdated && ! empty($changes)) { $log->changes = $changes; } $log->save(); // make a campaign admin log when restoring a post since it's an admin feature // not sure if this actually makes sense to have, since it's also available in the post logs // if ($event instanceof PostRestored) { // $event->user?->campaignLog( // $event->post->entity->campaign_id, // 'recovery', // 'post', // [ // 'name' => $event->post->name, // 'id' => $event->post->id, // ] // ); // } $event->user->log( UserAction::post, [ 'action' => $action, 'id' => $event->post->id, ] ); } } ================================================ FILE: app/Listeners/SendAdminInviteNotification.php ================================================ adminInvite); } } ================================================ FILE: app/Listeners/SendFeatureNotification.php ================================================ feature); } } ================================================ FILE: app/Listeners/SendSpotlightNotification.php ================================================ spotlightContent); } } ================================================ FILE: app/Listeners/UserEventSubscriber.php ================================================ user; // Log the user's login if (! $user) { Log::error('Missing user in login event'); return false; } $this->loginService ->user($user) ->logUserActivity() ->updateLastLoginTime() ->clearInactivityFlag() ->loadFlags(); // Update mailerlite for the login stuff if (! session()->has('first_login') && $user->hasNewsletter()) { MailSettingsChangeJob::dispatch($user); } // Process invite token or first login $inviteResult = $this->sessionService->user($user)->handleInviteToken(); if ($inviteResult !== null) { return $inviteResult; } $firstLoginResult = $this->sessionService->user($user)->handleFirstLogin(); if ($firstLoginResult !== null) { return $firstLoginResult; } return true; } /** * Handle user logout events. */ public function handleUserLogout(Logout $event) { // Log the activity if (! $event->user) { return; } if (! $event->user->isBanned()) { $event->user->log(UserAction::logout); } } public function handleUserRegistered(Registered $event) { // If the user has an invite-token, we don't want to do anything else if (session()->has('invite_token')) { return; } session()->put('first_login', true); } /** * Register the listeners for the subscriber. */ public function subscribe(Dispatcher $events): void { $events->listen( Login::class, [UserEventSubscriber::class, 'handleUserLogin'] ); $events->listen( Logout::class, [UserEventSubscriber::class, 'handleUserLogout'] ); $events->listen( Registered::class, [UserEventSubscriber::class, 'handleUserRegistered'] ); } } ================================================ FILE: app/Listeners/Users/ClearUserCache.php ================================================ campaignRoleUser->user ?? $event->application->user ?? $event->user ?? null; UserCache::user($user)->clear(); } } ================================================ FILE: app/Listeners/Users/SendEmailUpdate.php ================================================ user, $event->email); } } ================================================ FILE: app/Listeners/Users/Subscriptions/LogPremium.php ================================================ 'boosted', $event instanceof SuperBoost => 'superboosted', $event instanceof Upgrade => 'upgraded', $event instanceof Premium => 'premium', $event instanceof AutoRemove => 'auto-removed', $event instanceof Disable => 'disabled', }; $event->user->campaignLog( $event->campaign->id, 'premium', $action ); } } ================================================ FILE: app/Livewire/Campaigns/CsvImport.php ================================================ campaign = $campaign; UserCache::campaign($this->campaign); Avatar::campaign($this->campaign); CampaignCache::campaign($this->campaign); CampaignLocalization::forceCampaign($this->campaign); $this->tagLabel = Module::plural(config('entities.ids.tag'), __('entities.tags')); $this->import = $campaignImport; $entityTypeService = app(EntityTypeService::class); $excluded = [ config('entities.ids.bookmark'), config('entities.ids.whiteboard'), config('entities.ids.bookmark'), config('entities.ids.dice_roll'), config('entities.ids.attribute_template'), config('entities.ids.conversation'), config('entities.ids.calendar'), ]; $this->entityTypes = $entityTypeService ->campaign($campaign) ->exclude($excluded) ->toSelect(); $this->entityType = array_key_first($this->entityTypes); $csvValidatorService = app(CsvValidatorService::class); $this->headers = $csvValidatorService ->campaign($campaign) ->job($campaignImport) ->toSelect(); $this->preview = $csvValidatorService->preview(); } public function addPersonality() { $this->personalities[] = null; // Adds a new empty entry } public function addAppearance() { $this->appearances[] = null; // Adds a new empty entry } public function removePersonality($index) { unset($this->personalities[$index]); $this->personalities = array_values($this->personalities); } public function removeAppearance($index) { unset($this->appearances[$index]); $this->appearances = array_values($this->appearances); } public function selectEntity() { $this->type = EntityType::where('id', $this->entityType)->first(); $this->canAssign = true; $fields = app()->make(Entity::class)->getFillable(); if (! $this->type->isCustom()) { $fields = array_unique(array_merge($fields, $this->type->getMiscClass()->getFillable())); } $fields = array_values(array_filter($fields, fn ($field) => $this->isImportableField($field))); foreach ($fields as $field) { $this->fillableFields[$field] = $this->fieldName($field); } $this->fullColumns = $this->import->config['filled_columns']; } protected function isImportableField(string $field): bool { $excluded = [ 'is_template', 'is_attributes_private', 'slug', 'source', 'header_image', ]; if (str_ends_with($field, '_id') || str_ends_with($field, 'uuid')) { return false; } return ! in_array($field, $excluded); } public function fieldName(string $field): string { $keys = [ 'crud.fields.' . $field, 'fields.' . $field . '.label', $this->type->pluralCode() . '.fields.' . $field, ]; foreach ($keys as $key) { $translation = __($key); if ($translation !== $key) { return $translation; } } return $field; } public function updatedColumnMap($value, $key) { // If the selected value is empty or null if (blank($value)) { // Remove the key entirely from the array unset($this->columnMap[$key]); } } public function submit() { $tagIds = []; foreach ($this->tags as $tag) { $tagIds[] = $tag['id']; } $logs = $this->import->logs; $logs[] = 'Mapping form submitted'; $this->import->update(['status_id' => CampaignImportStatus::PROCESSING, 'logs' => $logs]); ImportCsv::dispatch($this->import, auth()->user()->id, $this->entityType, $this->columnMap, $tagIds, $this->appearances, $this->personalities)->onQueue('heavy'); return $this->redirectRoute( 'campaign.import', ['campaign' => $this->campaign] ); } public function render() { return view('livewire.campaigns.csv-import'); } } ================================================ FILE: app/Livewire/Campaigns/ExportsTable.php ================================================ '$refresh']; // Listen for table refresh event public Campaign $campaign; public int $pageNumber = 1; public bool $hasMorePages; public function mount(Campaign $campaign) { $this->campaign = $campaign; } public function sortBy($column) { // Toggle sorting direction if the same column is clicked if ($this->sortColumn === $column) { $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; } else { $this->sortColumn = $column; $this->sortDirection = 'asc'; } } public function render() { // $campaignExports = CampaignExport::orderBy($this->sortColumn, $this->sortDirection)->paginate(10); $campaignExports = $this->campaign->campaignExports() ->with(['user']) ->orderBy($this->sortColumn, $this->sortDirection) // ->orderBy('updated_at', 'DESC') ->paginate(); return view('livewire.campaigns.exports-table', [ 'campaignExports' => $campaignExports, ]); } public function status(CampaignExport $export): string { $key = 'running'; if ($export->failed()) { $key = 'failed'; } elseif ($export->scheduled()) { $key = 'scheduled'; } elseif ($export->finished()) { $key = 'finished'; } return __('campaigns/export.status.' . $key); } public function type(CampaignExport $export): string { $key = 'Markdown'; if ($export->type == 1) { $key = 'JSON'; } return $key; } public function progress(CampaignExport $model): string { if ($model->finished()) { return '100%'; } elseif (! $model->running()) { return ''; } elseif (empty($model->progress)) { return '' . __('Calculating') . ''; } return $model->progress . '%'; } public function size(CampaignExport $model): string { if (! $model->finished()) { return ''; } if (empty($model->size)) { return '<1 MB'; } return Number::format($model->size) . ' MB'; } public function download(CampaignExport $model): string { if (! $model->finished()) { return ''; } if ($model->path && Storage::disk('s3')->exists($model->path)) { // Sometimes there are bugs and a job doesn't get queued to delete the file. // This is a dirty hack in the meantime if ($model->created_at->diffInHours(Carbon::now()) > config('limits.campaigns.export')) { Storage::disk('s3')->delete($model->path); return ''; } if (Storage::disk('s3')->visibility($model->path) == 'private') { Storage::disk('s3')->setVisibility($model->path, 'public'); } $html = '' . '' . __('campaigns/export.actions.download') . ''; return $html; } return '' . __('campaigns/export.expired') . ''; } public function sortIcon(): string { $icon = 'fa-regular fa-arrow-down-z-a'; if ($this->sortDirection == 'asc') { $icon = 'fa-regular fa-arrow-up-a-z'; } return ''; } } ================================================ FILE: app/Livewire/Campaigns/Tags.php ================================================ campaign = $campaign; $this->selected = $selected; // Ensure campaign context is always set $this->bootCampaignContext(); } /** * Input focus → open dropdown immediately */ public function show(): void { $this->open = true; $this->loadDefaultTags(); } /** * Typing → debounce → search */ public function updatedSearch(): void { $this->open = true; // If empty, revert to default list if ($this->search === '') { $this->loadDefaultTags(); return; } // Do not search until 2+ chars if (strlen($this->search) < 2) { $this->options = []; return; } $this->bootCampaignContext(); $this->options = Tag::query() ->where('name', 'like', '%' . $this->search . '%') ->limit(10) ->get() ->map(fn ($tag) => [ 'id' => $tag->id, 'label' => $tag->name, ]) ->toArray(); } protected function loadDefaultTags(): void { $this->bootCampaignContext(); $this->options = Tag::query() ->orderBy('name') // or ->latest(), ->popular() ->limit(10) ->get() ->map(fn ($tag) => [ 'id' => $tag->id, 'label' => $tag->name, ]) ->toArray(); } protected function bootCampaignContext(): void { UserCache::campaign($this->campaign); Avatar::campaign($this->campaign); CampaignCache::campaign($this->campaign); CampaignLocalization::forceCampaign($this->campaign); } public function select($id, $label): void { if (! collect($this->selected)->pluck('id')->contains($id)) { $this->selected[] = compact('id', 'label'); } $this->search = ''; $this->open = false; } public function remove($id): void { $this->selected = array_values( array_filter($this->selected, fn ($item) => $item['id'] !== $id) ); } public function render() { return view('livewire.campaigns.tags'); } } ================================================ FILE: app/Livewire/EntityListing.php ================================================ entities = new Collection; $this->campaign = $campaign; $this->widget = $widget; $entities = $widget->entities(); $this->entities->push(...$entities->items()); $this->hasMorePages = $entities->hasMorePages(); // $this->loadEntities(); } public function loadEntities() { // We need this here for the "load more entities" button that loads more data request()->route()->setParameter('campaign', $this->campaign); UserCache::campaign($this->campaign); Avatar::campaign($this->campaign); CampaignCache::campaign($this->campaign); $this->pageNumber += 1; $entities = $this->widget->entities($this->pageNumber); $this->hasMorePages = $entities->hasMorePages(); $this->entities->push(...$entities->items()); } public function render() { // We need this here for when the widget gets re-rendered request()->route()?->setParameter('campaign', $this->campaign); UserCache::campaign($this->campaign); Avatar::campaign($this->campaign); CampaignCache::campaign($this->campaign); return view('livewire.dashboards.entity-listing'); } } ================================================ FILE: app/Livewire/Roadmap/Form.php ================================================ authorize('create', Feature::class); $this->validate(); $feat = new Feature; $feat->created_by = auth()->user()->id; $feat->name = $this->title; $feat->description = $this->description; $feat->status_id = FeatureStatus::Draft; if (! empty($this->from)) { $feat->meta = ['from' => $this->from]; } $feat->save(); if (auth()->user()->can('vote', $feat)) { /** @var FeatureVote $vote */ $vote = new FeatureVote; $vote->feature_id = $feat->id; $vote->user_id = auth()->user()->id; $vote->save(); $feat->upvote_count++; $feat->updateQuietly(); } if ($this->file) { $file = $this->file->storeAs('features/' . $feat->id, uniqid() . '.' . $this->file->getClientOriginalExtension(), 's3'); $featFile = new FeatureFile; $featFile->feature_id = $feat->id; $featFile->path = $file; $featFile->save(); } $this->success = true; $this->title = ''; $this->description = ''; $this->file = null; $this->iteration++; } public function updated() { if (empty($this->title)) { return; } $titles = explode(' ', $this->title); $words = []; $base = Feature::approved(); foreach ($titles as $word) { // Let's try and skip small descriptive words if (mb_strlen($word) <= 4) { continue; } $words[] = $word; } if (empty($words)) { return; } $base->where(function ($sub) use ($words) { foreach ($words as $word) { $sub->orWhere('name', 'like', '%' . $word . '%'); } return $sub; }); $this->duplicates = $base->limit(5)->get(); } public function open(Feature $feature) { $this->dispatch('open-idea', idea: $feature->id); } public function render() { return view('livewire.roadmap.form'); } } ================================================ FILE: app/Livewire/Roadmap/Ideas.php ================================================ dispatch('open-idea', idea: $feature->id); } public function render() { $ideas = Feature::approved(); if (auth()->check()) { $ideas->with('uservote'); } if (isset($this->search) && $this->search != '') { $ideasQuery = $ideas->search($this->search)->paginate(15, ['*'], 'page', 1); } else { $ideasQuery = $ideas->paginate(15); } return view('livewire.roadmap.ideas', [ 'ideas' => $ideasQuery, ]); } } ================================================ FILE: app/Livewire/Roadmap/Upvote.php ================================================ feature = $feature; $this->count = $feature->upvote_count; $this->col = $col; } public function upvote(): void { if (auth()->guest()) { $this->isGuest = true; return; } elseif (! auth()->user()->can('vote', $this->feature)) { $this->isUnsubbed = true; return; } /** @var FeatureVote $vote */ $vote = FeatureVote::where('user_id', auth()->user()->id) ->where('feature_id', $this->feature->id) ->firstOrNew(); if ($vote->exists) { $vote->delete(); $this->feature->upvote_count--; $this->feature->updateQuietly(); $this->count--; } else { $vote->feature_id = $this->feature->id; $vote->user_id = auth()->user()->id; $vote->save(); $this->feature->upvote_count++; $this->feature->updateQuietly(); $this->count++; } UpdateFeatureUpvotes::dispatch($this->feature->id); } public function render() { return view('livewire.roadmap.upvote'); } } ================================================ FILE: app/Livewire/Roadmap.php ================================================ idea) && ! empty($this->idea)) { $feat = Feature::visible()->where('id', $this->idea)->first(); if ($feat) { $this->feature = $feat; } } } public function inProgress() { $this->status = 'in-progress'; } public function ideas() { $this->status = 'ideas'; } public function done() { $this->status = 'done'; } #[On('open-idea')] public function openIdea($idea) { $this->idea = $idea; $this->feature = Feature::visible()->where('id', $this->idea)->first(); $this->dispatch('open-idea-dialog', url: route('roadmap.feature.show', $this->feature)); } public function open(Feature $feature) { $this->dispatch('open-idea', idea: $feature->id); } #[On('idea-closed')] public function close() { $this->idea = 0; unset($this->feature); } public function render() { $with = ['features', 'next', 'now', 'later', 'done']; if (auth()->check()) { $with[] = 'next.uservote'; $with[] = 'now.uservote'; $with[] = 'later.uservote'; $with[] = 'done.uservote'; } $categories = FeatureCategory::with($with)->get(); return view('livewire.roadmap', ['categories' => $categories]); } } ================================================ FILE: app/Livewire/Users/Otp.php ================================================ '$refresh']; // Listen for table refresh event #[Validate('required|numeric')] public string $otp = ''; /* * Generates secret code for 2fa */ public function generate2faSecretCode() { /** @var User $user */ $user = auth()->user(); $otp = new Google2FA; // Generate a new Google2FA code for User PasswordSecurity::create([ 'user_id' => $user->id, 'google2fa_enable' => 0, 'google2fa_secret' => $otp->generateSecretKey(), ]); $this->dispatch('refreshTable'); } /* * Enables 2fa for the current user. */ public function enable2fa() { $this->validate(); /** @var User $user */ $user = auth()->user(); // Enable OTP if the Authenticator code matches secret $otpModel = new Google2FA; $valid = $otpModel->verifyKey($user->passwordSecurity->google2fa_secret, $this->otp); // If OTP code is valid enable OTP if ($valid) { $user->passwordSecurity->update(['google2fa_enable' => 1]); // 2FA is enabled, log out the user and ask them to set up. auth()->logout(); session()->flush(); $this->redirectRoute('login', ['success' => __('settings.account.2fa.success_enable')]); } session()->flash('otp-error', __('settings.account.2fa.error_enable')); } /* * Disables 2fa for the current user. */ public function disable2fa() { if ($this->clickedBefore) { /** @var User $user */ $user = auth()->user(); // Update disabling OTP $user->passwordSecurity->google2fa_enable = 0; $user->passwordSecurity->save(); session()->flash('disable-success', __('settings.account.2fa.success_disable')); } else { $this->clickedBefore = true; } } public function render() { /** @var User $user */ $user = auth()->user(); return view('livewire.users.otp', ['user' => $user]); } } ================================================ FILE: app/Livewire/Widgets/GalleryCarousel.php ================================================ widget = $widget; $this->campaign = $campaign; $this->showName = $this->widget->conf('show_name') == '1'; } public function loadImages(): void { $this->readyToLoad = true; request()->route()->setParameter('campaign', $this->campaign); UserCache::campaign($this->campaign); CampaignCache::campaign($this->campaign); $folderId = $this->widget->conf('folder_id'); if (empty($folderId)) { return; } $images = Image::where('campaign_id', $this->campaign->id) ->where('folder_id', $folderId) ->where('is_folder', false) ->acl(true) ->orderBy('name', 'asc') ->limit(30) ->get(); $this->images = $images ->filter(fn (Image $image) => $image->hasThumbnail()) ->map(fn (Image $image) => [ 'url' => $image->getUrl(800, 600), 'full' => $image->getUrl(), 'name' => $image->name, ]) ->values() ->toArray(); } public function render() { request()->route()?->setParameter('campaign', $this->campaign); UserCache::campaign($this->campaign); CampaignCache::campaign($this->campaign); return view('livewire.widgets.gallery-carousel'); } } ================================================ FILE: app/Livewire/Widgets/RandomEntity.php ================================================ widget = $widget; $this->campaign = $campaign; } public function loadEntity(): void { $this->readyToLoad = true; // We need this here for the "load more entities" button that loads more data request()->route()->setParameter('campaign', $this->campaign); UserCache::campaign($this->campaign); Avatar::campaign($this->campaign); CampaignCache::campaign($this->campaign); $entity = $this->widget->randomEntity(); if (! $entity) { return; } $this->entity = $entity; Dashboard::add($this->entity); $this->widget->setEntity($this->entity); $this->specificPreview = 'dashboard.widgets.previews.' . $this->entity->entityType->code; if ($entity->isMap()) { $this->specificPreview = 'dashboard.widgets.previews.random-map'; } $this->customName = ! empty($this->widget->conf('text')) ? str_replace('{name}', $this->entity->name, $this->widget->conf('text')) : null; } public function render() { // We need this here for when the widget gets re-rendered request()->route()?->setParameter('campaign', $this->campaign); UserCache::campaign($this->campaign); Avatar::campaign($this->campaign); CampaignCache::campaign($this->campaign); return view('livewire.widgets.random-entity'); } } ================================================ FILE: app/Mail/Admin/Subscriptions/ConvertedMail.php ================================================ period->name) . ' ' . $this->user->pledge; return new Envelope( subject: $subject, tags: ['admin-new'], from: new Address(config('app.email'), 'Kanka Admin'), ); } /** * Get the message content definition. */ public function content(): Content { $trial = $this->user->flags()->where('flag', UserFlags::startTrial)->first(); return new Content( markdown: 'emails.subscriptions.new.md', with: ['lastCancel' => null, 'user' => $this->user, 'period' => $this->period, 'trial' => $trial], ); } /** * Get the attachments for the message. * * @return array */ public function attachments(): array { return []; } } ================================================ FILE: app/Mail/Purge/FirstWarning.php ================================================ user = $user; $this->campaigns = $campaigns; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( from: new Address('hello@kanka.io', 'Kanka'), subject: __('emails/purge/first.title', ['amount' => config('purge.users.first.limit')]), tags: ['purge', 'first'] ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.purge.first.md', ); } /** * Get the attachments for the message. * * @return array */ public function attachments(): array { return []; } } ================================================ FILE: app/Mail/Purge/SecondWarning.php ================================================ user = $user; $this->campaigns = $campaigns; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( from: new Address('hello@kanka.io', 'Kanka'), subject: __('emails/purge/second.title', ['amount' => config('purge.users.second.limit')]), tags: ['purge', 'second'] ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.purge.second.md', ); } /** * Get the attachments for the message. * * @return array */ public function attachments(): array { return []; } } ================================================ FILE: app/Mail/Subscription/Admin/CancelledSubscriptionMail.php ================================================ cancellation = $cancellation; $this->user = $cancellation->user; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: 'Sub: Cancelled ' . $this->user->pledge, tags: ['admin-cancelled'], from: new Address(config('app.email'), 'Kanka Admin'), ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.subscriptions.cancelled.md', with: ['cancellation' => $this->cancellation, 'user' => $this->user], ); } } ================================================ FILE: app/Mail/Subscription/Admin/DowngradedSubscriptionMail.php ================================================ user = $user; $this->reason = $reason; $this->custom = $custom; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: 'Subscription: Downgraded ' . $this->user->pledge, tags: ['admin-downgrade'], from: new Address(config('app.email'), 'Kanka Admin'), ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.subscriptions.changed.md', with: ['user' => $this->user, 'reason' => $this->reason, 'custom' => $this->custom], ); } } ================================================ FILE: app/Mail/Subscription/Admin/NewSubscriptionMail.php ================================================ user = $user; $this->period = $period; } /** * Get the message envelope. */ public function envelope(): Envelope { $action = 'New'; // Check if user was previously subbed // Auto-cancelled subs due to credit card issues don't trigger a cancellation, so we need to check previous // subs instead. $cancelled = Subscription::where('user_id', $this->user->id)->canceled()->count(); if ($cancelled > 0) { $action = 'Renewed'; } $subject = 'Sub: ' . $action . ' ' . ucfirst($this->period->name) . ' ' . $this->user->pledge; return new Envelope( subject: $subject, tags: ['admin-new'], from: new Address(config('app.email'), 'Kanka Admin'), ); } /** * Get the message content definition. */ public function content(): Content { $lastCancel = $this->user->cancellations()->orderByDesc('id')->first(); /** @var ?UserLog $log */ $log = $this->user->logs()->whereNotNull('country')->latest()->first(); return new Content( markdown: 'emails.subscriptions.new.md', with: ['lastCancel' => $lastCancel, 'user' => $this->user, 'period' => $this->period, 'trial' => false, 'country' => $log?->country], ); } } ================================================ FILE: app/Mail/Subscription/Admin/NewTrialAcceptedMail.php ================================================ user = $user; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: 'Trial accepted', tags: ['admin-trial-new'], from: new Address(config('app.email'), 'Kanka Admin'), ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.subscriptions.trial.md', with: ['user' => $this->user], ); } } ================================================ FILE: app/Mail/Subscription/Admin/PaypalRenewedMail.php ================================================ user = $user; } /** * Get the message envelope. */ public function envelope(): Envelope { $action = 'Renewed'; $subject = 'Sub: ' . $action . ' Yearly ' . $this->user->pledge; return new Envelope( subject: $subject, tags: ['admin-new'], from: new Address(config('app.email'), 'Kanka Admin'), ); } /** * Get the message content definition. */ public function content(): Content { $lastCancel = null; /** @var ?UserLog $log */ $log = $this->user->logs()->whereNotNull('country')->latest()->first(); return new Content( markdown: 'emails.subscriptions.new.md', with: ['lastCancel' => $lastCancel, 'user' => $this->user, 'period' => 'Yearly', 'trial' => false, 'country' => $log?->country], ); } } ================================================ FILE: app/Mail/Subscription/User/CancelledUserSubscriptionMail.php ================================================ cancellation = $cancellation; $this->user = $cancellation->user; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( from: new Address(config('app.email'), 'Kanka Team'), subject: 'Confirmation: ' . $this->cancellation->tier . ' subscription cancellation', tags: ['cancelled'] ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.subscriptions.cancelled.user-md', with: ['cancellation' => $this->cancellation, 'user' => $this->user], ); } } ================================================ FILE: app/Mail/Subscription/User/EmailChangeMail.php ================================================ user = $user; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: __('emails/activity/email.title'), tags: ['email-change'] ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.activity.email-change-md', ); } } ================================================ FILE: app/Mail/Subscription/User/ExpiringCardEmail.php ================================================ user = $user; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: __('emails/subscriptions/expiring.title'), tags: ['expiring'] ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.subscriptions.expiring.user-md', ); } } ================================================ FILE: app/Mail/Subscription/User/FailedUserSubscriptionMail.php ================================================ user = $user; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: 'Issue with your Kanka subscription', tags: ['failed'] ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.subscriptions.charge-failed.user-md', ); } } ================================================ FILE: app/Mail/Subscription/User/NewElementalSubscriptionMail.php ================================================ user = $user; $this->tier = Tier::where('name', Pledge::ELEMENTAL)->first(); } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( from: new Address(config('app.email'), 'Kanka Team'), subject: 'Thank you, and welcome!', tags: ['elemental'] ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.subscriptions.new.elemental', ); } } ================================================ FILE: app/Mail/Subscription/User/NewSubscriberMail.php ================================================ user = $user; $this->tier = $tier; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( from: new Address(config('app.email'), 'Kanka Team'), subject: 'Thank you, and welcome!', tags: ['elemental'] ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.subscriptions.new.' . $this->tier->code, ); } } ================================================ FILE: app/Mail/Subscription/User/PaypalExpiringMail.php ================================================ user = $user; $subscription = $user->subscription('kanka'); $this->expiryDate = $subscription?->ends_at?->isoFormat('MMMM D, Y') ?? ''; $this->renewUrl = route('paypal.renew'); $this->discord = (bool) $user->discord(); /** @var Collection $premiumCampaigns */ $premiumCampaigns = $user->boosts() ->with([ 'campaign' => fn ($sub) => $sub->select('campaigns.id', 'campaigns.name'), 'campaign.members', 'campaign.plugins', ]) ->groupBy('campaign_id') ->get(); $firstCampaign = $premiumCampaigns->first(); $this->premiumCampaignName = $firstCampaign?->campaign?->name; $this->premiumCampaignCount = $premiumCampaigns->count(); /** @var Collection $members */ $members = new Collection; $this->plugins = 0; if ($firstCampaign) { foreach ($firstCampaign->campaign->members as $member) { $members->push($member->user_id); } $this->plugins = $firstCampaign->campaign->plugins->count(); } $this->players = $members->unique()->reject(fn ($userId) => $userId === $user->id)->count(); } public function envelope(): Envelope { return new Envelope( subject: __('emails/subscriptions/paypal-expiring.title'), tags: ['user', 'paypal-expiring'], ); } public function content(): Content { return new Content( markdown: 'emails.subscriptions.paypal-expiring.user', ); } } ================================================ FILE: app/Mail/Subscription/User/UpcomingYearlyEmail.php ================================================ user = $user; $this->date = Carbon::now()->addMonth(); } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: __('emails/subscriptions/upcoming.title'), tags: ['user', 'upcoming-yearly'], ); } public function content(): Content { return new Content( markdown: 'emails.subscriptions.upcoming.user', with: ['user' => $this->user, 'date' => $this->date], ); } } ================================================ FILE: app/Mail/Subscription/User/ValidationEmail.php ================================================ user = $user; $this->url = $url; } /** * Build the message. * * @return $this */ public function build() { return $this ->from(['address' => config('app.email'), 'name' => 'Kanka Team']) ->subject(__('emails/subscriptions/validation.title')) ->view('emails.subscriptions.validation.user-html') ->tag('validation'); } } ================================================ FILE: app/Mail/Users/NewPassword.php ================================================ user = $user; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: __('emails/activity/password.title'), tags: ['user', 'new-password'], ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.activity.password', ); } } ================================================ FILE: app/Mail/WelcomeEmail.php ================================================ user = $user; } /** * Get the message envelope. */ public function envelope(): Envelope { return new Envelope( subject: __('emails/welcome.title'), tags: ['welcome'], from: new Address(config('app.email'), 'Kanka.io'), ); } /** * Get the message content definition. */ public function content(): Content { return new Content( markdown: 'emails.welcome.2024.text', with: ['user' => $this->user], ); } } ================================================ FILE: app/Models/Ability.php ================================================ belongsToMany(Entity::class, 'entity_abilities') ->withPivot(['visibility_id', 'id']); } public function entityAbilities(): HasMany { return $this->hasMany(EntityAbility::class, 'ability_id') ->with('entity') ->has('entity'); } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { $this->entities()->detach(); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.ability'); } /** * Attach an entity to the ability */ public function attachEntity(array $request): int { $entityIds = Arr::get($request, 'entities'); $count = 0; $visibility = Arr::get($request, 'visibility_id', Visibility::All); $sync = []; foreach ($entityIds as $entity) { $sync[$entity] = ['visibility_id' => $visibility]; $count++; } $this->entities()->syncWithoutDetaching($sync); return $count; } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if (! empty($this->charges)) { return true; } return parent::showProfileInfo(); } } ================================================ FILE: app/Models/Ad.php ================================================ where('section', $section); } public function isSidebar(): bool { return $this->section == self::SECTION_SIDEBAR; } } ================================================ FILE: app/Models/AdminInvite.php ================================================ */ public function campaign(): BelongsTo { return $this->belongsTo(Campaign::class); } public function scopeCheck(Builder $query, int $campaignId): Builder { return $query->where('campaign_id', $campaignId) ->whereNull('used_by'); } } ================================================ FILE: app/Models/ApiLog.php ================================================ 'array', ]; public $fillable = [ 'user_id', 'campaign_id', 'uri', 'params', 'duration', 'response', ]; /** * Determines the prunable query. */ public function prunable(): Builder { return $this->where('created_at', '<=', now()->subDays(30)); } } ================================================ FILE: app/Models/AppRelease.php ================================================ 'date', 'end_at' => 'date', 'category_id' => AppReleaseCategory::class, ]; /** * @return BelongsTo */ public function author(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } /** * Release's category */ public function category(): string { if ($this->category_id === AppReleaseCategory::release) { return __('releases.categories.release'); } elseif ($this->category_id === AppReleaseCategory::event) { return __('releases.categories.event'); } elseif ($this->category_id === AppReleaseCategory::vote) { return __('releases.categories.vote'); } elseif ($this->category_id === AppReleaseCategory::other) { return __('releases.categories.other'); } elseif ($this->category_id === AppReleaseCategory::livestream) { return __('releases.categories.livestream'); } return ''; } /** * Check if the user has already read this release */ public function alreadyRead(): bool { // Don't show announcements that are older than the account itself $firstVisibility = ! empty($this->published_at) ? $this->published_at : $this->created_at; if ($firstVisibility->isBefore(auth()->user()->created_at)) { return true; } // Check if the user has the release tutorial entry on the db. return UserCache::user(auth()->user())->dismissedTutorial('releases_' . $this->category_id->value . '_' . $this->id); } /** * Check if the release shouldn't be shown anymore */ public function isPast(): bool { return ! empty($this->end_at) && $this->end_at->isPast(); } } ================================================ FILE: app/Models/Application.php ================================================ ApplicationStatus::class, 'availability_days' => 'array', ]; protected array $sanitizable = [ 'text', 'character_concept', 'additional_notes', ]; protected $fillable = [ 'campaign_id', 'user_id', 'character_concept', 'experience', 'availability_days', 'time_start', 'time_end', 'timezone', 'pref_rp_combat', 'pref_tone', 'external_link', 'additional_notes', 'status', ]; } ================================================ FILE: app/Models/Attribute.php ================================================ AttributeType::class, ]; /** * Trigger for filtering based on the order request. */ protected string $orderTrigger = 'attributes/'; /** * Searchable fields */ protected array $searchableColumns = [ 'name', ]; protected string $numberRange = '`\[range:(-?[0-9]+),(-?[0-9]+)\]`i'; protected int|bool $numberMax; protected int|bool $numberMin; protected string $listRegexp = '`\[range:(.+)\]`i'; protected array|bool $listRange; protected string $mappedName; /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id', 'id'); } /** * @return BelongsTo */ public function origin(): BelongsTo { return $this->belongsTo('App\Models\Attribute', 'origin_attribute_id', 'id'); } /** * Get an entity's value parsed of mentions */ public function mappedValue(): string { if ($this->type_id == AttributeType::Section) { return $this->name; } return Mentions::mapAttribute($this); } /** * Get an entity's name parsed of mentions */ public function mappedName(): string { if (isset($this->mappedName)) { return $this->mappedName; } return (string) $this->mappedName = Attributes::map($this); } public function exposedName(bool $slug = true): string { $name = str_replace(' ', '', $this->name); if (Str::contains($name, '[range:')) { $name = Str::before($name, '[range:'); } return $slug ? Str::slug($name) : $name; } /** * Determine if an attribute is of the standard input field type */ public function isDefault(): bool { return $this->type_id === AttributeType::Standard; } /** * Determine if an attribute is of the "checkbox" type */ public function isCheckbox(): bool { return $this->type_id === AttributeType::Checkbox; } /** * Determine if an attribute is of the "text" type */ public function isText(): bool { return $this->type_id === AttributeType::Block; } /** * Determine if an attribute is of the "section" type */ public function isSection(): bool { return $this->type_id === AttributeType::Section; } /** * Determine if an attribute is of the "number" type */ public function isNumber(): bool { return $this->type_id === AttributeType::Number; } /** * Determine if an attribute is of the "list" type */ public function isList(): bool { return $this->type_id === AttributeType::List; } /** * Determine if an attribute is of the "random" type */ public function isRandom(): bool { return $this->type_id === AttributeType::Random; } /** * Copy an attribute to another target */ public function copyTo(Entity $target): bool { $new = $this->replicate(['entity_id']); $new->entity_id = $target->id; return $new->save(); } public function scopeOrdered(Builder $query, string $order = 'asc'): Builder { return $query->orderBy('default_order', $order); } public function scopeHidden(Builder $query, bool $hidden = false): Builder { return $query->where(['is_hidden' => $hidden]); } public function name(): string { $name = preg_replace('`\[icon:(.*?)\]`si', '', $this->name); $name = preg_replace($this->listRegexp, '', $name); return Mentions::mapAttribute($this, $name); } /** * Set the value of the attribute. Validates if there are constraints */ public function setValue($value): self { $this->value = $value; // Check if there is a constraint if (! $this->validConstraints()) { return $this; } if ($this->isNumber()) { $this->value = min($this->numberMax, max($this->numberMin, $value)); } elseif (! empty($this->listRange)) { if (! in_array($this->value, $this->listRange())) { $this->value = null; } } return $this; } public function numberMax(): int { $this->calculateConstraints(); return $this->numberMax; } public function numberMin(): int { $this->calculateConstraints(); return $this->numberMin; } /** * Generate the attribute's mention syntax */ public function mentionName(): string { return '{attribute:' . $this->id . '}'; } /** * Determine if an attribute's value is inside its numeric constraints (for ranged attributes) */ public function validConstraints(): bool { $this->calculateConstraints(); if ($this->isNumber()) { return $this->numberMax !== false && $this->numberMin !== false; } return isset($this->listRange) && $this->listRange !== false; } /** * Determine an attribute's constraints (for ranged and listed attributes) */ protected function calculateConstraints(): self { if ($this->isNumber()) { return $this->calculateNumberConstraints(); } elseif ($this->isDefault()) { return $this->calculateListConstraints(); } return $this; } /** * Define the min/max range of a number, if set */ protected function calculateNumberConstraints(): self { if (isset($this->numberMax)) { return $this; } $this->numberMax = false; $this->numberMin = false; // dump('checking ' . $this->name . '(' . $this->mappedName() . ')'); if (! Str::contains($this->mappedName(), '[range:')) { return $this; } // dump('check regexp'); preg_match($this->numberRange, $this->mappedName(), $constraints); if (count($constraints) !== 3) { return $this; } $this->numberMin = (int) $constraints[1]; $this->numberMax = (int) $constraints[2]; // dump($this->numberMin); // dd($this->numberMax); return $this; } /** * Generate a list of values possible for an attribute */ protected function calculateListConstraints(): self { if (isset($this->listRange)) { return $this; } $this->listRange = false; if (! Str::contains($this->mappedName(), '[range:')) { // dd('Missing range syntax'); return $this; } preg_match($this->listRegexp, $this->mappedName(), $constraints); if (count($constraints) !== 2) { return $this; } $this->listRange = explode(',', $constraints[1]); // dump($constraints); // dd($this->listRange); return $this; } public function listRange(): array { if (! is_array($this->listRange)) { return []; } return $this->listRange; } public function listRangeText(): string { return implode(', ', $this->listRange); } public function exportFields(): array { return [ 'id', 'type_id', 'name', 'value', 'is_private', 'default_order', 'is_pinned', 'is_hidden', ]; } /** * Get the value used to index the model. */ public function getScoutKey() { return $this->getTable() . '_' . $this->id; } /** * Get the name of the index associated with the model. */ public function searchableAs(): string { return 'entities'; } protected function makeAllSearchableUsing($query) { return $query ->select([$this->getTable() . '.*', 'entities.id as entity_id']) ->leftJoin('entities', $this->getTable() . '.entity_id', '=', 'entities.id') ->has('entity') ->with('entity'); } /** * @return array */ public function toSearchableArray() { if (! $this->entity) { return []; } return [ 'campaign_id' => $this->entity->campaign_id, 'entity_id' => $this->entity_id, 'name' => $this->name, 'type' => 'attribute', 'entry' => strip_tags($this->value), ]; } } ================================================ FILE: app/Models/AttributeTemplate.php ================================================ */ public function entityType(): BelongsTo { return $this->belongsTo('App\Models\EntityType', 'entity_type_id', 'id'); } /** * Performance with for datagrids */ public function scopeEnabled(Builder $query): Builder { return $query ->where('is_enabled', true); } /** * Apply a template to an entity * todo: move to service */ public function apply(Entity $entity, int $startingOrder = 0): int { $order = $startingOrder; $existing = array_values($entity->attributes()->pluck('name')->toArray()); // If adding to an entity that already has attributes, we need to add the new ones after the existing ones $lastOrder = 0; if ($startingOrder == 0) { $lastExisting = $entity->attributes()->orderByDesc('default_order')->first(); if (! empty($lastExisting)) { $lastOrder = $lastExisting->default_order + 1; } } /** @var RandomService $randomService */ $randomService = app()->make(RandomService::class); /** @var Attribute $attribute */ foreach ($this->entity->attributes()->orderBy('default_order', 'ASC')->get() as $attribute) { // Don't re-create existing attributes. if (in_array($attribute->name, $existing)) { continue; } [$type, $value] = $randomService->randomAttribute($attribute->type_id, $attribute->value); Attribute::create([ 'entity_id' => $entity->id, 'name' => $attribute->name, 'value' => $value, 'default_order' => $lastOrder + $order, 'is_private' => $attribute->is_private, 'is_pinned' => $attribute->isPinned(), 'type_id' => $type, ]); $order++; } // Loop through parents /** @var Entity $ancestor */ foreach ($this->entity->ancestors()->get() as $ancestor) { foreach ($ancestor->attributes()->orderBy('default_order', 'ASC')->get() as $attribute) { // Don't re-create existing attributes. if (in_array($attribute->name, $existing)) { continue; } [$type, $value] = $randomService->randomAttribute($attribute->type_id, $attribute->value); Attribute::create([ 'entity_id' => $entity->id, 'name' => $attribute->name, 'value' => $value, 'default_order' => $order, 'is_private' => $attribute->is_private, 'is_pinned' => $attribute->isPinned(), 'type_id' => $type, ]); $order++; } } $entity->touch(); return $order; } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.attribute_template'); } /** * Determine if the attribute templates has visible (to show on the entity creation _attributes tab) attributes */ public function hasVisibleAttributes(array $names = []): bool { if (! $this->entity) { return false; } $visible = false; foreach ($this->entity->attributes()->get() as $attribute) { if (! in_array($attribute->name, $names)) { $visible = true; } } return $visible; } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if ($this->entityType) { return true; } return parent::showProfileInfo(); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'is_enabled', ]; } /** * Grid mode sortable fields */ public function datagridSortableColumns(): array { $columns = [ 'name' => __('crud.fields.name'), ]; if (auth()->check() && auth()->user()->isAdmin()) { $columns['is_private'] = __('crud.fields.is_private'); } return $columns; } public function toSearchableArray() { if (! $this->entity) { return []; } return [ 'campaign_id' => $this->entity->campaign_id, 'entity_id' => $this->entity->id, 'name' => $this->name, 'type' => 'attribute_template', ]; } public function isEnabled(): bool { return $this->is_enabled; } } ================================================ FILE: app/Models/Bookmark.php ================================================ */ protected $casts = [ 'options' => 'array', ]; protected array $sanitizable = [ 'name', 'icon', 'css', ]; /** * Custom options array key filter * Used in the Menu link observer */ public array $optionsAllowedKeys = ['is_nested', 'default_dashboard', 'subview_filter']; /** * Searchable fields */ protected array $searchableColumns = ['name']; /** * Nullable values (foreign keys) * * @var string[] */ public array $nullableForeignKeys = [ 'entity_id', 'dashboard_id', ]; protected array $apiWith = [ 'target', ]; protected array $suggestions = [ BookmarkCache::class => 'clearSuggestion', ]; /** * Set to false if this entity type doesn't have relations */ public bool $hasRelations = false; /** * Fields that can be sorted on */ public array $sortableColumns = [ 'position', 'menu', 'tab', 'is_active', ]; /** @var string Default order for lists */ public string $defaultOrderField = 'position'; /** * Performance with for datagrids */ public function scopePreparedWith(Builder $query): Builder { return $query->with([ 'entity', 'entityType', 'randomEntityType', 'target', 'dashboard', ]); } public function scopePreparedSelect(Builder $query): Builder { return $query; } /** * Scope for Active menu links */ public function scopeActive(Builder $query): Builder { return $query->where('is_active', true); } /** * Scope for ordering models by default */ public function scopeOrdered(Builder $query): Builder { return $query ->orderBy('position', 'ASC') ->orderBy('name', 'ASC'); } /** * @return BelongsTo */ public function target(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id'); } /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id'); } /** * @return BelongsTo */ public function dashboard(): BelongsTo { return $this->belongsTo('App\Models\CampaignDashboard', 'dashboard_id'); } /** * @return BelongsTo */ public function entityType(): BelongsTo { return $this->belongsTo(EntityType::class); } /** * @return BelongsTo */ public function randomEntityType(): BelongsTo { return $this->belongsTo(EntityType::class, 'random_entity_type'); } /** * Override the get link */ public function getLink(string $route = 'show'): string { $campaign = CampaignLocalization::getCampaign(); return route('bookmarks.' . $route, [$campaign, $this->id]); } /** * Get the entity_type id from the entity_types table. * Needed to get custom module name */ public function entityTypeId(): int { return (int) config('entities.ids.bookmark'); } public function isRandom(): bool { return ! empty($this->random_entity_type); } public function isEntity(): bool { return ! empty($this->entity_id); } public function isDashboard(): bool { return ! empty($this->dashboard_id); } public function isList(): bool { return ! empty($this->entity_type_id); } /** * Icon HTML class */ public function iconClass(): string { if (! empty($this->icon)) { return e($this->icon); } elseif ($this->target) { return 'fa-regular fa-arrow-circle-right'; } elseif ($this->isRandom()) { return 'fa-regular fa-question'; } if (! empty($this->entityType->icon)) { return $this->entityType->icon; } return 'fa-regular fa-th-list'; } /** * Validate that the user has access to this dashboard */ public function isValidDashboard(): bool { return Dashboard::campaign($this->campaign)->getDashboard($this->dashboard_id) !== null; } public function customClass(Campaign $campaign): string { $class = ''; $request = request()->get('bookmark'); if (! empty($request) && $request == $this->id) { $class = 'active '; } if (! $campaign->boosted()) { return $class; } if (empty($this->css)) { return $class; } return (string) $class . $this->css; } /** * Determine if the bookmark is valid */ public function valid(Campaign $campaign): bool { $this->setRelation('campaign', $campaign); if ($this->dashboard) { return $campaign->boosted() && $this->isValidDashboard(); } elseif ($this->target) { return true; } elseif ($this->entityType) { return true; } return (bool) ($this->isRandom()); } public function activeModule(Campaign $campaign, Entity|EntityType|null $current = null): ?string { if (empty($current) || request()->has('bookmark')) { return null; } // We have no way of having a bookmark set "just to the custom module", so in cases where the campaign has a // bookmark to the module with no filters, and one with, we assume we want the one without filters to be // highlighted. if (! empty($this->filters)) { return null; } if ($current instanceof EntityType) { if ($current->isStandard() || $current->id != $this->entity_type_id) { return null; } return 'active'; } // @phpstan-ignore-next-line if (($current instanceof Entity && $current->entityType && $current->entityType->isStandard()) || $current->type_id != $this->entity_type_id) { return null; } return 'active'; } } ================================================ FILE: app/Models/BragiLog.php ================================================ 'array', ]; public function scopeRecent(Builder $query, $cutoffDate): Builder { return $query ->whereDate('created_at', '>=', $cutoffDate); } } ================================================ FILE: app/Models/Calendar.php ================================================ */ public $casts = [ 'parameters' => 'array', ]; protected array $foreignExport = [ 'calendarWeather', ]; protected array $loadedMonths; protected array $loadedWeekdays; protected array $loadedYears; protected array $loadedSeasons; protected array $loadedMoons; protected array $loadedWeeks; protected array $loadedMonthAliases; protected array $cachedCurrentDate; /** * Get the months decoded from the json into a usable array */ public function months(): array { if (isset($this->loadedMonths)) { return $this->loadedMonths; } return (array) $this->loadedMonths = ! empty($this->months) ? json_decode(strip_tags($this->months), true) : []; } /** * Get the weekdays */ public function weekdays(): array { if (! isset($this->loadedWeekdays)) { $this->loadedWeekdays = []; if (! empty($this->months)) { $this->loadedWeekdays = json_decode(strip_tags($this->weekdays), true); } } return $this->loadedWeekdays; } /** * Get the weekdays */ public function years(): array { if (! isset($this->loadedYears)) { $this->loadedYears = []; if (! empty($this->years)) { $this->loadedYears = json_decode(strip_tags($this->years), true); } } return $this->loadedYears; } /** * Get the calendar's moons */ public function moons(): array { if (! isset($this->loadedMoons)) { $this->loadedMoons = json_decode(empty($this->moons) ? '[]' : strip_tags($this->moons), true); } return $this->loadedMoons; } /** * Get the calendar's seasons */ public function seasons(): array { if (! isset($this->loadedSeasons)) { $this->loadedSeasons = json_decode(empty($this->seasons) ? '[]' : strip_tags($this->seasons), true); } return $this->loadedSeasons; } /** * Get the calendar's weeks */ public function weeks(): array { if (! isset($this->loadedWeeks)) { $this->loadedWeeks = json_decode(empty($this->week_names) ? '[]' : strip_tags($this->week_names), true); } return $this->loadedWeeks; } /** * Get the calendar's month aliases */ public function monthAliases(): array { if (! isset($this->loadedMonthAliases)) { $this->loadedMonthAliases = json_decode(empty($this->month_aliases) ? '[]' : strip_tags($this->month_aliases), true); } return $this->loadedMonthAliases; } public function currentDate(?string $value = null): mixed { // If we have no date saved at all, skip this part. This happens when an entity was changed to the calendar // type and most fields are missing. if (empty($this->date)) { return null; } if ($value == 'year') { return $this->cacheCurrentDate()[0] ?? 0; } elseif ($value == 'month') { return $this->cacheCurrentDate()[1] ?? 1; } elseif ($value == 'date') { return $this->cacheCurrentDate()[2] ?? 1; } return null; } /** * Get the calendar's current date */ public function currentYear(): int { return $this->cacheCurrentDate()[0] ?? 0; } /** * Get the calendar's current month */ public function currentMonth(): int { return $this->cacheCurrentDate()[1] ?? 1; } /** * Get the calendar's current day */ public function currentDay(): int { return $this->cacheCurrentDate()[2] ?? 1; } /** * Get the calendar's nice date */ public function niceDate(?string $date = null): string { if (empty($date)) { $date = $this->date; } if (empty($date)) { return ''; } [$year, $month, $day] = $this->dateArray($date); // Replace month with real month, and year maybe $months = $this->months(); $years = $this->years(); try { $return = $day . ' ' . (isset($months[$month - 1]) ? $months[$month - 1]['name'] : $month) . ', ' . ($years[$year] ?? $year) . ' ' . $this->suffix; return $return; } catch (Exception $e) { // @phpstan-ignore-line return $this->date; } } /** * Get a list of months for select fields */ public function monthList(): array { $months = []; $i = 1; foreach ($this->months() as $month) { $months[$i] = $month['name']; $i++; } return $months; } /** * Get the length as a data-property for each of the calendar's months */ public function monthDataProperties(): array { $monthData = []; $i = 1; foreach ($this->months() as $month) { $monthData[$i] = ['data-length' => $month['length']]; $i++; } return $monthData; } /** * Build the list of days for a month */ public function dayList(?int $month = null): array { if (empty($month)) { $month = $this->currentMonth(); } $monthId = $month - ($month > 0 ? 1 : 0); // if the current month no longer exists, reset it to the first month if ($monthId > (count($this->months()) - 1)) { $monthId = 0; } $days = []; $currentMonth = $this->months()[$monthId]; for ($i = 1; $i <= $currentMonth['length']; $i++) { $days[$i] = $i; } // No leaps days, or not on this month if (! $this->has_leap_year || $this->leap_year_month != $month) { return $days; } // We might be on a year with leap years, but the js is too complex so let's just assume $start = count($days); for ($i = 1; $i <= $this->leap_year_amount; $i++) { $day = $start + $i; $days[$day] = $day; } return $days; } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { foreach ($this->calendarEvents as $child) { $child->delete(); } } /** * Determine if the calendar is missing months of weekdays to be rendered successfully */ public function missingDetails(): bool { return count($this->months()) < 1 || count($this->weekdays()) < 2; } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.calendar'); } /** * Cache the current date explode method */ protected function cacheCurrentDate(): array { if (isset($this->cachedCurrentDate)) { return $this->cachedCurrentDate; } $date = mb_ltrim($this->date, '-'); $this->cachedCurrentDate = explode('-', $date); if (Str::startsWith($this->date, '-')) { $this->cachedCurrentDate[0] = '-' . $this->cachedCurrentDate[0]; } return $this->cachedCurrentDate; } /** * Get the date as an array */ public function dateArray(?string $date = null): array { if (empty($date)) { $date = $this->date; } $isNegativeYear = Str::startsWith($date, '-'); $date = explode('-', mb_ltrim($date, '-')); if (count($date) !== 3) { return [1, 1, 1]; } return [ $isNegativeYear ? '-' . $date[0] : $date[0], max($date[1], 1), max($date[2], 1), ]; } public function recurringOptions(bool $flat = false): array { $options = [ '' => __('calendars.options.events.recurring_periodicity.none'), 'month' => __('calendars.options.events.recurring_periodicity.month'), 'year' => __('calendars.options.events.recurring_periodicity.year'), ]; // Add options based on moons $unnamed = 0; foreach ($this->moons() as $moon) { if ($flat) { $options[$moon['id'] . '_f'] = __('calendars.options.events.recurring_periodicity.fullmoon_name', ['moon' => $moon['name']]); $options[$moon['id'] . '_n'] = __('calendars.options.events.recurring_periodicity.newmoon_name', ['moon' => $moon['name']]); continue; } $name = $moon['name']; if (empty($name)) { $unnamed++; $name = __('calendars.options.events.recurring_periodicity.unnamed_moon', ['number' => $unnamed]); } $options[$name] = [ $moon['id'] . '_f' => __('calendars.options.events.recurring_periodicity.fullmoon'), $moon['id'] . '_n' => __('calendars.options.events.recurring_periodicity.newmoon'), ]; } return $options; } /** * Get the number of days in a year */ public function daysInYear(): int { $days = 0; foreach ($this->months() as $month) { $days += Arr::get($month, 'length', 1); } return $days; } /** * Default calendar layout */ public function defaultLayout(): string { return $this->yearlyLayout() ? 'year' : 'month'; } /** * Determine if the calendar defaults to a yearly layout */ public function yearlyLayout(): bool { return Arr::get($this->parameters, 'layout') === 'yearly'; } /** * Determine if the calendar skips year zero. */ public function hasYearZero(): bool { return ! $this->skip_year_zero; } } ================================================ FILE: app/Models/CalendarWeather.php ================================================ */ public function calendar(): BelongsTo { return $this->belongsTo(Calendar::class); } public function tooltip(): string { return (! empty($this->temperature) ? __('calendars/weather.fields.temperature') . ': ' . e($this->temperature) . "
\n" : null) . (! empty($this->precipitation) ? __('calendars/weather.fields.precipitation') . ': ' . e($this->precipitation) . "
\n" : null) . (! empty($this->wind) ? __('calendars/weather.fields.wind') . ': ' . e($this->wind) . "
\n" : null) . (! empty($this->effect) ? __('calendars/weather.fields.effect') . ': ' . e($this->effect) . "
\n" : null); } public function weatherName(): string { if (! empty($this->name)) { return $this->name; } return __('calendars/weather.options.weather.' . $this->weather); } } ================================================ FILE: app/Models/Campaign.php ================================================ 'array', 'default_images' => 'array', 'settings' => 'array', 'visibility_id' => CampaignVisibility::class, ]; protected array $sanitizable = [ 'name', ]; protected array $imageFields = [ 'image', 'header_image', ]; /** @var Collection|EntityType[] */ protected Collection|array $cachedEntityTypes; public function getRouteKeyName() { return 'slug'; } /** * Does the campaign has a preview text that can be displayed */ public function hasPreview(): bool { return ! empty($this->preview()); } /** * Preview text for the dashboard */ public function preview(): string { if (! empty(strip_tags($this->excerpt))) { return $this->excerpt(); } if (! empty(strip_tags($this->entry))) { return strip_tags(mb_substr($this->parsedEntry(), 0, 1000)) . ' ...'; } return ''; } public function membersList($removedIds = []): array { $members = []; foreach ($this->members()->with('user')->get() as $m) { if (! in_array($m->user->id, $removedIds)) { $members[$m->user->id] = $m->user->name; } } return $members; } /** * Get a list of users who are admins of the campaign * * @return User[]|array|Collection */ public function admins() { $users = []; $roles = $this->roles() ->with(['users', 'users.user']) ->where('is_admin', '1') ->get(); foreach ($roles as $role) { foreach ($role->users as $user) { if (! isset($users[$user->id])) { $users[$user->user->id] = $user->user; } } } return $users; } /** * Determine if a campaign has a module enabled or not */ public function enabled(string|EntityType $module): bool { if ($module instanceof EntityType) { $module = $module->pluralCode(); } if ($module === 'attribute_templates') { $module = 'entity_attributes'; } return (bool) CampaignCache::campaign($this)->settings()->get($module); } public function isPublic(): bool { return $this->visibility_id == CampaignVisibility::public; } public function isPrivate(): bool { return $this->visibility_id == CampaignVisibility::private; } /** * Determine if the user is currently following the campaign */ public function isFollowing(): bool { return $this->followers()->where('user_id', auth()->user()->id)->count() === 1; } /** * Determine if a campaign is discreet */ public function isUnlisted(): bool { return $this->visibility_id === CampaignVisibility::unlisted; } /** * Determine if a campaign is open to applications */ public function isOpen(): bool { return $this->is_open; } /** * Determine if a campaign is hidden */ public function isHidden(): bool { return $this->is_hidden; } public function getEntryAttribute(): ?string { return $this->description?->description; } public function getDescriptionForEditionAttribute(): string { return Mentions::parseForEdit($this, 'entry'); } public function getExcerptAttribute(): ?string { return $this->description?->excerpt; } public function excerpt(): string { return Mentions::mapAny($this, 'excerpt'); } public function getExcerptForEditionAttribute() { return Mentions::parseForEdit($this, 'excerpt'); } public function defaultDescendantsMode(): Descendants { return Descendants::from(Arr::get($this->ui_settings, 'descendants', 0)); } public function defaultToConnection(): bool { return (bool) Arr::get($this->ui_settings, 'connections', false); } public function defaultToConnectionMode(): int { return (int) Arr::get($this->ui_settings, 'connections_mode', 0); } public function getHideMembersAttribute() { return Arr::get($this->ui_settings, 'hide_members', false); } public function getHideHistoryAttribute() { return Arr::get($this->ui_settings, 'hide_history', false); } public function existingDefaultImages(): array { if (empty($this->default_images)) { return []; } return array_keys($this->default_images); } /** * Prepare the default entity images */ public function defaultImages($withKey = false): array { if (empty($this->default_images)) { return []; } $imageIds = array_values($this->default_images); $images = Image::whereIn('id', $imageIds)->get(); $data = []; foreach ($this->default_images as $type => $uuid) { /** @var ?Image $image */ $image = $images->where('id', $uuid)->first(); if (empty($image) || in_array($type, ['relations', 'bookmarks', 'menu_links'])) { continue; } if ($withKey) { $data[$type] = [ 'type' => $type, 'uuid' => $uuid, 'path' => $image->path, ]; } else { $data[] = [ 'type' => $type, 'uuid' => $uuid, 'path' => $image->path, ]; } } return $data; } /** * Determine if a campaign has plugins of the theme type */ public function hasPluginTheme(): bool { return ! empty(CampaignCache::themes()); } public function getDefaultVisibilityAttribute(): mixed { return Arr::get($this->settings, 'default_visibility', 'all'); } public function getDefaultGalleryVisibilityAttribute(): mixed { return Arr::get($this->settings, 'gallery_visibility', 'all'); } public function showPrivateEntityMentions(): bool { return Arr::get($this->settings, 'private_mention_visibility', 0); } /** * Determine the campaign's default visibility_id select option */ public function defaultVisibility(): Visibility { $visibility = $this->getDefaultVisibilityAttribute(); if ($visibility == 'admin' && auth()->user()->isAdmin()) { return Visibility::Admin; } elseif ($visibility == 'admin-self') { return Visibility::AdminSelf; } elseif ($visibility == 'members') { return Visibility::Member; } elseif ($visibility == 'self') { return Visibility::Self; } return Visibility::All; } /** * Determine the gallery's default visibility_id select option */ public function defaultGalleryVisibility(): Visibility { $visibility = $this->getDefaultGalleryVisibilityAttribute(); if ($visibility == 'admin' && auth()->user()->isAdmin()) { return Visibility::Admin; } elseif ($visibility == 'admin-self') { return Visibility::AdminSelf; } elseif ($visibility == 'members') { return Visibility::Member; } elseif ($visibility == 'self') { return Visibility::Self; } return Visibility::All; } /** * Checks if the campaign's public role has no read permissions */ public function publicHasNoVisibility(): bool { $permissionCount = $this->publicRole->permissions() ->where('action', Permission::View->value) ->where('access', 1) ->count(); return $permissionCount == 0; } /** * Determine if a campaign has editing warnings (when multiple people are trying to edit * the same entity). This is enabled if the campaign has several members. */ public function hasEditingWarning(): bool { $members = CampaignCache::campaign($this)->members(); return $members->count() > 1; } /** * Send a notification to the campaign's admins */ public function notifyAdmins(Notification $notification): self { foreach ($this->admins() as $user) { $user->notify($notification); } return $this; } public function follower(): int { if (app()->hasDebugModeEnabled() && request()->has('_followers')) { return request()->get('_followers'); } return (int) ($this->followers_count ?? $this->followers()->count()); } public function hasModuleName(int $type, bool $plural = false): bool { $key = 'modules.' . $type . '.' . ($plural ? 'p' : 's'); return Arr::has($this->settings, $key); } public function moduleName(int $type, bool $plural = false): ?string { $key = 'modules.' . $type . '.' . ($plural ? 'p' : 's'); $val = Arr::get($this->settings, $key); return $val; } public function hasModuleIcon(int $type): bool { $key = 'modules.' . $type . '.i'; return Arr::has($this->settings, $key); } public function moduleIcon(int $type): ?string { $key = 'modules.' . $type . '.i'; $val = Arr::get($this->settings, $key); return $val; } public function hasVanity(): bool { return $this->slug != $this->id; } public function getSystems(): string { $systems = ''; foreach ($this->systems as $system) { if ($systems) { $systems .= ', '; } $systems .= $system->name; } return $systems; } public function imageStoragePath(): string { return 'w/' . $this->id; } /** * @return Collection|EntityType[] */ public function getEntityTypes(): Collection|array { if (isset($this->cachedEntityTypes)) { return $this->cachedEntityTypes; } $this->cachedEntityTypes = EntityType::inCampaign($this)->enabled()->get(); return $this->cachedEntityTypes; } public function link(): string { return '' . $this->name . ''; } public function adminRole(): array { return CampaignCache::campaign($this)->adminRole(); } public function adminRoleName(): string { $role = $this->adminRole(); return Arr::get($role, 'name', __('campaigns.roles.admin_role')); } /** * Helper to get a specific filter value by its Enum type */ public function getFilter(CampaignFilterType $type): ?string { $filter = $this->filters->firstWhere('type', $type); return $filter ? $filter->entry : null; } } ================================================ FILE: app/Models/CampaignBoost.php ================================================ */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } public function inCooldown(): bool { return app()->isProduction() && ! $this->created_at->isBefore(Carbon::now()->subDays(7)); } /** * Automatically prune old elements from the db */ public function prunable(): Builder { return static::where('deleted_at', '<=', now()->subDays(90)); } } ================================================ FILE: app/Models/CampaignDashboard.php ================================================ */ public function widgets(): HasMany { return $this->hasMany(CampaignDashboardWidget::class, 'dashboard_id', 'id'); } /** * @return HasMany */ public function roles(): HasMany { return $this->hasMany(CampaignDashboardRole::class, 'campaign_dashboard_id', 'id'); } /** * @return Builder */ public function scopeExclude(Builder $builder, ?CampaignDashboard $campaignDashboard = null) { if (empty($campaignDashboard)) { return $builder; } return $builder->where('id', '!=', $campaignDashboard->id); } /** * Check if a campaign role is set up */ public function permission(CampaignRole $role, bool $default = false): bool { $dashboardRole = $this->roles->where('campaign_role_id', $role->id) ->first(); if (empty($dashboardRole)) { return false; } if ($default) { return $dashboardRole->is_default; } return $dashboardRole->is_visible; } } ================================================ FILE: app/Models/CampaignDashboardRole.php ================================================ */ public function role(): BelongsTo { return $this->belongsTo(CampaignRole::class, 'campaign_role_id', 'id'); } /** * @return BelongsTo */ public function dashboard(): BelongsTo { return $this->belongsTo(CampaignDashboard::class, 'campaign_dashboard_id', 'id'); } } ================================================ FILE: app/Models/CampaignDashboardWidget.php ================================================ 'array', 'widget' => Widget::class, ]; protected LengthAwarePaginator $cachedEntities; /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo(Entity::class); } /** * @return BelongsTo */ public function entityType(): BelongsTo { return $this->belongsTo(EntityType::class); } /** * @return BelongsTo */ public function dashboard(): BelongsTo { return $this->belongsTo(CampaignDashboard::class, 'dashboard_id', 'id'); } /** * @return BelongsToMany< * Tag, * $this, * CampaignDashboardWidgetTag * > */ public function tags(): BelongsToMany { return $this->belongsToMany( Tag::class, 'campaign_dashboard_widget_tags', 'widget_id', 'tag_id' )->using(CampaignDashboardWidgetTag::class) ->with('entity') ->has('entity'); } /** * @return HasMany */ public function dashboardWidgetTags(): HasMany { return $this->hasMany(CampaignDashboardWidgetTag::class, 'widget_id', 'id'); } /** * Get the column size */ public function colSize(): int { if ($this->widget == Widget::Campaign) { return 12; } if (! empty($this->width)) { return $this->width; } return ($this->widget == Widget::Preview || $this->widget == Widget::Random || $this->widget == Widget::Gallery || ($this->widget == Widget::Recent && $this->conf('singular'))) ? 4 : 6; } /** * Get the column size for tablet devices */ public function mdColSize(): int { if ($this->widget == Widget::Campaign) { return 12; } if (! empty($this->width)) { return max(6, $this->width); } return ($this->widget == Widget::Preview || $this->widget == Widget::Random || $this->widget == Widget::Gallery || ($this->widget == Widget::Recent && $this->conf('singular'))) ? 6 : 6; } public function scopePositioned(Builder $query): Builder { return $query->with([ 'entity', 'entity.image', 'entity.entityType', 'entity.header', // 'tags', 'entity.mentions', 'entity.mentions.target', 'entity.mentions.target.tags:id,name,slug', 'entity.mentions.target.entityType:id,code,is_special', 'entityType', ]) ->orderBy('position'); } public function scopeOnDashboard(Builder $query, ?CampaignDashboard $dashboard = null): Builder { if (empty($dashboard)) { return $query->whereNull('dashboard_id'); } return $query->where('dashboard_id', $dashboard->id); } /** * @param string $value */ public function conf($value) { return Arr::get($this->config, $value, null); } /** * Copy a dashboard to another target */ public function copyTo(CampaignDashboard $target) { $new = $this->replicate(['dashboard_id']); $new->dashboard_id = $target->id; $new->save(); foreach ($this->dashboardWidgetTags as $tag) { $newTag = $tag->replicate(['widget_id']); $newTag->widget_id = $new->id; $newTag->save(); } } public function hasAdvancedOptions(): bool { return $this->conf('attributes') == 1 || $this->conf('members') == '1' || $this->conf('entity-header') == '1' || $this->conf('relations') == '1'; } public function showAttributes(): bool { if ($this->conf('attributes') != '1') { return false; } if (! in_array($this->widget, [Widget::Preview, Widget::Recent, Widget::Random])) { return false; } if ($this->widget == Widget::Recent) { return true; } return ! empty($this->entity); } public function showRelations(): bool { if ($this->conf('relations') != '1') { return false; } if (! in_array($this->widget, [Widget::Preview, Widget::Recent, Widget::Random])) { return false; } if ($this->widget == Widget::Recent) { return true; } return ! empty($this->entity); } /* * Show members of families and organisations * @param Entity|null $entity * @return bool */ public function showMembers(?Entity $entity = null): bool { if ($this->conf('members') !== '1') { return false; } if (! in_array($this->widget, [Widget::Preview, Widget::Recent, Widget::Random])) { return false; } $types = [ config('entities.ids.family'), config('entities.ids.organisation'), ]; // Preview, check the linked entity $entity = ! empty($entity) ? $entity : $this->entity; return $entity !== null && in_array($entity->typeId(), $types); } /** * Get the entities of a widget */ public function entities(int $page = 1) { if (isset($this->cachedEntities)) { return $this->cachedEntities; } $base = new Entity; $excludedTypes = []; if ($this->filterUnmentioned()) { $base = $base->unmentioned() ->whereNotIn($base->getTable() . '.type_id', $excludedTypes); } elseif ($this->filterMentionless()) { $base = $base->mentionless() ->whereNotIn($base->getTable() . '.type_id', $excludedTypes); } // Get only non archived entities $base = $base->whereNull('entities.archived_at'); $base = $base->select([ 'entities.id', 'entities.name', 'entities.is_private', 'entities.type_id', 'entities.entity_id', 'entities.image_path', 'entities.created_by', 'entities.updated_by', 'entities.updated_at', 'entities.image_uuid', ]); // Ordering $order = Arr::get($this->config, 'order', null); if (empty($order)) { $base = $base->recentlyModified(); } elseif ($order == 'oldest') { $base = $base->oldestModified(); } else { [$field, $order] = explode('_', $order); $base = $base->orderBy($field, $order); } $relations = [ 'image:campaign_id,id,ext,focus_x,focus_y', 'entityType:id,code,is_special', 'mentions', 'mentions.target', 'mentions.target.tags', 'mentions.target.entityType:id,code,is_special', ]; // If an entity type is provided, we can combine that with filters. We need to get the list of the misc // ids first to pass on to the entity query. if ($this->entityType && ! empty($this->config['filters']) && $this->entityType->isStandard()) { /** @var Character|mixed $model */ $model = $this->entityType->getClass(); /** @var FilterService $filterService */ $filterService = app()->make('App\Services\FilterService'); $filterService ->session(false) ->options($this->filterOptions()) ->model($model) ->entityType($this->entityType) ->make('dashboard'); $models = $model ->select($model->getTable() . '.id') ->filter($filterService->filters()) ->get(); $entityIds = $models->pluck('id'); // Add the filter to the base query $base = $base->whereIn('entities.entity_id', $entityIds); } return $this->cachedEntities = $base ->inTags($this->tags->pluck('id')->toArray()) ->inTypes($this->entityType?->id) ->with($relations) ->paginate(10, ['*'], 'page', $page); } /** * Get a random entity * Todo: refactor this code with the code from the quick link? * * @throws BindingResolutionException */ public function randomEntity() { $base = new Entity; if ($this->entityType) { if ($this->entityType->isCustom()) { return $base ->filter($this->filterOptions()) ->inTags($this->tags->pluck('id')->toArray()) ->whereNotIn('entities.id', Dashboard::excluding()) ->inTypes($this->entityType->id) ->with(['image', 'entityType', 'header', 'tags']) // We cannot use inRandomOrder, because for some reason, when copled with livewire, it always returns RAND(0) ->orderByRaw('RAND()') ->first(); } $model = $this->entityType->getClass(); /** @var FilterService $filterService */ $filterService = app()->make('App\Services\FilterService'); $filterService->entityType($this->entityType); $filterService ->session(false) ->options($this->filterOptions()) ->model($model) ->make('dashboard'); // @phpstan-ignore-next-line $models = $model ->select($model->getTable() . '.id') ->filter($filterService->filters()) ->get(); $entityIds = $models->pluck('id'); // Add the filter to the base query $base = $base->whereIn('entities.entity_id', $entityIds); } return $base ->inTags($this->tags->pluck('id')->toArray()) ->whereNotIn('type_id', [config('entities.ids.attribute_template'), config('entities.ids.conversation'), config('entities.ids.tag')]) ->whereNotIn('entities.id', Dashboard::excluding()) ->inTypes($this->entityType?->id) ->with(['image', 'entityType', 'header', 'tags']) ->orderByRaw('RAND()') ->first(); } /** * Get the widget filters */ public function filterOptions(): array { if (empty($this->config['filters'])) { return []; } try { $filters = []; $segments = explode('&', $this->config['filters']); foreach ($segments as $segment) { $params = explode('=', $segment); $name = $params[0]; if (Str::endsWith($name, '[]')) { $filters[mb_substr($name, 0, -2)][] = $params[1]; continue; } $filters[$params[0]] = $params[1]; } return $filters; } catch (Exception $e) { // Log::error('Widget error:' . $e->getMessage()); return []; } } /** * A way to set the entity, typically for the random widget */ public function setEntity(Entity $entity): self { $this->entity = $entity; return $this; } public function widgetIcon(): string { if ($this->widget === Widget::Recent) { return 'fa-regular fa-list'; } elseif ($this->widget === Widget::Header) { return 'fa-regular fa-heading'; } elseif ($this->widget === Widget::Preview) { return 'fa-regular fa-align-justify'; } elseif ($this->widget === Widget::Calendar) { return 'fa-regular fa-calendar'; } elseif ($this->widget === Widget::Random) { return 'fa-regular fa-dice-d20'; } elseif ($this->widget === Widget::Campaign) { return 'fa-regular fa-th-list'; } elseif ($this->widget === Widget::Welcome) { return 'fa-regular fa-party-horn'; } elseif ($this->widget === Widget::Onboarding) { return 'fa-regular fa-calendar-check'; } elseif ($this->widget === Widget::Gallery) { return 'fa-regular fa-images'; } return 'fa-regular fa-question-circle'; } public function customClass(Campaign $campaign): string { if (! $campaign->boosted()) { return ''; } if (empty($this->conf('class'))) { return ''; } return (string) $this->conf('class'); } public function customSize(): string { if (empty($this->conf('size'))) { return 'h3'; } return (string) $this->conf('size'); } protected function filterUnmentioned(): bool { return Arr::get($this->config, 'adv_filter') === 'unmentioned'; } protected function filterMentionless(): bool { return Arr::get($this->config, 'adv_filter') === 'mentionless'; } /** * Determine if a widget is visible. This is a simple check on the linked entity, if there is one. */ public function visible(): bool { // Not linked to an entity, easy if (empty($this->entity_id)) { return true; } // Linked but no entity or no child? Permission issue or deleted entity return ! empty($this->entity); } public function noGuest(): bool { return $this->widget == Widget::Onboarding; } /** * Some */ public function missingEntity(): bool { return in_array($this->widget, [ Widget::Calendar, Widget::Preview, Widget::Unmentioned, ]) && empty($this->entity); } } ================================================ FILE: app/Models/CampaignDashboardWidgetTag.php ================================================ */ public function tag(): BelongsTo { return $this->belongsTo(Tag::class); } /** * @return BelongsTo */ public function widget(): BelongsTo { return $this->belongsTo(CampaignDashboardWidget::class, 'id', 'widget_id'); } } ================================================ FILE: app/Models/CampaignDescription.php ================================================ */ public function campaign(): BelongsTo { return $this->belongsTo(Campaign::class); } } ================================================ FILE: app/Models/CampaignEvent.php ================================================ 'array', ]; } ================================================ FILE: app/Models/CampaignExport.php ================================================ CampaignExportStatus::class, ]; protected string $userField = 'created_by'; /** * Automatically prune old elements from the db */ public function prunable(): Builder { return static::where('updated_at', '<=', now()->subDays(90)); } /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } public function finished(): bool { return $this->status === CampaignExportStatus::finished; } public function running(): bool { return $this->status === CampaignExportStatus::running; } public function scheduled(): bool { return $this->status === CampaignExportStatus::scheduled; } public function failed(): bool { return $this->status === CampaignExportStatus::failed; } } ================================================ FILE: app/Models/CampaignFilter.php ================================================ CampaignFilterType::class, ]; public function campaign() { return $this->belongsTo(Campaign::class); } } ================================================ FILE: app/Models/CampaignFlag.php ================================================ CampaignFlags::class, ]; } ================================================ FILE: app/Models/CampaignFollower.php ================================================ */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } } ================================================ FILE: app/Models/CampaignGenre.php ================================================ */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } /** * @return BelongsTo */ public function genre(): BelongsTo { return $this->belongsTo('App\Models\Genre', 'genre_id', 'id'); } } ================================================ FILE: app/Models/CampaignImport.php ================================================ 'array', 'logs' => 'array', 'errors' => 'array', 'status_id' => CampaignImportStatus::class, ]; public $sortable = [ 'status_id', 'updated_at', 'created_by', ]; /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo(Campaign::class); } /** * Automatically prune old elements from the db */ public function prunable(): Builder { return static::where(function ($query) { // Stuck in PREPARED or QUEUED > 24h $query->whereIn('status_id', [ CampaignImportStatus::PREPARED, CampaignImportStatus::QUEUED, ]) ->where('created_at', '<=', now()->subDay()); }) // CSV imports that are (READY) > 24h ago ->orWhere(function ($query) { $query->where('status_id', CampaignImportStatus::READY) ->where('updated_at', '<=', now()->subHours(24)); }) // Existing: Catch-all cleanup for very old records ->orWhere('created_at', '<=', now()->subDays(config('imports.prune'))); } public function isPrepared(): bool { return $this->status_id == CampaignImportStatus::PREPARED; } public function isFailed(): bool { return $this->status_id == CampaignImportStatus::FAILED; } public function isCsv(): bool { $files = $this->config['files']; foreach ($files as $file) { if (is_string($file) && str_ends_with($file, '.csv')) { return true; } } return false; } protected function pruning(): void { $files = Arr::get($this->config, 'files'); if (empty($files)) { return; } foreach ($files as $file) { Storage::disk('s3')->delete($file); Storage::disk('export')->delete($file); } } } ================================================ FILE: app/Models/CampaignInvite.php ================================================ */ public function role(): BelongsTo { return $this->belongsTo('App\Models\CampaignRole', 'role_id', 'id'); } } ================================================ FILE: app/Models/CampaignPermission.php ================================================ */ public function campaignRole(): BelongsTo { return $this->belongsTo('App\Models\CampaignRole', 'campaign_role_id', 'id'); } /** * Optional entity * * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id'); } public function scopeRoleIDs(Builder $query, array $roleIds): Builder { return $query->whereIn('campaign_role_id', $roleIds); } public function scopeAction(Builder $query, int $action): Builder { return $query->whereIn('action', $action); } /** * Copy an entity permission to another target */ public function copyTo(Entity $target) { $new = $this->replicate(['entity_id']); $new->entity_id = $target->id; return $new->save(); } /** * Determine if the permission's action is the wanted one */ public function isAction(int $action): bool { return $this->action === $action; } /** * Get the "key" of the permission, used for caching and lookup in the permission engines */ public function key(): string { // Campaign actions have a different cache key if ($this->action >= 10) { return 'campaign_' . $this->action; } // If there is no entity attached, just go entity type + action if (! $this->entity_id) { return $this->entity_type_id . '_' . $this->action; } return '_' . $this->action . '_' . $this->entity_id; } public function isGallery(): bool { $galleryPermissions = [ self::ACTION_GALLERY, self::ACTION_GALLERY_BROWSE, self::ACTION_GALLERY_UPLOAD, ]; return in_array($this->action, $galleryPermissions); } public function isWhiteboard(): bool { $galleryPermissions = [ self::ACTION_WHITEBOARDS_VIEW, self::ACTION_WHITEBOARDS_CREATE, ]; return in_array($this->action, $galleryPermissions); } public function isTemplate(): bool { $templatePermissions = [ self::ACTION_TEMPLATES, self::ACTION_POST_TEMPLATES, ]; return in_array($this->action, $templatePermissions); } public function isBookmark(): bool { $templatePermissions = [ self::ACTION_BOOKMARKS, ]; return in_array($this->action, $templatePermissions); } } ================================================ FILE: app/Models/CampaignPlugin.php ================================================ */ public function plugin(): BelongsTo { return $this->belongsTo(Plugin::class); } /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo(Campaign::class); } /** * @return BelongsTo */ public function version(): BelongsTo { return $this->belongsTo(PluginVersion::class, 'plugin_version_id'); } public function scopeTemplates(Builder $builder, Campaign $campaign): Builder { return $builder->leftJoin('plugins as p', 'p.id', 'plugin_id') ->where('campaign_id', $campaign->id) ->where('p.type_id', 2) ->where('is_active', true) ->with('version') ->orderBy('p.name'); } public function canEnable(): bool { return $this->plugin->isTheme() && ! $this->is_active; } public function canDisable(): bool { return $this->plugin->isTheme() && $this->is_active; } /** * Determine if the plug can be rendered. This is needed for character sheets in draft statuses, * to only render a sheet for the author, as they can potentially add XSS injections. */ public function renderable(): bool { if (! $this->plugin->isAttributeTemplate()) { return false; } elseif ($this->version->status_id === 3) { // Published version? We good return true; } // The user needs to be an author return $this->isAuthor(); } /** * Check if the current user is an author of a plugin */ public function isAuthor(): bool { if (auth()->guest()) { return false; } return $this->plugin->created_by === auth()->user()->id; } } ================================================ FILE: app/Models/CampaignRole.php ================================================ is_public; } /** * Determine if the campaign role is the campaign's admin role */ public function isAdmin(): bool { return $this->is_admin; } /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } /** * @return HasMany */ public function users(): HasMany { return $this->hasMany('App\Models\CampaignRoleUser', 'campaign_role_id'); } /** * @return HasMany */ public function dashboardRoles(): HasMany { return $this->hasMany(CampaignDashboardRole::class, 'campaign_role_id', 'id'); } /** * Filter on a campaign role's public status */ public function scopePublic(Builder $query, int $value = 1): Builder { return $query->where('is_public', $value); } /** * Get all roles except admin */ public function scopeWithoutAdmin(Builder $query): Builder { // @phpstan-ignore-next-line return $query->admin(false); } /** * Get the admin role */ public function scopeAdmin(Builder $query, bool $with = true): Builder { return $query->where('is_admin', $with); } /** * @return HasMany */ public function permissions(): HasMany { return $this->hasMany('App\Models\CampaignPermission', 'campaign_role_id'); } /** * @return HasMany */ public function rolePermissions() { return $this->permissions() ->whereNull('entity_id'); } public function scopeSearch(Builder $builder, ?string $search = null): Builder { return $builder ->where('name', 'like', "%{$search}%"); } public function url(string $sub): string { return 'campaign_roles.' . $sub; } public function duplicate(CampaignRole $campaignRole): self { foreach ($this->permissions as $permission) { $newPermission = $permission->replicate(['campaign_role_id']); $newPermission->campaign_role_id = $campaignRole->id; $newPermission->save(); } return $this; } } ================================================ FILE: app/Models/CampaignRoleUser.php ================================================ */ public function campaignRole(): BelongsTo { return $this->belongsTo('App\Models\CampaignRole', 'campaign_role_id', 'id'); } public function recentlyCreated(): bool { return $this->created_at->diffInMinutes() <= 15; } } ================================================ FILE: app/Models/CampaignSetting.php ================================================ */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } /** * Count the number of activated modules */ public function countEnabledModules(): int { $count = 0; foreach ($this->fillable as $col) { if ($col != 'campaign_id' && $this->$col == true) { $count++; } } return $count; } } ================================================ FILE: app/Models/CampaignStyle.php ================================================ where('is_enabled', $enabled); } public function scopeTheme(Builder $query, bool $theme = true): Builder { return $query->where('is_theme', $theme); } public function length(): string { return (string) Number::format(mb_strlen($this->content)); } public function url(string $sub): string { return 'campaign_styles.' . $sub; } public function isTheme(): bool { return $this->is_theme; } public function content(): ?string { if (! $this->isTheme()) { return $this->content; } try { $theme = [':root {']; $config = json_decode($this->content); foreach ($config as $k => $v) { $theme[] = ' --' . $k . ': ' . $v . ';'; } $theme[] = '}'; return implode("\n", $theme); } catch (Exception $e) { return '/** Issue with the theme, please contact us */' . "\n\n"; } } public function jsonConfig(): string { if (empty($this->content)) { return ''; } $rootless = Str::remove([':root ', "\n"], $this->content); $json = json_encode($rootless); dd($json); } } ================================================ FILE: app/Models/CampaignSystem.php ================================================ */ public function gameSystem(): BelongsTo { return $this->belongsTo(GameSystem::class); } } ================================================ FILE: app/Models/CampaignUser.php ================================================ */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } /** * Get the user's roles * * @return HasManyThrough */ public function roles(): HasManyThrough { return $this->hasManyThrough( 'App\Models\CampaignRole', 'App\Models\CampaignRoleUser', 'user_id', 'id', 'user_id', 'campaign_role_id' ) ->where('campaign_id', $this->campaign_id); } /** * Determin if the user is part of an admin role */ public function isAdmin(): bool { return $this->roles()->where(['is_admin' => true])->count() > 0; } public function scopeSearch(Builder $builder, ?string $search = null): Builder { return $builder ->select($this->getTable() . '.*') ->leftJoin('users as u', 'u.id', $this->getTable() . '.user_id') ->where('u.name', 'like', "%{$search}%"); } /** * Only get users of a campaign who aren't admins (used for the bulk permission UI) */ public function scopeWithoutAdmins(Builder $builder): Builder { return $builder->whereNotExists(function ($query) { $query->from('campaign_role_users as cru') ->join('campaign_roles as cr', 'cr.id', 'cru.campaign_role_id') ->whereColumn('cru.user_id', $this->getTable() . '.user_id') ->whereColumn('cr.campaign_id', $this->getTable() . '.campaign_id') ->where('cr.is_admin', true); }); } public function scopeCampaignUser(Builder $builder, int $campaignID, int $userID): Builder { return $builder ->where('campaign_id', $campaignID) ->where('user_id', $userID); } } ================================================ FILE: app/Models/CategoryStatus.php ================================================ 'boolean', ]; } /** * @return BelongsTo */ public function entityType(): BelongsTo { return $this->belongsTo(EntityType::class, 'category_id'); } public function name(): string { return trans('entities/statuses.' . $this->entityType->code . '.' . $this->key); } public function icon(): string { return 'fa-regular ' . ($this->icon ?? ''); } public function isCustom(): bool { return false; } } ================================================ FILE: app/Models/Character.php ================================================ 'clearSuggestion', ]; protected array $sanitizable = [ 'name', 'sex', 'pronouns', 'title', 'age', ]; /** * Casting for order by * * @var array */ protected $orderCasting = [ 'age' => 'unsigned', ]; /** * Explicit filters */ protected array $explicitFilters = [ 'sex', ]; /** * Foreign relations to add to export */ protected array $foreignExport = [ 'characterTraits', 'characterFamilies', 'characterRaces', 'organisationMemberships', ]; /** * @var string[] Extra relations loaded for the API endpoint */ public array $apiWith = ['characterTraits', 'characterRaces', 'characterFamilies']; /** * Filter for characters in a specific list of organisations */ public function scopeMember(Builder $query, ?string $value, FilterOption $filter): Builder { if ($filter === FilterOption::NONE) { // If called with a param, it's being called too early and will be called later in the process if (! empty($value)) { return $query; } $query ->select($this->getTable() . '.*') ->leftJoin('organisation_member as memb', function ($join) { $join->on('memb.character_id', '=', $this->getTable() . '.id'); }) ->where('memb.organisation_id', null); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('memb.is_private', 0); } return $query; } elseif ($filter === FilterOption::EXCLUDE) { return $query ->whereRaw('(select count(*) from organisation_member as memb where memb.character_id = ' . $this->getTable() . '.id and memb.character_id = ' . ((int) $value) . ' ' . $this->subPrivacy('and memb.is_private') . ') = 0'); } $ids = [$value]; if ($filter === FilterOption::CHILDREN) { /** @var ?Organisation $model */ $model = Organisation::find($value); if (! empty($model)) { $ids = [...$model->entity->descendants->pluck('id')->toArray(), $model->id]; } } $query ->select($this->getTable() . '.*') ->leftJoin('organisation_member as memb', function ($join) { $join->on('memb.character_id', '=', $this->getTable() . '.id'); }) ->whereIn('memb.organisation_id', $ids); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('memb.is_private', 0); } return $query->distinct(); } /** * @return BelongsToMany */ public function families(): BelongsToMany { return $this->belongsToMany(Family::class) ->orderBy('character_family.id') ->with([ 'entity' => function ($sub) { $sub->select('id', 'name', 'entity_id', 'type_id'); }, ]); } /** * @return HasMany */ public function characterFamilies(): HasMany { return $this->hasMany(CharacterFamily::class, 'character_id') ->orderBy('id') ->has('family') ->has('family.entity') ->with([ 'family' => function ($sub) { $sub->select('id', 'name', 'is_private'); }, 'family.entity' => function ($sub) { $sub->select('id', 'name', 'entity_id', 'type_id'); }, ]); } /** * @return HasMany */ public function characterRaces(): HasMany { return $this->hasMany(CharacterRace::class, 'character_id') ->orderBy('id') ->has('race') ->has('race.entity') ->with([ 'race' => function ($sub) { $sub->select('id', 'name', 'is_private'); }, 'race.entity' => function ($sub) { $sub->select('id', 'name', 'entity_id', 'type_id'); }, ]); } /** * @return BelongsToMany */ public function races(): BelongsToMany { return $this->belongsToMany(Race::class) ->orderBy('character_race.id') ->with([ 'entity' => function ($sub) { $sub->select('id', 'name', 'entity_id', 'type_id'); }, ]); } /** * @return HasMany */ public function organisationMemberships(): HasMany { return $this->hasMany('App\Models\OrganisationMember', 'character_id', 'id'); } /** * @return BelongsToMany */ public function organisations(): BelongsToMany { return $this->belongsToMany('App\Models\Organisation', 'organisation_member') ->orderBy('organisation_member.id') ->with([ 'entity' => function ($sub) { $sub->select('id', 'name', 'entity_id', 'type_id'); }, ]); } /** * @return HasMany */ public function items(): HasMany { return $this->hasMany('App\Models\Item', 'character_id', 'id'); } /** * @return HasMany */ public function diceRolls(): HasMany { return $this->hasMany('App\Models\DiceRoll', 'character_id', 'id'); } /** * @return HasManyThrough */ public function conversations(): HasManyThrough { return $this->hasManyThrough( 'App\Models\Conversation', 'App\Models\ConversationParticipant', 'character_id', 'id', 'id', 'conversation_id' ); } /** * @return HasMany */ public function conversationParticipants(): HasMany { return $this->hasMany('App\Models\ConversationParticipant', 'character_id'); } /** * @return HasMany */ public function characterTraits(): HasMany { return $this->hasMany('App\Models\CharacterTrait', 'character_id', 'id'); } public function appearances() { return $this->characterTraits()->appearance()->orderBy('default_order'); } public function personality() { return $this->characterTraits()->personality()->orderBy('default_order'); } public function pinnedMembers() { return $this ->organisationMemberships() ->has('organisation') ->with(['organisation', 'organisation.entity']) ->whereIn('pin_id', [ OrganisationMemberPin::character, OrganisationMemberPin::both, ]) ->orderBy('role'); } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { foreach ($this->items as $child) { $child->character_id = null; $child->save(); } foreach ($this->diceRolls as $child) { $child->character_id = null; $child->save(); } $this->organisations()->detach(); $this->races()->detach(); $this->families()->detach(); } /** * Tooltip subtitle (character title) */ public function tooltipSubtitle(): string { if (empty($this->title)) { return ''; } return strip_tags($this->title); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.character'); } /** * Determine if the character has profile data to be displayed */ public function showProfileInfo(): bool { // Test text fields first if (! empty($this->age) || ! empty($this->sex) || ! empty($this->pronouns)) { return true; } if ($this->characterRaces->isNotEmpty() || $this->characterFamilies->isNotEmpty()) { return true; } if ($this->entity->elapsedEvents->isNotEmpty()) { return true; } return parent::showProfileInfo(); } /** * Determine if the character has an age. 0 counts as a valide age. */ public function hasAge(): bool { return ! empty($this->age) || $this->age === '0'; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'title', 'age', 'sex', 'pronouns', 'locations', 'organisations', 'races', 'families', 'member_id', 'race_id', 'family_id', 'races', ]; } /** * Available sorting on the grid view */ public function datagridSortableColumns(): array { $columns = [ 'name' => __('crud.fields.name'), 'type' => __('crud.fields.type'), 'title' => __('characters.fields.title'), 'sex' => __('characters.fields.sex'), 'status_id' => __('characters.fields.status'), 'locations.name' => __('entities.locations'), ]; if (auth()->check() && auth()->user()->isAdmin()) { $columns['is_private'] = __('crud.fields.is_private'); } return $columns; } public function scopeFilteredCharacters(Builder $query): Builder { // @phpstan-ignore-next-line return $query ->select([$this->getTable() . '.id', 'title', $this->getTable() . '.is_private']) ->sort(request()->only(['o', 'k']), ['entities.name' => 'asc']) ->with([ 'characterRaces', 'characterFamilies', 'entity', 'entity.tags', 'entity.tags.entity', 'entity.image', 'entity.locations', 'entity.status']) ->has('entity'); } } ================================================ FILE: app/Models/CharacterFamily.php ================================================ */ public function character(): BelongsTo { return $this->belongsTo(Character::class); } /** * @return BelongsTo */ public function family(): BelongsTo { return $this->belongsTo(Family::class); } public function getCharacterFamiliesAttribute() { return $this->character->races; } public function exportFields(): array { return [ 'character_id', 'family_id', ]; } } ================================================ FILE: app/Models/CharacterOrganisation.php ================================================ */ public function character(): BelongsTo { return $this->belongsTo(Character::class); } /** * @return BelongsTo */ public function race(): BelongsTo { return $this->belongsTo(Race::class); } public function getCharacterRacesAttribute() { return $this->character->races; } } ================================================ FILE: app/Models/CharacterTrait.php ================================================ */ public function character(): BelongsTo { return $this->belongsTo('App\Models\Character', 'character_id'); } public function scopePersonality(Builder $query): Builder { return $query->where('section_id', self::SECTION_PERSONALITY); } public function scopeAppearance(Builder $query): Builder { return $query->where('section_id', self::SECTION_APPEARANCE); } public function copyTo(int $character): self { $copy = $this->replicate(['character_id']); $copy->character_id = $character; $copy->save(); return $this; } public function exportFields(): array { return [ 'name', 'entry', 'is_private', 'section_id', 'default_order', ]; } } ================================================ FILE: app/Models/CommunityEvent.php ================================================ 'date', 'end_at' => 'date', ]; /** * Determine if the event can be participated in */ public function isOngoing(): bool { return $this->start_at->isPast() && $this->end_at->isFuture(); } /** * Determine if the event is in the future */ public function isScheduled(): bool { return $this->start_at->isFuture(); } /** * Get the image (or default image) of an entity */ public function thumbnail(int $width = 400, ?int $height = null, string $field = 'image'): string { if (empty($this->$field)) { return ''; } return Img::crop($width, (! empty($height) ? $height : $width))->url($this->$field); } /** * @return HasMany */ public function entries(): HasMany { return $this->hasMany(CommunityEventEntry::class); } public function getSlug(): string { return $this->uuid . '-' . Str::slug($this->name); } /** * @return Model|HasMany|object|null */ public function userEntry(int $userId) { return $this->entries()->where('created_by', $userId)->first(); } public function rankedResults() { return $this->entries() ->with('user') ->has('user') ->where(function ($rank) { // MySQL is weird where != -1 means that null gets also caught? return $rank ->whereNull('rank') ->orWhere('rank', '<>', -1); }) ->orderByRaw('-rank desc'); } /** * Determine if the event is finished & has a winner */ public function hasRankedResults(): bool { return ! $this->rankedResults->where('rank', 1)->isEmpty(); } /** * @return BelongsTo */ public function jury(): BelongsTo { return $this->belongsTo(User::class, 'jury_id'); } } ================================================ FILE: app/Models/CommunityEventEntry.php ================================================ */ public function event(): BelongsTo { return $this->belongsTo(CommunityEvent::class, 'community_event_id'); } } ================================================ FILE: app/Models/CommunityVote.php ================================================ 'date', 'published_at' => 'date', ]; /** * @return HasMany */ public function ballots(): HasMany { return $this->hasMany(CommunityVoteBallot::class); } /** * Get the vote status as a string */ public function status(): string { if ($this->cachedStatus === false) { if (empty($this->visible_at)) { $this->cachedStatus = self::STATUS_DRAFT; } elseif ($this->visible_at->isFuture()) { $this->cachedStatus = self::STATUS_SCHEDULED; } elseif (empty($this->published_at) || $this->published_at->isFuture()) { $this->cachedStatus = self::STATUS_VOTING; } else { $this->cachedStatus = self::STATUS_PUBLISHED; } } return $this->cachedStatus; } public function getSlug(): string { return $this->id . '-' . Str::slug($this->name); } /** * Get the options as an array expression */ public function options(): array { if (empty($this->options)) { return []; } $options = json_decode($this->options, true); if (is_array($options)) { return $options; } return []; } public function ballotWidth(string $option): int { return Arr::get($this->voteStats(), $option, 0); } public function isVoting(): bool { return $this->status() === self::STATUS_VOTING; } /** * Returns if a user casted a ballot for this vote */ public function votedFor(string $option): bool { if (Auth::guest()) { return false; } $user = Auth::user(); return $this->ballots->contains(function ($ballot) use ($user, $option) { return $ballot->user_id === $user->id && $ballot->vote === $option; }); } public function voteStats(): array { if ($this->cachedResults === false) { // Prepare null results $this->cachedResults = []; foreach ($this->options() as $key => $name) { $this->cachedResults[$key] = 0; } $totalBallots = $this->ballots->count(); $votes = $this->ballots()->groupBy('vote')->select('vote', \DB::raw('count(*) as total'))->get(); foreach ($votes as $vote) { // @phpstan-ignore-next-line $this->cachedResults[$vote->vote] = floor(($vote->total / $totalBallots) * 100); } } return $this->cachedResults; } } ================================================ FILE: app/Models/CommunityVoteBallot.php ================================================ */ public function vote(): BelongsTo { return $this->belongsTo(CommunityVote::class); } } ================================================ FILE: app/Models/Concerns/Acl.php ================================================ where($this->getTable() . '.is_private', $private); } } ================================================ FILE: app/Models/Concerns/Blameable.php ================================================ */ public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } /** * Get the user who updated this model * * @return BelongsTo */ public function updater(): BelongsTo { return $this->belongsTo(User::class, 'updated_by'); } /** * Get the user who deleted this model * * @return BelongsTo */ public function remover(): BelongsTo { return $this->belongsTo(User::class, 'deleted_by'); } public function scopeCreatedBy(Builder $query, $userId): Builder { if ($userId instanceof Model) { $userId = $userId->getKey(); } return $query->where(['created_by' => $userId]); } public function scopeUpdatedBy(Builder $query, $userId): Builder { if ($userId instanceof Model) { $userId = $userId->getKey(); } return $query->where(['updated_by' => $userId]); } /** * Check if the current model uses SoftDeletes. */ public function useSoftDeletes(): bool { return in_array(SoftDeletes::class, class_uses_recursive($this), true); } } ================================================ FILE: app/Models/Concerns/Boosted.php ================================================ */ public function boosts(): HasMany { return $this->hasMany('App\Models\CampaignBoost', 'campaign_id', 'id') ->with('user:id,name,pledge'); } /** * Determine if the campaign is boosted */ public function boosted(bool $superboosted = false): bool { if (request()->get('_boosted') === '0') { return false; } return $this->boost_count > ($superboosted ? 2 : 0); } /** * Determine if a campaign is superboosted */ public function superboosted(): bool { return $this->boosted(true); } public function legacyBoosted(): bool { return $this->boost_count > 0 && $this->boost_count < 4; } /** * Determine if a campaign is premium */ public function premium(): bool { if (request()->get('_boosted') === '0') { return false; } return $this->boost_count >= 4; } /** * Determine if a campaign is boosted by a wyvern */ public function isWyvern(): bool { $boost = $this->boosts->first(); return $boost?->user->isWyvern() ?? false; } /** * Determine if a campaign is boosted by an elemental */ public function isElemental(): bool { $boost = $this->boosts->first(); return $boost?->user->isElemental() ?? false; } } ================================================ FILE: app/Models/Concerns/CampaignLimit.php ================================================ boosted()) { return null; } return config('limits.campaigns.members'); } /** * Get the role limit for the campaign */ public function roleLimit(): ?int { if ($this->boosted()) { return null; } return config('limits.campaigns.roles'); } /** * Get the quick link limit for the campaign */ public function bookmarkLimit(): ?int { if ($this->boosted()) { return null; } return config('limits.campaigns.bookmarks'); } /** * Determine if the campaign can have more roles added to it */ public function canHaveMoreRoles(): bool { $limit = $this->roleLimit(); if (empty($limit)) { return true; } return $this->roles()->count() < $limit; } /** * Determine if the campaign can have more members added to it */ public function canHaveMoreMembers(): bool { $limit = $this->memberLimit(); if (empty($limit)) { return true; } return $this->members()->count() < $limit; } /** * Determine if the campaign can have more quick links added */ public function canHaveMoreBookmarks(): bool { $limit = $this->bookmarkLimit(); if (empty($limit)) { return true; } return $this->bookmarks()->count() < $limit; } } ================================================ FILE: app/Models/Concerns/CompositeKey.php ================================================ getKeyName(); if (! is_array($keys)) { return parent::setKeysForSaveQuery($query); } foreach ($keys as $keyName) { $query->where($keyName, '=', $this->getKeyForSaveQuery($keyName)); } return $query; } /** * Get the primary key value for a save query. */ protected function getKeyForSaveQuery(?string $keyName = null) { if ($keyName === null) { $keyName = $this->getKeyName(); } if (isset($this->original[$keyName])) { return $this->original[$keyName]; } return $this->getAttribute($keyName); } } ================================================ FILE: app/Models/Concerns/Copiable.php ================================================ isLink; } public function isFile(): bool { return $this->isFile; } public function isAlias(): bool { return $this->isAlias; } } ================================================ FILE: app/Models/Concerns/EntityLogs.php ================================================ hasUpdateLog = false; return $this; } public function hasUpdateLog(): bool { return $this->hasUpdateLog; } /** * Boot the trait's observers */ public static function bootEntityLogs(): void { // Don't add this observer if in console mode if (app()->runningInConsole()) { return; } static::observe(app(EntityLogObserver::class)); } } ================================================ FILE: app/Models/Concerns/EntityType.php ================================================ type_id === config('entities.ids.ability'); } public function isCharacter(): bool { return $this->type_id === config('entities.ids.character'); } public function isLocation(): bool { return $this->type_id === config('entities.ids.location'); } public function isFamily(): bool { return $this->type_id === config('entities.ids.family'); } public function isMap(): bool { return $this->type_id === config('entities.ids.map'); } public function isQuest(): bool { return $this->type_id === config('entities.ids.quest'); } public function isOrganisation(): bool { return $this->type_id === config('entities.ids.organisation'); } public function isRace(): bool { return $this->type_id === config('entities.ids.race'); } public function isTimeline(): bool { return $this->type_id === config('entities.ids.timeline'); } public function isCreature(): bool { return $this->type_id === config('entities.ids.creature'); } public function isEvent(): bool { return $this->type_id === config('entities.ids.event'); } public function isDiceRoll(): bool { return $this->type_id === config('entities.ids.dice_roll'); } public function isAttributeTemplate(): bool { return $this->type_id === config('entities.ids.attribute_template'); } public function isTag(): bool { return $this->type_id === config('entities.ids.tag'); } public function isItem(): bool { return $this->type_id === config('entities.ids.item'); } } ================================================ FILE: app/Models/Concerns/HasCampaign.php ================================================ withCampaignLimit = false; return $builder; } /** * Check if limited to the current campaign context */ public function withCampaignLimit(): bool { return $this->withCampaignLimit; } /** * @return void */ public static function bootHasCampaign() { static::addGlobalScope(new CampaignScope); } /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo(Campaign::class, 'campaign_id'); } } ================================================ FILE: app/Models/Concerns/HasEntity.php ================================================ runningInConsole() && ! app()->runningUnitTests()) { return; } static::observe(app(EntryObserver::class)); } public function entryFieldName(): string { return $this->entryField ?? 'entry'; } public function tooltipFieldName(): string { return $this->tooltipField ?? 'tooltip'; } /** * Get the entry where mentions are parsed to html links */ public function parsedEntry(): string { return Mentions::mapAny($this, $this->entryFieldName()); } /** * Get the entry where mentions are made to look nice for the text editor */ public function getEntryForEditionAttribute(): string { return Mentions::parseForEdit($this, $this->entryFieldName()); } /** * Determine if the marker has a filled out entry */ public function hasEntry(): bool { // If all that's in the entry is two \n, then there is no real content $stripped = mb_trim(preg_replace('/\s\s+/', ' ', $this->{$this->entryFieldName()})); return ! empty($stripped); } } ================================================ FILE: app/Models/Concerns/HasFilters.php ================================================ , %%, %{term}, etc */ protected string $filterOperator; protected array $filterParams = []; /** * Get all available filterable columns of the entity. Merge the custom * with the default ones (if not overwritten) */ public function getFilterableColumns(): array { $custom = []; if (method_exists($this, 'filterableColumns')) { $custom = $this->filterableColumns(); } $default = $this->defaultFilterableColumns(); return array_unique(array_merge($custom, $default)); } /** * Default available filterable columns to every model using the HasFilters trait. * * @return string[] */ protected function defaultFilterableColumns(): array { return [ 'name', 'type', 'is_private', 'template', 'tag_id', 'tags', 'has_image', 'has_posts', 'has_entry', 'has_entity_files', 'has_attributes', 'created_by', 'updated_by', 'attribute_name', 'attribute_value', 'connections', 'connection_target', 'connection_name', 'archived', 'parent_id', 'status_id', ]; } public function scopeFilter(Builder $query, array $params = []): Builder { $fields = $this->getFilterableColumns(); if (! is_array($params) || empty($params) || empty($fields)) { return $query; } $this->filterParams = $params; foreach ($this->filterParams as $key => $value) { if (isset($value) && in_array($key, $fields)) { // The requested field is an array, which we don't support for anything other than tags, and locations ("or" searches) if (is_array($value) && ! in_array($key, ['tags', 'locations', 'organisations', 'races', 'families'])) { continue; } $this->filterOption = ! empty($params[$key . '_option']) ? $params[$key . '_option'] : null; $this->extractSearchOperator($value, $key); // Foreign key search $segments = explode('-', $key); if (count($segments) > 1) { $this->foreign($query, $segments[0], $key); return $query; } // Explicit filters (numbers typically, foreign ids) if (in_array($key, $this->explicitFilters())) { if ($this->filterOperator == 'IS NULL') { $query->whereNull($this->getTable() . '.' . $key); } else { $query->where($this->getTable() . '.' . $key, $this->filterOperator, $this->filterValue); } continue; } if ($key === 'tags') { $this->filterTags($query, $value); } elseif ($key === 'archived') { $this->filterArchived($query, $value); } elseif ($key == 'locations') { $this->filterLocations($query, $value); } elseif ($key == 'organisations') { $this->filterOrganisations($query, $value); } elseif ($key == 'races') { $this->filterRaces($query, $value); } elseif ($key == 'families') { $this->filterFamilies($query, $value); } elseif (in_array($key, ['date_start', 'date_end'])) { $this->filterDateRange($query, $key, $params); } elseif ($key == 'races') { $this->filterRaces($query, $value); } elseif ($key == 'location_id') { $this->filterLocation($query, $value, $key); } elseif ($key == 'tag_id') { $query ->joinEntity() ->leftJoin('entity_tags as et', 'et.entity_id', 'e.id') ->where('et.tag_id', $value); } elseif (in_array($key, ['attribute_value', 'attribute_name'])) { $this->filterAttributes($query, $key); } elseif (in_array($key, ['connection_target', 'connection_name'])) { $this->filterConnections($query, $key); } elseif ($key == 'race_id') { $this->filterRace($query, $value); } elseif ($key == 'family_id') { $this->filterFamily($query, $value); } elseif ($key == 'member_id') { $this->filterMember($query, $value); } elseif ($key == 'quest_element_id') { $query->element($value, $this->getFilterOption()); } elseif ($key == 'element_role') { $query->elementRole($value, $this->filterOperator); } elseif ($key == 'has_image') { $this->filterHasImage($query, $value); } elseif ($key == 'template') { $this->filterTemplate($query, $value); } elseif ($key == 'type') { $this->filterType($query, $value); } elseif ($key == 'has_posts') { $this->filterHasPosts($query, $value); } elseif ($key == 'has_entry') { $this->filterHasEntry($query, $value); } elseif ($key == 'is_equipped') { $this->filterIsEquipped($query, $value); } elseif ($key == 'has_attributes') { $this->filterHasAttributes($query, $value); } elseif ($key == 'has_entity_files') { $this->filterHasFiles($query, $value); } elseif ($key == 'parent') { $this->filterParent($query); } elseif ($key == 'parent_id') { $this->filterParent($query); } elseif ($key == 'status_id') { $this->filterStatus($query); } elseif (in_array($key, ['created_by', 'updated_by'])) { $query ->joinEntity() ->where('e.' . $key, (int) $value); } elseif ($this->filterOperator === 'IS NULL') { $query->where(function ($sub) use ($key) { $sub->whereNull($this->getTable() . '.' . $key) ->orWhere($this->getTable() . '.' . $key, '=', ''); }); } else { // If we have an exclude option filter, change the operator $this->filterFallback($query, $key); } } elseif (Str::endsWith($key, '_option') && $value == 'none') { $this->filterNoneOptions($query, $key, $fields); } elseif ($key == 'archived' && ! isset($value)) { $query ->joinEntity() ->whereNull('e.archived_at'); } } return $query; } /** * @param string|array $value (array for tags) */ protected function extractSearchOperator($value, string $key): void { $operator = 'like'; $filterValue = $value; if (! in_array($key, ['tags', 'locations', 'organisations', 'races', 'families'])) { if ($value == '!!') { $operator = 'IS NULL'; $filterValue = null; } elseif (Str::startsWith($value, '!')) { $operator = 'not like'; $filterValue = mb_ltrim($value, '!'); } elseif (Str::endsWith($value, '!')) { $operator = '='; $filterValue = mb_rtrim($value, '!'); } elseif (Str::endsWith($key, '_id')) { $operator = '='; } } $this->filterOperator = $operator; $this->filterValue = $filterValue; } /** * Add a query on a foreign relationship of the model */ protected function foreign(Builder $query, string $relationName, string $key): void { $relation = $this->{$relationName}(); $foreignName = $relation->getQuery()->getQuery()->from; $query ->select($this->getTable() . '.*') ->with($relationName) ->leftJoin($foreignName . ' as f', 'f.id', $this->getTable() . '.' . $relation->getForeignKeyName()) ->where( str_replace($relationName, 'f', str_replace('-', '.', $key)), $this->filterOperator, ($this->filterOperator == '=' ? $this->filterValue : "%{$this->filterValue}%") ); } /** * Determine if the filter option is the one required */ protected function filterOption(string $condition): bool { return ! empty($this->filterOption) && $this->filterOption === $condition; } /** * Filter on the attributes of an entity */ protected function filterAttributes(Builder $query, string $key): void { if ($key == 'attribute_value') { return; } $query->joinEntity(); // No attribute with this name if ($this->filterOperator === 'not like') { $query ->whereRaw('(select count(*) from attributes as att where att.entity_id = e.id and att.name = \'' . ($this->filterValue) . '\') = 0'); return; } $query ->select($this->getTable() . '.*') ->leftJoin('attributes as att', function ($join) { $join->on('att.entity_id', '=', 'e.id'); }) ->where('att.name', $this->filterValue); $attributeValue = Arr::get($this->filterParams, 'attribute_value'); if ($attributeValue === '!') { $query ->whereRaw('att.value <> ""'); } elseif ($attributeValue !== '' && $attributeValue !== null) { $query ->where('att.value', $attributeValue); } } /** * Filter on the connections of an entity */ protected function filterConnections(Builder $query, string $key): void { if ($key == 'connection_target' && Arr::get($this->filterParams, 'connection_name')) { return; } $query->joinEntity(); $query ->leftJoin('relations as rel', function ($join) { $join->on('rel.owner_id', '=', 'e.id'); }); $connectionTarget = Arr::get($this->filterParams, 'connection_target'); if ($connectionTarget !== '' && $connectionTarget !== null) { $query ->where('rel.target_id', $connectionTarget); } $connectionName = Arr::get($this->filterParams, 'connection_name'); if ($connectionName !== '' && $connectionName !== null) { $connectionName = $this->filterValue; if ($this->filterOperator != '=') { $connectionName = '%' . $this->filterValue . '%'; } $query ->where('rel.relation', $this->filterOperator, $connectionName); } } /** * General fallback filter for what wasn't cought in specific cases */ protected function filterFallback(Builder $query, string $key): void { if ($this->filterOption('exclude')) { $query->where(function ($subquery) use ($key) { return $subquery->where( $this->getTable() . '.' . $key, '!=', $this->filterValue )->orWhereNull($this->getTable() . '.' . $key); }); return; } $searchTerms = explode(';', $this->filterValue); $firstTerm = true; foreach ($searchTerms as $searchTerm) { if (empty($searchTerm) && $searchTerm != '0') { continue; } // If it isn't the first term, we need to re-extract the search operators if (! $firstTerm) { $this->extractSearchOperator($searchTerm, $key); $searchTerm = $this->filterValue; } $query->where( $this->getTable() . '.' . $key, $this->filterOperator, ($this->filterOperator == '=' ? $this->filterValue : "%{$searchTerm}%") ); $firstTerm = false; } } /** * Filter on entities with files */ protected function filterHasFiles(Builder $query, ?string $value = null): void { $query ->joinEntity() ->leftJoin('entity_assets', 'entity_assets.entity_id', '=', 'e.id') ->where('entity_assets.type_id', EntityAssetType::file); if ($value) { $query->whereNotNull('entity_assets.id'); } else { $query->whereNull('entity_assets.id'); } } /** * Filter on entities with or without an uploaded image */ protected function filterHasImage(Builder $query, ?string $value = null): void { $query->joinEntity(); if ($value) { $query->where(function ($sub) { return $sub ->whereNotNull('e.image_path') ->orWhereNotNull('e.image_uuid'); }); } else { $query->where(function ($sub) { return $sub ->whereNull('e.image_path') ->whereNull('e.image_uuid'); }); } } /** * Filter on entities that are or aren't templates */ protected function filterTemplate(Builder $query, ?string $value = null): void { $query->joinEntity(); if ($value) { $query->where('e.is_template', 1); } else { $query->where(function ($sub) { $sub->whereNull('e.is_template') ->orWhere('e.is_template', '<>', 1); }); } } /** * Filter on entities's type */ protected function filterType(Builder $query, ?string $value = null): void { $query->joinEntity(); $searchTerms = explode(';', $this->filterValue); $firstTerm = true; foreach ($searchTerms as $searchTerm) { if (empty($searchTerm) && $searchTerm != '0') { continue; } // If it isn't the first term, we need to re-extract the search operators if (! $firstTerm) { $this->extractSearchOperator($searchTerm, 'type'); $searchTerm = $this->filterValue; } $query->where( 'e.type', $this->filterOperator, ($this->filterOperator == '=' ? $this->filterValue : "%{$searchTerm}%") ); $firstTerm = false; } } /** * Filter on entities with posts */ protected function filterHasPosts(Builder $query, ?string $value = null): void { $query ->joinEntity() ->leftJoin('posts', 'posts.entity_id', 'e.id'); if ($value) { $query->whereNotNull('posts.id'); } else { $query->whereNull('posts.id'); } } /** * Filter on entities with an entry */ protected function filterHasEntry(Builder $query, ?string $value = null): void { $query ->joinEntity(); if ($value) { $query->whereNotNull('e.entry') ->where('e.entry', '!=', ''); } else { $query->whereNull('e.entry') ->orWhere('e.entry', ''); } } /** * Filter on entities that are equipped */ protected function filterIsEquipped(Builder $query, ?string $value = null): void { $query ->leftJoin('inventories', 'inventories.item_id', 'items.id'); if ($value) { $query->whereNotNull('inventories.id'); } else { $query->whereNull('inventories.id'); } $query->distinct('items.id'); } /** * Filter on entities with attributes */ protected function filterHasAttributes(Builder $query, ?string $value = null): void { $query ->joinEntity() ->leftJoin('attributes', 'attributes.entity_id', 'e.id'); if ($value) { $query->whereNotNull('attributes.id'); } else { $query->whereNull('attributes.id'); } } /** * Filter on characters on multiple races */ protected function filterRaces(Builder $query, null|string|array $value = null): void { // "none" filter keys is handled later if ($this->filterOption('none')) { return; } $query ->joinEntity(); // Make sure we always have an array if (! is_array($value)) { $value = [$value]; } $raceIds = []; foreach ($value as $v) { $raceIds[] = (int) $v; } if ($this->filterOption('exclude')) { $query->whereRaw('(select count(*) from character_race as cr where cr.character_id = ' . $this->getTable() . '.id and cr.race_id in (' . implode(', ', $raceIds) . ')) = 0'); return; } if ($this->filterOption('children')) { $value = Entity::whereIn('entity_id', $raceIds) ->where('type_id', config('entities.ids.race')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } $values = collect($value)->map(fn ($v) => (int) $v)->toArray(); // Ensure values are integers $query ->leftJoin('character_race as cr', 'cr.character_id', '=', $this->getTable() . '.id') ->whereIn('cr.race_id', $values); } /** * Filter characters in location */ protected function filterLocation(Builder $query, ?string $value = null, ?string $key = null): void { if (method_exists($this, 'scopeLocation')) { $query->location($value, $this->getFilterOption()); return; } if ($this->filterOption('children')) { $locationIds = Entity::where('entity_id', $value) ->where('type_id', config('entities.ids.location')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); $query->whereIn($this->getTable() . '.location_id', $locationIds)->distinct(); return; } $this->filterFallback($query, $key); } /** * Filter on multiple locations */ protected function filterLocations(Builder $query, null|string|array $value = null, ?string $key = null): void { // "none" filter keys is handled later if ($this->filterOption('none')) { return; } $query ->joinEntity(); // Make sure we always have an array if (! is_array($value)) { $value = [$value]; } $locationIds = []; foreach ($value as $v) { $locationIds[] = (int) $v; } if ($this->filterOption('exclude')) { $query->whereRaw('( select count(*) from entity_locations as el where el.entity_id = e.id and el.location_id in (' . implode(', ', $locationIds) . ') ) = 0'); return; } if ($this->filterOption('children')) { $locationIds = Entity::whereIn('entity_id', $locationIds) ->where('type_id', config('entities.ids.location')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } $query ->join('entity_locations', 'entity_locations.entity_id', '=', 'e.id') ->whereIn('entity_locations.location_id', $locationIds)->distinct(); } /** * Filter on characters on multiple organisations */ protected function filterOrganisations(Builder $query, null|string|array $value = null, ?string $key = null): void { // "none" filter keys is handled later if ($this->filterOption('none')) { return; } $query ->joinEntity(); // Make sure we always have an array if (! is_array($value)) { $value = [$value]; } $orgIds = []; foreach ($value as $v) { $orgIds[] = (int) $v; } if ($this->filterOption('exclude')) { $query->whereRaw('(select count(*) from organisation_member as cr where cr.character_id = ' . $this->getTable() . '.id and cr.organisation_id in (' . implode(', ', $orgIds) . ')) = 0'); return; } if ($this->filterOption('children')) { $value = Entity::whereIn('entity_id', $orgIds) ->where('type_id', config('entities.ids.organisation')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } $values = collect($value)->map(fn ($v) => (int) $v)->toArray(); // Ensure values are integers $query ->leftJoin('organisation_member as om', 'om.character_id', '=', $this->getTable() . '.id') ->whereIn('om.organisation_id', $values); } /** * Filter on characters on multiple families */ protected function filterFamilies(Builder $query, null|string|array $value = null, ?string $key = null): void { // "none" filter keys is handled later if ($this->filterOption('none')) { return; } $query ->joinEntity(); // Make sure we always have an array if (! is_array($value)) { $value = [$value]; } $familyIds = []; foreach ($value as $v) { $familyIds[] = (int) $v; } if ($this->filterOption('exclude')) { $query->whereRaw('(select count(*) from character_family as cr where cr.character_id = ' . $this->getTable() . '.id and cr.family_id in (' . implode(', ', $familyIds) . ')) = 0'); return; } if ($this->filterOption('children')) { $value = Entity::whereIn('entity_id', $familyIds) ->where('type_id', config('entities.ids.family')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } $values = collect($value)->map(fn ($v) => (int) $v)->toArray(); // Ensure values are integers $query ->leftJoin('character_family as cf', 'cf.character_id', '=', $this->getTable() . '.id') ->whereIn('cf.family_id', $values); } /** * Filter characters on a single race */ protected function filterRace(Builder $query, ?string $value = null): void { $ids = [$value]; if ($this->filterOption('exclude')) { if (auth()->check() && auth()->user()->isAdmin()) { $query->whereRaw('(select count(*) from character_race as cr where cr.character_id = ' . $this->getTable() . '.id and cr.race_id = ' . ((int) $value) . ') = 0'); } else { $query->whereRaw('(select count(*) from character_race as cr where cr.character_id = ' . $this->getTable() . '.id and cr.race_id = ' . ((int) $value) . ' and cr.is_private = 0) = 0'); } return; } elseif ($this->filterOption('children')) { $ids = Entity::where('entity_id', $value) ->where('type_id', config('entities.ids.race')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } $query ->select($this->getTable() . '.*') ->leftJoin('character_race as cr', function ($join) { $join->on('cr.character_id', '=', $this->getTable() . '.id'); })->whereIn('cr.race_id', $ids); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('cr.is_private', false); } $query->distinct(); } /** * Filter characters on a single family */ protected function filterFamily(Builder $query, ?string $value = null): void { $ids = [$value]; if ($this->filterOption('exclude')) { $query->whereRaw('(select count(*) from character_family as cf where cf.character_id = ' . $this->getTable() . '.id and cf.family_id = ' . ((int) $value) . ' ' . /* $this->subPrivacy('and cf.is_private') . */ ') = 0'); return; } elseif ($this->filterOption('children')) { $ids = Entity::where('entity_id', $value) ->where('type_id', config('entities.ids.family')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } $query ->select($this->getTable() . '.*') ->leftJoin('character_family as cf', function ($join) { $join->on('cf.character_id', '=', $this->getTable() . '.id'); })->whereIn('cf.family_id', $ids); /*if (auth()->guest() || !auth()->user()->isAdmin()) { $query->where('cf.is_private', false); }*/ $query->distinct(); } /** * Filter on entities with specific tags */ protected function filterTags(Builder $query, null|string|array $value = null): void { // "none" filter tags is handled later (because this won't be called if the tags field is empty) if ($this->filterOption('none')) { return; } $query ->joinEntity(); // Make sure we always have an array if (! is_array($value)) { $value = [$value]; } if ($this->filterOption('exclude')) { $tagIds = []; foreach ($value as $v) { $tagIds[] = (int) $v; } // $query->leftJoin('entity_tags as et_tags', "et_tags.entity_id", 'e.id') $query->whereRaw('( select count(*) from entity_tags as et where et.entity_id = e.id and et.tag_id in (' . implode(', ', $tagIds) . ') ) = 0'); return; } if ($this->filterOption('any')) { $tagIds = []; foreach ($value as $v) { $tagIds[] = (int) $v; } // $query->leftJoin('entity_tags as et_tags', "et_tags.entity_id", 'e.id') // $query $query->leftJoin('entity_tags as et_tags', 'et_tags.entity_id', 'e.id') ->whereIn('et_tags.tag_id', $tagIds); return; } foreach ($value as $v) { if (! is_numeric($v)) { continue; } $v = (int) $v; $query ->leftJoin('entity_tags as et' . $v, "et{$v}.entity_id", 'e.id') ->where("et{$v}.tag_id", $v); } } /** * Filter on archived entities */ protected function filterArchived(Builder $query, ?string $value = null): void { $query ->joinEntity(); if ($value) { $query->whereNotNull('e.archived_at'); return; } } /** * Filter on models at or between given real dates */ protected function filterDateRange(Builder $query, string $key, array $params = []): void { // Don't apply twice if both fields are set if ($key === 'date_end' && ! empty($params['date_start'])) { return; } $start = Arr::get($params, 'date_start'); $end = Arr::get($params, 'date_end'); if ($start && $end) { $query->whereBetween('date', [$start, $end]); } elseif ($end) { $query->whereDate('date', '=', $end); } else { $query->whereDate('date', '=', $start); } } /** * Filter for elements with a specific member (character) in them */ protected function filterMember(Builder $query, ?string $value = null): void { $filter = $this->getFilterOption(); $query->member($value, $filter); } /** * Filter on entities that have one of the targets as "none" selected */ protected function filterNoneOptions(Builder $query, string $key, array $fields = []): void { $key = Str::beforeLast($key, '_option'); // Handle locations filter through entity_locations if ($key === 'locations' && in_array('locations', $fields)) { $query ->joinEntity() ->leftJoin('entity_locations as el_none', 'el_none.entity_id', 'e.id') ->whereNull('el_none.location_id'); return; } if (in_array($key, ['races', 'families', 'organisations'])) { $names = ['races' => 'race_id', 'families' => 'family_id', 'organisations' => 'organisation_id']; $key = $names[$key]; } // Validate the key is a filter if (! in_array($key, $fields)) { return; } // Left join shenanigans if (! in_array($key, ['race_id', 'family_id', 'tags', 'quest_element_id', 'member_id'])) { $query->whereNull($this->getTable() . '.' . $key); } elseif ($key === 'tags') { $query ->joinEntity() ->leftJoin('entity_tags as no_tags', 'no_tags.entity_id', 'e.id') ->whereNull('no_tags.tag_id'); } elseif ($key === 'race_id') { $query ->select($this->getTable() . '.*') ->leftJoin('character_race as cr2', function ($join) { $join->on('cr2.character_id', '=', $this->getTable() . '.id'); }) ->where('cr2.race_id', null); } elseif ($key === 'family_id') { $query ->select($this->getTable() . '.*') ->leftJoin('character_family as cf2', function ($join) { $join->on('cf2.character_id', '=', $this->getTable() . '.id'); }) ->where('cf2.family_id', null); } elseif ($key === 'quest_element_id') { $query->element(null, FilterOption::NONE); } elseif ($key === 'member_id') { $query->member(null, FilterOption::NONE); } } /** * Get the filter option enum */ protected function getFilterOption(): FilterOption { match ($this->filterOption) { 'exclude' => $filter = FilterOption::EXCLUDE, 'none' => $filter = FilterOption::NONE, 'children' => $filter = FilterOption::CHILDREN, default => $filter = FilterOption::INCLUDE, }; return $filter; } protected function filterParent(Builder $query): void { $query->whereHas('entity', fn ($q) => $q->where('entities.parent_id', $this->filterValue)); } protected function filterStatus(Builder $query): void { $query->whereHas('entity', fn ($q) => $q->where('entities.status_id', $this->filterValue)); } protected function explicitFilters(): array { if (property_exists($this, 'explicitFilters')) { return $this->explicitFilters; } return []; } protected function subPrivacy(string $field): ?string { // Campaign admins don't have private data hidden from them if (auth()->check() && auth()->user()->isAdmin()) { return null; } return ' ' . $field . ' = 0'; } } ================================================ FILE: app/Models/Concerns/HasImage.php ================================================ imageFields ?? ['image']; } /** * Get the campaign's thumbnail url */ public function thumbnail(int $width = 400, ?int $height = null, string $field = 'image'): string { if (empty($this->$field)) { return ''; } return Img::resetCrop() ->crop($width, (! empty($height) ? $height : $width)) ->url($this->$field); } } ================================================ FILE: app/Models/Concerns/HasLocation.php ================================================ */ public function location(): BelongsTo { return $this ->belongsTo('App\Models\Location', 'location_id', 'id') ->select('locations.id', 'locations.name') ->with([ 'entity' => function ($sub) { $sub->select('id', 'name', 'entity_id', 'type_id'); }, ]); } } ================================================ FILE: app/Models/Concerns/HasLocations.php ================================================ */ public function locations(): BelongsToMany { return $this->belongsToMany('App\Models\Location', $this->getLocationPivotTableName()) ->has('entity') ->with('entity'); } protected function getLocationPivotTableName(): string { return $this->locationPivot; } protected function getLocationPivotKey(): string { return $this->locationPivotKey; } /** * Filter on models tied to a specific location or its descendants */ public function scopeLocation(Builder $query, ?int $location, FilterOption $filter): Builder { if ($filter === FilterOption::NONE) { if (! empty($location)) { return $query; } return $query ->whereRaw('(select count(*) from ' . $this->getLocationPivotTableName() . ' as lp where lp.' . $this->getLocationPivotKey() . ' = ' . $this->getTable() . '.id and lp.location_id = ' . ((int) $location) . ') = 0'); } elseif ($filter === FilterOption::EXCLUDE) { return $query ->whereRaw('(select count(*) from ' . $this->getLocationPivotTableName() . ' as lp where lp.' . $this->getLocationPivotKey() . ' = ' . $this->getTable() . '.id and lp.location_id = ' . ((int) $location) . ') = 0'); } $ids = [$location]; if ($filter === FilterOption::CHILDREN) { /** @var ?Location $model */ $model = Location::find($location); if (! empty($model)) { $ids = [...$model->descendants->pluck('id')->toArray(), $model->id]; } } return $query ->select($this->getTable() . '.*') ->leftJoin($this->getLocationPivotTableName() . ' as lp', function ($join) { $join->on('lp.' . $this->getLocationPivotKey(), '=', $this->getTable() . '.id'); }) ->whereIn('lp.location_id', $ids)->distinct(); } } ================================================ FILE: app/Models/Concerns/HasMentions.php ================================================ */ public function mentions(): HasMany { return $this->hasMany('App\Models\EntityMention', 'entity_id', 'id'); } /** * List of images used by this entity * * @return HasMany */ public function imageMentions(): HasMany { return $this->hasMany('App\Models\ImageMention', 'entity_id', 'id'); } /** * List of entities that mention this entity * * @return HasMany */ public function targetMentions(): HasMany { return $this->hasMany('App\Models\EntityMention', 'target_id', 'id'); } /** * Get entities that are unmentioned */ public function scopeUnmentioned(Builder $query): Builder { return $query->select($this->getTable() . '.*') ->leftJoin('entity_mentions as em', 'em.target_id', $this->getTable() . '.id') ->whereNull('em.id'); } /** * Get entities that aren't mentioned anywhere */ public function scopeMentionless(Builder $query): Builder { return $query->select($this->getTable() . '.*') ->leftJoin('entity_mentions as em', 'em.entity_id', $this->getTable() . '.id') ->whereNull('em.id'); } /** * Count the number of mentions this entity has */ public function mentionsCount(): int { return $this->targetMentions() ->filterValid() ->count(); } } ================================================ FILE: app/Models/Concerns/HasReminder.php ================================================ hasCalendarDate() && $this->calendarDate->calendar !== null; } public function hasCalendarButNoAccess(): bool { return $this->hasCalendarDate() && $this->calendarDate->calendar === null; } public function getDate(): string { $reminder = $this->calendarDate; $months = $reminder->calendar->months(); $count = 0; $monthCount = 1; foreach ($months as $month) { $monthType = Arr::get($month, 'type'); if ($monthType === 'standard') { $count++; } if ($monthCount == $reminder->month) { if ($monthType === 'intercalary') { return $reminder->year . '-' . $month['name'] . '-' . $reminder->day; } return $reminder->year . '-' . $count . '-' . $reminder->day; } $monthCount++; } return $reminder->year . '-' . $reminder->month . '-' . $reminder->day; } public function getCalendarIdAttribute(): ?int { if (! $this->hasCalendarDate()) { return null; } return $this->calendarDate->calendar_id; } public function getCalendarYearAttribute(): ?int { if (! $this->hasCalendarDate()) { return null; } return $this->calendarDate->year; } public function getCalendarMonthAttribute(): ?int { if (! $this->hasCalendarDate()) { return null; } return $this->calendarDate->month; } public function getCalendarDayAttribute(): ?int { if (! $this->hasCalendarDate()) { return null; } return $this->calendarDate->day; } public function getCalendarLengthAttribute(): ?int { if (! $this->hasCalendarDate()) { return null; } return (int) $this->calendarDate->length; } /** * recurring_periodicity */ public function getCalendarRecurringPeriodicityAttribute(): ?string { if (! $this->hasCalendarDate()) { return null; } return $this->calendarDate->recurring_periodicity; } /** * Calendar Colour * * @return null|string */ public function getCalendarColourAttribute() { if (! $this->hasCalendarDate()) { return '#cccccc'; } return $this->calendarDate->colour; } public function calendarReminder(): ?Reminder { return $this->calendarDate; } protected function hasCalendarDate(): bool { return isset($this->calendarDate); } } ================================================ FILE: app/Models/Concerns/HasSlug.php ================================================ suggestions; } } ================================================ FILE: app/Models/Concerns/HasUser.php ================================================ */ public function user(): BelongsTo { return $this->belongsTo(User::class, $this->getUserFieldName()); } protected function getUserFieldName(): string { if (! property_exists($this, 'userField')) { return 'user_id'; } return $this->userField; } } ================================================ FILE: app/Models/Concerns/HasVisibility.php ================================================ skipAllIcon = true; return $this; } /** * Generate the data for the visibility icon */ public function visibilityIcon(?string $extra = null): array { $icon = []; if ($this->isVisibleAll()) { if ($this->skipAllIcon) { $icon['skip'] = true; return $icon; } $icon['class'] = 'fa-regular fa-eye'; $icon['key'] = __('visibilities.helpers.all'); } elseif ($this->isVisibleAdmin()) { $icon['class'] = 'fa-regular fa-lock'; $icon['key'] = __('visibilities.helpers.admin'); } elseif ($this->visibility_id === Visibility::Self) { $icon['class'] = 'fa-regular fa-user-secret'; $icon['key'] = __('visibilities.helpers.self'); } elseif ($this->visibility_id === Visibility::AdminSelf) { $icon['class'] = 'fa-regular fa-user-lock'; $icon['key'] = __('visibilities.helpers.admin-self'); } elseif ($this->visibility_id === Visibility::Member) { $icon['class'] = 'fa-regular fa-users'; $icon['key'] = __('visibilities.helpers.members'); } $icon['class'] = mb_rtrim($icon['class'] . ' ' . $extra); return $icon; } public function visibilityName(): string { if ($this->isVisibleAll()) { if ($this->skipAllIcon) { return ''; } return __('crud.visibilities.all'); } elseif ($this->isVisibleAdmin()) { return __('crud.visibilities.admin'); } elseif ($this->visibility_id === Visibility::Self->value) { return __('crud.visibilities.self'); } elseif ($this->visibility_id === Visibility::AdminSelf->value) { return __('crud.visibilities.admin-self'); } elseif ($this->visibility_id === Visibility::Member->value) { return __('crud.visibilities.members'); } return __('crud.visibilities.all'); } /** * Get a list of visibility options when editing an element */ public function visibilityOptions(): array { $options = []; $options[Visibility::All->value] = __('crud.visibilities.all'); if (auth()->user()->isAdmin()) { $options[Visibility::Admin->value] = __('crud.visibilities.admin'); $options[Visibility::Member->value] = __('crud.visibilities.members'); } if ($this->isCreator()) { $options[Visibility::Self->value] = __('crud.visibilities.self'); $options[Visibility::AdminSelf->value] = __('crud.visibilities.admin-self'); } // If it's a visibility self & admin, and we're not the creator, we can't change this if ($this->visibility_id === Visibility::AdminSelf->value && ! $this->isCreator()) { $options = [Visibility::AdminSelf->value => __('crud.visibilities.admin-self')]; } elseif ($this->visibility_id === Visibility::Self->value && ! $this->isCreator()) { $options = [Visibility::Self->value => __('crud.visibilities.self')]; } return $options; } /** * Determine if the current user is the creator */ protected function isCreator(): bool { return $this->created_by == auth()->user()->id; } public function isVisibleAll(): bool { return $this->visibility_id === Visibility::All; } public function isVisibleAdmin(): bool { return $this->visibility_id === Visibility::Admin; } } ================================================ FILE: app/Models/Concerns/LastSync.php ================================================ getTable(); return $query->where($tableName . '.updated_at', '>', $lastSync); } } ================================================ FILE: app/Models/Concerns/Orderable.php ================================================ defaultOrderField ?: 'name'; $direction = $this->defaultOrderDirection ?: 'asc'; if (! empty($data) && auth()->check()) { foreach ($data as $key => $value) { $field = $key; $direction = $value; } } // Calendar dates are handled differently since we have three fields. // However, we should do a left join instead if ($field === 'calendar_date') { return $query ->joinEntity() ->leftJoin('reminders as cd', function ($on) { return $on->on('cd.remindable_id', 'e.id') ->on('cd.remindable_type', '=', DB::raw("'" . addslashes(Entity::class) . "'")) ->where('cd.type_id', EntityEventTypes::calendarDate); }) ->orderBy('cd.year', $direction) ->orderBy('cd.month', $direction) ->orderBy('cd.day', $direction); } elseif ($field === 'type') { return $query ->joinEntity() ->orderBy('e.type', $direction); } elseif ($field === 'locations') { return $query ->joinEntity() ->leftJoin('entity_locations', 'entity_locations.entity_id', '=', 'e.id') ->leftJoin('locations', 'locations.id', '=', 'entity_locations.location_id') ->orderBy('locations.name', $direction); } if (! empty($field)) { $segments = explode('.', $field); if (count($segments) > 1) { $relationName = $segments[0]; /** @var BelongsTo $relation */ $relation = $this->{$relationName}(); $foreignName = $relation->getQuery()->getQuery()->from; return $query ->with($relationName) ->leftJoin( $foreignName . ' as orderable_j', 'orderable_j.id', $this->getTable() . '.' . $relation->getForeignKeyName() ) ->orderBy(str_replace($relationName, 'orderable_j', $field), $direction); } else { // Order by related table? Yeah that's fun. // While this would be possible, this would mean injecting the acl/permission system // just for an order by, which seems quite overkill. // A better solution might present itself during a future rewrite of the acl engine. // if (substr($field, 0, 6) == 'count(') { // $relationName = preg_replace('/count\((.*)\)/si', '$1', $field); // $relation = $this->{$relationName}(); // $foreignName = $relation->getQuery()->getQuery()->from; // // return $query // ->orderByRaw('(select count(*) from ' . $foreignName . ' where ' . // $relation->getForeignKeyName() . ' = ' . $this->getTable() . '.' . $this->primaryKey . ') ' . $direction); // } // If the field has a casting if (property_exists($this, 'orderCasting') && ! empty($this->orderCasting[$field])) { return $query->orderByRaw( 'cast(' . $this->getTable() . '.' . $field . ' as ' . $this->orderCasting[$field] . ')' . $direction ); } return $query->orderBy($this->getTable() . '.' . $field, $direction); } } return $query; } } ================================================ FILE: app/Models/Concerns/Paginatable.php ================================================ check()) { $pageSize = auth()->user()->pagination; $pagService = app()->make(PaginationService::class); $this->pageSizeMax = $pagService->user(auth()->user())->max(); } // Currently exporting single or bulk? Rise limit to 100. // @phpstan-ignore-next-line $request = request()->route()->getAction(); if (! empty($request['as']) && in_array($request['as'], ['bulk.process'])) { return 100; } if (Domain::isApi()) { $this->pageSizeMinimum = 45; } return min(max($pageSize, $this->pageSizeMinimum), $this->pageSizeMax); } } ================================================ FILE: app/Models/Concerns/Privatable.php ================================================ runningInConsole()) { return $query; } // Only admins have access to private models if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where($this->getTable() . '.is_private', false); } return $query; } } ================================================ FILE: app/Models/Concerns/Purifiable.php ================================================ runningInConsole() && ! app()->runningUnitTests()) { return; } static::observe(app(PurifiableObserver::class)); } public function getPurifiableFields(): array { return $this->purifiableFields ?? ['entry']; } } ================================================ FILE: app/Models/Concerns/Sanitizable.php ================================================ sanitizable; } } ================================================ FILE: app/Models/Concerns/Searchable.php ================================================ searchableFields(); return $query->where(function ($q) use ($term, $searchFields) { foreach ($searchFields as $field) { $q->orWhere($this->getTable() . '.' . $field, 'like', "%{$term}%"); } }); } public function hasSearchableFields(): bool { return true; } /** * Available searchable fields. Defaults to name, type and type * * @return string[] */ protected function searchableFields(): array { if (property_exists($this, 'searchableColumns')) { return $this->searchableColumns; } return [ 'name', ]; } } ================================================ FILE: app/Models/Concerns/SimpleSortableTrait.php ================================================ request(request()->all()); } $columns = $datagridSorter->column(); if (! is_array($columns)) { $columns = [$columns]; } $order = $datagridSorter->order(); foreach ($columns as $column) { if (Str::contains($column, '.')) { $segments = explode('.', $column); if (count($segments) == 2) { $relationName = $segments[0]; $relation = $this->{$relationName}(); $foreignName = $relation->getQuery()->getQuery()->from; $builder ->select($this->getTable() . '.*') ->with($relationName) ->leftJoin( $foreignName . ' as f', 'f.id', $this->getTable() . '.' . $relation->getForeignKeyName() ) ->orderBy(str_replace($relationName, 'f', $column), $order); continue; } } $builder->orderBy($this->getTable() . '.' . $column, $order); } return $builder; } } ================================================ FILE: app/Models/Concerns/Sortable.php ================================================ customSortableColumns(), $base); } protected function customSortableColumns(): array { if (! property_exists($this, 'sortableColumns')) { return []; } return $this->sortableColumns; } } ================================================ FILE: app/Models/Concerns/SortableTrait.php ================================================ defaultOrder(); } foreach ($defaultOrder as $field => $order) { $query->orderBy($field, $order); } return $query; } // dump($this->sortable); // dd($filters); if (empty($this->sortable)) { return $query; } if (empty($filters['k'])) { return $query; } $key = Arr::get($filters, 'k'); if (! in_array($key, $this->sortable)) { return $query; } // Force the order to be valid $order = mb_strtolower(Arr::get($filters, 'o', 'asc')); if (! in_array($order, ['asc', 'desc'])) { $order = 'asc'; } if (Str::contains($key, '.')) { $segments = explode('.', $key); if (count($segments) == 2) { // @phpstan-ignore-next-line return $query->sortOnForeign($key, $order); } } elseif ($key === 'type') { if ($this instanceof Entity) { return $query->orderBy($this->getTable() . '.type', $order); } return $query ->select($this->getTable() . '.*') ->leftJoin('entities as e', function ($join) { $join->on('e.entity_id', '=', $this->getTable() . '.id'); // @phpstan-ignore-next-line $join->where('e.type_id', '=', $this->entityTypeID()) ->whereRaw('e.campaign_id = ' . $this->getTable() . '.campaign_id'); }) ->groupBy($this->getTable() . '.id') ->orderBy('e.type', $order); } // Custom sort method? $custom = 'scopeCustomSort' . ucfirst($key); if (method_exists($this, $custom)) { return $query->{'customSort' . ucfirst($key)}($order); } return $query->orderBy($key, $order); } public function scopeDefaultOrder(Builder $query): Builder { if (! isset($this->defaultSort)) { return $query; } $defaults = is_array($this->defaultSort) ? $this->defaultSort : [$this->defaultSort]; foreach ($defaults as $default) { if (is_array($default)) { $key = array_key_first($default); $query->orderBy($key, $default[$key]); continue; } $query->orderBy($default); } return $query; } /** * Sort on a foreign relation */ protected function scopeSortOnForeign(Builder $query, string $key, string $order): Builder { $segments = explode('.', $key); $relationName = $segments[0]; // Querying a pivot table? Let's let the other query builder take care of this. if ($relationName === 'pivot') { return $query; } $relation = $this->{$relationName}(); $foreignName = $relation->getQuery()->getQuery()->from; return $query ->select($this->getTable() . '.*') ->leftJoin( $foreignName . ' as f', 'f.id', $this->getTable() . '.' . $relation->getForeignKeyName() ) ->orderBy(str_replace($relationName, 'f', $key), $order); } } ================================================ FILE: app/Models/Concerns/Taggable.php ================================================ runningInConsole()) { return; } static::observe(app(TaggableObserver::class)); } /** * @return BelongsToMany */ public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class, $this->getTagPivotTableName()) ->with(['entity' => function ($query) { $query->select('entities.id', 'entities.name', 'entities.entity_id', 'entities.type_id'); }]) ->has('entity'); } protected function getTagPivotTableName(): ?string { if (property_exists($this, 'tagPivotName')) { return $this->tagPivotName; } return null; } public function visibleTags(): \Illuminate\Support\Collection { return $this->tags ->where('is_hidden', 0); } } ================================================ FILE: app/Models/Concerns/Templatable.php ================================================ is_template; } public function scopeTemplate(Builder $query, bool $template = true): Builder { return $query->select($this->getTable() . '.*') ->where($this->getTable() . '.is_template', $template); } public function scopePostTemplates(Builder $query, Campaign $campaign, bool $template = true): Builder { return $query->select([ $this->getTable() . '.id', $this->getTable() . '.name', $this->getTable() . '.entity_id', ]) ->with('entity') ->leftJoin('entities as e', 'e.id', $this->getTable() . '.entity_id') ->where('e.campaign_id', $campaign->id) ->where($this->getTable() . '.is_template', $template); } } ================================================ FILE: app/Models/Concerns/TouchSilently.php ================================================ getFillable())) { // Still log who edited the entity $this->updated_by = auth()->user()->id; } return $this->touch(); }); } } ================================================ FILE: app/Models/Concerns/UserBoosters.php ================================================ maxBoosts() - $this->boosting(); } /** * Get amount of campaigns the user is boosting */ public function boosting(): int { if ($this->hasBoosterNomenclature()) { return $this->boosts->count(); } return $this->boosts->groupBy('campaign_id')->count(); } /** * Get max number of boosts a user can give */ public function maxBoosts(): int { // Allows admins to give boosters to members of the community $base = 0; if (! empty($this->booster_count)) { $base += $this->booster_count; } if (! $this->isSubscriber()) { return $base; } if ($this->hasRole('admin')) { return max(3, $base); } $levels = [ Pledge::KOBOLD => 0, Pledge::GOBLIN => 0, Pledge::OWLBEAR => 1, Pledge::WYVERN => 3, Pledge::ELEMENTAL => 7, ]; if ($this->hasBoosterNomenclature()) { $levels = [ Pledge::KOBOLD => 0, Pledge::GOBLIN => 1, Pledge::OWLBEAR => 3, Pledge::WYVERN => 6, Pledge::ELEMENTAL => 10, ]; } return Arr::get($levels, $this->pledge ?? 'unknown', 0) + $base; } } ================================================ FILE: app/Models/Concerns/UserTokens.php ================================================ isElemental() || $this->isWyvern() || $this->hasRole('admin') || $this->id === 158800; } public function availableTokens(): int { return max($this->maxTokens() - $this->usedTokens(), 0); } /** * Get the max amount of tokens for Bragi */ public function maxTokens(): int { $key = 'all'; if ($this->hasRole('admin')) { $key = 'admin'; } elseif ($this->isElemental()) { $key = 'elemental'; } elseif ($this->isWyvern()) { $key = 'wyvern'; } elseif ($this->id === 158800) { return 20; } return config('bragi.tokens.' . $key); } /** * Count the number of recently used tokens (calls to the API). These reset every month for the user */ public function usedTokens(): int { $subDay = $this->tokenRenewalDay(); $currentDay = date('d'); $date = new Carbon; $date->setDay($subDay); // If the sub was on the 7th, and we're the 3rd, move the cutoff to a month ago if ($subDay >= $currentDay) { $date->subMonth(); } return $this->bragiLogs()->recent($date->format('Y-m-d'))->count(); } /** * Get the next date the token count resets */ public function tokenRenewalDate(): string { $subDay = $this->tokenRenewalDay(); $currentDay = date('d'); $date = new Carbon; $date->setDay($subDay); // If the sub was on the 7th, and we're the 11th, move the next date to the following month if ($subDay < $currentDay) { $date->addMonth(); } return $date->format('M d, Y'); } /** * Get the day the renewal of tokens is based on */ protected function tokenRenewalDay(): int { // Admins aren't subbed, take their creation date for debugging if ($this->hasRole('admin')) { return $this->created_at->format('d'); } $data = $this->subscription('kanka'); return ! empty($data) ? $data->created_at->format('d') : 1; } } ================================================ FILE: app/Models/Conversation.php ================================================ ConversationTarget::class, ]; /** * Searchable fields */ protected array $searchableColumns = ['name']; /** * Fields that can be sorted on */ protected array $sortableColumns = [ 'target_id', 'colour', ]; protected array $sanitizable = [ 'name', ]; /** * Set to false if this entity type doesn't have relations */ public bool $hasRelations = false; /** * @var string[] Extra relations loaded for the API endpoint */ public array $apiWith = ['messages', 'participants']; /** * @return HasMany */ public function messages(): HasMany { return $this->hasMany('App\Models\ConversationMessage', 'conversation_id'); } /** * @return HasMany */ public function participants(): HasMany { return $this->hasMany('App\Models\ConversationParticipant', 'conversation_id') ->with('character'); } /** * Get a list of participants * * @return array */ public function participantsList(bool $withNames = true, bool $users = false) { $participants = []; foreach ($this->participants as $participant) { if (! $participant->character) { continue; } if (auth()->check() && auth()->user()->can('update', $participant->character->entity)) { $participants[$participant->id()] = $participant->name(); } elseif ($users == true) { $participants[$participant->id()] = $participant->name(); } } if (! $withNames) { return array_keys($participants); } return $participants; } /** * @return false|string */ public function jsonParticipants() { return json_encode($this->participantsList()); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.conversation'); } public function forCharacters(): bool { return $this->target_id === ConversationTarget::characters; } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { return true; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'target_id', 'is_closed', ]; } } ================================================ FILE: app/Models/ConversationMessage.php ================================================ */ public function character(): BelongsTo { return $this->belongsTo('App\Models\Character', 'character_id'); } /** * @return BelongsTo */ public function conversation(): BelongsTo { return $this->belongsTo('App\Models\Conversation', 'conversation_id'); } /** * @return int|null */ public function target() { return ! empty($this->character_id) ? ConversationTarget::characters->value : (! empty($this->user_id) ? ConversationTarget::users->value : null); } /** * @return string|null */ public function author() { if (! empty($this->user_id)) { return $this->user; } elseif (! empty($this->character_id)) { return $this->character; } return null; } public function authorID(): ?int { if (! empty($this->user_id)) { return $this->user_id; } elseif (! empty($this->character_id)) { return $this->character_id; } return null; } public function scopeDefault(Builder $query, ?int $oldestId = null, ?int $newestId = null) { $query->with(['user', 'character']) ->latest() ->take(20); if (! empty($oldestId)) { $query->where('id', '<', $oldestId); } elseif (! empty($newestId)) { $query->where('id', '>', $newestId); } } public function isMine(): bool { return Auth::check() && $this->created_by == Auth::user()->id; } public function grouppedWith(?ConversationMessage $previous = null): self { if (empty($previous)) { return $this; } if ($previous->authorID() != $this->authorID()) { return $this; } // Same 60 seconds $this->isGroupped = $previous->created_at->diffInSeconds($this->created_at) < 60; return $this; } public function isGroup(): bool { return $this->isGroupped; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ ]; } } ================================================ FILE: app/Models/ConversationParticipant.php ================================================ */ public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } /** * @return BelongsTo */ public function character(): BelongsTo { return $this->belongsTo('App\Models\Character', 'character_id'); } /** * @return BelongsTo */ public function conversation(): BelongsTo { return $this->belongsTo('App\Models\Conversation', 'conversation_id'); } public function name(): string { return $this->entity()->name ?? __('conversations.messages.author_unknown'); } public function target(): ?int { return ! empty($this->character_id) ? ConversationTarget::characters->value : (! empty($this->user_id) ? ConversationTarget::users->value : null); } public function isMember(): bool { return ! empty($this->user_id); } public function id() { $entity = $this->loadEntity(); return ! empty($entity) ? $entity->id : null; } /** * @return Character|User|bool|HasOne|mixed */ public function entity() { return $this->loadEntity(); } /** * @return Character|User|bool|mixed */ protected function loadEntity() { if (isset($this->loadedEntity)) { return $this->loadedEntity; } if (! empty($this->user_id)) { return $this->loadedEntity = $this->user; } return $this->loadedEntity = $this->character; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'conversation_id', 'created_at', ]; } } ================================================ FILE: app/Models/Creature.php ================================================ entity->locations->isNotEmpty()) { return true; } return parent::showProfileInfo(); } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { // Pivot tables can be deleted directly $this->entity->locations()->detach(); } } ================================================ FILE: app/Models/DiceRoll.php ================================================ */ public function character(): BelongsTo { return $this->belongsTo('App\Models\Character', 'character_id'); } /** * @return HasMany */ public function diceRollResults(): HasMany { return $this->hasMany('App\Models\DiceRollResult', 'dice_roll_id'); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.dice_roll'); } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { return $this->parameters || $this->character; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'character_id', ]; } } ================================================ FILE: app/Models/DiceRollResult.php ================================================ */ public function diceRoll(): BelongsTo { return $this->belongsTo('App\Models\DiceRoll', 'dice_roll_id'); } public function character() { return $this->diceRoll->character(); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'dice_roll_id', 'created_at', 'created_by', 'diceRoll-character_id', ]; } } ================================================ FILE: app/Models/Entity.php ================================================ isAttributeTemplate()) { return $this->attributeTemplate(); } elseif ($this->isDiceRoll()) { return $this->diceRoll(); } return $this->{$this->entityType->code}(); } /** * Child attribute * * @return HasMany|HasOne|MiscModel */ public function getChildAttribute() { // When in console mode (queue), don't cache results as the queue won't re-validate them return app()->runningInConsole() ? $this->child()->first() : EntityCache::child($this); } /** * @return Entity */ public function reloadChild() { if ($this->isAttributeTemplate()) { return $this->load('attributeTemplate'); } elseif ($this->isDiceRoll()) { return $this->load('diceRoll'); } return $this->load($this->entityType->code); } /** * Preview of the entity with mapped mentions. For map markers */ public function mappedPreview(): string { $campaign = CampaignLocalization::getCampaign(); if ($campaign->boosted()) { $boostedTooltip = strip_tags($this->tooltip); if (! empty(mb_trim($boostedTooltip))) { $text = $this->parsedEntry(); return (string) strip_tags($text); } } if (! $this->hasEntry()) { return ''; } return Str::limit(strip_tags($this->parsedEntry()), 500); } /** * @return string */ public function url(string $action = 'show', array $options = []) { $campaign = CampaignLocalization::getCampaign(); try { if ($action == 'index') { return route($this->entityType->code . '.index', [$campaign, $this->entityType]); } elseif ($action === 'show') { return route('entities.show', [$campaign, $this] + $options); } elseif ($action === 'edit') { return route('entities.edit', [$campaign, $this] + $options); } $routeOptions = array_merge([$campaign, $this->entity_id], $options); return route($this->entityType->code . '.' . $action, $routeOptions); } catch (Exception $e) { return route('dashboard', $campaign); } } /** * Get the entity's type id */ public function typeId() { return $this->type_id; } public function isType(array|int $types): bool { if (! is_array($types)) { $types = [$types]; } return in_array($this->type_id, $types); } /** * Get the image (or default image) of an entity * * @param int $width = 200 */ public function thumbnail(int $width = 400, ?int $height = null, string $field = 'header_image'): string { if (empty($this->$field)) { return ''; } return Img::resetCrop()->crop($width, $height ?? $width)->url($this->$field); } /** * If an entity has entity files */ public function hasFiles(): bool { return $this->type_id != config('entities.ids.bookmark'); } public function hasHeaderImage(): bool { if (! empty($this->header_image)) { return true; } return ! empty($this->header_uuid) && ! empty($this->header); } /** * Determine if an entity has an image that can be shown. This can be either uploaded * directly on them, or from the gallery */ public function hasImage(bool $boosted = false): bool { return ! empty($this->image_path) || ! empty($this->image); } public function hasLinks(): bool { return $this->links()->count() > 0; } /** * Get the entity background header image */ public function getHeaderUrl(int $width = 1200, int $height = 400): ?string { if (! empty($this->header_image)) { return $this->thumbnail($width, $height, 'header_image'); } if (empty($this->header)) { return null; } return $this->header->getUrl($width, $height); } /** * Determine if an entity has pinned elements to display */ public function hasPins(): bool { if ($this->pinnedRelations->isNotEmpty()) { return true; } if ($this->starredAttributes()->isNotEmpty()) { return true; } return (bool) ($this->pinnedFiles->isNotEmpty()); } /** * @return array|string[] */ public function postPositionOptions(?int $position = null): array { $options = $position ? [ null => __('posts.position.dont_change'), ] : []; $layers = $this->posts->sortBy('position'); $hasFirst = false; foreach ($layers as $layer) { if (! $hasFirst) { $hasFirst = true; $options[$layer->position < 0 ? $layer->position - 1 : 1] = __('posts.position.first'); } $key = $layer->position > 0 ? $layer->position + 1 : $layer->position; $lang = __('maps/layers.placeholders.position_list', ['name' => $layer->name]); if (config('app.debug')) { $lang .= ' (' . $key . ')'; } $options[$key] = $lang; } // Didn't have a first option added, add one now if (! $hasFirst) { $options[1] = __('posts.position.first'); } // If is the last position remove last+1 position from the options array /*if ($position == array_key_last($options) - 1 && count($options) > 1) { array_pop($options); }*/ return $options; } public function export(): array { $fields = [ 'id', 'entity_id', 'parent_id', 'type_id', 'name', 'type', 'entry', 'is_private', 'tooltip', 'is_template', 'is_attributes_private', 'focus_x', 'focus_y', 'created_at', 'updated_at', 'created_by', 'updated_by', 'image_path', 'image_uuid', 'header_image', 'header_uuid', 'marketplace_uuid', 'status_id', ]; $data = []; foreach ($fields as $field) { $data[$field] = $this->$field; } // Entity relations $relations = [ 'entityTags', 'relationships', 'posts', 'abilities', 'reminders', 'entityAttributes', 'assets', 'mentions', 'inventories', 'entityLocations', ]; foreach ($relations as $relation) { foreach ($this->$relation as $model) { if ($relation === 'abilities' && empty($model->ability)) { continue; } if ($relation === 'inventories' && empty($model->item)) { continue; } // here if (method_exists($model, 'exportFields')) { $export = []; foreach ($model->exportFields() as $field) { $export[$field] = $model->$field; } $data[$relation][] = $export; } elseif (method_exists($model, 'export')) { $data[$relation][] = $model->export(); } else { $data[$relation][] = $model->toArray(); } } } return $data; } /** * List of inventory items, group by alphabetical position */ public function orderedInventory(): Collection { $inventory = []; $items = $this->inventories()->with(['image', 'item', 'item.entity', 'item.entity.image'])->get(); foreach ($items as $item) { if ($item->item_id && (empty($item->item) || empty($item->item->entity))) { continue; } $position = $item->position ?: __('entities/inventories.default_position'); $inventory[$position][] = $item; } // We want the inventory ordered by position, then by item name $collator = new Collator(app()->getLocale()); $positions = array_keys($inventory); $collator->asort($positions); $ordered = []; foreach ($positions as $position) { $items = new Collection($inventory[$position]); $ordered[$position] = $items->sortBy(function ($model) { /** @var Inventory $model */ return $model->itemName(); }); } return new Collection($ordered); } public function hasChild(): bool { return $this->entityType->isStandard(); } public function isMissingChild(): bool { return $this->entityType->isStandard() && empty($this->child); } /** * Get the status key from category_statuses */ public function statusKey(): ?string { return $this->status?->key; } /** * Get the CSS class for the entity's current status */ public function statusClass(): string { $status = $this->status; return $status !== null ? $this->entityType->code . '-' . $status->key : ''; } /** * Generate the entity's body css classes */ public function bodyClasses(): string { $classes = [ 'kanka-entity-' . $this->id, 'kanka-entity-' . $this->entityType->code, ]; $classes[] = 'kanka-type-' . Str::slug($this->type); foreach ($this->tags as $tag) { $classes[] = 'kanka-tag-' . $tag->id; $classes[] = 'kanka-tag-' . $tag->slug; if ($tag->tag_id) { $classes[] = 'kanka-tag-' . $tag->tag_id; } } // Entity status flag $statusClass = $this->statusClass(); if ($statusClass !== '') { $classes[] = $statusClass; } if ($this->is_private) { $classes[] = 'kanka-entity-private'; } if ($this->archived_at) { $classes[] = 'kanka-entity-archived'; } if ($this->hasHeaderImage()) { $classes[] = 'entity-with-banner'; } return (string) implode(' ', $classes); } /** * Get the value used to index the model. */ public function getScoutKey() { return $this->getTable() . '_' . $this->id; } /** * Get the name of the index associated with the model. */ public function searchableAs(): string { return 'entities'; } public function toSearchableArray() { return [ 'campaign_id' => $this->campaign_id, 'entity_id' => $this->id, 'name' => $this->name, 'type' => $this->type, 'entry' => strip_tags($this->entry), ]; } public function getLocationPivotTableName(): string { return 'entity_locations'; } } ================================================ FILE: app/Models/EntityAbility.php ================================================ Visibility::class, ]; protected array $sanitizable = [ 'note', ]; /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity'); } /** * @return BelongsTo */ public function ability(): BelongsTo { return $this->belongsTo('App\Models\Ability'); } /** * @return Builder */ public function scopeDefaultOrder(Builder $query) { return $query ->orderBy('position') ->orderBy('ae.type') ->orderBy('a.name'); } /** * Copy an entity ability to another target */ public function copyTo(Entity $target) { $new = $this->replicate(['entity_id']); $new->entity_id = $target->id; return $new->save(); } public function exportFields(): array { return [ 'ability_id', 'visibility_id', 'created_by', 'charges', 'position', 'note', ]; } public function url(string $sub): string { return 'entities.entity_abilities.' . $sub; } } ================================================ FILE: app/Models/EntityAsset.php ================================================ 'array', 'visibility_id' => Visibility::class, 'type_id' => EntityAssetType::class, ]; protected array $sanitizable = [ 'name', 'metadata.icon', 'metadata.url', ]; protected array $suggestions = [ EntityAssetCache::class => 'clearSuggestion', ]; /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo(Entity::class); } /** * @return HasOne */ public function image(): HasOne { return $this->hasOne(Image::class, 'id', 'image_uuid'); } /** * Determine if the asset is a file */ public function isFile(): bool { return $this->type_id === EntityAssetType::file; } /** * Determine if the asset is a link */ public function isLink(): bool { return $this->type_id === EntityAssetType::link; } /** * Determine if the asset is an alias */ public function isAlias(): bool { return $this->type_id === EntityAssetType::alias; } /** * Determine if the file is an image */ public function isImage(): bool { return $this->isFile() && Str::startsWith($this->metadata['type'], 'image/'); } /** * Determine if the file is audio */ public function isAudio(): bool { return $this->isFile() && Str::startsWith($this->metadata['type'], 'audio/'); } /** * Get the image's url */ public function imageUrl(): string { if ($this->image) { return $this->image->getUrl(128, 80); } return Img::crop(128, 80)->url($this->metadata['path']); } /** * Get the fontawesome custom icon */ public function icon(): string { if (empty($this->metadata['icon'])) { return 'fa-regular fa-link'; } return (string) $this->metadata['icon']; } public function previewIcon(): string { if (! $this->image) { return 'fa-regular fa-file'; } return match ($this->image->ext) { 'pdf' => 'fa-regular fa-file-pdf', 'json' => 'fa-regular fa-brackets-curly', 'mp3', 'mp4', 'ogg' => 'fa-regular fa-file-music', 'xls', 'xlsx' => 'fa-regular fa-file-xls', 'csv' => 'fa-regular fa-file-csv', default => 'fa-regular fa-file', }; } public function getIconAttribute(): mixed { return Arr::get($this->metadata, 'icon'); } /** * A virtual getter for the image path for the image observer delete loop */ public function getImagePathAttribute(): string { if ($this->image && ! $this->image->isUsed()) { return (string) $this->image->path; } return (string) $this->metadata['path']; } /** * Copy the asset to another target */ public function copyTo(Entity $target): bool { $new = $this->replicate(['entity_id']); $new->entity_id = $target->id; return $new->save(); } /** * Get the url's domain (skip the rest) */ public function urlDomain(): string { $url = $this->metadata['url']; try { $params = parse_url($url); return $params['host']; } catch (Exception $e) { return ''; } } public function url(): string { if ($this->image_uuid) { return $this->image?->url() ?? ''; } $path = $this->metadata['path']; $cdn = config('cdn.ugc'); if ($cdn) { return $cdn . '/' . $path; } return Storage::url($path); } /** * A file can be linked to a gallery image, but the target image is hidden from the current user. */ public function hiddenImage(): bool { return ! empty($this->image_uuid) && ! $this->image; } } ================================================ FILE: app/Models/EntityEventType.php ================================================ EntityEventTypes::class, ]; public $timestamps = false; } ================================================ FILE: app/Models/EntityListingPreference.php ================================================ 'array', 'nested' => 'boolean', 'per_page' => 'integer', ]; } public function user(): BelongsTo { return $this->belongsTo(User::class); } public function campaign(): BelongsTo { return $this->belongsTo(Campaign::class); } public function entityType(): BelongsTo { return $this->belongsTo(EntityType::class, 'type_id'); } } ================================================ FILE: app/Models/EntityLocation.php ================================================ */ public function location(): BelongsTo { return $this->belongsTo(Location::class, 'location_id'); } /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo(Entity::class, 'entity_id'); } public function exportFields(): array { return [ 'location_id', ]; } } ================================================ FILE: app/Models/EntityLog.php ================================================ 'array', ]; protected array $custom = [ 'header_uuid' => 'fields.header-image.title', 'image_uuid' => 'crud.fields.image', 'is_template' => 'entities/actions.archetype.toggle', 'is_attributes_private' => 'entities/attributes.fields.is_private', ]; protected string $userField = 'created_by'; public function parent(): MorphTo { return $this->morphTo(); } /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } /** * @return BelongsTo */ public function impersonator(): BelongsTo { return $this->belongsTo(User::class, 'impersonated_by'); } public function actionCode(): string { if ($this->action == self::ACTION_CREATE) { return 'create'; } elseif ($this->action == self::ACTION_UPDATE) { return 'update'; } elseif ($this->action == self::ACTION_DELETE) { return 'delete'; } elseif ($this->action == self::ACTION_RESTORE) { return 'restore'; } elseif ($this->action == self::ACTION_CREATE_POST) { return 'create_post'; } elseif ($this->action == self::ACTION_UPDATE_POST) { return 'update_post'; } elseif ($this->action == self::ACTION_DELETE_POST) { return 'delete_post'; } elseif ($this->action == self::ACTION_REORDER_POST) { return 'reorder_post'; } elseif ($this->action == self::ACTION_UPDATE_FAMILY_TREE) { return 'update_tree'; } return 'unknown'; } public function actionIcon(): string { if ($this->action == self::ACTION_CREATE || $this->action == self::ACTION_CREATE_POST) { return 'fa-plus'; } elseif ($this->action == self::ACTION_UPDATE || $this->action == self::ACTION_UPDATE_POST || $this->action == self::ACTION_UPDATE_FAMILY_TREE) { return 'fa-pencil'; } elseif ($this->action == self::ACTION_REORDER_POST) { return 'fa-arrows-rotate'; } elseif ($this->action == self::ACTION_DELETE || $this->action == self::ACTION_DELETE_POST) { return 'fa-trash-can'; } elseif ($this->action == self::ACTION_RESTORE) { return 'fa-history'; } return 'fa-question-circle'; } public function actionBackground(): string { if ($this->action == self::ACTION_CREATE || $this->action == self::ACTION_CREATE_POST) { return 'bg-green-300'; } elseif ($this->action == self::ACTION_UPDATE || $this->action == self::ACTION_UPDATE_POST || $this->action == self::ACTION_UPDATE_FAMILY_TREE) { return 'bg-blue-200'; } elseif ($this->action == self::ACTION_REORDER_POST) { return 'bg-yellow-300'; } elseif ($this->action == self::ACTION_DELETE || $this->action == self::ACTION_DELETE_POST) { return 'bg-red-300'; } elseif ($this->action == self::ACTION_RESTORE) { return 'bg-orange-300'; } return 'bg-gray'; } /** * @return Builder */ public function scopeRecent(Builder $query) { return $query->orderBy('created_at', 'DESC')->orderBy('id', 'DESC'); } /** * @return Builder */ public function scopeAction(Builder $query, int $action) { return $query->where(['action' => $action]); } public function isBoolean(string $attribute): bool { return Str::startsWith($attribute, ['has_', 'is_']); } /** * Replace the field edited with it's translated name */ public function attributeKey(string $transKey, string $attribute): string { $name = Str::beforeLast($attribute, '_id'); // Entity name $key = 'entities.' . $name; $translation = __($key); if ($key !== $translation && ! is_array($translation)) { return $translation; } // Crud field $key = 'crud.fields.' . $name; $translation = __($key); if ($key !== $translation) { return $translation; } $key = $transKey . '.fields.' . $name; $translation = __($key); if ($key !== $translation) { return $translation; } // Custom mapping if (isset($this->custom[$name])) { return __($this->custom[$name]); } if (app()->isProduction()) { return '' . __('crud.users.unknown') . ''; } return '' . $name . ''; } /** * Automatically prune old elements from the db */ public function prunable(): Builder { $delay = config('entities.logs_delete'); return static::where('updated_at', '<=', now()->subDays($delay)); } public function day(): int { return (int) $this->created_at->format('Ymd'); } public function userLink(): string { if (! $this->user) { return '' . __('crud.users.unknown') . ''; } return '' . $this->user->name . ''; } public function actions($action): array { if ($action == self::ACTION_CREATE || $action == self::ACTION_CREATE_POST) { return [self::ACTION_CREATE, self::ACTION_CREATE_POST]; } elseif ($action == self::ACTION_UPDATE) { return [self::ACTION_UPDATE, self::ACTION_UPDATE_POST, self::ACTION_REORDER_POST, self::ACTION_UPDATE_FAMILY_TREE]; } elseif ($action == self::ACTION_DELETE) { return [self::ACTION_DELETE, self::ACTION_DELETE_POST]; } elseif ($action == self::ACTION_RESTORE) { return [self::ACTION_RESTORE]; } elseif ($action == self::ACTION_REORDER_POST) { return [self::ACTION_REORDER_POST]; } return []; } public function scopeFilter(Builder $builder, array $filters): Builder { $user = Arr::get($filters, 'user'); if (! empty($user)) { $builder->where($this->getTable() . '.created_by', (int) $user); } $action = Arr::get($filters, 'action'); if (! empty($action)) { $actions = $this->actions($action); $builder->whereIn($this->getTable() . '.action', $actions); } if (Arr::has($filters, 'q')) { $q = mb_trim(Arr::get($filters, 'q')); $builder->whereLike('changes', '%' . $q . '%'); } return $builder; } public function isPost(): bool { return $this->parent_type == Post::class; } } ================================================ FILE: app/Models/EntityMention.php ================================================ */ public function target(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'target_id', 'id'); } /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id', 'id'); } /** * @return BelongsTo */ public function post(): BelongsTo { return $this->belongsTo('App\Models\Post', 'post_id', 'id'); } /** * @return BelongsTo */ public function timelineElement(): BelongsTo { return $this->belongsTo('App\Models\TimelineElement', 'timeline_element_id', 'id'); } /** * @return BelongsTo */ public function questElement(): BelongsTo { return $this->belongsTo('App\Models\QuestElement', 'quest_element_id', 'id'); } /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo('App\Models\Campaign', 'campaign_id', 'id'); } /** * Determine if the mention goes to a post */ public function isPost(): bool { return ! empty($this->post_id); } /** * Determine if the mention goes to an entity */ public function isEntity(): bool { return ! empty($this->entity_id); } /** * Determine if the mention goes to a timeline element */ public function isTimelineElement(): bool { return ! empty($this->timeline_element_id); } /** * Determine if the mention goes to a quest element */ public function isQuestElement(): bool { return ! empty($this->quest_element_id); } /** * Determine if the mention goes to a campaign */ public function isCampaign(): bool { return ! empty($this->campaign_id); } /** * Build the query that will loop on the various mentions to get the total count. * The AclTrait on entities and posts makes sure only visible things get added to the query. */ public function scopeFilterValid(Builder $query): Builder { return $query->where(function ($sub) { return $sub ->where(function ($subEnt) { // @phpstan-ignore-next-line return $subEnt ->onEntity() ->has('entity'); }) ->orWhere(function ($subPost) { // @phpstan-ignore-next-line return $subPost ->onPost() ->has('post.entity'); }) ->orWhere(function ($subQuestElement) { // @phpstan-ignore-next-line return $subQuestElement ->onQuestElement() ->has('questElement.quest.entity'); }) ->orWhere(function ($subTimelineElement) { // @phpstan-ignore-next-line return $subTimelineElement ->onTimelineElement() ->has('timelineElement.timeline.entity'); }) ->orWhere(function ($subCam) { // @phpstan-ignore-next-line return $subCam->onCampaign(); }); }); } public function scopeOnEntity(Builder $query): Builder { return $query->where(function ($sub) { $sub->whereNotNull('entity_mentions.entity_id') ->whereNull('entity_mentions.post_id') ->whereNull('entity_mentions.timeline_element_id') ->whereNull('entity_mentions.quest_element_id'); }); } public function scopeOnPost(Builder $query): Builder { return $query->whereNotNull('entity_mentions.post_id'); } public function scopeOnTimelineElement(Builder $query): Builder { return $query->whereNotNull('entity_mentions.timeline_element_id'); } public function scopeOnQuestElement(Builder $query): Builder { return $query->whereNotNull('entity_mentions.quest_element_id'); } public function scopeOnCampaign(Builder $query): Builder { return $query->whereNotNull('entity_mentions.campaign_id'); } public function scopeDatagridElements(Builder $query, array $options): Builder { $column = Arr::get($options, 'k', 'name'); $order = Arr::get($options, 'o', 'ASC'); $query->select('entity_mentions.*') ->leftJoin('entities as e', 'e.id', 'entity_mentions.entity_id'); if ($column == 'name') { $query->orderByRaw('CASE WHEN e.name IS NULL THEN 1 ELSE 0 END'); $query->orderBy('e.name', $order); } elseif ($column == 'type') { $query->orderByRaw('CASE WHEN e.type_id IS NULL THEN 1 ELSE 0 END'); $query->orderBy('e.type_id', $order); } return $query ->orderBy('campaign_id'); } /** * Todo: move this out of the model */ public function getLink(): string { $campaign = CampaignLocalization::getCampaign(); if ($this->isQuestElement()) { return route('quests.quest_elements.index', [$campaign, $this->entity->entity_id, '#quest-element-' . $this->quest_element_id]); } elseif ($this->isTimelineElement()) { return route('entities.show', [$campaign, $this->entity, '#timeline-element-' . $this->timeline_element_id]); } elseif ($this->isPost()) { return route('entities.show', [$campaign, $this->entity, '#post-' . $this->post_id]); } return '#'; } /** * Determine if the mention is linked to an entity. * In theory, this is true for everything except a campaign mention, but in practice it's more complicated. */ public function hasEntity(): bool { return ! empty($this->entity_id) && ! empty($this->entity); } public function exportFields(): array { return [ 'entity_id', 'campaign_id', 'post_id', 'timeline_element_id', 'quest_element_id', 'target_id', ]; } } ================================================ FILE: app/Models/EntityTag.php ================================================ */ public function tag(): BelongsTo { return $this->belongsTo('App\Models\Tag', 'tag_id'); } /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id'); } public function exportFields(): array { return [ 'tag_id', ]; } } ================================================ FILE: app/Models/EntityType.php ================================================ id; } return $query->where(function ($sub) use ($campaign) { return $sub->where('campaign_id', $campaign) ->orWhereNull('campaign_id'); }); } public function scopeDefault(Builder $query): Builder { return $query->whereNull('campaign_id'); } public function scopeEnabled(Builder $query): Builder { return $query ->where(['is_enabled' => true]) ->orderBy('position'); } public function scopeExclude(Builder $query, array $exclude): Builder { return $query->whereNotIn('id', $exclude); } /** * @return HasMany */ public function entities(): HasMany { return $this->hasMany(Entity::class, 'type_id'); } /** * @return HasMany */ public function attributeTemplates(): HasMany { return $this->hasMany(AttributeTemplate::class, 'entity_type_id'); } /** * @return HasMany */ public function bookmarks(): HasMany { return $this->hasMany(Bookmark::class, 'entity_type_id'); } /** * @return HasMany */ public function widgets(): HasMany { return $this->hasMany(CampaignDashboardWidget::class, 'entity_type_id'); } /** * Get the class model of the entity type */ public function getClass(): MiscModel|Model { $className = 'App\Models\\' . Str::studly($this->code); return app()->make($className); } /** * Freaking hate PHPStan */ public function getMiscClass(): MiscModel { $className = 'App\Models\\' . Str::studly($this->code); return app()->make($className); } /** * Get the translated name of the entity */ public function name(): string { if (! empty($this->singular)) { return $this->singular; } return Module::singular($this->id, __('entities.' . $this->code)); } /** * Get the translated name of the entity */ public function plural(): string { // Custom module always uses the defined plural if (! empty($this->plural)) { return $this->plural; } return Module::plural($this->id, $this->defaultPluralKey()); } public function defaultPluralKey(): string { return 'entities.' . $this->pluralCode(); } /** * Get the translated name of the entity */ public function icon(): string { // Custom module? Always use the icon if (! empty($this->campaign_id)) { return $this->icon; } return Module::duoIcon($this); } /** * Get the translated name of the entity */ public function pluralCode(): string { if (isset($this->cachedPluralCode)) { return $this->cachedPluralCode; } return $this->cachedPluralCode = Str::plural($this->code); } public function getNameAttribute(): string { return $this->name(); } /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo(Campaign::class); } public function isCustom(): bool { return (bool) $this->is_special; } public function isStandard(): bool { return ! $this->isCustom(); } public function isEnabled(): bool { return (bool) $this->is_enabled; } public function isBookmark(): bool { return $this->id == config('entities.ids.bookmark'); } public function isAttributeTemplate(): bool { return $this->id == config('entities.ids.attribute_template'); } public function isCharacter(): bool { return $this->id == config('entities.ids.character'); } public function isLocation(): bool { return $this->id == config('entities.ids.location'); } public function createRoute(Campaign $campaign, array $params = []): string { if ($this->isCustom()) { return route('entities.create', [$campaign, $this] + $params); } return route($this->pluralCode() . '.create', [$campaign] + $params); } public function isDeprecated(): bool { return in_array($this->id, [config('entities.ids.conversation'), config('entities.ids.dice_roll')]); } /** * For some weird reason, bookmarks are an entity type, despite bookmarks not being entities */ public function hasEntity(): bool { return $this->code !== 'bookmark'; } public function isNested(): bool { return ! in_array($this->id, [ config('entities.ids.character'), config('entities.ids.conversation'), config('entities.ids.dice_roll'), ]); } public function hasTable(): bool { return true; } } ================================================ FILE: app/Models/EntityUser.php ================================================ */ public function entity(): BelongsTo { return $this->belongsTo(Entity::class, 'entity_id'); } /** * @return BelongsTo */ public function campaign(): BelongsTo { return $this->belongsTo(Campaign::class, 'campaign_id'); } /** * @return BelongsTo */ public function post(): BelongsTo { return $this->belongsTo(Post::class, 'post_id'); } /** * @return BelongsTo */ public function timelineElement(): BelongsTo { return $this->belongsTo(TimelineElement::class, 'timeline_element_id'); } /** * @return BelongsTo */ public function questElement(): BelongsTo { return $this->belongsTo(QuestElement::class, 'quest_element_id'); } public function scopeKeepAlive(Builder $query): Builder { return $query->where('type_id', self::TYPE_KEEPALIVE); } public function scopeUserID(Builder $query, int $userID): Builder { return $query->where('user_id', $userID); } public function scopeCampaignID(Builder $query, int $campaignID): Builder { return $query->where('campaign_id', $campaignID); } /** * Automatically prune old elements from the db */ public function prunable(): Builder { return static::keepAlive() ->where('created_at', '<=', now()->subDay()); } } ================================================ FILE: app/Models/Event.php ================================================ select(['events.id', 'events.name', 'events.date', 'events.is_private']) ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->with([ 'entity.locations', 'entity.locations.entity', 'entity', 'entity.parent', 'entity.tags', 'entity.tags.entity', 'entity.image']) ->has('entity'); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.event'); } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if (! empty($this->type)) { return true; } if ($this->entity->locations->isNotEmpty() || ! empty($this->entity->calendarReminder())) { return true; } return parent::showProfileInfo(); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'date', 'locations', ]; } /** * Grid mode sortable fields */ public function datagridSortableColumns(): array { $columns = [ 'name' => __('crud.fields.name'), 'type' => __('crud.fields.type'), 'date' => __('events.fields.date'), ]; if (auth()->check() && auth()->user()->isAdmin()) { $columns['is_private'] = __('crud.fields.is_private'); } return $columns; } } ================================================ FILE: app/Models/Family.php ================================================ select($this->getTable() . '.*') ->leftJoin('character_family as memb', function ($join) { $join->on('memb.family_id', '=', $this->getTable() . '.id'); }) ->where('memb.character_id', null); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('memb.is_private', 0); } return $query; } elseif ($filter === FilterOption::EXCLUDE) { return $query ->whereRaw('(select count(*) from character_family as memb where memb.family_id = ' . $this->getTable() . '.id and memb.family_id = ' . ((int) $value) . ' ' . $this->subPrivacy('and memb.is_private') . ') = 0'); } $ids = [$value]; $query ->select($this->getTable() . '.*') ->leftJoin('character_family as memb', function ($join) { $join->on('memb.family_id', '=', $this->getTable() . '.id'); }) ->whereIn('memb.character_id', $ids); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('memb.is_private', 0); } return $query->distinct(); } /** * @return HasOne */ public function familyTree(): HasOne { return $this->hasOne(FamilyTree::class); } public function members(): BelongsToMany { $query = $this->belongsToMany('App\Models\Character', 'character_family'); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->wherePivot('is_private', false); } return $query; } /** * @return HasMany */ public function pivotMembers(): HasMany { return $this->hasMany(CharacterFamily::class) ->with(['character', 'character.entity']); } /** * All members of a family and descendants */ public function allMembers() { $familyId = [$this->id]; foreach ($this->entity->descendants as $descendant) { $familyId[] = $descendant->entity_id; } $query = Character::select('characters.*') ->distinct('characters.id') ->leftJoin('character_family as cf', function ($join) { $join->on('cf.character_id', '=', 'characters.id'); }) ->has('entity') ->whereIn('cf.family_id', $familyId); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('cf.is_private', false); } return $query; } /** * Get all characters in the family and descendants */ public function allCharacterFamilies() { $familyIDs = [$this->id]; foreach ($this->entity->descendants as $descendant) { $familyIDs[] = $descendant->entity_id; } return CharacterFamily::groupBy('character_id') ->distinct('character_id') ->whereIn('character_family.family_id', $familyIDs) ->with('character'); } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { $this->members()->detach(); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.family'); } /** * Determine if the model is extinct. */ public function isExtinct(): bool { return (bool) $this->is_extinct; } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { // Test text fields first if (! empty($this->type)) { return true; } if (! empty($this->entity->parent)) { return true; } if ($this->entity->elapsedEvents->isNotEmpty()) { return true; } return parent::showProfileInfo(); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'location_id', 'member_id', ]; } } ================================================ FILE: app/Models/FamilyTree.php ================================================ 'array', ]; /** * @return BelongsTo */ public function family(): BelongsTo { return $this->belongsTo(Family::class); } } ================================================ FILE: app/Models/Faq.php ================================================ where('is_visible', $visible); } public function scopeLocale(Builder $query, string $locale = 'en'): Builder { return $query->where('locale', $locale); } /** * @param string $order */ public function scopeOrdered(Builder $query, $order = 'ASC'): Builder { return $query->orderBy('order', $order); } /** * @return BelongsTo */ public function category(): BelongsTo { return $this->belongsTo('App\Models\FaqCategory', 'faq_category_id', 'id'); } /** * @return HasMany */ public function translations(): HasMany { return $this->hasMany(FaqTranslation::class); } public function localeTranslation() { return $this->hasOne(FaqTranslation::class, 'faq_id', 'id') ->where('locale', app()->getLocale()); } public function slug(): string { if ($this->cachedSlug !== null) { return $this->cachedSlug; } return $this->cachedSlug = Str::slug($this->question); } /** * Get the question */ public function question(): string { if ($this->localeTranslation && ! empty($this->localeTranslation->question)) { return $this->localeTranslation->question; } return $this->question; } /** * Get the answer */ public function answer(): string { if ($this->localeTranslation && ! empty($this->localeTranslation->answer)) { return $this->localeTranslation->answer; } return $this->answer; } public function translatedQuestion(string $locale): string { $translation = $this->translations->where('locale', $locale)->first(); if (! $translation) { return ''; } return $translation->question; } public function translatedAnswer(string $locale): string { $translation = $this->translations->where('locale', $locale)->first(); if (! $translation) { return ''; } return $translation->answer; } } ================================================ FILE: app/Models/FaqCategory.php ================================================ where('is_visible', $visible); } /** * @param string $locale */ public function scopeLocale(Builder $query, $locale = 'en') { return $query->where('locale', $locale); } /** * @param string $order * @return Builder */ public function scopeOrdered(Builder $query, $order = 'ASC') { return $query->orderBy('order', $order); } /** * @return HasMany */ public function faqs(): HasMany { return $this->hasMany('App\Models\Faq', 'faq_category_id', 'id'); } /** * @return string */ public function getNameAttribute() { return $this->title; } /** * @return Faq[]|Collection */ public function sortedFaqs() { return $this->faqs ->where('is_visible', true) ->sortBy('order'); } } ================================================ FILE: app/Models/FaqTranslation.php ================================================ */ public function faq(): BelongsTo { return $this->belongsTo('App\Models\Faq', 'faq_id', 'id'); } public function scopeLocale(Builder $query, string $locale = 'en'): Builder { return $query->where('locale', $locale); } public function scopeFaqID(Builder $query, int $faq): Builder { return $query->where('faq_id', $faq); } } ================================================ FILE: app/Models/Feature.php ================================================ 'array', ]; protected array $sanitizable = [ 'name', 'description', ]; /** * @return BelongsTo */ public function category(): BelongsTo { return $this->belongsTo(FeatureCategory::class); } /** * @return BelongsTo */ public function status(): BelongsTo { return $this->belongsTo(FeatureStatus::class); } /** * @return HasOne */ public function uservote(): HasOne { return $this->hasOne(FeatureVote::class) ->where('user_id', auth()->user()->id); } /** * @return HasMany */ public function featureFiles(): HasMany { return $this->hasMany(FeatureFile::class, 'feature_id', 'id'); } public function scopeApproved(Builder $builder): Builder { return $builder->whereIn('status_id', [\App\Enums\FeatureStatus::Approved]) ->orderBy('upvote_count', 'DESC'); } public function scopeVisible(Builder $builder): Builder { $statuses = [ \App\Enums\FeatureStatus::Approved, \App\Enums\FeatureStatus::Later, \App\Enums\FeatureStatus::Next, \App\Enums\FeatureStatus::Now, \App\Enums\FeatureStatus::Done, ]; return $builder->whereIn('status_id', $statuses) ->orderBy('upvote_count', 'DESC'); } public function scopeSearch(Builder $builder, string $search): Builder { if (empty($search) | mb_strlen($search) < 3) { return $builder; } return $builder->where('name', 'like', '%' . $search . '%'); } public function isUpvoted(): bool { if (auth()->guest()) { return false; } return auth()->user()->upvotes()->forFeature($this)->count() === 1; } public function cleanDescription(): string { if (Str::startsWith($this->description, '

')) { return $this->description; } return '

' . nl2br($this->description) . '

'; } } ================================================ FILE: app/Models/FeatureCategory.php ================================================ */ public function features(): HasMany { return $this->hasMany(Feature::class, 'category_id', 'id'); } public function progress(): HasMany { return $this->features() ->whereIn('features.status_id', [ FeatureStatus::Later, FeatureStatus::Next, FeatureStatus::Now, ]); } public function done(): HasMany { return $this->features()->where('status_id', FeatureStatus::Done)->orderBy('updated_at', 'DESC'); } public function now(): HasMany { return $this->features()->where('status_id', FeatureStatus::Now); } public function next(): HasMany { return $this->features()->where('status_id', FeatureStatus::Next); } public function later(): HasMany { return $this->features()->where('status_id', FeatureStatus::Later); } public function nothingPlanned(): bool { return $this->now->count() + $this->later->count() + $this->next->count() === 0; } public function nothingDone(): bool { return $this->done->count() === 0; } } ================================================ FILE: app/Models/FeatureFile.php ================================================ */ public function feature(): BelongsTo { return $this->belongsTo(Feature::class, 'feature_id'); } } ================================================ FILE: app/Models/FeatureStatus.php ================================================ */ public function features(): HasMany { return $this->hasMany(Feature::class); } } ================================================ FILE: app/Models/FeatureUpvote.php ================================================ */ public function feature(): BelongsTo { return $this->belongsTo(Feature::class); } } ================================================ FILE: app/Models/FeatureVote.php ================================================ */ public function feature(): BelongsTo { return $this->belongsTo(Feature::class); } public function scopeForFeature(Builder $query, Feature $feature): Builder { return $query->where('feature_id', $feature->id); } } ================================================ FILE: app/Models/GameSystem.php ================================================ */ public function campaignSystem(): HasMany { return $this->hasMany(CampaignSystem::class, 'system_id', 'id'); } } ================================================ FILE: app/Models/Genre.php ================================================ Visibility::class, ]; protected string $userField = 'created_by'; protected array $sanitizable = [ 'name', ]; /** * @return BelongsTo */ public function imageFolder(): BelongsTo { return $this->belongsTo(Image::class, 'folder_id', 'id'); } /** * @return HasMany */ public function images(): HasMany { return $this->hasMany(Image::class, 'folder_id', 'id'); } /** * @return HasMany */ public function folders(): HasMany { return $this->hasMany(Image::class, 'folder_id', 'id') ->where('is_folder', true); } /** * @return HasMany */ public function entities(): HasMany { return $this->hasMany(Entity::class, 'image_uuid', 'id'); } /** * @return HasMany */ public function mapLayers(): HasMany { return $this->hasMany(MapLayer::class, 'image_uuid', 'id'); } /** * @return HasMany */ public function inventories(): HasMany { return $this->hasMany(Inventory::class, 'image_uuid', 'id'); } /** * @return HasMany */ public function entityAssets(): HasMany { return $this->hasMany(EntityAsset::class, 'image_uuid', 'id') ->with('entity') ->has('entity'); } /** * @return HasMany */ public function headers(): HasMany { return $this->hasMany(Entity::class, 'header_uuid', 'id'); } /** * @return HasMany */ public function mentions(): HasMany { return $this->hasMany(ImageMention::class, 'image_id', 'id') ->with('entity') ->with('post') ->has('entity'); } public function inEntities(): array { $entities = []; foreach ($this->entities as $entity) { if (isset($entities[$entity->id])) { continue; } $entities[$entity->id] = $entity; } foreach ($this->headers as $entity) { if (isset($entities[$entity->id])) { continue; } $entities[$entity->id] = $entity; } foreach ($this->entityAssets as $asset) { if (isset($entities[$asset->entity->id])) { continue; } $entities[$asset->entity->id] = $asset->entity; } return $entities; } public function isUsed(): bool { $entities = count($this->inEntities()); $mentions = $this->mentions()->count(); $layers = $this->mapLayers()->count(); $inventories = $this->inventories()->count(); return $entities || $mentions || $layers || $inventories; } public function inEntitiesCount(): int { if (isset($this->_usageCount)) { return $this->_usageCount; } return $this->_usageCount = count($this->inEntities()); } /** * @return bool */ public function getIncrementing() { return false; } /** * @return string */ public function getKeyType() { return 'string'; } public function getPathAttribute(): string { return $this->folder . '/' . $this->file; } public function getFileAttribute(): string { return $this->id . '.' . $this->ext; } public function getFolderAttribute(): string { return 'campaigns/' . $this->campaign_id; } public function niceSize(): string { if ($this->size > 1000) { return round($this->size / 1024, 2) . ' MB'; } return $this->size . ' KB'; } public function scopeImageFolder(Builder $query, ?string $folder = null): Builder { if (empty($folder)) { return $query->whereNull('folder_id'); } return $query->where('folder_id', $folder); } public function scopeDefaultOrder(Builder $query): Builder { return $query ->orderBy('is_folder', 'desc') ->orderBy('updated_at', 'desc') ->orderBy('name', 'asc'); } public function scopeSortOrder(Builder $query, string $sort = 'asc'): Builder { return $query ->orderBy('is_folder', 'desc') ->orderBy('name', $sort) ->orderBy('updated_at', 'desc'); } public function scopeFolders(Builder $query): Builder { return $query ->where('is_folder', true) ->orderBy('name', 'asc'); } public function scopeAcl(Builder $query, bool $browse): Builder { if (! $browse) { return $query->where('created_by', auth()->user()->id); } return $query; } public function scopeNamed(Builder $query, ?string $term): Builder { if (empty($term)) { return $query; } return $query->where($this->getTable() . '.name', 'like', '%' . $term . '%'); } public function scopeSearch(Builder $query, ?string $folder, ?string $term): Builder { if (empty($term)) { // @phpstan-ignore-next-line return $query->imageFolder($folder); } // @phpstan-ignore-next-line return $query->named($term); } public function hasNoFolders(): bool { return $this->images()->where('is_folder', '1')->count() == 0; } public function getImagePath($width = 40, $height = 40): string { return Img::resetCrop()->crop($width, $height)->url($this->path); } public function isFolder(): bool { return (bool) $this->is_folder; } public function isFont(): bool { return in_array($this->ext, ['woff', 'woff2']); } public function hasThumbnail(): bool { return in_array($this->ext, ['jpg', 'png', 'jpeg', 'gif', 'webp']); } public function getUrl(?int $sizeX = null, ?int $sizeY = null): string { if ($this->isSvg()) { return $this->url(); } Img::reset(); if (! $sizeY && $sizeX) { $sizeY = $sizeX; } elseif (! $sizeX && $sizeY) { $sizeX = $sizeY; } if ($sizeX && $sizeY) { if (! $this->focus_x && ! $this->focus_y) { return Img::crop($sizeX, $sizeY)->url($this->path); } return Img::focus($this->focus_x, $this->focus_y)->crop($sizeX, $sizeY)->url($this->path); } if ($this->focus_x && $this->focus_y) { return Img::focus($this->focus_x, $this->focus_y)->url($this->path); } return Img::url($this->path); } public function isSvg(): bool { return $this->ext == 'svg'; } public function url(): string { $path = $this->path; $cdn = config('cdn.ugc'); if ($cdn) { return $cdn . '/' . $path; } return Storage::url($path); } } ================================================ FILE: app/Models/ImageMention.php ================================================ */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id', 'id'); } /** * @return BelongsTo */ public function post(): BelongsTo { return $this->belongsTo('App\Models\Post', 'post_id', 'id'); } /** * Determine if the mention goes to a post */ public function isPost(): bool { return ! empty($this->post_id); } /** * Build the query that will loop on the various mentions to get the total count. * The AclTrait on entities and posts makes sure only visible things get added to the query. */ public function scopePrepareCount(Builder $query): Builder { return $query->where(function ($sub) { return $sub ->where(function ($subEnt) { // @phpstan-ignore-next-line return $subEnt ->entity() ->has('entity'); }) ->orWhere(function ($subPost) { // @phpstan-ignore-next-line return $subPost ->post() ->has('post.entity'); }); }); } public function scopeEntity(Builder $query): Builder { return $query->whereNotNull('image_mentions.entity_id'); } public function scopePost(Builder $query): Builder { return $query->whereNotNull('image_mentions.post_id'); } } ================================================ FILE: app/Models/Inventory.php ================================================ Visibility::class, ]; protected array $sanitizable = [ 'name', 'position', 'description', ]; /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity'); } /** * @return BelongsTo */ public function item(): BelongsTo { return $this->belongsTo('App\Models\Item'); } /** * @return HasOne */ public function image(): HasOne { return $this->hasOne('App\Models\Image', 'id', 'image_uuid'); } /** * List of recently used positions for the form suggestions */ public function scopePositionList(Builder $builder, Campaign $campaign): Builder { return $builder->groupBy('position') ->whereNotNull('position') ->leftJoin('entities as e', 'e.id', 'inventories.entity_id') ->where('e.campaign_id', $campaign->id) ->orderBy('position', 'ASC') ->limit(50); } /** * Get the item name, either custom or attached object */ public function itemName(): string { if (empty($this->name) && ! empty($this->item)) { return $this->item->name; } return (string) $this->name; } /** * Copy an entity inventory to another target */ public function copyTo(Entity $target, bool $sameCampaign): bool { $without = $sameCampaign ? ['entity_id'] : ['entity_id', 'item_id', 'image_uuid']; $new = $this->replicate($without); $new->entity_id = $target->id; if ($sameCampaign) { return $new->save(); } if (empty($new->name)) { return false; } return $new->save(); } public function isEquipped(): bool { return $this->is_equipped; } } ================================================ FILE: app/Models/Item.php ================================================ 'unsigned', ]; /** * Nullable values (foreign keys) * * @var string[] */ public array $nullableForeignKeys = [ 'location_id', ]; /** * Foreign relations to add to export */ protected array $foreignExport = [ 'itemCreators', ]; protected array $exportFields = [ 'base', 'price', 'size', 'weight', 'location_id', ]; /** * @var string[] Extra relations loaded for the API endpoint */ public array $apiWith = ['itemCreators']; /** * Tooltip subtitle (item price/size) */ public function tooltipSubtitle(): string { $extra = []; if (! empty($this->price)) { $extra[] = __('items.fields.price') . ': ' . e($this->price); } if (! empty($this->size)) { $extra[] = __('items.fields.size') . ': ' . e($this->size); } if (! empty($this->weight)) { $extra[] = __('items.fields.weight') . ': ' . e($this->weight); } if (empty($extra)) { return ''; } return implode('
', $extra); } /** * @return HasMany */ public function itemCreators(): HasMany { return $this->hasMany(ItemCreator::class, 'item_id') ->orderBy('id'); } /** * @return BelongsToMany */ public function creators(): BelongsToMany { return $this->belongsToMany(Entity::class, 'item_creator', 'item_id', 'creator_id') ->orderBy('item_creator.id'); } /** * @return HasMany */ public function inventories(): HasMany { return $this->hasMany('App\Models\Inventory', 'item_id'); } /** * @return HasManyThrough */ public function entities(): HasManyThrough { return $this->hasManyThrough( 'App\Models\Entity', 'App\Models\Inventory', 'item_id', 'id', 'id', 'entity_id' ); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.item'); } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if (! empty($this->price) || ! empty($this->size) || ! empty($this->weight)) { return true; } if ($this->itemCreators->isNotEmpty() || $this->location) { return true; } return parent::showProfileInfo(); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'location_id', 'creators', 'price', 'size', 'weight', ]; } /** * Grid mode sortable fields */ public function datagridSortableColumns(): array { $columns = [ 'name' => __('crud.fields.name'), 'type' => __('crud.fields.type'), 'price' => __('items.fields.price'), 'size' => __('items.fields.size'), 'weight' => __('items.fields.weight'), ]; if (auth()->check() && auth()->user()->isAdmin()) { $columns['is_private'] = __('crud.fields.is_private'); } return $columns; } } ================================================ FILE: app/Models/ItemCreator.php ================================================ */ public function item(): BelongsTo { return $this->belongsTo(Item::class); } /** * @return BelongsTo */ public function creator(): BelongsTo { return $this->belongsTo(Entity::class, 'creator_id'); } public function exportFields(): array { return [ 'item_id', 'creator_id', ]; } } ================================================ FILE: app/Models/JobLog.php ================================================ entity->id]; foreach ($this->entity->descendants as $descendant) { $entityIds[] = $descendant->id; } return Journal::whereHas('entity', fn ($q) => $q->whereIn('entities.parent_id', $entityIds)) ->has('entity') ->with('entity.parent'); } /** * @return BelongsTo */ public function character(): BelongsTo { return $this->belongsTo('App\Models\Character', 'character_id'); } /** * @return BelongsTo */ public function author(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'author_id'); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.journal'); } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if (! empty($this->date)) { return true; } if (! empty($this->author) || ! empty($this->location)) { return true; } if (! empty($this->entity->calendarReminder())) { return true; } return parent::showProfileInfo(); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'date', 'character_id', 'location_id', 'author_id', 'date_start', 'date_end', ]; } /** * Grid mode sortable fields */ public function datagridSortableColumns(): array { $columns = [ 'name' => __('crud.fields.name'), 'type' => __('crud.fields.type'), 'date' => __('journals.fields.date'), 'calendar_date' => __('crud.fields.calendar_date'), ]; if (auth()->check() && auth()->user()->isAdmin()) { $columns['is_private'] = __('crud.fields.is_private'); } return $columns; } } ================================================ FILE: app/Models/Location.php ================================================ */ public function races(): BelongsToMany { return $this->belongsToMany('App\Models\Race', 'race_location'); } /** * @return BelongsToMany */ public function creatures(): BelongsToMany { return $this->belongsToMany('App\Models\Creature', 'creature_location'); } /** * @return BelongsToMany */ public function entities(): BelongsToMany { return $this->belongsToMany('App\Models\Entity', 'entity_locations'); } /** * @return HasMany */ public function items(): HasMany { return $this->hasMany('App\Models\Item', 'location_id', 'id'); } /** * @return HasMany */ public function maps(): HasMany { return $this->hasMany('App\Models\Map', 'location_id', 'id') ->with('entity') ->with('entity.image') ->has('entity') ->select(['id', 'name', 'is_real']); } /** * Get all events in the location and descendants */ public function allEvents(): Builder|Event { $locationIds = [$this->id]; foreach ($this->entity->descendants as $descendant) { $locationIds[] = $descendant->entity_id; } return Event::distinct() ->join('entities', function ($join) { $join ->on('entities.entity_id', '=', 'events.id') ->where('entities.type_id', config('entities.ids.event')); }) ->join('entity_locations as all_el', 'all_el.entity_id', '=', 'entities.id') ->whereIn('all_el.location_id', $locationIds); } /** * Get all characters in the location and descendants */ public function allCharacters(bool $direct = false): Builder|Character { $locationIds = [$this->id]; if ($direct) { foreach ($this->entity->descendants as $descendant) { $locationIds[] = $descendant->entity_id; } } return Character::distinct() ->join('entities', function ($join) { $join ->on('entities.entity_id', '=', 'characters.id') ->where('entities.type_id', config('entities.ids.character')); }) ->join('entity_locations', 'entity_locations.entity_id', '=', 'entities.id') ->whereIn('entity_locations.location_id', $locationIds); } /** * Get all quests in the location and descendants */ public function allQuests(): Builder|Quest { $locationIds = [$this->id]; foreach ($this->entity->descendants as $descendant) { $locationIds[] = $descendant->entity_id; } $table = new Quest; return Quest::whereIn($table->getTable() . '.location_id', $locationIds) ->with('location') ->has('entity'); } /** * @return HasMany */ public function families(): HasMany { return $this->hasMany('App\Models\Family', 'location_id', 'id'); } /** * @return HasMany */ public function journals(): HasMany { return $this->hasMany('App\Models\Journal', 'location_id', 'id'); } /** * @return BelongsToMany */ public function organisations(): BelongsToMany { return $this->belongsToMany('App\Models\Organisation', 'organisation_location'); } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { foreach ($this->families as $child) { $child->location_id = null; $child->save(); } foreach ($this->items as $child) { $child->location_id = null; $child->save(); } // Pivot tables can be deleted directly $this->races()->delete(); $this->creatures()->delete(); $this->organisations()->delete(); $this->entities()->delete(); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.location'); } /** * If the profile is shown */ public function showProfileInfo(): bool { if ($this->maps->isNotEmpty() || $this->entity->elapsedEvents->isNotEmpty()) { return true; } return parent::showProfileInfo(); } /** * Get the value of the is_destroyed variable */ public function isDestroyed(): bool { return (bool) $this->is_destroyed; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return []; } } ================================================ FILE: app/Models/Map.php ================================================ 'array', ]; protected array $sortable = [ 'name', 'type', ]; /** * Nullable values (foreign keys) * * @var string[] */ public array $nullableForeignKeys = [ 'location_id', 'center_marker_id', ]; /** * Foreign relations to add to export */ protected array $foreignExport = [ 'layers', 'groups', 'markers', ]; protected array $exportFields = [ 'base', 'location_id', 'grid', 'height', 'width', 'min_zoom', 'max_zoom', 'initial_zoom', 'center_x', 'center_y', 'center_marker_id', 'is_real', 'has_clustering', 'config', ]; protected array $sanitizable = [ 'name', ]; /** * Extra relations loaded for the API endpoint * * @var string[] */ public array $apiWith = ['groups', 'layers']; /** * @return HasMany */ public function layers(): HasMany { return $this->hasMany('App\Models\MapLayer', 'map_id', 'id') ->with('image'); } /** * @return HasMany */ public function groups(): HasMany { return $this->hasMany('App\Models\MapGroup', 'map_id', 'id'); } /** * @return HasMany */ public function markers(): HasMany { return $this->hasMany('App\Models\MapMarker', 'map_id', 'id') ->with(['entity', 'entity.entityType', 'group', 'map', 'entity.image']); } /** * @return HasOne */ public function centerMarker(): HasOne { return $this->hasOne('App\Models\MapMarker', 'id', 'center_marker_id'); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.map'); } public function grids(): array { $lines = []; // Horizontal lines $grid = $this->grid; for ($i = $grid; $i <= $this->height; $i += $grid) { $lines[] = [$i, 0, $i, $this->width]; } // Vertical lines for ($i = $grid; $i <= $this->width; $i += $grid) { $lines[] = [0, $i, $this->height, $i]; } return $lines; } /** * @return array|string[] */ public function groupOptions(): array { $options = [null => '']; $groups = $this->groups->sortBy('name'); foreach ($groups as $group) { $options[$group->id] = $group->name; } return $options; } /** * @return array|string[] */ public function groupPositionOptions(?int $position = null): array { $options = [1 => __('maps/groups.placeholders.position')]; $groups = $this->groups->sortBy('position'); foreach ($groups as $group) { $options[$group->position + 1] = __('maps/groups.placeholders.position_list', ['name' => $group->name]); } // If is the last position remove last+1 position from the options array if ($position == array_key_last($options) - 1 && count($options) > 1) { array_pop($options); } return $options; } /** * @return array|string[] */ public function layerPositionOptions(?int $position = null): array { $options = [1 => __('maps/layers.placeholders.position')]; $layers = $this->layers->sortBy('position'); foreach ($layers as $layer) { $options[$layer->position + 1] = __('maps/layers.placeholders.position_list', ['name' => $layer->name]); } // If is the last position remove last+1 position from the options array if ($position == array_key_last($options) - 1 && count($options) > 1) { array_pop($options); } return $options; } public function activeLayers(bool $groups = true): string { $layers = []; if (! $this->isReal()) { $layers = ['baseLayer' . $this->id]; } if ($groups) { foreach ($this->groups->where('is_shown', true) as $group) { $layers[] = 'group' . $group->id; } foreach ($this->layers->where('type_id', 2)->whereNotNull('image') as $layer) { $layers[] = 'layer' . $layer->id; } } return implode(', ', $layers); } /** * List of markers for the map legend (ordered by "name") */ public function legendMarkers(bool $link = true): array { $markers = new Collection; $groups = []; foreach ($this->markers as $marker) { if (! $marker->visible()) { continue; } if (! empty($marker->group)) { if (empty($groups[$marker->group_id])) { $groups[$marker->group_id] = [ 'name' => $marker->group->name, 'lower_name' => mb_strtolower($marker->group->name), 'id' => $marker->group_id, 'markers' => new Collection, ]; } $groups[$marker->group_id]['markers']->add([ 'id' => $marker->id, 'longitude' => $marker->longitude, 'latitude' => $marker->latitude, 'name' => str_replace("\\'", "'", $marker->markerTitle($link)), 'lower_name' => mb_strtolower($marker->markerTitle(false)), ]); continue; } $markers->add([ 'id' => $marker->id, 'longitude' => $marker->longitude, 'latitude' => $marker->latitude, 'name' => $marker->markerTitle($link), 'lower_name' => mb_strtolower($marker->markerTitle(false)), 'visibility' => $marker->skipAllIcon()->visibilityIcon(), ]); } $all = $markers->sortBy('lower_name')->toArray(); usort($groups, function ($a, $b) { return strcmp($a['lower_name'], $b['lower_name']); }); foreach ($groups as $id => $group) { // Reorder group $group['markers'] = $group['markers']->sortBy('lower_name')->toArray(); $all[] = $group; } usort($all, function ($a, $b) { return strcmp($a['lower_name'], $b['lower_name']); }); return $all; } /** * Minimum zoom of a map */ public function minZoom(): int { if (! is_numeric($this->min_zoom)) { if ($this->isReal() || $this->isChunked()) { return self::MIN_ZOOM_REAL; } return -2; } // if the initial zoom is further away than the min zoom, adapt if ($this->min_zoom > $this->initial_zoom && $this->initial_zoom > self::MIN_ZOOM) { return $this->initial_zoom; } // The max zoom is based on the chunked image so we trust this. if ($this->isChunked()) { return $this->min_zoom; } $min = $this->isReal() ? self::MIN_ZOOM_REAL : self::MIN_ZOOM; return (int) max($this->min_zoom, $min); } /** * Maximum zoom of a map */ public function maxZoom(): float { if (! is_numeric($this->max_zoom)) { if ($this->isChunked()) { return 13; } if ($this->isReal()) { return self::MAX_ZOOM_REAL; } return 2.75; } // The max zoom is based on the chunked image so we trust this. if ($this->isChunked()) { return $this->max_zoom; } $max = $this->isReal() ? self::MAX_ZOOM_REAL : self::MAX_ZOOM; return (float) min($this->max_zoom, $max); } /** * Initiall zoom of a map */ public function initialZoom(): int { if (! is_numeric($this->initial_zoom)) { if ($this->isReal() || $this->isChunked()) { return 12; } return 0; } if ($this->initial_zoom > self::MAX_ZOOM) { return self::MAX_ZOOM; } if ($this->initial_zoom < self::MIN_ZOOM) { return self::MIN_ZOOM; } return (int) $this->initial_zoom; } public function centerFocus(): string { // Init position in the middle of the map $latitude = $longitude = 0; if ($this->isReal()) { $latitude = 46.205; $longitude = 6.147; } elseif ($this->isChunked()) { $latitude = 0; $longitude = 0; } else { $latitude = floor($this->height / 2); $longitude = floor($this->width / 2); } // If we have a center marker if ($this->centerMarker != null) { // use his position $latitude = $this->centerMarker->latitude; $longitude = $this->centerMarker->longitude; } else { // Use the center positions if they exist if (! empty($this->center_y)) { $latitude = $this->center_y; } if (! empty($this->center_x)) { $longitude = $this->center_x; } } return "{$latitude}, {$longitude}"; } /** * Build the image's bounds for leaflet. * If the height or width is 0, which can happen with an svg with no height/width property, * we just assume 1000/1000 and wait for a user to come in discord for help. */ public function bounds(bool $extend = false): string { $this->prepareBounds(); $extra = $extend ? 50 : 0; $height = empty($this->height) ? 1000 : $this->height; $width = empty($this->width) ? 1000 : $this->width; $height = floor(($height) / 1) + $extra; $width = floor(($width) / 1) + $extra; $min = $extend ? -50 : 0; return "[[{$min}, {$min}], [{$height}, {$width}]]"; } /** * Whenever a map gets updated, its height and width are reset to re-calculate them on rendering * This is because the map's image is on the entity, or from the gallery */ protected function prepareBounds(): void { if (! empty($this->height)) { return; } // Prioritize the gallery image, and fall back on the uploaded image if (! empty($this->entity->image)) { $path = $this->entity->image->path; } elseif ($this->entity->image_path) { $path = $this->entity->image_path; } if (empty($path)) { return; } $contents = Storage::get($path); if (Str::endsWith($path, '.svg')) { $xml = simplexml_load_string($contents); $width = $xml->attributes()->width; $height = $xml->attributes()->height; } else { $image = imagecreatefromstring($contents); $width = imagesx($image); $height = imagesy($image); } $this->height = $height; $this->width = $width; // Don't save on the replica db if (auth()->check()) { $this->saveQuietly(); } } /** * Determine if a map can be explored */ public function explorable(): bool { if ($this->isReal()) { return true; } if (! $this->entity->hasImage()) { return false; } return ! ($this->isChunked() && ($this->chunkingError() || $this->chunkingRunning())); } /** * Prepare groups for clustering */ public function checkinGroups(): string { if (empty($this->groups)) { return '[]'; } $ids = []; foreach ($this->groups as $group) { $ids[] = 'group' . $group->id; } return '[' . implode(', ', $ids) . ']'; } /** * Check if a map is using the "real" world (openstreetmaps) */ public function isReal(): bool { return (bool) $this->is_real; } /** * Check if a map has a chunked tileset */ public function isChunked(): bool { return ! empty($this->chunking_status); } /** * Check if a map is currently being chunked */ public function chunkingReady(): bool { return ! $this->chunkingError() && ! $this->chunkingRunning(); } /** * Check if a map encountered a chunking error */ public function chunkingError(): bool { return $this->chunking_status == self::CHUNKING_ERROR; } /** * Check if a map encountered a chunking error */ public function chunkingRunning(): bool { return $this->chunking_status == self::CHUNKING_RUNNING; } /** * Determine if the map uses marker clustering or not */ public function isClustered(): bool { return $this->has_clustering; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'location_id', ]; } public function hasDistanceUnit(): bool { // return false; return ! empty($this->config['distance_measure']); } /** * Available datagrid actions */ public function datagridActions(Campaign $campaign): array { $newActions = []; $actions = parent::datagridActions($campaign); if (auth()->check() && auth()->user()->can('update', $this)) { $newActions[] = null; $newActions[] = [ 'route' => route('maps.map_layers.index', [$campaign, $this]), 'icon' => 'fa-regular fa-layer-group', 'label' => 'maps.panels.layers', ]; $newActions[] = [ 'route' => route('maps.map_groups.index', [$campaign, $this]), 'icon' => 'fa-regular fa-map-signs', 'label' => 'maps.panels.groups', ]; $newActions[] = [ 'route' => route('maps.map_markers.index', [$campaign, $this]), 'icon' => 'fa-regular fa-map-pin', 'label' => 'maps.panels.markers', ]; } array_splice($actions, 2, 0, $newActions); return $actions; } public function buildGroupTree(): string { $groups = $this->groups()->with('children')->whereNull('parent_id')->get()->sortBy('position'); return $this->buildGroup($groups); } protected function buildGroup($groups): string { $json = ''; foreach ($groups as $group) { $children = $group->children; $element = "{label: '" . str_replace('&', '&', e($group->name)) . "', layer: group" . $group->id; if ($children) { $element .= ',children: [' . $this->buildGroup($children) . ']'; } $json .= $element . '},'; } return $json; } } ================================================ FILE: app/Models/MapGroup.php ================================================ Visibility::class, ]; protected array $sanitizable = [ 'name', ]; protected static function booted(): void { static::saving(function (MapGroup $model) { if (! $model->parent) { return; } $bloodline = $model->parent->ancestors()->pluck('id')->toArray(); if (in_array($model->id, $bloodline)) { $model->parent_id = null; } }); static::deleting(function (MapGroup $model) { foreach ($model->children as $child) { $child->parent_id = null; $child->save(); } }); } /** * @return BelongsTo */ public function map(): BelongsTo { return $this->belongsTo(Map::class, 'map_id'); } /** * @return BelongsTo */ public function parent(): BelongsTo { return $this->belongsTo(MapGroup::class, 'parent_id'); } /** * @return HasMany */ public function children(): HasMany { return $this->hasMany(MapGroup::class, 'parent_id')->with('children'); } public function descendantGroupIds(): array { $ids = []; foreach ($this->children as $child) { $ids[] = $child->id; $ids = array_merge($ids, $child->descendantGroupIds()); } return $ids; } /** * @return Builder */ public function scopeOrdered(Builder $query) { return $query ->orderByDesc('position') ->orderBy('name'); } /** * @return HasMany */ public function markers(): HasMany { return $this->hasMany(MapMarker::class, 'group_id'); } public function markersWithEntity() { $ids = $this->descendantGroupIds(); $ids[] = $this->id; // include this group itself return MapMarker::whereIn('group_id', $ids) ->with(['entity', 'entity.entityType', 'group']) ->get(); } public function markerGroupHtml(): string { $data = []; /** @var MapMarker[] $markers */ $markers = $this->markersWithEntity(); // dd($markers); foreach ($markers as $marker) { if ($marker->visible()) { $data[] = 'marker' . $marker->id; } } return implode(',', $data); } /** * Functions for the datagrid2 */ public function url(string $where): string { return 'maps.map_groups.' . $where; } public function routeParams(array $options = []): array { return $options + ['map' => $this->map_id, 'map_group' => $this->id]; } /** * Patch an entity from the datagrid2 batch editing */ public function patch(array $data): bool { return $this->updateQuietly($data); } /** * Override the get link */ public function getLink(): string { $campaign = CampaignLocalization::getCampaign(); return route('maps.map_groups.edit', [$campaign, 'map' => $this->map_id, $this->id]); } } ================================================ FILE: app/Models/MapLayer.php ================================================ Visibility::class, ]; protected array $sanitizable = [ 'name', ]; /** * @return BelongsTo */ public function map(): BelongsTo { return $this->belongsTo(Map::class, 'map_id'); } /** * @return BelongsTo */ public function image(): BelongsTo { return $this->belongsTo(Image::class, 'image_uuid'); } /** * Default order maps based on their position field */ public function scopeOrdered(Builder $query): Builder { return $query ->orderByDesc('position') ->orderBy('name'); } /** * Get the image (or default image) of an entity */ public function thumbnail(int $width = 400, ?int $height = null): string { if ($this->image) { return $this->image->getUrl($width, $height); } return Img::crop($width, (! empty($height) ? $height : $width))->url($this->image_path); } public function typeName(): string { if (empty($this->type_id)) { return 'standard'; } elseif ($this->type_id == 1) { return 'overlay'; } return 'overlay_shown'; } /** * Functions for the datagrid2 */ public function url(string $where): string { return 'maps.map_layers.' . $where; } public function routeParams(array $options = []): array { return $options + ['map' => $this->map_id, 'map_layer' => $this->id]; } /** * Patch an entity from the datagrid2 batch editing */ public function patch(array $data): bool { return $this->updateQuietly($data); } /** * Override the get link */ public function getLink(): string { $campaign = CampaignLocalization::getCampaign(); return route('maps.map_layers.edit', [$campaign, 'map' => $this->map_id, $this->id]); } public function hasImage(): bool { return $this->image || ! empty($this->image_path); } public function getEntryForEditionAttribute() { return Mentions::parseForEdit($this); } public function exportFields(): array { return [ 'id', 'map_id', 'image_uuid', 'image_path', 'name', 'entry', 'position', 'height', 'width', 'visibility_id', 'type_id', ]; } } ================================================ FILE: app/Models/MapMarker.php ================================================ 'array', 'visibility_id' => Visibility::class, 'shape_id' => MapMarkerShape::class, ]; protected array $suggestions = [ MapMarkerCache::class => 'clearSuggestion', ]; protected array $sanitizable = [ 'name', 'css', ]; /** Editing the map */ protected bool $editing = false; /** Exploring the map */ protected bool $exploring = false; /** Marker MouseOver Popup on explore */ protected string $tooltipPopup = ''; /** size multiplier for circles */ protected int $sizeMultiplier = 1; /** * @return BelongsTo */ public function map(): BelongsTo { return $this->belongsTo(Map::class, 'map_id'); } /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo(Entity::class, 'entity_id'); } /** * @return BelongsTo */ public function group(): BelongsTo { return $this->belongsTo(MapGroup::class, 'group_id'); } /** * Get the marker's size, and make it 20 times bigger for a "pixel" size equivalent */ public function size(): int { return ($this->size_id * 20) + 20; } /** * Determine if the marker is of the label type */ public function isLabel(): bool { return $this->shape_id === MapMarkerShape::label; } /** * Determine if the marker is of the circle type */ public function isCircle(): bool { return $this->shape_id === MapMarkerShape::circle; } /** * Determine if the marker is of the polygon type and has a custom shape */ public function isPolygon(): bool { return $this->shape_id === MapMarkerShape::poly && ! empty($this->custom_shape); } /** * Determine the type of the marker */ public function typeLabel(): string { if ($this->isPolygon()) { return __('maps/markers.tabs.polygon'); } elseif ($this->isLabel()) { return __('maps/markers.tabs.label'); } elseif ($this->isCircle()) { return __('maps/markers.tabs.circle'); } return __('maps/markers.tabs.marker'); } /** * Determine the icon of the marker for the datagrid. */ public function datagridMarkerIcon(): string { if (in_array($this->shape_id, [2, 3, 5])) { return ''; } $icon = ''; $campaign = CampaignLocalization::getCampaign(); if (! empty($this->custom_icon) && $campaign->boosted()) { if (Str::startsWith($this->custom_icon, 'custom_icon; } elseif (Str::startsWith($this->custom_icon, ['fa-', 'ra '])) { $icon = ' '; } elseif (Str::startsWith($this->custom_icon, ''; } } elseif ($this->icon == 2) { $icon = ''; } elseif ($this->icon == 3) { $icon = ''; } return $icon; } /** * Generate the marker for leaflet */ public function marker(): string { if ($this->isCircle()) { return $this->circleMarker(); } elseif ($this->isLabel()) { return $this->labelMarker(); } elseif ($this->isPolygon()) { $coords = []; $segments = explode(' ', str_replace("\r\n", ' ', $this->custom_shape)); foreach ($segments as $segment) { $coord = explode(',', $segment); if (! empty($coord[0]) && ! empty($coord[1])) { $coords[] = '[' . $coord[0] . ', ' . Str::before($coord[1], ' ') . ']'; } } return 'L.polygon([' . implode(', ', $coords) . '], { color: \'' . Arr::get($this->polygon_style, 'stroke', $this->colour) . '\', weight: ' . max(1, Arr::get($this->polygon_style, 'stroke-width', 1)) . ', opacity: ' . $this->strokeOpacity() . ', fillOpacity: ' . $this->floatOpacity() . ', fillColor: \'' . e($this->colour) . '\', smoothFactor: 1, linecap: \'round\', linejoin: \'round\', })' . $this->popup(); // ' . ($this->editing ? 'draggable: true,' : null) . ' } return 'L.marker([' . $this->latitude . ', ' . $this->longitude . '], { title: \'' . $this->markerTitle() . '\', opacity: ' . $this->floatOpacity() . ',' . ($this->isDraggable() ? 'draggable: true,' : null) . ' ' . $this->markerIcon() . ' })' . $this->popup() . $this->draggable(); } /** * Generate a circle marker */ protected function circleMarker(): string { return 'L.circle([' . $this->latitude . ', ' . $this->longitude . '], { radius: ' . $this->circleRadius() . ', fillColor: \'' . e($this->colour) . '\', title: \'' . $this->markerTitle() . '\', stroke: false, fillOpacity: ' . $this->floatOpacity() . ', className: \'marker marker-circle marker-' . $this->id . ' size-' . $this->size_id . ' ' . $this->css . '\',' . ($this->isDraggable() ? 'draggable: true' : null) . ' })' . $this->popup() . $this->draggable(); } /** * Generate a label marker */ protected function labelMarker(): string { return 'L.marker([' . $this->latitude . ', ' . $this->longitude . '], { opacity: 0.1, icon: labelShapeIcon,' . ($this->editing ? null : null) . ' }).bindTooltip(`' . str_replace('`', '\'', $this->markerTitle()) . '`, { direction: \'center\', permanent: true, offset: [0,0], opacity: ' . $this->floatOpacity() . ', })' . $this->popup(); } /** * Generate the marker's popup that is usually opened on hover */ protected function popup(): ?string { if ($this->editing) { return null; } $body = null; $campaign = CampaignLocalization::getCampaign(); if (! empty($this->entity)) { if (! empty($this->name)) { // Name is set, include link to the entity $url = $this->entity->url(); if ($this->entity->isMap()) { $url = $this->entity->child->getLink('explore'); } $body .= "

" . str_replace('`', '\'', $this->entity->name) . '

'; } // No entry field, include the entity tooltip if (! $this->isLabel()) { $body .= $this->entity->mappedPreview(); // Replace backslashes because javascript can think that things like \6e is an octogonal string $body = str_replace('\\', '/', $body); } } // When exploring, we want the texts to be slightly shorter, to avoid lots of jittering on maps if ($this->isExploring()) { $body = Str::limit($body, 300); $output = '.on(`click`, function (ev) { window.markerDetails(`' . route('maps.markers.details', [$campaign, $this->map_id, $this->id]) . '`) })'; if ($this->is_popupless) { return $output; } return '.bindPopup(`

' . str_replace('`', '\'', $this->markerTitle(true)) . '

' . (! empty($this->entry) ? '

' . Str::limit(Mentions::mapAny($this), 300) . '

' : null) . '
' . $body . '
`) ' . $this->tooltipPopup . $output; } $editButton = $copyButton = $deleteButton = ''; if (auth()->check()) { if (auth()->user()->can('update', $this)) { $editButton = '' . __('crud.edit') . ''; $copyButton = '' . __('crud.actions.copy') . ''; } if (auth()->user()->can('delete', $this)) { $route = route('confirm-delete', [$campaign, 'route' => route('maps.map_markers.destroy', [$campaign, $this->map_id, $this->id]), 'name' => $this->markerTitle(), 'permanent' => true]); $deleteButton = ' ' . __('crud.remove') . ' '; } } return '.bindPopup(`

' . str_replace('`', '\'', $this->markerTitle(true)) . '

' . (! empty($this->entry) ? '

' . Mentions::mapAny($this) . '

' : null) . '
' . $body . '
' . $editButton . $copyButton . $deleteButton . '
` )'; } /** * Determine if a marker is draggable */ protected function isDraggable(): bool { if (! auth()->check()) { return false; } return $this->editing || ($this->isExploring() && $this->is_draggable); } /** * Generate the draggable event for a marker */ protected function draggable(): string { if (! $this->isDraggable()) { return ''; } // Exploring and moving? Update through ajax if ($this->isExploring() && $this->is_draggable) { $campaign = CampaignLocalization::getCampaign(); return '.on(`dragstart`, function() { this.closePopup(); }) .on(\'dragend\', function() { var coordinates = marker' . $this->id . '.getLatLng(); //console.log(`dragend`, coordinates); $.ajax({ url: `' . route('maps.markers.move', [$campaign, $this->map_id, $this->id]) . '`, type: `post`, data: {latitude: coordinates.lat.toFixed(3), longitude: coordinates.lng.toFixed(3)} }).done(function (data) { console.log(`moved marker`); }); })'; } return '.on(\'dragend\', function() { var coordinates = marker' . $this->id . '.getLatLng(); //console.log(\'coords\', coordinates); //console.log(\'new coords\', coordinates.lat, coordinates.lng); var shapeId = $(\'input[name="shape_id"]\').val(); var polyCoords = $(\'textarea[name="custom_shape"]\'); //console.log(\'shape id\', shapeId); if (shapeId == "5") { //console.log(\'poly\', polyCoords.val()); polyCoords.val(polyCoords.val() + \' \' + coordinates.lat.toFixed(3) + \',\' + coordinates.lng.toFixed(3)); } else { $(\'#marker-latitude\').val(coordinates.lat.toFixed(3)); $(\'#marker-longitude\').val(coordinates.lng.toFixed(3)); } })'; } /** * Marker icon as shown in explore and edit mode */ protected function markerIcon(): string { if ($this->icon == 5) { return ''; } $iconStyles = []; $iconStyles[] = 'background-color: ' . $this->backgroundColour(); $iconShape = '
'; $icon = '`' . $iconShape . '`'; $campaign = CampaignLocalization::getCampaign(); if (! empty($this->custom_icon) && $campaign->boosted()) { if (Str::startsWith($this->custom_icon, 'custom_icon . '`'; } elseif (Str::startsWith($this->custom_icon, ['fa-', 'ra '])) { $icon = '`' . $iconShape . ' `'; } elseif (Str::startsWith($this->custom_icon, '' . $this->resizedCustomIcon() . '`)'; } } elseif ($this->icon == 2) { $icon = '`' . $iconShape . '`'; } elseif ($this->icon == 3) { $icon = '`' . $iconShape . '`'; } elseif ($this->icon == 4) { $icon = '`' . $iconShape . '`'; } // dd($this->pin_size ?: 40); $size = (int) $this->pinSize(false); return 'icon: L.divIcon({ html: ' . $icon . ', iconSize: [' . $size . ', ' . $size . '], iconAnchor: [' . ceil($size / 2) . ', ' . ($size + ceil($size / 4)) . '], popupAnchor: [0, -' . ($size + ceil($size / 4)) . '], className: \'marker marker-' . $this->id . ' ' . $this->css . '\' })'; } public function pinSize(bool $withPx = true): string { $size = max(10, $this->pin_size ?: 40); return (string) $size . ($withPx ? 'px' : null); } /** * The name of the marker: name or entity * * @param bool $link = false */ public function markerTitle(bool $link = false): string { if (empty($this->name) && ! empty($this->entity)) { if ($link) { $url = $this->entity->url(); if ($this->entity->isMap()) { $url = $this->entity->child->getLink('explore'); } return '' . $this->entity->name . ''; } return str_replace("'", "\'", $this->entity->name); } return $link ? e($this->name) : str_replace("'", "\'", $this->name); } /** * Set the current mode to editing the marker */ public function editing(): self { $this->editing = true; return $this; } /** * Set the current mode to exploring the map */ public function exploring(bool $popup = true): self { $this->exploring = true; if ($popup) { $this->tooltipPopup = '.on(`mouseover`, function (ev) {this.openPopup();})'; } return $this; } /** * Determine if the marker is being viewed in the "explore" page. * Refactor potential: move all of the rendering logic to a separate class. */ public function isExploring(): bool { return $this->exploring; } /** * Used for calculating sizes and distances when using open street map where everything is way more * zoomed in. */ public function multiplier(bool $isReal = false): self { $this->sizeMultiplier = $isReal ? 50 : 1; return $this; } /** * Get the opacity of a point. Users input a %, convert it to a float for leaflet */ protected function floatOpacity(): float { if ($this->opacity > 100) { return 1.0; } if (empty($this->opacity) && $this->editing) { return 0.5; } return round($this->opacity / 100, 1); } /** * Get the polygon's stroke opacity */ protected function strokeOpacity(): float { $opacity = Arr::get($this->polygon_style, 'stroke-opacity'); if (empty($opacity)) { return 1.0; } // The number is an int (1 to 100), but needs to be a float return round($opacity / 100, 1); } /** * Resize any custom svg icon to be limited in height and width to the pin */ protected function resizedCustomIcon(): string { $resized = preg_replace('`(width|height)=\".*?\"`sui', '$1="32"', $this->custom_icon); $resized = str_replace('height="32"', 'height="32" style="margin-top: 4px;"', $resized); return $resized; } /** * Marker background colour */ public function backgroundColour(): string { if (! empty($this->colour)) { return $this->colour; } // Entity with no image? Add a grey background colour to make the pin visible if ($this->icon == 4 && $this->entity && ! $this->entity->hasImage()) { return '#ccc'; } if ($this->icon != 1 || ! empty($this->custom_icon)) { return 'unset'; } return '#ccc'; } /** * Check if a marker is visible (pointing to an entity that shouldn't be visible) */ public function visible(): bool { $campaign = CampaignLocalization::getCampaign(); if ($this->isPolygon() && ! $campaign->boosted()) { return false; } // Part of a private group, don't show either if (! empty($this->group_id) && ! $this->group) { return false; } return empty($this->entity_id) || (! empty($this->entity) && ! $this->entity->isMissingChild()); } /** * Calculate the circle radius */ protected function circleRadius(): int { if (! empty($this->circle_radius)) { return (int) $this->circle_radius * $this->sizeMultiplier; } return (int) ($this->size_id * 20) * ($this->size_id * $this->sizeMultiplier); } /** * For legacy tinymce editor */ public function hasEntity(): bool { return false; } /** * Functions for the datagrid2 */ public function url(string $where): string { return 'maps.map_markers.' . $where; } public function routeParams(array $options = []): array { return $options + ['map' => $this->map_id, 'map_marker' => $this->id]; } public function routeCopyParams(array $options = []): array { return $options + ['map' => $this->map_id, 'source' => $this->id]; } /** * Patch an entity from the datagrid2 batch editing */ public function patch(array $data): bool { if (isset($data['group_id']) && $data['group_id'] == -1) { $data['group_id'] = null; } return $this->updateQuietly($data); } /** * Override the get link */ public function getLink(): string { $campaign = CampaignLocalization::getCampaign(); return route('maps.map_markers.edit', [$campaign, 'map' => $this->map_id, $this->id]); } /** * Generate link for the datagrid */ public function markerLink(?string $displayName = null): string { return '' . (! empty($displayName) ? $displayName : e($this->name)) . ''; } } ================================================ FILE: app/Models/MiscModel.php ================================================ hasOne('App\Models\Entity', 'entity_id', 'id') ->where('type_id', $this->entityTypeID()); } /** * Determine if model inheriting miscModel has an actual entity */ public function hasEntity(): bool { return method_exists($this, 'entityTypeID'); } public function getLink(string $action = 'show'): string { if (empty($this->entity)) { return '#'; } try { $campaign = CampaignLocalization::getCampaign(); if (in_array($action, ['show', 'update'])) { return route('entities.' . $action, [$campaign, $this->entity]); } return route($this->entity->entityType->pluralCode() . '.' . $action, [$campaign, $this->id]); } catch (Exception $e) { return '#'; } } /** * Create the model's Entity */ public function createEntity(): Entity { $entity = new Entity; $entity->entity_id = $this->id; $entity->name = $this->name; $entity->campaign_id = $this->campaign_id; $entity->type_id = $this->entityTypeId(); $entity->is_private = $this->isPrivate(); $entity->save(); $this->setRelation('entity', $entity); return $entity; } /** * Available datagrid actions * Todo: move this out of the model * * @throws Exception */ public function datagridActions(Campaign $campaign): array { $actions = []; // Relations & Inventory if (! isset($this->hasRelations)) { $actions[] = [ 'route' => route('entities.relations.index', [$campaign, $this->entity]), 'icon' => 'fa-regular fa-circle-nodes', 'label' => 'entries/tabs.relations', ]; if ($campaign->enabled('inventories')) { $actions[] = [ 'route' => route('entities.inventory', [$campaign, $this->entity]), 'icon' => 'fa-regular fa-gem', 'label' => 'crud.tabs.inventory', ]; } } if (auth()->check() && auth()->user()->can('update', $this->entity)) { if (! empty($actions)) { $actions[] = null; } $actions[] = [ 'route' => route('entities.edit', [$campaign, $this->entity]), 'icon' => 'edit', 'label' => 'crud.edit', ]; } return $actions; } /** * To be overwritten by the model instance */ public function showProfileInfo(): bool { return ! empty($this->entity->type) || $this->entity->aliases->isNotEmpty(); } /** * Row classes for entities */ public function rowClasses(): string { $classes = []; if ($this->is_private) { $classes[] = 'entity-private'; } $statusClass = $this->entity->statusClass(); if ($statusClass !== '') { $classes[] = $statusClass; } return implode(' ', $classes); } /** * Boilerplate */ public function entityTypeId(): int { return 0; } /** * Boilerplate for sortable columns in the datagrid dropdowns */ public function datagridSortableColumns(): array { $columns = [ 'name' => __('crud.fields.name'), 'type' => __('crud.fields.type'), ]; if (auth()->check() && auth()->user()->isAdmin()) { $columns['is_private'] = __('crud.fields.is_private'); } return $columns; } public function isPrivate(): bool { return (bool) $this->is_private; } } ================================================ FILE: app/Models/Note.php ================================================ where('read_at', '<=', now()->subYear()); } } ================================================ FILE: app/Models/OTPAuthentication.php ================================================ getUser()->passwordSecurity)) { return true; } return ! $this->getUser()->passwordSecurity->google2fa_enable || ! $this->isEnabled() || $this->noUserIsAuthenticated() || $this->twoFactorAuthStillValid(); } protected function getGoogle2FaSecretkey() { // Get User secret column try { $secret = $this->getUser()->passwordSecurity->{$this->config('otp_secret_column')}; } catch (Exception $e) { // If User has not set up Google2FA $secret = $this->getUser()->passwordSecurity; } // If User is not Authenticated through 2FA if (empty($secret)) { // return Action return redirect()->action('PasswordSecurityController@generate2faSecretCode'); } // If user has Google2FA setup and is Authenticated return $secret; } } ================================================ FILE: app/Models/Organisation.php ================================================ select($this->getTable() . '.*') ->leftJoin('organisation_member as memb', function ($join) { $join->on('memb.organisation_id', '=', $this->getTable() . '.id'); }) ->where('memb.character_id', null); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('memb.is_private', 0); } return $query; } elseif ($filter === FilterOption::EXCLUDE) { return $query ->whereRaw('(select count(*) from organisation_member as memb where memb.organisation_id = ' . $this->getTable() . '.id and memb.character_id = ' . ((int) $value) . ' ' . $this->subPrivacy('and memb.is_private') . ') = 0'); } $ids = [$value]; $query ->select($this->getTable() . '.*') ->leftJoin('organisation_member as memb', function ($join) { $join->on('memb.organisation_id', '=', $this->getTable() . '.id'); }) ->whereIn('memb.character_id', $ids); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('memb.is_private', 0); } return $query->distinct(); } public function pinnedMembers() { return $this ->members() ->has('character') ->with(['character', 'character.entity']) ->whereIn('pin_id', [ OrganisationMemberPin::organisation, OrganisationMemberPin::both, ]) ->orderBy('role'); } /** * @return HasMany */ public function members(): HasMany { return $this->hasMany('App\Models\OrganisationMember', 'organisation_id', 'id'); } /** * Get all characters in the organisation and descendants */ public function allMembers() { $organisationId = $this->organisationAndDescendantIds(); return OrganisationMember::whereIn('organisation_member.organisation_id', $organisationId) ->with('character') ->has('character.entity'); } public function allMembersCount(): int { if (isset($this->allMembersCount)) { return $this->allMembersCount; } return $this->allMembersCount = $this->allMembers()->count(); } /** * Get a list of this organisation and descendant ids */ public function organisationAndDescendantIds(): array { if (! isset($this->organisationAndDescendantIds)) { $this->organisationAndDescendantIds = [$this->id]; foreach ($this->entity->descendants as $descendant) { $this->organisationAndDescendantIds[] = $descendant->entity_id; } } return $this->organisationAndDescendantIds; } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { // Pivot tables can be deleted directly $this->members()->delete(); $this->entity->locations()->detach(); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.organisation'); } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if ($this->entity->elapsedEvents->isNotEmpty() || $this->entity->locations->isNotEmpty()) { return true; } return parent::showProfileInfo(); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'locations', 'member_id', ]; } } ================================================ FILE: app/Models/OrganisationMember.php ================================================ OrganisationMemberStatus::class, 'pin_id' => OrganisationMemberPin::class, ]; /** * @return BelongsTo */ public function character(): BelongsTo { return $this->belongsTo('App\Models\Character', 'character_id'); } /** * @return BelongsTo */ public function organisation(): BelongsTo { return $this->belongsTo('App\Models\Organisation', 'organisation_id'); } /** * @return BelongsTo */ public function parent(): BelongsTo { return $this->belongsTo('App\Models\OrganisationMember', 'parent_id'); } public function tags() { return $this->organisation->entity->visibleTags(); } public function pinned(): bool { return ! empty($this->pin_id); } /** * Determine if the member is pinned to the character */ public function pinnedToCharacter(): bool { return $this->pin_id === OrganisationMemberPin::character; } /** * Determine if the member is pinned to the org */ public function pinnedToOrganisation(): bool { return $this->pin_id === OrganisationMemberPin::organisation; } /** * Determine if the member is pinned to both the org and character */ public function pinnedToBoth(): bool { return $this->pin_id === OrganisationMemberPin::both; } /** * Determine if the member is inactive */ public function inactive(): bool { return $this->status_id === OrganisationMemberStatus::inactive; } /** * Determine if the member status is unknown */ public function unknown(): bool { return $this->status_id === OrganisationMemberStatus::unknown; } public function scopePinned(Builder $query, int $pin): Builder { return $query->where('pin_id', $pin); } /** * Datagrid2: delete name */ public function deleteName(): string { return $this->character->name; } /** * Foreign selected */ public function getNameAttribute(): string { return $this->character->name; } /** * Datagrid2: url */ public function url(string $where): string { return 'characters.character_organisations.' . $where; } /** * Datagrid2: route options */ public function routeParams(array $options = []): array { return $options + ['character' => $this->character, 'character_organisation' => $this]; } public function scopeRows(Builder $query): Builder { // @phpstan-ignore-next-line return $query ->select('organisation_member.*') ->sort(request()->only(['o', 'k']), ['c.name' => 'asc']) ->with(['character', 'character.entity', 'organisation', 'organisation.entity', 'organisation.entity.locations', 'organisation.entity.locations.entity']) ->has('organisation') ->has('organisation.entity') ->leftJoin('organisations as c', 'c.id', 'organisation_member.organisation_id'); } public function getSuperiorAttribute() { return $this->parent?->character; } } ================================================ FILE: app/Models/PasswordSecurity.php ================================================ passwordSecurity)) { $google2Fa = new Google2FA; // $google2Fa->setAllowInsecureCallToGoogleApis(true); $appName = config('app.name'); if (! app()->isProduction()) { $appName .= ':' . app()->environment(); } $google2FaUrl = $google2Fa->getQRCodeUrl( $appName, $user->email, $user->passwordSecurity->google2fa_secret ); } $renderer = new ImageRenderer( new RendererStyle(200), new SvgImageBackEnd ); $writer = new Writer($renderer); return $writer->writeString($google2FaUrl); } } ================================================ FILE: app/Models/Playstyle.php ================================================ */ public function campaigns(): BelongsToMany { return $this->belongsToMany(Campaign::class); } } ================================================ FILE: app/Models/Pledge.php ================================================ type_id == 1) { return 'theme'; } elseif ($this->type_id == 2) { return 'attribute'; } return 'pack'; } public function hasUpdate(bool $withDraft = false): bool { if (isset($this->cachedHasUpdate)) { return ! empty($this->cachedHasUpdate); } $statuses = [3]; if ($withDraft) { $statuses[] = 1; } $this->cachedHasUpdate = $this ->versions ->whereIn('status_id', $statuses) ->where('id', '>', $this->pivot->plugin_version_id) // @phpstan-ignore-line ->last(); return ! empty($this->cachedHasUpdate); } public function updateVersionNumber(): string { return $this->cachedHasUpdate->version ?? '0.0.0'; } /** * @return HasMany */ public function versions(): HasMany { return $this->hasMany(PluginVersion::class); } public function scopePreparedSelect(Builder $builder): Builder { $update = 'null'; if (auth()->check()) { $update = 'CASE WHEN ( SELECT upd.id FROM plugin_versions AS upd WHERE upd.plugin_id = ' . $this->getTable() . '.id AND (upd.status_id = 3 OR (upd.status_id in (1,3) AND ' . $this->getTable() . '.created_by = ' . auth()->user()->id . ')) AND upd.id > campaign_plugins.plugin_version_id LIMIT 1 ) IS NOT NULL THEN 1 ELSE 0 END AS has_update'; } return $builder ->distinct() ->select([ $this->getTable() . '.*', DB::raw($update), ]); } public function author(): string { if (empty($this->user)) { return __('crud.users.unknown'); } if (! empty($this->user->settings['marketplace_name'])) { return e($this->user->settings['marketplace_name']); } return e($this->user->name); } public function isContentPack(): bool { return $this->type_id == PluginType::TYPE_PACK; } public function isTheme(): bool { return $this->type_id == PluginType::TYPE_THEME; } public function isAttributeTemplate(): bool { return $this->type_id == PluginType::TYPE_ATTRIBUTE; } public function scopeHighlighted(Builder $query, ?string $uuid = null): Builder { if (empty($uuid) || ! Str::isUuid($uuid)) { return $query; } return $query->orderByRaw( $this->getTable() . '.uuid = ? DESC', [$uuid] ); } public function url(string $sub): string { return 'campaign_plugins.' . $sub; } public function libraryUrl(): string { return config('marketplace.url') . '/plugins/' . $this->uuid; } /** * Determine if the plugin is obsolete */ public function obsolete(): bool { return $this->is_obsolete; } } ================================================ FILE: app/Models/PluginType.php ================================================ */ protected $casts = [ 'json' => 'array', ]; /** * Get the attributes (stored in the JSON) */ public function getAttributesAttribute(): array { return Arr::get($this->json, 'attributes', []); } /** * Get the CSS (stored in the JSON) */ public function getCssAttribute(): string { return Arr::get($this->json, 'css', ''); } /** * Get the translations (stored in the JSON) */ public function getTranslationsAttribute(): array { return Arr::get($this->json, 'translations', []); } public function css(): string { return (string) $this->css; } public function scopePublishedVersions(Builder $query, bool $withDrafts = false): Builder { $ids = [3]; if ($withDrafts) { $ids[] = 1; } return $query->whereIn('status_id', $ids); } /** * @return HasMany */ public function entities(): HasMany { return $this->hasMany(PluginVersionEntity::class); } /** * Determine if the current version is a draft */ public function isDraft(): bool { return $this->status_id === 1; } } ================================================ FILE: app/Models/PluginVersionEntity.php ================================================ 'array', 'related' => 'array', 'posts' => 'array', ]; /** * @return BelongsTo */ public function version(): BelongsTo { return $this->belongsTo(PluginVersion::class, 'plugin_version_id'); } /** * @return BelongsTo */ public function type(): BelongsTo { return $this->belongsTo(EntityType::class, 'type_id'); } } ================================================ FILE: app/Models/Post.php ================================================ */ public $casts = [ 'settings' => 'array', 'visibility_id' => Visibility::class, ]; protected array $sanitizable = [ 'name', ]; /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id'); } /** * @return BelongsTo */ public function layout(): BelongsTo { return $this->belongsTo('App\Models\PostLayout', 'layout_id'); } /** * @return HasMany */ public function permissions(): HasMany { return $this->hasMany(PostPermission::class, 'post_id', 'id'); } /** * List of entities that mention this entity * * @return HasMany */ public function mentions(): HasMany { return $this->hasMany('App\Models\EntityMention', 'post_id', 'id'); } /** * List of images that mention this entity * * @return HasMany */ public function imageMentions(): HasMany { return $this->hasMany('App\Models\ImageMention', 'post_id', 'id'); } /** * Copy a post to another target */ public function copyTo(Entity $target, bool $sameCampaign): Post { $without = ['entity_id', 'created_by', 'updated_by', 'is_template']; if (! $sameCampaign) { $without[] = 'location_id'; } $new = $this->replicate($without); $new->entity_id = $target->id; $new->created_by = auth()->user()->id; /** @var MentionsService $mentionsService */ $mentionsService = app()->make(MentionsService::class); $newEntry = $mentionsService->mapCopiedEntry($new->entry); $new->entry = $newEntry; $new->saveQuietly(); if (! $sameCampaign) { return $new; } foreach ($this->permissions as $perm) { $newPerm = $perm->replicate(['post_id']); $newPerm->post_id = $new->id; $newPerm->save(); } foreach ($this->postTags as $tag) { $newTag = $tag->replicate(['post_id']); $newTag->post_id = $new->id; $newTag->save(); } return $new; } /** * @return HasMany */ public function postTags(): HasMany { return $this->hasMany(PostTag::class, 'post_id', 'id'); } public function export(): array { $post = $this->toArray(); if (array_key_exists('post_tags', $post)) { foreach ($post['post_tags'] as $postTag) { $post['postTags'][] = ['tag_id' => $postTag['tag_id']]; } unset($post['post_tags']); } return $post; } public function scopeOrdered(Builder $query): Builder { return $query ->orderBy('position'); } public function collapsed(): bool { return Arr::get($this->settings, 'collapsed', false); } /** * @return BelongsToMany< * User, * $this, * EntityUser * > */ public function editingUsers(): BelongsToMany { return $this->belongsToMany(User::class, 'entity_user', 'post_id') ->using(EntityUser::class) ->withPivot('type_id'); } /** * Get the value used to index the model. */ public function getScoutKey() { return $this->getTable() . '_' . $this->id; } /** * Get the name of the index associated with the model. */ public function searchableAs(): string { return 'entities'; } protected function makeAllSearchableUsing($query) { return $query ->select([$this->getTable() . '.*', 'entities.id as entity_id', 'entities.campaign_id as campaign_id']) ->leftJoin('entities', $this->getTable() . '.entity_id', '=', 'entities.id') ->has('entity') ->with('entity'); } public function toSearchableArray() { if (! $this->entity) { return []; } return [ 'campaign_id' => $this->entity->campaign_id, 'entity_id' => $this->entity_id, 'name' => $this->name, 'type' => 'post', 'entry' => strip_tags($this->entry), ]; } /** * @return MorphMany */ public function reminders(): MorphMany { return $this->morphMany(Reminder::class, 'remindable'); } /** * @return MorphMany */ public function logs(): MorphMany { return $this->morphMany(EntityLog::class, 'parent'); } /** * Calendar Date Events are used by Journals and Quests to link them directly to a calendar */ public function calendarDateEvents(): MorphMany { return $this->reminders() ->with('calendar') ->has('calendar') ->calendarDate(); } /** * @return MorphOne */ public function calendarDate(): MorphOne { return $this->morphOne(Reminder::class, 'remindable') ->with('calendar') ->has('calendar') ->where('type_id', EntityEventTypes::calendarDate); } public function elapsedEvents(): MorphMany { return $this->reminders()->with('calendar')->whereNotNull('type_id'); } } ================================================ FILE: app/Models/PostLayout.php ================================================ */ public function entityType(): BelongsTo { return $this->belongsTo('App\Models\EntityType', 'entity_type_id', 'id'); } public function scopeEntity(Builder $query, EntityType $entityType): Builder { if (! $entityType->isNested()) { $query->where('code', '!=', 'children'); } return $query->where(function ($sub) use ($entityType) { $sub->whereNull('entity_type_id') ->orWhere('entity_type_id', $entityType->id); }); } public function name(): string { if (in_array($this->code, ['abilities', 'inventory', 'reminders'])) { return __('crud.tabs.' . $this->code); } elseif ($this->code === 'attributes') { return __('entries/tabs.properties'); } elseif ($this->code === 'assets') { return __('entries/tabs.media'); } elseif ($this->code === 'entry') { return __('crud.fields.' . $this->code); } elseif ($this->code === 'children') { return __('tags.fields.' . $this->code); } return __('post_layouts.' . $this->code); } } ================================================ FILE: app/Models/PostPermission.php ================================================ */ public function role(): BelongsTo { return $this->belongsTo(CampaignRole::class); } /** * @return BelongsTo */ public function post(): BelongsTo { return $this->belongsTo(Post::class, 'post_id', 'id'); } public function permText(): string { if ($this->permission == 0) { return __('crud.view'); } elseif ($this->permission == 2) { return __('crud.permissions.actions.bulk.deny'); } return __('crud.update'); } public function scopeOnlyUsers($query) { return $query->whereNotNull('user_id'); } public function scopeOnlyRoles($query) { return $query->whereNotNull('role_id'); } public function isUser(): bool { return ! empty($this->user_id); } } ================================================ FILE: app/Models/PostTag.php ================================================ */ public function tag(): BelongsTo { return $this->belongsTo('App\Models\Tag', 'tag_id'); } /** * @return BelongsTo */ public function post(): BelongsTo { return $this->belongsTo('App\Models\Post', 'post_id'); } public function exportFields(): array { return [ 'tag_id', ]; } } ================================================ FILE: app/Models/Preset.php ================================================ 'array', 'visibility_id' => Visibility::class, ]; protected array $sanitizable = [ 'name', ]; /** * @return BelongsTo */ public function type(): BelongsTo { return $this->belongsTo(PresetType::class); } public function scopeInType(Builder $builder, int $type): Builder { return $builder->where('type_id', $type); } } ================================================ FILE: app/Models/PresetType.php ================================================ select(['id', 'name', 'location_id', 'is_private']) ->sort(request()->only(['o', 'k']), ['name' => 'asc']) ->with([ 'location', 'location.entity', 'elements', 'entity', 'entity.tags', 'entity.tags.entity', 'entity.image']) ->has('entity'); } /** * Filter quests on specific elements (entities) */ public function scopeElement(Builder $query, ?string $value, FilterOption $filter): Builder { // "none" filter keys is handled later if ($filter === FilterOption::NONE) { if (! empty($value)) { return $query; } return $query ->select($this->getTable() . '.*') ->leftJoin('quest_elements as qe2', function ($join) { $join->on('qe2.quest_id', '=', $this->getTable() . '.id'); }) ->where('qe2.entity_id', null); } elseif ($filter === FilterOption::EXCLUDE) { return $query ->whereRaw('(select count(*) from quest_elements as qe where qe.quest_id = ' . $this->getTable() . '.id and qe.entity_id = ' . ((int) $value) . ') = 0'); } $ids = [$value]; return $query ->select($this->getTable() . '.*') ->leftJoin('quest_elements as qe', function ($join) { $join->on('qe.quest_id', '=', $this->getTable() . '.id'); })->whereIn('qe.entity_id', $ids)->distinct(); } /** * Filter quests on specific element roles */ public function scopeElementRole(Builder $query, string $value, string $operator): Builder { // No attribute with this name if ($operator === 'not like') { return $query ->whereRaw('(select count(*) from quest_elements as qe where qe.quest_id =' . $this->getTable() . '.id and qe.role = \'' . mb_ltrim($value, '!') . '\') = 0'); } return $query ->select($this->getTable() . '.*') ->leftJoin('quest_elements as qe', function ($join) { $join->on('qe.quest_id', '=', $this->getTable() . '.id'); }) ->where('qe.role', $value); } public function shortDescription() { return $this->name; } /** * The Quest Giver * * @return BelongsTo */ public function instigator(): BelongsTo { return $this->belongsTo(Entity::class); } /** * The Starting location * * @return BelongsTo */ public function location(): BelongsTo { return $this->belongsTo(Location::class); } /** * Elements of the quest * * @return HasMany */ public function elements(): HasMany { return $this->hasMany(QuestElement::class) ->with(['entity', 'entity.image']); } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { foreach ($this->elements as $child) { $child->delete(); } $this->instigator_id = null; } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.quest'); } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if ( $this->instigator || ! empty($this->date) || ! empty($this->entity->calendarReminder()) || ! empty($this->location) ) { return true; } return parent::showProfileInfo(); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'date', 'instigator_id', 'date_start', 'location_id', 'date_end', 'quest_element_id', 'element_role', ]; } /** * Grid mode sortable fields */ public function datagridSortableColumns(): array { $columns = [ 'name' => __('crud.fields.name'), 'type' => __('crud.fields.type'), 'calendar_date' => __('crud.fields.calendar_date'), ]; if (auth()->check() && auth()->user()->isAdmin()) { $columns['is_private'] = __('crud.fields.is_private'); } return $columns; } } ================================================ FILE: app/Models/QuestElement.php ================================================ Visibility::class, ]; protected array $sanitizable = [ 'name', 'role', 'colour', ]; protected array $suggestions = [ QuestCache::class => 'clearSuggestion', ]; /** * @return BelongsTo */ public function quest(): BelongsTo { return $this->belongsTo('App\Models\Quest', 'quest_id'); } /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'entity_id'); } public function colourClass(): string { if (empty($this->colour)) { return 'bg-none'; } return $this->colour == 'grey' ? 'bg-gray' : 'bg-' . $this->colour; } public function name(): string { if (empty($this->name) && $this->entity) { return $this->entity->name; } return (string) $this->name; } /** * For the legacy editor */ public function hasEntity(): bool { return false; } /** * List of entities that mention this entity * * @return HasMany */ public function mentions(): HasMany { return $this->hasMany('App\Models\EntityMention', 'quest_element_id', 'id'); } /** * @return BelongsToMany< * User, * $this, * EntityUser * > */ public function editingUsers(): BelongsToMany { return $this->belongsToMany(User::class, 'entity_user') ->using(EntityUser::class) ->withPivot('type_id'); } /** * Get the value used to index the model. */ public function getScoutKey() { return $this->getTable() . '_' . $this->id; } /** * Get the name of the index associated with the model. */ public function searchableAs(): string { return 'entities'; } protected function makeAllSearchableUsing($query) { return $query ->select([$this->getTable() . '.*', 'entities.id as entity_id']) ->leftJoin('quests', 'quests.id', '=', 'quest_elements.quest_id') ->leftJoin('entities', function ($join) { $join->on('entities.entity_id', $this->getTable() . '.id'); }) ->has('quest') ->has('quest.entity') ->with('quest', 'quest.entity'); } public function toSearchableArray() { if (! $this->quest || ! $this->quest->entity) { return []; } return [ 'campaign_id' => $this->quest->entity->campaign_id, 'entity_id' => $this->quest->entity->id, 'name' => $this->name, 'type' => 'quest_element', 'entry' => strip_tags($this->entry), ]; } } ================================================ FILE: app/Models/Race.php ================================================ belongsToMany('App\Models\Character', 'character_race'); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->wherePivot('is_private', false); } return $query; } /** * @return HasMany */ public function characterRaces(): HasMany { return $this->hasMany(CharacterRace::class, 'race_id') ->with(['character', 'character.entity']); } /** * Get all characters in the race and descendants */ public function allCharacters() { $raceIds = [$this->id]; foreach ($this->entity->descendants as $descendant) { $raceIds[] = $descendant->entity_id; } $query = Character::select('characters.*') ->distinct('characters.id') ->leftJoin('character_race as cr', function ($join) { $join->on('cr.character_id', '=', 'characters.id'); }) ->whereIn('cr.race_id', $raceIds); if (auth()->guest() || ! auth()->user()->isAdmin()) { $query->where('cr.is_private', false); } return $query; } /** * Get all characters in the race and descendants */ public function allCharacterRaces() { $raceIds = [$this->id]; foreach ($this->entity->descendants as $descendant) { $raceIds[] = $descendant->entity_id; } $model = new CharacterRace; return CharacterRace::groupBy('character_id') ->distinct('character_id') ->whereIn($model->getTable() . '.race_id', $raceIds)->with('character'); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.race'); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'locations', ]; } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if ($this->entity->locations->isNotEmpty()) { return true; } return parent::showProfileInfo(); } /** * Determine if the model is extinct. */ public function isExtinct(): bool { return (bool) $this->is_extinct; } /** * Detach children when moving this entity from one campaign to another */ public function detach(): void { // Pivot tables can be deleted directly $this->characters()->detach(); $this->entity->locations()->detach(); } } ================================================ FILE: app/Models/Referral.php ================================================ ReferralEventType::class, ]; } ================================================ FILE: app/Models/Relation.php ================================================ Visibility::class, ]; protected array $sanitizable = [ 'relation', ]; public function scopeOrdered(Builder $query, string $order = 'asc'): Builder { return $query ->orderBy('relation', $order) ->orderBy('attitude', 'asc'); } /** * @return BelongsTo */ public function owner(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'owner_id', 'id'); } /** * @return BelongsTo */ public function target(): BelongsTo { return $this->belongsTo('App\Models\Entity', 'target_id', 'id'); } /** * @return BelongsTo */ public function mirror(): BelongsTo { return $this->belongsTo('App\Models\Relation', 'mirror_id', 'id'); } /** * Check if a relation is mirrored */ public function isMirrored(): bool { return ! empty($this->mirror_id); } /** * Create a mirror of the relation */ public function createMirror(): void { $target = request()->get('target_relation'); $mirror = Relation::create([ 'owner_id' => $this->target_id, 'target_id' => $this->owner_id, 'campaign_id' => $this->campaign_id, 'relation' => ! empty($target) ? $target : $this->relation, 'attitude' => $this->attitude, 'colour' => $this->colour, 'visibility_id' => $this->visibility_id, 'is_pinned' => $this->isPinned(), 'mirror_id' => $this->id, ]); // Update this relation to keep track of everything $this->update(['mirror_id' => $mirror->id]); } /** * Performance with for datagrids */ public function scopePreparedWith(Builder $query): Builder { return $query ->with([ 'owner', 'owner.entityType', 'target', 'target.entityType', ]) ->has('owner') ->has('target'); } /** * Performance with for datagrids */ public function scopePreparedSelect(Builder $query): Builder { $defaults = ['id', 'target_id', 'owner_id', 'relation', 'mirror_id', 'is_pinned', 'attitude', 'visibility_id', 'colour']; $tableName = $this->getTable(); $prefixedFields = []; foreach ($defaults as $field) { $prefixedFields[] = $tableName . '.' . $field; } return $query->select($prefixedFields); } /** * When setting the colour, remove the '#' from the db * * @param string $colour */ public function setColourAttribute($colour) { $this->attributes['colour'] = mb_ltrim($colour ?? '', '#'); } /** * When getting the colour, remove the '#' from the db */ public function getColourAttribute(): string { if (empty($this->attributes['colour'])) { return ''; } return '#' . $this->attributes['colour']; } /** Fake entity type ID */ public function entityTypeID(): int { return 0; } /** * Functions for the datagrid2 */ public function deleteName(): string { return (string) $this->relation; } public function url(string $where): string { return 'entities.relations.' . $where; } public function routeParams(array $options = []): array { return $options + ['entity' => $this->owner_id, 'relation' => $this->id, 'mode' => 'table']; } public function actionDeleteConfirmOptions(): array { return ['mirrored' => $this->isMirrored()]; } /** * Relations don't use the default filterable columns available to entities */ protected function defaultFilterableColumns(): array { return []; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'name', 'attitude', 'relation', 'owner_id', 'target_id', 'is_pinned', 'is_mirrored', ]; } public function hasSearchableFields(): bool { return false; } public function exportFields(): array { return [ 'id', 'owner_id', 'target_id', 'relation', 'visibility_id', 'mirror_id', 'attitude', 'is_pinned', 'colour', 'marketplace_uuid', ]; } /** * On the datagrid tables, add data-* attributes to help people style with css */ public function rowAttributes(): array { $attributes = []; $attributes['attitude'] = $this->attitude; $attributes['visibility'] = $this->visibility_id; return $attributes; } } ================================================ FILE: app/Models/Relations/CalendarRelations.php ================================================ */ public function calendarEvents(): HasMany { return $this->hasMany(Reminder::class, 'calendar_id'); } /** * @return HasMany */ public function calendarWeather(): HasMany { return $this->hasMany(CalendarWeather::class, 'calendar_id'); } /** * @return BelongsTo */ public function calendar(): BelongsTo { return $this->belongsTo(Calendar::class); } /** * @return HasMany */ public function calendars(): HasMany { return $this->hasMany(Calendar::class); } } ================================================ FILE: app/Models/Relations/CampaignRelations.php ================================================ */ public function users(): BelongsToMany { return $this->belongsToMany(User::class, 'campaign_user')->using('App\Models\CampaignUser'); } /** * @return BelongsToMany< * User, * $this, * CampaignFollower * > */ public function followers(): BelongsToMany { return $this ->belongsToMany(User::class, 'campaign_followers') ->using(CampaignFollower::class); } /** * @return BelongsTo */ public function setting(): BelongsTo { return $this->belongsTo('App\Models\CampaignSetting', 'id', 'campaign_id'); } /** * @return HasMany */ public function members(): HasMany { return $this->hasMany('App\Models\CampaignUser'); } /** * @return HasMany */ public function nonAdminMembers(): HasMany { return $this ->members() ->withoutAdmins() ->with('user'); } /** * @return HasMany */ public function roles(): HasMany { return $this->hasMany(CampaignRole::class); } public function publicRole(): HasOne { return $this->roles()->public()->one(); } /** * @return HasMany */ public function webhooks(): HasMany { return $this->hasMany(Webhook::class); } /** * @return HasMany */ public function characters(): HasMany { return $this->hasMany(Character::class); } /** * @return HasMany */ public function locations(): HasMany { return $this->hasMany(Location::class); } /** * @return HasMany */ public function calendars(): HasMany { return $this->hasMany(Calendar::class); } /** * @return HasMany */ public function events(): HasMany { return $this->hasMany(Event::class); } /** * @return HasMany */ public function families(): HasMany { return $this->hasMany(Family::class); } /** * @return HasMany */ public function items(): HasMany { return $this->hasMany(Item::class); } /** * @return HasMany */ public function journals(): HasMany { return $this->hasMany(Journal::class); } /** * @return HasMany */ public function maps(): HasMany { return $this->hasMany(Map::class); } /** * @return HasMany */ public function notes(): HasMany { return $this->hasMany(Note::class); } /** * @return HasMany */ public function organisations(): HasMany { return $this->hasMany(Organisation::class); } /** * @return HasMany */ public function quests(): HasMany { return $this->hasMany(Quest::class); } /** * @return HasMany */ public function abilities(): HasMany { return $this->hasMany(Ability::class); } /** * @return HasMany */ public function attributeTemplates(): HasMany { return $this->hasMany(AttributeTemplate::class); } /** * @return HasMany */ public function tags(): HasMany { return $this->hasMany(Tag::class); } /** * @return HasMany */ public function timelines(): HasMany { return $this->hasMany(Timeline::class); } /** * @return HasMany */ public function bookmarks(): HasMany { return $this->hasMany(Bookmark::class) ->with(['dashboard']); } /** * @return HasMany */ public function diceRolls(): HasMany { return $this->hasMany(DiceRoll::class); } /** * @return HasMany */ public function conversations(): HasMany { return $this->hasMany(Conversation::class); } /** * @return HasMany */ public function races(): HasMany { return $this->hasMany(Race::class); } /** * @return HasMany */ public function creatures(): HasMany { return $this->hasMany(Creature::class); } public function images(): HasMany|Image { return $this->hasMany(Image::class) ->where('is_default', false); } /** * List of entities that are mentioned in the campaign's description * * @return HasMany */ public function mentions(): HasMany { return $this->hasMany('App\Models\EntityMention', 'campaign_id', 'id'); } /** * @return HasMany */ public function entities(): HasMany { return $this->hasMany(Entity::class, 'campaign_id', 'id'); } /** * @return BelongsTo */ public function theme(): BelongsTo { return $this->belongsTo('App\Models\Theme'); } /** * @return HasMany */ public function applications(): HasMany { return $this->hasMany(Application::class); } /** * @return HasMany */ public function entityRelations(): HasMany { return $this->hasMany('App\Models\Relation'); } /** * @return HasManyThrough */ public function posts(): HasManyThrough { return $this->hasManyThrough(Post::class, Entity::class); } /** * @return HasManyThrough */ public function entityAliases(): HasManyThrough { return $this->hasManyThrough(EntityAsset::class, Entity::class)->where('entity_assets.type_id', EntityAssetType::alias); } /** * @return BelongsToMany */ public function plugins(): BelongsToMany { return $this->belongsToMany('App\Models\Plugin', 'campaign_plugins', 'campaign_id', 'plugin_id') // ->using('App\Models\CampaignPlugin') ->withPivot('is_active') ->withPivot('plugin_version_id'); } /** * @return HasMany */ public function campaignPlugins(): HasMany { return $this->hasMany(CampaignPlugin::class); } /** * @return HasMany */ public function widgets(): HasMany { return $this->hasMany(CampaignDashboardWidget::class); } /** * @return HasMany */ public function dashboards(): HasMany { return $this->hasMany(CampaignDashboard::class); } /** * @return HasMany */ public function campaignExports(): HasMany { return $this->hasMany(CampaignExport::class); } public function queuedCampaignExports() { return $this->campaignExports() ->whereIn('status', [ CampaignExportStatus::scheduled, CampaignExportStatus::running, ]); } /** * @return HasMany */ public function campaignImports(): HasMany { return $this->hasMany(CampaignImport::class); } /** * @return HasMany */ public function styles(): HasMany { return $this->hasMany(CampaignStyle::class); } /** * @return BelongsToMany< * User, * $this, * EntityUser * > */ public function editingUsers(): BelongsToMany { return $this->belongsToMany(User::class, 'entity_user') ->using(EntityUser::class) ->withPivot('type_id'); } /** * @return HasMany */ public function invites(): HasMany { return $this->hasMany('App\Models\CampaignInvite'); } /** * @return BelongsToMany */ public function genres(): BelongsToMany { return $this->belongsToMany(Genre::class); } /** * @return BelongsToMany */ public function systems(): BelongsToMany { return $this->belongsToMany(GameSystem::class, 'campaign_system', 'campaign_id', 'system_id'); } /** * @return HasMany */ public function entityTypes(): HasMany { return $this->hasMany(EntityType::class); } /** * List of the campaign's flags * * @return HasMany */ public function flags(): HasMany { return $this->hasMany(CampaignFlag::class, 'campaign_id', 'id'); } /** * @return HasOne */ public function spotlight(): HasOne { return $this->hasOne(Spotlight::class); } /** * @return BelongsToMany */ public function playstyles(): BelongsToMany { return $this->belongsToMany(Playstyle::class); } /** * @return HasMany */ public function filters(): HasMany { return $this->hasMany(CampaignFilter::class); } /** * @return HasOne */ public function description(): HasOne { return $this->hasOne(CampaignDescription::class); } } ================================================ FILE: app/Models/Relations/EntityRelations.php ================================================ */ public function entityType(): BelongsTo { return $this->belongsTo(EntityType::class, 'type_id'); } /** * @return HasMany */ public function attributes(): HasMany { return $this->hasMany('App\Models\Attribute', 'entity_id', 'id'); } public function allAttributes() { return $this->attributes(); } /** * Call $entity->entityAttributes to avoid multiple calls to the db */ public function entityAttributes() { return $this->attributes() ->with(['entity' => function ($sub) { $sub->select('entities.id', 'entities.name'); }]) ->ordered(); } /** * @return HasMany */ public function entityLocations(): HasMany { return $this->hasMany(EntityLocation::class, 'entity_id', 'id'); } /** * @return HasOne */ public function attributeTemplate(): HasOne { return $this->hasOne('App\Models\AttributeTemplate', 'id', 'entity_id'); } /** * @return HasOne */ public function ability(): HasOne { return $this->hasOne('App\Models\Ability', 'id', 'entity_id'); } /** * @return HasOne */ public function character(): HasOne { return $this->hasOne('App\Models\Character', 'id', 'entity_id'); } /** * @return HasOne */ public function diceRoll(): HasOne { return $this->hasOne('App\Models\DiceRoll', 'id', 'entity_id'); } /** * @return HasOne */ public function event(): HasOne { return $this->hasOne('App\Models\Event', 'id', 'entity_id'); } /** * @return HasOne */ public function family(): HasOne { return $this->hasOne('App\Models\Family', 'id', 'entity_id'); } /** * @return HasOne */ public function item(): HasOne { return $this->hasOne('App\Models\Item', 'id', 'entity_id'); } /** * @return HasOne */ public function journal(): HasOne { return $this->hasOne('App\Models\Journal', 'id', 'entity_id'); } /** * @return HasOne */ public function location(): HasOne { return $this->hasOne('App\Models\Location', 'id', 'entity_id'); } /** * @return HasOne */ public function map(): HasOne { return $this->hasOne('App\Models\Map', 'id', 'entity_id'); } /** * @return HasOne */ public function note(): HasOne { return $this->hasOne('App\Models\Note', 'id', 'entity_id'); } /** * @return HasOne */ public function organisation(): HasOne { return $this->hasOne('App\Models\Organisation', 'id', 'entity_id'); } /** * @return HasOne */ public function quest(): HasOne { return $this->hasOne('App\Models\Quest', 'id', 'entity_id'); } /** * @return HasOne */ public function calendar(): HasOne { return $this->hasOne('App\Models\Calendar', 'id', 'entity_id'); } /** * @return HasOne */ public function tag(): HasOne { return $this->hasOne('App\Models\Tag', 'id', 'entity_id'); } /** * @return HasOne */ public function timeline(): HasOne { return $this->hasOne('App\Models\Timeline', 'id', 'entity_id'); } /** * @return HasOne */ public function whiteboard(): HasOne { return $this->hasOne('App\Models\Whiteboard', 'id', 'entity_id'); } /** * @return HasMany */ public function entityTags(): HasMany { return $this->hasMany('App\Models\EntityTag', 'entity_id', 'id'); } /** * @return HasMany */ public function inventories(): HasMany { return $this->hasMany('App\Models\Inventory', 'entity_id'); } /** * @return HasMany */ public function timelines(): HasMany { return $this->hasMany('App\Models\TimelineElement', 'entity_id'); } /** * @return HasMany */ public function abilities(): HasMany { return $this->hasMany('App\Models\EntityAbility', 'entity_id'); } /** * @return HasMany */ public function quests(): HasMany { return $this->hasMany('App\Models\QuestElement', 'entity_id'); } /** * @return HasOne */ public function conversation(): HasOne { return $this->hasOne('App\Models\Conversation', 'id', 'entity_id'); } /** * @return HasOne */ public function race(): HasOne { return $this->hasOne('App\Models\Race', 'id', 'entity_id'); } /** * @return HasOne */ public function creature(): HasOne { return $this->hasOne('App\Models\Creature', 'id', 'entity_id'); } /** * @return HasMany */ public function widgets(): HasMany { return $this->hasMany('App\Models\CampaignDashboardWidget', 'id', 'entity_id'); } /** * @return HasMany */ public function relationships(): HasMany { return $this->hasMany('App\Models\Relation', 'owner_id', 'id'); } public function allRelationships() { return $this ->relationships() ->select('relations.*') ->with(['target', 'target.entityType', 'owner']) ->has('target') ->leftJoin('entities as t', 't.id', '=', 'relations.target_id'); } /** * @return HasMany */ public function targetRelationships(): HasMany { return $this->hasMany('App\Models\Relation', 'target_id', 'id'); } /** * @return HasMany */ public function posts(): HasMany { return $this->hasMany('App\Models\Post', 'entity_id', 'id'); } public function files(): HasMany { return $this->assets() ->where('type_id', 1); } public function pinnedFiles(): HasMany { return $this->files() ->where('is_pinned', 1) ->with('image'); } public function pinnedAliases(): HasMany { return $this->aliases() ->where('is_pinned', 1); } public function aliases(): HasMany { return $this->assets() ->where('type_id', 3); } /** * @return MorphMany */ public function reminders(): MorphMany { return $this->morphMany(Reminder::class, 'remindable'); } /** * Calendar Date Events are used by Journals and Quests to link them directly to a calendar */ public function calendarDateEvents(): MorphMany { return $this->reminders() ->with('calendar') ->has('calendar') ->calendarDate(); } /** * @return MorphOne */ public function calendarDate(): MorphOne { return $this->morphOne(Reminder::class, 'remindable') ->with('calendar') ->has('calendar') ->where('type_id', EntityEventTypes::calendarDate); } public function elapsedEvents(): MorphMany { return $this->reminders()->with('calendar')->whereNotNull('type_id'); } /** * @return MorphMany */ public function logs(): MorphMany { return $this->morphMany(EntityLog::class, 'parent'); } /** * @return HasMany */ public function permissions(): HasMany { return $this->hasMany('App\Models\CampaignPermission', 'entity_id', 'id'); } /** * @return HasMany */ public function mapMarkers(): HasMany { return $this->hasMany('App\Models\MapMarker', 'entity_id', 'id'); } /** * @return HasMany */ public function authoredJournals(): HasMany { return $this->hasMany(Journal::class, 'author_id', 'id'); } /** * Entity image stored in the gallery * * @return HasOne */ public function image(): HasOne { return $this->hasOne('App\Models\Image', 'id', 'image_uuid'); } /** * Header image stored in the gallery * * @return HasOne */ public function header(): HasOne { return $this->hasOne('App\Models\Image', 'id', 'header_uuid'); } /** * @return EntityAsset[]|Collection */ public function links() { return $this->assets ->where('type_id', EntityAssetType::link); } /** * @return HasMany */ public function assets(): HasMany { return $this->hasMany(EntityAsset::class, 'entity_id', 'id') ->with('image'); } public function starredAttributes() { return $this->entityAttributes->where('is_pinned', 1); } public function pinnedRelations(): HasMany { return $this->relationships() ->pinned() ->ordered() ->with(['target', 'target.image']) ->has('target'); } /** * @return BelongsToMany< * User, * $this, * EntityUser * > */ public function editingUsers(): BelongsToMany { return $this->belongsToMany(User::class) ->using(EntityUser::class) ->withPivot('type_id'); } /** * @return BelongsTo */ public function status(): BelongsTo { return $this->belongsTo(CategoryStatus::class, 'status_id'); } } ================================================ FILE: app/Models/Relations/UserRelations.php ================================================ */ public function lastCampaign(): BelongsTo { return $this->belongsTo(Campaign::class, 'last_campaign_id', 'id'); } /** * List of campaigns the user is a member of * * @return BelongsToMany< * Campaign, * $this, * CampaignUser * > */ public function campaigns(): BelongsToMany { return $this->belongsToMany('App\Models\Campaign', 'campaign_user') ->withPivot('created_at') ->using('App\Models\CampaignUser'); } /** * List of campaigns the user is following * * @return BelongsToMany< * Campaign, * $this, * CampaignFollower * > */ public function following(): BelongsToMany { return $this->belongsToMany('App\Models\Campaign', 'campaign_followers') ->withPivot('created_at') ->using('App\Models\CampaignFollower'); } /** * @return HasManyThrough */ public function campaignRoles(): HasManyThrough { return $this->hasManyThrough( 'App\Models\CampaignRole', 'App\Models\CampaignRoleUser', 'user_id', 'id', 'id', 'campaign_role_id' ); } /** * List of campaign roles the user is part of * * @return HasMany */ public function campaignRoleUser(): HasMany { return $this->hasMany('App\Models\CampaignRoleUser'); } /** * List of boosts the user is giving * * @return HasMany */ public function boosts(): HasMany { return $this->hasMany('App\Models\CampaignBoost', 'user_id', 'id'); } /** * List of logs the user has recently done * * @return HasMany */ public function logs(): HasMany { return $this->hasMany('App\Models\UserLog', 'user_id', 'id'); } /** * List of connected apps (Discord) the user has set up * * @return HasMany */ public function apps(): HasMany { return $this->hasMany(UserApp::class, 'user_id', 'id'); } /** * List of campaign permissions the user has * * @return HasMany */ public function permissions(): HasMany { return $this->hasMany('App\Models\CampaignPermission', 'user_id'); } /** * The referral code a user used * * @return BelongsTo */ public function referrer(): BelongsTo { return $this->belongsTo(User::class, 'referred_by', 'id'); } /** * List of campaign applications the user is trying to join * * @return HasMany */ public function applications(): HasMany { return $this->hasMany(Application::class); } /** * List of entities the user is currently editing * * @return BelongsToMany< * Entity, * $this, * EntityUser * > */ public function entities(): BelongsToMany { return $this->belongsToMany(Entity::class) ->using(EntityUser::class); } /** * @return HasMany */ public function plugins(): HasMany { return $this->hasMany(Plugin::class, 'created_by'); } /** * Return alternative User Roles. * * @return BelongsToMany */ public function roles(): BelongsToMany { return $this->belongsToMany(Role::class, 'user_roles'); } /** * Logs created each time a user uses Bragi * * @return HasMany */ public function bragiLogs(): HasMany { return $this->hasMany(BragiLog::class); } /** * List of subscription cancellations for the user * * @return HasMany */ public function cancellations(): HasMany { return $this->hasMany('App\Models\SubscriptionCancellation', 'user_id', 'id'); } /** * List of the user's flags, used to know when a user can be deleted * * @return HasMany */ public function flags(): HasMany { return $this->hasMany(UserFlag::class, 'user_id', 'id'); } /** * List of tutorials the user has completed * * @return HasMany */ public function tutorials(): HasMany { return $this->hasMany(Tutorial::class); } /** * List of ideas the user has upvoted in the roadmap * * @return HasMany */ public function upvotes(): HasMany { return $this->hasMany(FeatureVote::class); } /** * User email validation done */ public function userValidation(): HasOne|UserValidation { return $this->hasOne(UserValidation::class, 'user_id', 'id'); } } ================================================ FILE: app/Models/Reminder.php ================================================ EntityEventTypes::class, ]; public function remindable() { return $this->morphTo(); } /** Last occurrence of the reminder */ protected int $cachedLast; /** Next occurrence of the reminder */ protected int $cachedNext; /** * @return BelongsTo */ public function calendar(): BelongsTo { return $this->belongsTo(Calendar::class, 'calendar_id'); } /** * @return BelongsTo */ public function type(): BelongsTo { return $this->belongsTo(EntityEventType::class, 'type_id'); } public function isPost(): bool { return $this->remindable instanceof Post; } public function isEntity(): bool { return $this->remindable instanceof Entity; } public function readableDate(): string { if (! isset($this->readableDate)) { // Replace month with real month, and year maybe $months = $this->calendar->months(); $years = $this->calendar->years(); try { if ($this->calendar->format) { $this->readableDate = Str::replace( ['d', 's', 'y', 'm', 'M'], [$this->day, $this->calendar->suffix, $years[$this->year] ?? $this->year, $this->month, isset($months[$this->month - 1]) ? $months[$this->month - 1]['name'] : $this->month], $this->calendar->format ); } else { $this->readableDate = $this->day . ' ' . (isset($months[$this->month - 1]) ? $months[$this->month - 1]['name'] : $this->month) . ', ' . ($years[$this->year] ?? $this->year) . ' ' . $this->calendar->suffix; } } catch (Exception $e) { $this->readableDate = $this->date(); } } return $this->readableDate; } /** * Length of the event in a readable format (appends "days") */ public function readableLength(): string { return trans_choice('calendars.fields.length_days', $this->length, ['count' => $this->length]); } public function isToday(Calendar $calendar): bool { return $this->date() === $calendar->date; } public function date(): string { return $this->year . '-' . $this->month . '-' . $this->day; } public function getLabelColour(): string { if (empty($this->colour) || in_array($this->colour, ['default', 'grey', '#cccccc'])) { return 'colour-pallet bg-gray'; } return 'colour-pallet ' . (Str::startsWith($this->colour, '#') ? '' : 'bg-' . $this->colour); } public function getLabelBackgroundColour(): string { if (Str::startsWith($this->colour, '#')) { return $this->colour; } return ''; } /** * Generate the Entity Event label for the calendar */ public function getLabel(): string { $label = ''; if ($this->is_recurring) { $label .= ' '; } if ($this->comment) { $label .= '' . e($this->comment) . ''; } return $label; } /** * Check if a reminder is after the current date of a given calendar */ public function isPast(Calendar $calendar): bool { // First check the year if ($this->year < $calendar->currentDate('year')) { return true; } elseif ($this->year > $calendar->currentDate('year')) { return false; } // Current year is reminder's year, check month if ($this->month < $calendar->currentDate('month')) { return true; } elseif ($this->month > $calendar->currentDate('month')) { return false; } // Current month, check on day return $this->day < $calendar->currentDate('date'); } public function isPastDate(int $year, int $month, int $day): bool { if ($this->year < $year) { return true; } elseif ($this->year > $year) { return false; } if ($this->month < $month) { return true; } elseif ($this->month > $month) { return false; } return $this->day <= $day; } /** * Calculate the elapsed time since the event happened * * @return int years */ public function calcElapsed(?Reminder $event = null): int { // Have the value cached? Don't bother with more work if (empty($event) && ! empty($this->elapsed)) { return $this->elapsed; } if (! empty($event)) { $year = $event->year; $month = $event->month; $day = $event->day; } else { $year = $this->calendar->currentDate('year'); $month = $this->calendar->currentDate('month'); $day = (int) $this->calendar->currentDate('date'); } // Current reminder is pre 0, calendar is > 0 $baseYear = $this->year; if ($this->year < 0 && $year > 0 && ! $this->calendar->hasYearZero()) { $baseYear++; } $years = $year - $baseYear; if ($month < $this->month) { return $this->saveElapsed($years - 1, empty($event)); } if ($month > $this->month) { return $this->saveElapsed($years, empty($event)); } // Same month return $this->saveElapsed($years - ($day < $this->day ? 1 : 0), empty($event)); } protected function saveElapsed(int $number, bool $save): int { // If comparing two days, don't save the "elapsed" part, we need to re-calc those one each page load if (! $save || $number < 0) { return $number; } $this->elapsed = $number; $this->saveQuietly(); return $this->elapsed; } /** * Functions for the datagrid2 */ public function deleteName(): string { return (string) $this->remindable->name; } public function url(string $where): string { return 'reminders.' . $where; } public function routeParams(array $options = []): array { return $options + ['entity' => $this->remindable_id, 'reminder' => $this->id, 'next' => 'entity.reminders']; } public function getNameAttribute(): string { return $this->deleteName(); } /** * Calculate how long ago the event happened */ public function mostRecentOccurrence(int $year, int $month, int $day, array $months, int $daysInYear): int { $reminderYear = $this->year; $reminderMonth = $this->month; $reminderDay = $this->day; // Recurring? We need to switch around the data a bit to figure out the most recent date if (! empty($this->is_recurring)) { if ($this->recurringMonthly()) { // dump('monthly'); $reminderMonth = $month; $reminderYear = $year; // If it repeats monthly, we need to see if it happened "last month" or "this month". // dump('Reminder ' . $reminderDay . ' > ' . $day); if ($reminderDay > $day) { $reminderMonth--; // Switched to previous month? if ($reminderMonth === 0) { $reminderMonth = $month; $reminderYear--; } } } else { // Yearly is easy // dump('yearly recurring'); $reminderYear = $year; $reminderMonth = $month; } } // Diff in years between current year and reminder's year if (! $this->calendar->hasYearZero() && $year > 0 && $reminderYear < 0) { $days = ($year - 1 - $reminderYear) * $daysInYear; } else { $days = ($year - $reminderYear) * $daysInYear; } // Not the same month? We need to do some math if ($month != $reminderMonth) { // dump('month diff ' . $month . ' (current) vs ' . $reminderMonth . '(reminder)'); // dump('amount of months ' . count($months)); $totalMonths = count($months); // If the reminder a month in the future, meaning it was another year if ($reminderMonth > $month) { // dump('last year'); $days -= $daysInYear; // Loop through the beginning of the year for ($m = 1; $m < $month; $m++) { // dump('beginning of the year'); // Month status $previousMonth = $this->previousMonth($m, $totalMonths); $monthData = $months[$previousMonth]; $days += $monthData['length']; } for ($m = $reminderMonth; $m <= $totalMonths; $m++) { // dump('end of previous year'); $previousMonth = $this->previousMonth($m, $totalMonths); $monthData = $months[$previousMonth]; $days += $monthData['length']; // dump('days increase by ' . $monthData['length']); } } else { // The event happened earlier this year for ($m = $reminderMonth; $m < $month; $m++) { // dump('previous month'); $previousMonth = $this->previousMonth($m, $totalMonths); $monthData = $months[$previousMonth]; $days += $monthData['length']; } } } // Diff in days $days += ($day - $reminderDay); if ($days > 1 && $this->length > 1) { $days -= $this->length - 1; } // dump($days); return $this->cachedLast = $days; } /** * Calculate when this reminder happens next. We want the YYYYYYYMMMMDDDD format, and use that as a string to * order by, instead of doing complicated math. Or do we? I don't know, I'm so confused. This is all super * hard to calculate :( */ public function nextUpcomingOccurrence(int $calendarYear, int $calendarMonth, int $day, array $months, int $daysInYear): int { if (isset($this->cachedNext)) { return $this->cachedNext; } $reminderYear = $this->year; $reminderMonth = $this->month; $reminderDay = $this->day; // dump("Event #" . $this->id . " " . $this->remindable->name . ": " . $this->year . "-" . $this->month . "-" . $this->day); // Recurring? We need to switch around the data a bit to figure out the most recent date if (! empty($this->is_recurring)) { if ($this->recurringMonthly()) { $reminderMonth = $calendarMonth; // Max to properly track reminders starting in the future $reminderYear = max($reminderYear, $calendarYear); // If it repeats monthly, we need to see if it happened "last month" or "this month". // dump('Reminder ' . $reminderDay . ' > ' . $day); if ($reminderDay < $day) { $reminderMonth++; // Switched to previous month? if ($reminderMonth === count($months) - 1) { $reminderMonth = $calendarMonth; $reminderYear++; } } } else { // It's a yearly reoccurring event, so we need to put the reminder's date to the "next" available one // in the future. $reminderYear = $calendarYear; // Make sure it's the same year // If the month is earlier from this year, we need to push the next occurrence to next year. The month // stays the same if ($reminderMonth === $calendarMonth && $reminderDay < $day) { $reminderYear++; } } } $days = 0; // We loop on every extra year, ex 2000 to 2004 for ($y = $calendarYear; $y < $reminderYear; $y++) { $days += $daysInYear; // dump("Add a year"); } if (! $this->calendar->hasYearZero() && $calendarYear < 0 && $reminderYear > 0) { $days -= $daysInYear; } // If the reminder happens "before" the same month / same date, we need to reduce the days by one year // current: 2004-05-01 and reminder is 2005-03-15 if ($days > 0 && ($reminderMonth < $calendarMonth)) { $days -= $daysInYear; // dump("Remove a year"); } // Now we need to loop on the remaining months. $monthStart = $calendarMonth; // ex August $monthEnd = $reminderMonth; // ex September $totalMonths = count($months); // dump("Comparing $reminderMonth < $calendarMonth"); if ($reminderMonth < $calendarMonth) { // The reminder's month is before the current calendar month, so we jumped a year. // ex reminder is in April and calendar is currently in August $monthStart = 1; // We still need to add days to the end of the current year before switching to the next one // dump("Backfilling $calendarMonth to $totalMonths"); for ($m = $calendarMonth; $m <= $totalMonths; $m++) { $previousMonth = $this->previousMonth($m, $totalMonths); $monthData = $months[$previousMonth]; $days += $monthData['length']; } // $monthEnd = $reminderMonth; } // dump("Month start $monthStart and $monthEnd"); for ($m = $monthStart; $m < $monthEnd; $m++) { $previousMonth = $this->previousMonth($m, $totalMonths); $monthData = $months[$previousMonth]; $days += $monthData['length']; } $days += ($reminderDay - $day); return $this->cachedNext = $days; } /** * Determine if a reminder is recurring every year */ public function recurringYearly(): bool { return $this->is_recurring && $this->recurring_periodicity === 'year'; } /** * Determine if a reminder is recurring every month */ public function recurringMonthly(): bool { return $this->is_recurring && $this->recurring_periodicity === 'month'; } /** * How many days ago the last occurrence was */ public function daysAgo(): int { return $this->cachedLast; } /** * In how many days the next reminder is */ public function inDays(): int { return $this->cachedNext; } /** * Determine if an event is of the character birth type */ public function isBirth(): bool { return $this->type_id === EntityEventTypes::birth; } /** * Determine if an event is of the entity foundation type */ public function isFounded(): bool { return $this->type_id === EntityEventTypes::founded; } /** * Determine if an event is of the character death type */ public function isDeath(): bool { return $this->type_id === EntityEventTypes::death; } /** * Determine if an event is of the calendar date type */ public function isCalendarDate(): bool { return $this->type_id === EntityEventTypes::calendarDate; } /** * Reduce the month by one, making sure it's still in the bounds of a valid month */ protected function previousMonth(int $month, int $min): int { if ($month >= $min) { return $min - 1; } return $month--; } /** * @return MorphOne */ public function death(): MorphOne { return $this->morphOne(Reminder::class, 'remindable') ->whereColumn('calendar_id', 'reminders.calendar_id') ->where('type_id', EntityEventTypes::death); } /** * Patch an entity from the datagrid2 batch editing */ public function patch(array $data): bool { return $this->updateQuietly($data); } public function exportFields(): array { return [ 'id', 'calendar_id', 'length', 'comment', 'is_recurring', 'recurring_until', 'recurring_periodicity', 'colour', 'day', 'month', 'year', 'type_id', 'visibility_id', 'created_by', ]; } /** * Copy a reminder to another target */ public function copyTo(Entity $target): Reminder { $new = $this->replicate(['remindable_id', 'created_by']); $new->remindable_id = $target->id; $new->created_by = auth()->user()->id; $new->saveQuietly(); return $new; } } ================================================ FILE: app/Models/Role.php ================================================ belongsToMany(User::class, 'user_roles'); } } ================================================ FILE: app/Models/Scopes/AclScope.php ================================================ extensions as $extension) { $this->{"add{$extension}"}($builder); } } /** * Add the with-invisible extension to the builder. * * @return void */ protected function addWithInvisible(Builder $builder) { $builder->macro('withInvisible', function (Builder $builder, $withInvisible = true) { if (! $withInvisible) { // Sends the default scope return $builder; } // @phpstan-ignore-next-link return $builder->withoutGlobalScope($this); }); } /** * Our main logic for this scope: filtering on elements the user has access to. * * @return Builder|void */ public function apply(Builder $query, Model $model) { // No permission engine on console for the time being. In the future, we might want // to build a system to limit exposing private stuff on a campaign export. if (app()->runningInConsole() && (! app()->environment('testing') || config('app.skip_permissions') === true)) { return $query; } // For posts, we need a different hook because they can be private even for an admin if ($model instanceof Post) { return $this->applyToPost($query, $model); } // Campaign admins don't have any restrictions on base Permissions::campaign(CampaignLocalization::getCampaign()) ->action(Permission::View); if (auth()->check()) { Permissions::user(auth()->user()); } /*if ($model instanceof MiscModel) { Permissions::entityTypeID($model->entityTypeId()); }*/ if (Permissions::isAdmin()) { // Check if this is a visibility entity or a global kanka entity return $query; } if ($model instanceof Entity) { return $this->applyToEntity($query, $model); } return $query; } /** * Permission scope on an Entity model */ protected function applyToEntity(Builder $query, Entity $model): Builder { if (auth()->check()) { Permissions::createTemporaryTable(); // @phpstan-ignore-next-line return $query ->private(false) ->where(function ($subquery) use ($model) { return $subquery ->where(function ($sub) use ($model) { return $sub ->whereRaw(DB::raw('EXISTS (SELECT * FROM tmp_permissions as perm WHERE perm.id = ' . $model->getTable() . '.id)')) ->orWhereIn($model->getTable() . '.type_id', Permissions::allowedEntityTypes()); }) ->whereNotIn($model->getTable() . '.id', Permissions::deniedEntities()); }); } // Unlogged users have a read-only replica db to query, so a left join is needed directly on the permission table $publicRoleId = Permissions::publicRoleID(); // @phpstan-ignore-next-line return $query ->private(false) ->where(function ($subquery) use ($model, $publicRoleId) { return $subquery ->where(function ($sub) use ($model, $publicRoleId) { return $sub ->whereRaw(DB::raw('EXISTS (SELECT * FROM campaign_permissions as perm WHERE perm.entity_id = ' . $model->getTable() . '.id AND perm.access = 1 AND perm.campaign_role_id = ' . $publicRoleId . ')')) // ->orWhereIn($model->getTable() . '.id', Permissions::allowedEntities()) ->orWhereIn($model->getTable() . '.type_id', Permissions::allowedEntityTypes()); }) ->whereNotIn($model->getTable() . '.id', Permissions::deniedEntities()); }); } /** * Apply the ACL scope to posts. * * @return Builder */ protected function applyToPost(Builder $query, Model $model) { $campaign = CampaignLocalization::getCampaign(); $table = $model->getTable(); // Guest, or not part of the campaign either, just get the all visibility if (auth()->guest() || ! auth()->user()->can('member', $campaign)) { return $query->where($table . '.visibility_id', Visibility::All); } Permissions::campaign($campaign); Permissions::user(auth()->user()); // Either mine (self && created_by = me) or (if admin: !self, else: all) return $query // Ignore the Visibility scope because we're overriding it here with the permission engine of posts ->withoutGlobalScope(VisibilityIDScope::class) ->where(function ($sub) use ($table) { $visibilities = Permissions::isAdmin() ? [Visibility::All, Visibility::Admin, Visibility::AdminSelf, Visibility::Member] : [Visibility::All, Visibility::Member]; $sub ->where(function ($self) use ($table) { $self ->whereIn($table . '.visibility_id', [ Visibility::Self, Visibility::AdminSelf, ]) ->where($table . '.created_by', auth()->user()->id); }) ->orWhereIn($table . '.visibility_id', $visibilities) ->orWhereIn($table . '.id', Permissions::allowedPosts()); }) ->whereNotIn($table . '.id', Permissions::deniedPosts()); } } ================================================ FILE: app/Models/Scopes/CalendarWeatherScopes.php ================================================ where('year', $year); } /** * @return Builder */ public function scopeMonth(Builder $builder, int $month) { return $builder->where('month', $month); } public function scopeDated(Builder $builder, int $calendarId, int $year, int $month, int $day) { // @phpstan-ignore-next-line return $builder ->where('calendar_id', $calendarId) ->year($year) ->month($month) ->where('day', $day); } } ================================================ FILE: app/Models/Scopes/CampaignScope.php ================================================ runningInConsole()) { $campaign = CampaignLocalization::getCampaign(); if ($campaign && $model->withCampaignLimit()) { $builder->where($model->getTable() . '.campaign_id', '=', $campaign->id); } return $builder; } // In console mode, we still sometimes need scoping to a campaign (for example when deleting nested // elements to not interfere with data from other campaigns. $campaignId = CampaignLocalization::getConsoleCampaign(); if ($campaignId && $model->withCampaignLimit()) { $builder->where($model->getTable() . '.campaign_id', '=', $campaignId); } return $builder; } } ================================================ FILE: app/Models/Scopes/CampaignScopes.php ================================================ guest()) { return $this->public()->slug($slug); } // If we are impersonating, that gives us only a single choice if (Identity::isImpersonating()) { return $this ->slug($slug) // Use ID and not slug to avoid shenanigans when updating the slug ->where($this->getTable() . '.id', Identity::getCampaignId()); } // Limit to the campaigns the user is in return $this ->select([ $this->getTable() . '.*', ]) ->leftJoin('campaign_user as cu', function (JoinClause $sub) { return $sub->on('cu.campaign_id', $this->getTable() . '.id') ->where('cu.user_id', auth()->user()->id); }) ->slug($slug) ->where(function ($sub) { return $sub ->public() ->orWhereNotNull('cu.user_id'); }); } /** * Filter on a campaign's ID or slug. Since slugs always require at least one letter, * we can have this is_numeric logic to differentiate between the two. We also need * this for the APIs, as those work on the ID and not the slug. */ public function scopeSlug(Builder $query, string|int $slug): Builder { $key = is_numeric($slug) ? 'id' : 'slug'; return $query->where($this->getTable() . '.' . $key, '=', $slug); } public function scopeVisibility(Builder $query, CampaignVisibility|array $visibility): Builder { if ($visibility instanceof CampaignVisibility) { return $query->where($this->getTable() . '.visibility_id', $visibility->value); } return $query->whereIn($this->getTable() . '.visibility_id', $visibility); } public function scopeOpen(Builder $query, bool $open = true): Builder { return $query->where('is_open', $open); } /** * Admin crud datagrid */ public function scopeAdmin(Builder $query): Builder { return $query->with('users'); } /** * Featured Campaigns */ public function scopeShowcased(Builder $query, ?int $limit = 4): Builder { $activeSpotlights = DB::table('spotlights') ->selectRaw('campaign_id, MAX(featured_at) as featured_at') ->where('status', SpotlightStatus::active) ->groupBy('campaign_id'); $query ->select($this->getTable() . '.*') ->joinSub($activeSpotlights, 'active_spotlights', function ($join) { $join->on('active_spotlights.campaign_id', '=', $this->getTable() . '.id'); }) ->orderByDesc('active_spotlights.featured_at'); if ($limit !== null) { $query->limit($limit); } return $query; } /** * Public campaigns */ public function scopePublic(Builder $query, bool $withUnlisted = true): Builder { // @phpstan-ignore-next-line $values = [ CampaignVisibility::public, ]; if ($withUnlisted) { $values[] = CampaignVisibility::unlisted; } // @phpstan-ignore-next-line return $query->visibility($values); } /** * Filtered campaigns for the front end */ public function scopeFront(Builder $query, ?int $sort = null): Builder { if (! config('app.debug')) { $query ->where('visible_entity_count', '>', 0); } $defaultSort = $sort == 1 ? 'campaign_followers_count' : 'visible_entity_count'; $query ->with(['systems', 'spotlight']) ->withCount('followers') ->where('is_hidden', 0) ->orderBy($defaultSort, 'desc') ->orderBy('name'); return $query; } public function scopeFilterPublic(Builder $query, array $options): Builder { $language = Arr::get($options, 'language'); $genre = Arr::get($options, 'genre'); $system = Arr::get($options, 'system'); if (! empty($language)) { $query->where('locale', $language); } if (! empty($genre)) { $query ->select('campaigns.*') ->leftJoin('campaign_genre as cg', function ($join) { $join->on('cg.campaign_id', '=', 'campaigns.id'); })->where('cg.genre_id', $genre); } if (! empty($system)) { $query ->select('campaigns.*') ->leftJoin('campaign_system as cs', function ($join) { $join->on('cs.campaign_id', '=', 'campaigns.id'); }) ->whereIn('cs.system_id', $system) ->distinct(); } $boosted = Arr::get($options, 'is_boosted'); if ($boosted === '1') { $query->where('boost_count', '>=', 1); } elseif ($boosted === '0') { $query->where(function ($sub) { return $sub->where('boost_count', 0)->orWhereNull('boost_count'); }); } $open = Arr::get($options, 'is_open'); if ($open === '1') { $query->open() // @phpstan-ignore-line ->reorder() ->orderByDesc('is_prioritised') ->orderByDesc('visible_entity_count') ->orderBy('name'); } elseif ($open === '0') { $query->open(false); // @phpstan-ignore-line } $featured = Arr::get($options, 'featured_until'); if ($featured === '1') { $query->whereNull('featured_until'); } elseif ($featured === '0') { $query->whereNotNull('featured_until'); } return $query; } public function scopePreparedWith(Builder $query): Builder { return $query; } /** * Unboosted campaigns */ public function scopeUnboosted(Builder $query): Builder { return $query->where(function ($sub) { return $sub->where('boost_count', 0) ->orWhereNull('boost_count'); }); } public function scopeHidden(Builder $query, int $hidden = 1): Builder { return $query->where(['is_hidden' => $hidden]); } public function scopeUserOrdered(Builder $query, User $user): Builder { switch ($user->campaignSwitcherOrderBy) { case 'alphabetical': $query->orderBy('name', 'asc'); break; case 'r_alphabetical': $query->orderBy('name', 'desc'); break; case 'date_joined': // @phpstan-ignore-next-line $query->orderBy('pivot_created_at', 'asc'); break; case 'r_date_joined': // @phpstan-ignore-next-line $query->orderBy('pivot_created_at', 'desc'); break; case 'r_date_created': $query->orderBy('created_at', 'desc'); break; } return $query; } } ================================================ FILE: app/Models/Scopes/CommunityEventScopes.php ================================================ where('end_at', '>=', Carbon::now()) ->where('start_at', '<=', Carbon::now()); } public function scopeFinished(Builder $builder) { return $builder->where('end_at', '<=', Carbon::now()); } public function scopeVoting(Builder $builder) { // @phpstan-ignore-next-line return $builder ->where('published_at', '>=', Carbon::now()) ->visible(); } public function scopeRecent(Builder $builder) { // @phpstan-ignore-next-line return $builder ->published() ->orderBy('published_at', 'DESC') ->take(5); } } ================================================ FILE: app/Models/Scopes/CommunityVoteScopes.php ================================================ where('visible_at', '<=', Carbon::now()); } public function scopePublished(Builder $builder): Builder { return $builder->where('published_at', '<=', Carbon::now()); } public function scopeVoting(Builder $builder): Builder { // @phpstan-ignore-next-line return $builder ->where('published_at', '>=', Carbon::now()) ->visible(); } public function scopeRecent(Builder $builder): Builder { // @phpstan-ignore-next-line return $builder ->published() ->orderBy('published_at', 'DESC') ->take(5); } } ================================================ FILE: app/Models/Scopes/EntityAssetScopes.php ================================================ where('type_id', $type instanceof EntityAssetType ? $type->value : $type); } public function scopeFiltered(Builder $query, bool $premium = false): Builder { $types = [ EntityAssetType::file, ]; if ($premium) { $types[] = EntityAssetType::link; $types[] = EntityAssetType::alias; } return $query->whereIn('type_id', $types); } public function scopeFile(Builder $query) { // @phpstan-ignore-next-line return $query->type(EntityAssetType::file); } public function scopeLink(Builder $query): Builder { // @phpstan-ignore-next-line return $query->type(EntityAssetType::link); } public function scopeAlias(Builder $query) { // @phpstan-ignore-next-line return $query->type(EntityAssetType::alias); } public function scopeWithoutAliases(Builder $query) { // @phpstan-ignore-next-line return $query->whereNot('type_id', EntityAssetType::alias); } } ================================================ FILE: app/Models/Scopes/EntityEventScopes.php ================================================ currentDate('year'); $month = $calendar->currentDate('month'); $day = $calendar->currentDate('date'); return $query->where('year', '<', $year) ->orWhere(function ($sub) use ($year, $month) { $sub->where('year', '=', $year) ->where('month', '<', $month); }) ->orWhere(function ($sub) use ($year, $month, $day) { $sub->where('year', '=', $year) ->where('month', '=', $month) ->where('day', '<=', $day); }); } /** * All events today and after today */ public function scopeAfter(Builder $query, Calendar $calendar): Builder { $year = $calendar->currentDate('year'); $month = $calendar->currentDate('month'); $day = $calendar->currentDate('date'); return $query->where('year', '>', $year) ->orWhere(function ($sub) use ($year, $month) { $sub->where('year', '=', $year) ->where('month', '>', $month); }) ->orWhere(function ($sub) use ($year, $month, $day) { $sub->where('year', '=', $year) ->where('month', '=', $month) ->where('day', '>=', $day); }); } /** * Sort order for the datagrid page */ public function scopeCustomSortDate(Builder $query, ?string $order = null): Builder { return $query ->orderBy('year', $order) ->orderBy('month', $order) ->orderBy('day', $order); } public function scopeEntity(Builder $query, int $entity_id): Builder { return $query->where('entity_id', $entity_id); } public function scopeCalendar(Builder $query, int $calendar_id): Builder { return $query->where('calendar_id', $calendar_id); } public function scopeCalendarDate(Builder $query): Builder { return $query->where('type_id', EntityEventTypes::calendarDate); } } ================================================ FILE: app/Models/Scopes/EntityScopes.php ================================================ orderBy($this->getTable() . '.updated_at', 'desc'); } /** * Order entities by their olderst modified */ public function scopeOldestModified(Builder $query): Builder { return $query ->orderBy($this->getTable() . '.updated_at', 'asc'); } /** * Filter entities on specific tags */ public function scopeInTags(Builder $query, ?array $tags = null): Builder { if (empty($tags)) { return $query; } $query->distinct() ->select($this->getTable() . '.*'); foreach ($tags as $tag) { $v = (int) $tag; $query ->leftJoin('entity_tags as et' . $v, "et{$v}.entity_id", $this->getTable() . '.id') ->where("et{$v}.tag_id", $v); } return $query; } /** * Get entity templates of a specific entity type */ public function scopeTemplates(Builder $query, int $entityTypeID): Builder { // @phpstan-ignore-next-line return $query ->template() ->where('type_id', $entityTypeID); } /** * Default API filter options */ public function scopeApiFilter(Builder $query, Campaign $campaign, array $request = []): Builder { $related = Arr::get($request, 'related', false); $types = Arr::get($request, 'types'); if (! empty($types)) { $typeNames = explode(',', $types); $typeIds = []; foreach ($typeNames as $type) { $id = config('entities.ids.' . $type); if (empty($id)) { continue; } $typeIds[] = $id; } $query->whereIn('type_id', $typeIds); } $modules = Arr::get($request, 'type_id'); if (! empty($modules) && is_array($modules)) { $validateModules = EntityType::inCampaign($campaign)->whereIn('id', $modules)->pluck('id')->toArray(); if (! empty($validateModules)) { $query->whereIn('type_id', $validateModules); } } // Other available: $filterableFields = [ 'name', 'is_private', 'is_template', 'created_by', 'updated_by', 'tags', 'type', ]; foreach ($request as $field => $value) { if (! in_array($field, $filterableFields)) { continue; } if (Str::startsWith($field, ['is_'])) { $bool = in_array($value, ['true', 1]) ? true : false; $query->where($field, $bool); } elseif (Str::endsWith($field, '_by')) { $query->where($field, (int) $value); } elseif ($field === 'tags') { // Something something tags if (! is_array($value)) { $value = [$value]; } $query ->whereHas('tags', function ($query) use ($value) { return $query->whereIn('tags.id', $value); }); } else { $query->where($field, 'LIKE', '%' . $value . '%'); } } return $query ->with($related ? [ 'attributes', 'attributes.entity', 'posts', 'posts.permissions', 'posts.entity', 'reminders', 'reminders.remindable', 'inventories', 'inventories.entity', 'relationships', 'abilities', 'tags', 'image', 'assets', 'entityType', 'locations' => function ($query) { $query->select('locations.id'); }, ] : ['tags', 'image', 'entityType', 'locations' => function ($query) { $query->select('locations.id'); }, ]); } /** * Filter entities on specific types. * Valid option is 'all' */ public function scopeInTypes(Builder $query, mixed $types = null): Builder { if (empty($types)) { return $query; } if (! is_array($types)) { $types = [$types]; } // Use to do [0] but that can be get unset by the exclude if (head($types) == 'all') { return $query; } return $query->whereIn($this->getTable() . '.type_id', $types); } public function scopeOrder(Builder $query, array $config = [], ?EntityType $entityType = null): Builder { $entityFields = ['name', 'type', 'is_private', 'status_id']; foreach ($config as $field => $order) { if ($field === 'parent.name') { $query->leftJoin('entities as parent_order', 'parent_order.id', '=', 'entities.parent_id') ->orderBy('parent_order.name', $order); } elseif ($field === 'calendar_date') { $query->leftJoin('reminders as cd', function ($join) { $join->on('cd.remindable_id', '=', 'entities.id') ->on('cd.remindable_type', '=', DB::raw("'" . addslashes(Entity::class) . "'")) ->where('cd.type_id', EntityEventTypes::calendarDate); }) ->orderBy('cd.year', $order) ->orderBy('cd.month', $order) ->orderBy('cd.day', $order); } elseif ($field === 'tags') { $query->leftJoin('entity_tags as tags_order', 'tags_order.entity_id', '=', 'entities.id') ->leftJoin('tags as tag_order', 'tag_order.id', '=', 'tags_order.tag_id') ->orderBy('tag_order.name', $order); } elseif (! in_array($field, $entityFields) && $entityType?->isStandard()) { // Field lives on the child model's table $childModel = $entityType->getClass(); $childTable = $childModel->getTable(); $query->leftJoin($childTable . ' as child_order', 'child_order.id', '=', 'entities.entity_id'); $segments = explode('.', $field); if (count($segments) > 1) { // Dotted field like location.name — resolve the relationship on the child model $relationName = $segments[0]; /** @var BelongsTo $relation */ $relation = $childModel->{$relationName}(); $relatedTable = $relation->getQuery()->getQuery()->from; $query->leftJoin( $relatedTable . ' as orderable_j', 'orderable_j.id', 'child_order.' . $relation->getForeignKeyName() )->orderBy(str_replace($relationName, 'orderable_j', $field), $order); } else { $query->orderBy('child_order.' . $field, $order); } } else { $query->orderBy('entities.' . $field, $order); } } return $query; } public function scopeFilter(Builder $query, array $filters = [], ?EntityType $entityType = null): Builder { $childFilterKeys = []; foreach ($filters as $name => $values) { // Skip null or empty-string values (empty form fields) if ($values === null || $values === '') { continue; } elseif (is_array($values) && empty(array_filter($values, fn ($v) => $v !== '' && $v !== null))) { continue; } elseif (in_array($name, ['is_private', 'parent_id', 'status_id'])) { $query->where('entities.' . $name, $values); } elseif (in_array($name, ['name', 'type'])) { // @phpstan-ignore-next-line $query->textFilter($name, $values); } elseif (in_array($name, ['has_image', 'template'])) { $property = 'is_template'; if ($name === 'has_image') { $property = 'image_uuid'; } if ($values) { $query->whereNotNull($property); } else { $query->whereNull($property); } } elseif ($name === 'archived') { if ($values) { $query->whereNotNull('archived_at'); } } elseif ($name === 'has_entity_files') { // @phpstan-ignore-next-line $query->filterHasFiles($values); } elseif ($name === 'has_posts') { // @phpstan-ignore-next-line $query->filterHasPosts($values); } elseif ($name === 'has_entry') { // @phpstan-ignore-next-line $query->filterHasEntry($values); } elseif ($name === 'has_attributes') { // @phpstan-ignore-next-line $query->filterHasAttributes($values); } elseif (in_array($name, ['attribute_name', 'attribute_value'])) { // attribute_value is handled together with attribute_name if ($name === 'attribute_name') { // @phpstan-ignore-next-line $query->filterAttributes($values, Arr::get($filters, 'attribute_value')); } } elseif (in_array($name, ['connection_target', 'connection_name'])) { // connection_name is handled together with connection_target if ($name === 'connection_target' || ! isset($filters['connection_target'])) { // @phpstan-ignore-next-line $query->filterConnections( Arr::get($filters, 'connection_target'), Arr::get($filters, 'connection_name') ); } } elseif (in_array($name, ['created_by', 'updated_by'])) { $option = Arr::get($filters, $name . '_option'); if ($option === 'exclude') { $query->where(function ($sub) use ($name, $values) { $sub->where('entities.' . $name, '!=', (int) $values) ->orWhereNull('entities.' . $name); }); } else { $query->where('entities.' . $name, (int) $values); } } elseif ($name === 'creators') { // Handled after the loop (like tags) so that creators_option works even with an empty array continue; } elseif ($name === 'tags') { // Handled after the loop so that tags_option works even with an empty array continue; } elseif ($entityType?->isStandard() && ! Str::endsWith($name, '_option')) { $childFilterKeys[$name] = $values; } } // Entity-type-specific filters (e.g. status_id on characters) if (! empty($childFilterKeys) && $entityType?->isStandard()) { $childModel = $entityType->getClass(); $childTable = $childModel->getTable(); $query->leftJoin($childTable . ' as child_filter', 'child_filter.id', '=', 'entities.entity_id'); foreach ($childFilterKeys as $name => $values) { $ids = collect((array) $values)->map(fn ($v) => (int) $v)->toArray(); $option = Arr::get($filters, $name . '_option'); if ($name === 'families') { if ($option === 'children') { $ids = Entity::whereIn('entity_id', $ids) ->where('type_id', config('entities.ids.family')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } if ($option === 'exclude') { $query->whereRaw('(select count(*) from `character_family` where `character_family`.`character_id` = `child_filter`.`id` and `character_family`.`family_id` in (' . implode(', ', $ids) . ')) = 0'); } else { $query->whereExists(fn ($q) => $q->select(DB::raw(1))->from('character_family')->whereColumn('character_family.character_id', 'child_filter.id')->whereIn('character_family.family_id', $ids)); } } elseif ($name === 'races') { if ($option === 'children') { $ids = Entity::whereIn('entity_id', $ids) ->where('type_id', config('entities.ids.race')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } if ($option === 'exclude') { $query->whereRaw('(select count(*) from `character_race` where `character_race`.`character_id` = `child_filter`.`id` and `character_race`.`race_id` in (' . implode(', ', $ids) . ')) = 0'); } else { $query->whereExists(fn ($q) => $q->select(DB::raw(1))->from('character_race')->whereColumn('character_race.character_id', 'child_filter.id')->whereIn('character_race.race_id', $ids)); } } elseif ($name === 'organisations') { if ($option === 'children') { $ids = Entity::whereIn('entity_id', $ids) ->where('type_id', config('entities.ids.organisation')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } if ($option === 'exclude') { $query->whereRaw('(select count(*) from `organisation_member` where `organisation_member`.`character_id` = `child_filter`.`id` and `organisation_member`.`organisation_id` in (' . implode(', ', $ids) . ')) = 0'); } else { $query->whereExists(fn ($q) => $q->select(DB::raw(1))->from('organisation_member')->whereColumn('organisation_member.character_id', 'child_filter.id')->whereIn('organisation_member.organisation_id', $ids)); } } elseif ($name === 'locations') { if ($option === 'children') { $ids = Entity::whereIn('entity_id', $ids) ->where('type_id', config('entities.ids.location')) ->with('descendants') ->get() ->flatMap(fn ($e) => [$e->entity_id, ...$e->descendants->pluck('entity_id')->toArray()]) ->unique() ->toArray(); } if ($option === 'exclude') { $query->whereRaw('(select count(*) from `entity_locations` where `entity_locations`.`entity_id` = `entities`.`id` and `entity_locations`.`location_id` in (' . implode(', ', $ids) . ')) = 0'); } else { $query->whereExists(fn ($q) => $q->select(DB::raw(1))->from('entity_locations')->whereColumn('entity_locations.entity_id', 'entities.id')->whereIn('entity_locations.location_id', $ids)); } } elseif ($name === 'location_id') { if ($option === 'exclude') { $query->where(function ($sub) use ($values) { $sub->where('child_filter.location_id', '!=', (int) $values) ->orWhereNull('child_filter.location_id'); }); } else { $query->where('child_filter.location_id', (int) $values); } } elseif ($name === 'member_id') { // @phpstan-ignore-next-line $memberTable = $entityType?->id == config('entities.ids.family') ? ['table' => 'character_family', 'fk' => 'family_id'] : ['table' => 'organisation_member', 'fk' => 'organisation_id']; if ($option === 'exclude') { $query->whereRaw('(select count(*) from `' . $memberTable['table'] . '` where `' . $memberTable['table'] . '`.`' . $memberTable['fk'] . '` = `child_filter`.`id` and `' . $memberTable['table'] . '`.`character_id` = ' . (int) $values . ') = 0'); } else { $query->whereExists(fn ($q) => $q->selectRaw(1)->from($memberTable['table'])->whereColumn($memberTable['table'] . '.' . $memberTable['fk'], 'child_filter.id')->where($memberTable['table'] . '.character_id', (int) $values)); } } else { $query->where('child_filter.' . $name, $values); } } } foreach (['created_by', 'updated_by'] as $field) { if (Arr::get($filters, $field . '_option') === 'none') { $query->whereNull('entities.' . $field); } } $noneJoinTables = [ 'races' => ['table' => 'character_race', 'fk' => 'character_id', 'ref' => 'entities.entity_id'], 'families' => ['table' => 'character_family', 'fk' => 'character_id', 'ref' => 'entities.entity_id'], 'organisations' => ['table' => 'organisation_member', 'fk' => 'character_id', 'ref' => 'entities.entity_id'], 'locations' => ['table' => 'entity_locations', 'fk' => 'entity_id', 'ref' => 'entities.id'], ]; foreach ($noneJoinTables as $field => $join) { if (Arr::get($filters, $field . '_option') === 'none') { $query->whereNotExists( fn ($q) => $q->selectRaw(1)->from($join['table'])->whereColumn($join['table'] . '.' . $join['fk'], $join['ref']) ); } } if (Arr::get($filters, 'member_id_option') === 'none') { $memberTable = $entityType?->id == config('entities.ids.family') ? ['table' => 'character_family', 'fk' => 'family_id'] : ['table' => 'organisation_member', 'fk' => 'organisation_id']; $query->whereNotExists( fn ($q) => $q->selectRaw(1)->from($memberTable['table'])->whereColumn($memberTable['table'] . '.' . $memberTable['fk'], 'entities.entity_id') ); } if (Arr::get($filters, 'location_id_option') === 'none' && $entityType?->isStandard()) { $childTable = $entityType->getClass()->getTable(); $query->whereExists( fn ($q) => $q->selectRaw(1) ->from($childTable) ->whereColumn($childTable . '.id', 'entities.entity_id') ->whereNull($childTable . '.location_id') ); } if (Arr::hasAny($filters, ['tags', 'tags_option'])) { // @phpstan-ignore-next-line $query->filterTags(Arr::get($filters, 'tags', []), Arr::get($filters, 'tags_option')); } // Creators filter (handled outside loop so creators_option works even with empty array) if (Arr::hasAny($filters, ['creators', 'creators_option'])) { $creatorsOption = Arr::get($filters, 'creators_option'); if ($creatorsOption === 'none') { $query->whereNotExists(function ($sub) { $sub->selectRaw(1) ->from('item_creator') ->whereColumn('item_creator.item_id', 'entities.entity_id'); }); } else { $creatorValues = Arr::get($filters, 'creators', []); $creatorIds = array_values(array_filter(array_map('intval', is_array($creatorValues) ? $creatorValues : [$creatorValues]))); if (! empty($creatorIds)) { if ($creatorsOption === 'exclude') { foreach ($creatorIds as $creatorId) { $query->whereNotExists(function ($sub) use ($creatorId) { $sub->selectRaw(1) ->from('item_creator') ->whereColumn('item_creator.item_id', 'entities.entity_id') ->where('item_creator.creator_id', $creatorId); }); } } else { foreach ($creatorIds as $creatorId) { $query->whereExists(function ($sub) use ($creatorId) { $sub->selectRaw(1) ->from('item_creator') ->whereColumn('item_creator.item_id', 'entities.entity_id') ->where('item_creator.creator_id', $creatorId); }); } } } } } return $query; } /** * Filter on entities with files */ protected function scopeFilterHasFiles(Builder $query, bool $value = true): void { $query ->leftJoin('entity_assets', 'entity_assets.entity_id', '=', 'entities.id') ->where('entity_assets.type_id', EntityAssetType::file); if ($value) { $query->whereNotNull('entity_assets.id'); } else { $query->whereNull('entity_assets.id'); } } /** * Filter on entities with posts */ protected function scopeFilterHasPosts(Builder $query, bool $value = true): void { $query ->leftJoin('posts', 'posts.entity_id', 'entities.id'); if ($value) { $query->whereNotNull('posts.id'); } else { $query->whereNull('posts.id'); } } /** * Filter on entities with posts */ protected function scopeFilterHasEntry(Builder $query, bool $value = true): void { if ($value) { $query->whereNotNull('entities.entry') ->where('entities.entry', '!=', ''); } else { $query->whereNull('entities.entry') ->orWhere('entities.entry', ''); } } /** * Filter on entities with specific tags */ protected function scopeFilterTags(Builder $query, array $tags = [], ?string $type = null): void { // Gets handled differently for some reason? if ($type === 'none') { $query ->leftJoin('entity_tags as no_tags', 'no_tags.entity_id', 'entities.id') ->whereNull('no_tags.tag_id'); return; } elseif ($type === 'exclude') { $tagIds = []; foreach ($tags as $v) { $tagIds[] = (int) $v; } // $query->leftJoin('entity_tags as et_tags', "et_tags.entity_id", 'e.id') $query->whereRaw('( select count(*) from entity_tags as et where et.entity_id = entities.id and et.tag_id in (' . implode(', ', $tagIds) . ') ) = 0'); return; } foreach ($tags as $v) { if (! is_numeric($v)) { continue; } $v = (int) $v; $query ->leftJoin('entity_tags as et' . $v, "et{$v}.entity_id", 'entities.id') ->where("et{$v}.tag_id", $v); } } protected function scopeTextFilter(Builder $query, string $field, ?string $value = null): Builder { $searchTerms = explode(';', $value); foreach ($searchTerms as $searchTerm) { if (empty($searchTerm) && $searchTerm != '0') { continue; } [$operator, $text] = $this->extractSearchOperator($searchTerm, 'type'); $searchTerm = $text; $query->where( 'entities.' . $field, $operator, ($operator == '=' ? $text : "%{$searchTerm}%") ); } return $query; } /** * Filter on entities with attributes */ protected function scopeFilterHasAttributes(Builder $query, bool $value = true): void { $query ->leftJoin('attributes', 'attributes.entity_id', 'entities.id'); if ($value) { $query->whereNotNull('attributes.id'); } else { $query->whereNull('attributes.id'); } } /** * Filter on entities by attribute name and optionally value */ protected function scopeFilterAttributes(Builder $query, ?string $name = null, ?string $attributeValue = null): void { if ($name === null) { return; } [$operator, $filterName] = $this->extractSearchOperator($name, 'attribute_name'); // No attribute with this name (exclude) if ($operator === 'not like') { $query ->whereRaw('(select count(*) from attributes as att where att.entity_id = entities.id and att.name = ?) = 0', [$filterName]); return; } $query ->leftJoin('attributes as att', 'att.entity_id', '=', 'entities.id') ->where('att.name', $filterName); if ($attributeValue === '!') { $query->whereRaw('att.value <> ""'); } elseif ($attributeValue !== '' && $attributeValue !== null) { $query->where('att.value', $attributeValue); } } /** * Filter on entities by their connections */ protected function scopeFilterConnections(Builder $query, ?string $targetId = null, ?string $connectionName = null): void { $query ->leftJoin('relations as rel', 'rel.owner_id', '=', 'entities.id'); if ($targetId !== '' && $targetId !== null) { $query->where('rel.target_id', $targetId); } if ($connectionName !== '' && $connectionName !== null) { [$operator, $filterName] = $this->extractSearchOperator($connectionName, 'connection_name'); $searchValue = $operator === '=' ? $filterName : '%' . $filterName . '%'; $query->where('rel.relation', $operator, $searchValue); } } /** * @param string|array $value (array for tags) */ protected function extractSearchOperator(mixed $value, string $key): array { $operator = 'like'; $filterValue = $value; if ($value == '!!') { $operator = 'IS NULL'; $filterValue = null; } elseif (Str::startsWith($value, '!')) { $operator = 'not like'; $filterValue = mb_ltrim($value, '!'); } elseif (Str::endsWith($value, '!')) { $operator = '='; $filterValue = mb_rtrim($value, '!'); } elseif (Str::endsWith($key, '_id')) { $operator = '='; } return [$operator, $filterValue]; } } ================================================ FILE: app/Models/Scopes/Pinnable.php ================================================ where(['is_pinned' => $pin]); } public function isPinned(): bool { return (bool) $this->is_pinned; } } ================================================ FILE: app/Models/Scopes/PrivateScope.php ================================================ extensions as $extension) { $this->{"add{$extension}"}($builder); } } protected function addWithPrivate(Builder $builder) { $builder->macro('withPrivate', function (Builder $builder, $withInvisible = true) { if (! $withInvisible) { // Sends the default scope return $builder; } // @phpstan-ignore-next-line return $builder->withoutGlobalScope($this); }); } /** * Apply the scope to a given Eloquent query builder. * * @return void */ public function apply(Builder $builder, Model $model) { // Only apply these scopes in non-console mode. if (app()->runningInConsole()) { return; } // If we aren't authenticated, just see what is set to all if (auth()->guest() || ! auth()->user()->isAdmin()) { $builder->where($model->getTable() . '.is_private', false); } } } ================================================ FILE: app/Models/Scopes/SubEntityScopes.php ================================================ orderBy('updated_at', 'desc'); } public function scopeWithApi(Builder $query): Builder { $relations = [ 'entity', 'entity.image', 'entity.header', 'entity.entityType', 'entity.tags', 'entity.posts', 'entity.posts.permissions', 'entity.reminders', 'entity.relationships', 'entity.attributes', 'entity.inventories', 'entity.inventories', 'entity.assets', 'entity.abilities', ]; if (method_exists($this, 'ancestors')) { $relations[] = 'ancestors'; $relations[] = 'children'; } $with = ! empty($this->apiWith) ? $this->apiWith : []; foreach ($with as $relation) { $relations[] = $relation; } return $query ->select($this->getTable() . '.*') ->has('entity') ->with($relations); } public function scopeJoinEntity(Builder $query): Builder { if ($this->hasJoinedEntity) { return $query; } $this->hasJoinedEntity = true; return $query ->leftJoin('entities as e', function ($join) { $join->on('e.entity_id', '=', $this->getTable() . '.id'); // @phpstan-ignore-next-line $join->where('e.type_id', '=', $this->entityTypeID()) ->whereRaw('e.campaign_id = ' . $this->getTable() . '.campaign_id'); }) ->groupBy($this->getTable() . '.id'); } } ================================================ FILE: app/Models/Scopes/TagScopes.php ================================================ where('is_auto_applied', true); } /** * Get tags that are not hidden */ public function scopeOnlyVisible(Builder $query): Builder { return $query->where('is_hidden', 0); } } ================================================ FILE: app/Models/Scopes/UserScope.php ================================================ extensions as $extension) { $this->{"add{$extension}"}($builder); } } protected function addWithPrivate(Builder $builder) { $builder->macro('withPrivate', function (Builder $builder, $withInvisible = true) { if (! $withInvisible) { // Sends the default scope return $builder; } // @phpstan-ignore-next-line return $builder->withoutGlobalScope($this); }); } /** * Apply the scope to a given Eloquent query builder. * * @return void */ public function apply(Builder $builder, Model $model) { // Only apply these scopes in non-console mode. if (app()->runningInConsole()) { return; } // If we aren't authenticated, just see what is set to all if (! auth()->check()) { $builder->where($model->getTable() . '.visibility_id', Visibility::All); return; } $campaign = CampaignLocalization::getCampaign(); if (! auth()->user()->can('member', $campaign)) { $builder->where($model->getTable() . '.visibility_id', Visibility::All); return; } // Either mine (self && created_by = me) or (if admin: !self, else: all) $builder->where(function ($sub) use ($model) { $visibilities = auth()->user()->isAdmin() ? [Visibility::All, Visibility::Admin, Visibility::AdminSelf, Visibility::Member] : [Visibility::All, Visibility::Member]; $sub ->where(function ($self) use ($model) { $self ->whereIn($model->getTable() . '.visibility_id', [ Visibility::Self, Visibility::AdminSelf, ]) ->where($model->getTable() . '.created_by', auth()->user()->id); }) ->orWhereIn($model->getTable() . '.visibility_id', $visibilities); }); } } ================================================ FILE: app/Models/Spotlight.php ================================================ SpotlightStatus::class, ]; } ================================================ FILE: app/Models/SpotlightContent.php ================================================ SpotlightContentStatus::class, 'content_json' => 'json', ]; public function isDraft(): bool { return $this->status === SpotlightContentStatus::draft; } public function isApplied(): bool { return $this->status === SpotlightContentStatus::applied; } public function isRejected(): bool { return $this->status === SpotlightContentStatus::rejected; } public function isApproved(): bool { return $this->status === SpotlightContentStatus::approved; } } ================================================ FILE: app/Models/SubscriptionCancellation.php ================================================ method, ['giropay', 'sofort', 'ideal']) ? 'eur' : 'eur'; } public function plan(): string { if ($this->tier === Pledge::ELEMENTAL) { if ($this->period === 'yearly') { return config('subscription.elemental.eur.yearly'); } return config('subscription.elemental.eur.monthly'); } if ($this->period === 'yearly') { return config('subscription.owlbear.eur.yearly'); } return config('subscription.owlbear.eur.monthly'); } } ================================================ FILE: app/Models/Tag.php ================================================ allChildren()->get() as $child) { $child->tags()->detach($this->id); } } /** * Get all the children */ public function allChildren(): Builder { $descendantIds = $this->entity->descendants()->pluck('entity_id'); return Entity::whereIn('id', function ($query) use ($descendantIds) { $query->select('entity_id') ->from('entity_tags') ->whereIn('tag_id', $descendantIds->push($this->id)); }); } /** * @return BelongsToMany */ public function entities(): BelongsToMany { return $this->belongsToMany( 'App\Models\Entity', 'entity_tags', 'tag_id', 'entity_id', 'id', 'id' ); } /** * @return HasMany */ public function entityTags(): HasMany { return $this->hasMany(EntityTag::class); } /** * @return BelongsToMany */ public function posts(): BelongsToMany { return $this->belongsToMany( 'App\Models\Post', 'post_tag', 'tag_id', 'post_id', 'id', 'id' ); } /** * @return HasMany */ public function postTags(): HasMany { return $this->hasMany(PostTag::class); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.tag'); } /** * Strip '#' when setting colour */ public function setColourAttribute(?string $colour): void { $this->attributes['colour'] = mb_ltrim($colour ?? '', '#'); } /** * Prepend '#' when getting colour */ public function getColourAttribute(): string { if (empty($this->attributes['colour'])) { return ''; } return '#' . $this->attributes['colour']; } /** * Get the tag's structural colour classes */ public function colourClass(): string { if (! $this->hasColour()) { return 'border-0!'; } return 'color-palette color-tag border-0!'; } /** * Get inline CSS style for the tag's colour */ public function colourStyle(): string { if (! $this->hasColour()) { return ''; } $hex = $this->colour; $textColour = $this->isLightColour($hex) ? '#000' : '#fff'; return 'background-color: ' . e($hex) . '; color: ' . $textColour . ';'; } /** * Determine if a hex colour is light based on luminance */ protected function isLightColour(string $hex): bool { $hex = ltrim($hex, '#'); if (strlen($hex) !== 6) { return false; } $r = hexdec(substr($hex, 0, 2)); $g = hexdec(substr($hex, 2, 2)); $b = hexdec(substr($hex, 4, 2)); // Relative luminance formula $luminance = (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255; return $luminance > 0.5; } public function hasColour(): bool { return ! empty($this->attributes['colour']); } public function hasIcon(): bool { return ! empty($this->icon); } /** * Attach entities to the tag */ public function attachEntities(array $entityIds): int { $data = $this->entities()->syncWithoutDetaching($entityIds); return count($data['attached']); } /** * Determine if the model has profile data to be displayed */ public function showProfileInfo(): bool { if ($this->hasColour() || $this->hasIcon()) { return true; } return parent::showProfileInfo(); } /** * Determine if the model is a tag that has to be applied to all newly created entities */ public function isAutoApplied(): bool { return (bool) $this->is_auto_applied; } /** * Determine if the model is a tag that is hidden */ public function isHidden(): bool { return (bool) $this->is_hidden; } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'colour', 'is_auto_applied', 'is_hidden', ]; } public function shortname(): string { return grapheme_extract($this->name, 1); } } ================================================ FILE: app/Models/Theme.php ================================================ */ public function campaigns(): HasMany { return $this->hasMany('App\Models\Campaign', 'theme_id'); } public function __toString(): string { return __('profiles.theme.themes.' . $this->name); } } ================================================ FILE: app/Models/Tier.php ================================================ */ public function prices(): HasMany { return $this->hasMany(TierPrice::class); } public function scopeOrdered(Builder $query): Builder { return $query->orderBy('position'); } public function isFree(): bool { return $this->name === Pledge::KOBOLD; } public function isPopular(): bool { return $this->name === Pledge::OWLBEAR; } public function isBestValue(): bool { return $this->name === Pledge::WYVERN; } public function image(): string { return match ($this->name) { 'Owlbear' => 'https://d3a4xjr8r2ldhu.cloudfront.net/app/tiers/owlbear-128.png', 'Wyvern' => 'https://d3a4xjr8r2ldhu.cloudfront.net/app/tiers/wyvern-128.png', 'Elemental' => 'https://d3a4xjr8r2ldhu.cloudfront.net/app/tiers/elemental-128.png', default => 'https://d3a4xjr8r2ldhu.cloudfront.net/app/tiers/kobold-128.png' }; } public function isCurrent(User $user): bool { if ($this->name === Pledge::OWLBEAR && $user->isOwlbear()) { return true; } elseif ($this->name === Pledge::WYVERN && $user->isWyvern()) { return true; } return (bool) ($this->name === Pledge::ELEMENTAL && $user->isElemental()); } public function monthlyPlans(): array { return config('subscription.' . $this->code . '.monthly'); } public function yearlyPlans(): array { return config('subscription.' . $this->code . '.yearly'); } public function plans(): array { return array_merge( config('subscription.' . $this->code . '.monthly'), config('subscription.' . $this->code . '.yearly'), ); } public function price(string $currency, PricingPeriod $period): float { /** @var TierPrice $price */ $price = $this->prices ->where('currency', $currency) ->where('period', $period) ->first(); if (empty($price)) { return 0.00; } return $price->cost; } public function isWyvern(): bool { return $this->code === 'wyvern'; } } ================================================ FILE: app/Models/TierPrice.php ================================================ PricingPeriod::class, ]; /** * @return BelongsTo */ public function tier(): BelongsTo { return $this->belongsTo(Tier::class); } public function isYearly(): bool { return $this->period->isYearly(); } public function scopeStripe(Builder $query, string $id): Builder { return $query->where('stripe_id', $id); } } ================================================ FILE: app/Models/Timeline.php ================================================ */ public function calendar(): BelongsTo { return $this->belongsTo('App\Models\Calendar', 'calendar_id', 'id'); } /** * @return HasMany */ public function eras(): HasMany { return $this->hasMany('App\Models\TimelineEra'); } /** * @return HasMany */ public function elements(): HasMany { return $this->hasMany( 'App\Models\TimelineElement', ); } /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.timeline'); } /** * Define the fields unique to this model that can be used on filters * * @return string[] */ public function filterableColumns(): array { return [ 'calendar_id', ]; } } ================================================ FILE: app/Models/TimelineElement.php ================================================ Visibility::class, ]; protected array $suggestions = [ TimelineElementCache::class => 'clearSuggestion', ]; protected array $sanitizable = [ 'name', 'date', 'icon', ]; /** * @return BelongsTo */ public function timeline(): BelongsTo { return $this->belongsTo(Timeline::class, 'timeline_id'); } /** * @return BelongsTo */ public function era(): BelongsTo { return $this->belongsTo(TimelineEra::class, 'era_id'); } /** * @return BelongsTo */ public function entity(): BelongsTo { return $this->belongsTo(Entity::class, 'entity_id'); } public function scopeOrdered(Builder $query): Builder { return $query ->with('entity') ->orderBy('position'); } public function elementName(): string { if (! empty($this->entity_id)) { return $this->entity->name ?? __('crud.history.unknown'); } return $this->name; } public function mentionName(): string { if (! empty($this->name)) { return strip_tags(htmlentities($this->name)); } // @phpstan-ignore-next-line return strip_tags($this->entity?->name); } /** * For legacy tinymce editor */ public function hasEntity(): bool { return false; } public function collapsed(): bool { return $this->is_collapsed; } /** * Check if the element has an entity, but it's not accessible (permission issue for non-admins) */ public function invisibleEntity(): bool { if (empty($this->entity_id)) { return false; } return empty($this->entity); } /** * @return BelongsToMany< * User, * $this, * EntityUser * > */ public function editingUsers(): BelongsToMany { return $this->belongsToMany(User::class, 'entity_user') ->using(EntityUser::class) ->withPivot('type_id'); } /** * List of entities that mention this entity * * @return HasMany */ public function mentions(): HasMany { return $this->hasMany('App\Models\EntityMention', 'timeline_element_id', 'id'); } public function visible(): bool { if (empty($this->entity_id)) { return true; } return $this->entity && ! $this->entity->isMissingChild(); } /** * Get the value used to index the model. */ public function getScoutKey() { return $this->getTable() . '_' . $this->id; } /** * Get the name of the index associated with the model. */ public function searchableAs(): string { return 'entities'; } protected function makeAllSearchableUsing($query) { return $query ->select([$this->getTable() . '.*', 'entities.id as entity_id']) ->leftJoin('timelines', 'timelines.id', '=', 'timeline_elements.timeline_id') ->leftJoin('entities', function ($join) { $join->on('entities.entity_id', $this->getTable() . '.id'); }) ->has('timeline') ->has('timeline.entity') ->with(['timeline', 'timeline.entity']); } public function toSearchableArray() { if (! $this->timeline || ! $this->timeline->entity) { return []; } return [ 'campaign_id' => $this->timeline->entity->campaign_id, 'entity_id' => $this->timeline->entity->id, 'name' => $this->name, 'type' => 'timeline_element', 'entry' => strip_tags($this->entry), ]; } } ================================================ FILE: app/Models/TimelineEra.php ================================================ */ public function timeline(): BelongsTo { return $this->belongsTo(Timeline::class, 'timeline_id'); } /** * @return HasMany */ public function elements(): HasMany { return $this->hasMany(TimelineElement::class, 'era_id'); } public function orderedElements(): HasMany { return $this->elements() ->ordered(); } public function scopeOrdered(Builder $query): Builder { return $query ->orderBy('position') ->orderBy('start_year') ->orderBy('end_year') ->orderBy('name'); } public function collapsed(): bool { return $this->is_collapsed; } /** * Get the age header of the era */ public function ages(): string { $a = new \NumberFormatter(app()->getLocale(), \NumberFormatter::DECIMAL); $from = mb_strlen($this->start_year); $to = mb_strlen($this->end_year); if ($from == 0 && $to == 0) { return ''; } if ($from == 0) { return '< ' . $a->format($this->start_year); } elseif ($to == 0) { return '> ' . $a->format($this->start_year); } return $a->format($this->start_year) . ' — ' . $a->format($this->end_year); } public function hasEntity(): bool { return false; } /** * Functions for the datagrid2 */ public function url(string $where): string { return 'timelines.timeline_eras.' . $where; } public function routeParams(array $options = []): array { return $options + ['timeline' => $this->timeline_id, 'timeline_era' => $this->id]; } /** * Override the get link */ public function getLink(): string { $campaign = CampaignLocalization::getCampaign(); return route('timelines.timeline_eras.edit', [$campaign, 'timeline' => $this->timeline_id, $this->id]); } public function getEntryForEditionAttribute() { $text = Mentions::parseForEdit($this); return $text; } /** * @return array|string[] */ public function positionOptions($position = null, bool $new = false): array { $options = [null => __('posts.position.dont_change')]; $elements = $this->orderedElements; $hasFirst = false; foreach ($elements as $element) { if (! $element->visible()) { continue; } if (! $hasFirst) { $hasFirst = true; $options[1] = __('posts.position.first'); } $key = $element->position; $lang = __('maps/layers.placeholders.position_list', ['name' => $element->elementName()]); if (config('app.debug')) { $lang .= ' (' . $key . ')'; } if (! ($position == $key)) { $options[$key + 1] = $lang; } } // Didn't have a first option added, add one now if (! $hasFirst) { $options[1] = __('posts.position.first'); } if ($new) { unset($options[null]); } return $options; } } ================================================ FILE: app/Models/User.php ================================================ */ protected $casts = [ 'settings' => 'array', 'profile' => 'array', 'card_expires_at' => 'datetime', 'last_login_at' => 'date', 'banned_until' => 'date', 'trial_ends_at' => 'date', ]; protected array $imageFields = ['avatar']; protected bool $isAdmin; public function getAvatarUrl(int $size = 40): string { if ($this->hasAvatar()) { return $this->thumbnail($size, null, 'avatar'); } else { return '/images/defaults/user.svg'; } } public function hasAvatar(): bool { return ! empty($this->avatar) && $this->avatar != 'users/default.png'; } /** * Determine if a user has a specific role */ public function hasCampaignRole(int $roleId): bool { return $this->campaignRoles->where('id', $roleId)->count() > 0; } /** * Figure out if the user is an admin of the current campaign. * $campaign can be provided, for example when listing a user's campaigns */ public function isAdmin(?Campaign $campaign = null): bool { if (isset($this->isAdmin)) { return $this->isAdmin; } if (empty($campaign) && CampaignLocalization::hasCampaign()) { $campaign = CampaignLocalization::getCampaign(); } if (empty($campaign)) { return false; } return $this->isAdmin = $this->campaignRoles ->where('campaign_id', $campaign->id) ->where('is_admin', 1) ->count() === 1; } /** * Determine if a user is a subscriber */ public function isSubscriber(): bool { return $this->hasRole(Pledge::ROLE) || $this->hasRole('admin') || $this->onTrial(); } /** * Determine if a user has a legacy patreon sync set up */ public function isLegacyPatron(): bool { return $this->hasRole(Pledge::ROLE) && ! empty($this->patreon_email); } /** * Determine if a user is a goblin (deprecated) */ public function isGoblin(): bool { return ! empty($this->pledge) && $this->pledge !== Pledge::KOBOLD; } /** * Determine if a user is an elemental */ public function isElemental(): bool { return (bool) (! empty($this->pledge) && $this->pledge == Pledge::ELEMENTAL); } public function isOwlbear(): bool { return ! empty($this->pledge) && $this->pledge == Pledge::OWLBEAR; } public function isWyvern(): bool { return ! empty($this->pledge) && $this->pledge == Pledge::WYVERN; } /** * API throttling is increased for subscribers */ public function getRateLimitAttribute(): int { return $this->isGoblin() ? config('limits.api.throttle.subscriber') : config('limits.api.throttle.default'); } /** * Currency symbol */ public function currencySymbol(): string { if ($this->billedInEur()) { return '€'; } elseif ($this->billedInBrl()) { return 'R$'; } return 'US$'; } /** * Determine if the user is billed in EUR. */ public function billedInEur(): bool { return $this->currency() === 'eur'; } /** * Determine if the user is billed in BRL */ public function billedInBrl(): bool { return $this->currency() === 'brl'; } /** * Check if the user has a Role(s) associated. * * @param string|array $name The role(s) to check. */ public function hasRole($name): bool { $roles = $this->roles->pluck('name')->toArray(); foreach ((is_array($name) ? $name : [$name]) as $role) { if (in_array($role, $roles)) { return true; } } return false; } /** * Determine if a user is using a social login */ public function isSocialLogin(): bool { return ! empty($this->provider); } /** * Number of entities the user has created */ public function createdEntitiesCount(): string { return (string) Number::format(SingleUserCache::user($this)->entitiesCreatedCount()); } /** * Determine if the user has published plugins on the marketplace */ public function hasPlugins(): bool { return config('marketplace.enabled') && $this->plugins->count(); } /** * Get the Discord app of the user */ public function discord() { return $this->apps->where('app', 'discord')->first(); } /** * Log an event on the user */ public function log(UserAction $action, array $data = []): self { // todo: move to a facade if (! config('logging.enabled')) { return $this; } $log = new UserLog([ 'user_id' => $this->id, ]); $log->type_id = $action; $log->data = ! empty($data) ? $data : null; $log->save(); return $this; } public function campaignLog(int $campaign, string $module, string $action, array $data = []): self { // todo: move to a facade if (! config('logging.enabled')) { return $this; } $log = new UserLog([ 'user_id' => $this->id, ]); $log->type_id = UserAction::campaign; $log->campaign_id = $campaign; $first = []; $first['module'] = $module; $first['action'] = $action; $log->data = $first + $data; $log->impersonated_by = Identity::getImpersonatorId(); $log->save(); return $this; } /** * Determine if the user is banned */ public function isBanned(): bool { return ! empty($this->banned_until) && $this->banned_until->isFuture(); } /** * Determine if the user has achievements to display on their profile page */ public function hasAchievements(): bool { return $this->isWordsmith(); } /** * Determine if a user has the Wordsmith role */ public function isWordsmith(): bool { return $this->hasRole('wordsmith'); } /** * Check if user has 2FA. * * @return HasOne */ public function passwordSecurity(): HasOne { return $this->hasOne('App\Models\PasswordSecurity'); } /** * When auto-login is enabled, the code to check if the user needs to input their 2FA code checks for this property */ public function getGoogle2faSecretAttribute(): ?string { return $this->passwordSecurity?->google2fa_secret; } /** * Get the user's initial for some UI elements */ public function initials(): string { // If the username has no spaces, use the two first letters of the name if (! Str::contains(' ', $this->name)) { return Str::limit($this->name, 2, ''); } $explode = explode(' ', $this->name); return $explode[0] . $explode[1]; } /** * Determine if the user has unread notifications or kanka alerts */ public function hasUnread(): bool { if (Identity::isImpersonating()) { return false; } // Unread notifications $releases = ReleaseCache::latest(); /** @var AppRelease $release */ foreach ($releases as $release) { if (! $release->alreadyRead()) { return true; } } return $this->unreadNotifications()->count() > 0; } /** * Fraud detection system */ public function isFrauding(): bool { // Fraud detection can be turned on or off if (! config('subscription.fraud_detection')) { return false; } // Someone with a provider (twitter, fb) login is always considered safe if (! empty($this->provider)) { return false; } $validation = $this->userValidation()->valid()->first(); if ($validation) { return false; } // If the account was created recently, add some small checks /*if ($this->created_at->isAfter(Carbon::now()->subHour())) { // User's name is directly in the campaign name if (Str::startsWith($this->email, $this->name . '@')) { return true; } elseif ($this->campaigns()->count() === 1) { $campaign = $this->campaigns()->first(); // Only the 4 starting entities if ($campaign->entities()->withInvisible()->count() === 4) { return true; } } }*/ // Recent fails are a clear indicator of someone cycling through cards return $this->logs() ->where('type_id', UserAction::failedChargeEmail) ->whereDate('created_at', '>=', Carbon::now()->subHour()->toDateString()) ->count() >= 2; } /** * Check if the user is subscribed via PayPal */ public function hasPayPal(): bool { return $this->subscribed('kanka') && $this->subscription('kanka') && str_contains($this->subscription('kanka')->stripe_price, 'paypal'); } /** * Check if the user is subscribed via a manual sub */ public function hasManualSubscription(): bool { return $this->subscribed('kanka') && $this->subscription('kanka') && Str::startsWith($this->subscription('kanka')->stripe_id, 'manual_'); } /** * Determine in which folder to store the user's avatar. We group them by 1000 user ids to avoid * having one massive folder containing everything. */ public function imageStoragePath(): string { return 'users/' . (int) floor($this->id / 1000); } } ================================================ FILE: app/Models/UserApp.php ================================================ 'date', 'settings' => 'array', ]; public function scopeApp(Builder $query, string $type): Builder { return $query->where('app', $type); } public function scopeDiscord(Builder $query): Builder { return $this->app('discord'); } } ================================================ FILE: app/Models/UserFlag.php ================================================ UserFlags::class, ]; public function scopeFreeTrial(Builder $query): Builder { return $query->where('flag', UserFlags::freeTrial); } public function scopeUploadLimit(Builder $query): Builder { return $query->where('flag', UserFlags::uploadSize); } } ================================================ FILE: app/Models/UserLog.php ================================================ UserAction::class, 'data' => 'array', ]; protected $fillable = [ 'user_id', 'type_id', 'ip', 'campaign_id', 'data', 'impersonated_by', ]; /** * Automatically prune old elements from the db */ public function prunable(): Builder { $cutoff = config('logging.prune_months'); return static::where('updated_at', '<=', now()->subMonths($cutoff)); } public function scopeLogins(Builder $builder): Builder { return $builder->whereIn('type_id', [UserAction::login->value, UserAction::autoLogin->value]); } /** * @return BelongsTo */ public function impersonator(): BelongsTo { $this->setConnection(config('database.default')); return $this->belongsTo(User::class, 'impersonated_by'); } /** * @return BelongsTo */ public function user(): BelongsTo { $this->setConnection(config('database.default')); return $this->belongsTo(User::class, $this->getUserFieldName()); } public function requiresPremium(): bool { $cutoff = config('limits.campaigns.logs.standard'); return $this->created_at->diffInDays() > $cutoff; } } ================================================ FILE: app/Models/UserRole.php ================================================ settings, 'patreon_email', ''); } public function getEditorAttribute() { return Arr::get($this->settings, 'editor'); } /** * @param string|null $value */ public function setNewEntityWorkflowAttribute($value) { $this->setSettingsOption('new_entity_workflow', $value); } public function getNewEntityWorkflowAttribute() { return Arr::get($this->settings, 'new_entity_workflow'); } public function getEntityExploreAttribute() { return Arr::get($this->settings, 'entity_explore'); } /** * @param string|null $value */ public function setCampaignSwitcherOrderByAttribute($value) { $this->setSettingsOption('campaign_switcher_order_by', $value); } public function getCampaignSwitcherOrderByAttribute() { return Arr::get($this->settings, 'campaign_switcher_order_by'); } /** * @param string|null $value */ public function setAdvancedMentionsAttribute($value) { $this->setSettingsOption('advanced_mentions', $value); } public function getAdvancedMentionsAttribute() { return Arr::get($this->settings, 'advanced_mentions', false); } /** * @param string|null $value */ public function setMailReleaseAttribute($value) { $this->setSettingsOption('mail_release', $value); } public function getMailReleaseAttribute() { return Arr::get($this->settings, 'mail_release', false); } /** * @param string|null $key * @param string|null $value */ protected function setSettingsOption($key, $value) { $settings = $this->settings; $settings[$key] = $value; $this->settings = $settings; } /** * Save the user settings into the array mutator */ public function saveSettings(array $data): self { $settings = $this->settings; // Flatten if provided if (isset($data['settings']) && is_array($data['settings'])) { $data = $data['settings']; } foreach ($data as $key => $value) { if (empty($value) && isset($settings[$key])) { unset($settings[$key], $settings[$key]); } elseif (! empty($value)) { if ($key == 'link') { $settings[$key] = Purify::config(['URI.AllowedSchemes' => ['https']])->clean($value); // Allows https URLs } else { $settings[$key] = Purify::clean($value); } } } $this->settings = $settings; return $this; } /** * Save user's custom billing info */ public function updateBillingInfo($billing): self { $profile = $this->profile; $profile['billing'] = $billing; if ($billing == '') { unset($profile['billing']); } $this->profile = $profile; return $this; } public function updateSettings($data): self { $fields = ['mail_release']; foreach ($fields as $field) { if (! Arr::has($data, $field)) { continue; } $this->$field = Arr::get($data, $field); } return $this; } /** * Get the user's public display name */ public function displayName(): string { if (empty($this->settings['marketplace_name'])) { return (string) $this->name; } return (string) $this->settings['marketplace_name']; } /** * Determine if a user only wants advance mentions in their text editor */ public function alwaysAdvancedMentions(): bool { return (bool) Arr::get($this->settings, 'advanced_mentions', false); } public function currency() { return Arr::get($this->settings, 'currency', 'usd'); } public function getPaginationAttribute(): ?int { return (int) Arr::get($this->settings, 'pagination'); } public function getDateformatAttribute(): ?string { return Arr::get($this->settings, 'date_format'); } /** * Determine if a user is subsribed to the newsletter */ public function hasNewsletter(): bool { return ! empty($this->mail_release); } /** * Determine if a user has the old booster nomenclature */ public function hasBoosterNomenclature(): bool { if (config('app.debug') && request()->get('_legacy') == 1) { return true; } return Arr::get($this->settings, 'grandfathered_boost') === 1; } } ================================================ FILE: app/Models/UserValidation.php ================================================ where('created_at', '<=', now()->subDays(1)); } public function scopeValid(Builder $query, bool $valid = true): Builder { return $query->where(['is_valid' => $valid]); } } ================================================ FILE: app/Models/Users/Tutorial.php ================================================ 'array', ]; protected array $sortable = [ 'type', 'status', 'action', ]; public function url(string $sub): string { return 'webhooks.' . $sub; } /** * @return BelongsToMany */ public function tags(): BelongsToMany { return $this->belongsToMany( 'App\Models\Tag', 'webhook_tags', 'webhook_id', 'tag_id', 'id', 'id' ); } public function typeKey(): string { if ($this->type == 2) { return __('campaigns/webhooks.fields.types.payload'); } return __('campaigns/webhooks.fields.types.custom'); } public function actionKey(): string { $campaign = CampaignLocalization::getCampaign(); $action = __('campaigns/webhooks.fields.events.deleted'); if ($this->action === WebhookAction::CREATED->value) { $action = __('campaigns/webhooks.fields.events.new'); } elseif ($this->action === WebhookAction::EDITED->value) { $action = __('campaigns/webhooks.fields.events.edited'); } return '' . $action . ''; } public function shortUrl(): string { $pieces = parse_url($this->url); $domain = $pieces['host'] ?? ''; if (preg_match('/(?P[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', $domain, $regs)) { return mb_strstr($regs['domain'], '.', true); } return $this->url; } public function scopeActive(Builder $query, int $campaignId, int $action): Builder { return $query ->where('campaign_id', $campaignId) ->where('action', $action) ->where('status', 1); } public function isActive(): bool { return $this->status == 1; } public function skipPrivate(): bool { if (Arr::has($this->settings, 'skip_private')) { return Arr::get($this->settings, 'skip_private'); } return false; } } ================================================ FILE: app/Models/Whiteboard.php ================================================ 'array', ]; protected array $sortable = [ 'name', ]; protected array $sanitizable = [ 'name', ]; /** * Get the entity_type id from the entity_types table */ public function entityTypeId(): int { return (int) config('entities.ids.whiteboard'); } public function shapes(): HasMany { return $this->hasMany(WhiteboardShape::class); } } ================================================ FILE: app/Models/WhiteboardShape.php ================================================ 'float', 'y' => 'float', 'width' => 'float', 'height' => 'float', 'rotation' => 'float', 'shape' => 'array', ]; public function whiteboard(): BelongsTo { return $this->belongsTo(Whiteboard::class); } public function group(): BelongsTo { return $this->belongsTo(WhiteboardShape::class, 'id', 'group_id'); } public function strokes(): HasMany { return $this->hasMany(WhiteboardStroke::class, 'shape_id'); } public function isRectangle(): bool { return $this->type === 'rect'; } public function isCircle(): bool { return $this->type === 'circle'; } public function isText(): bool { return $this->type === 'text'; } public function isEntity(): bool { return $this->type === 'entity'; } public function isImage(): bool { return $this->type === 'image'; } public function isDrawing(): bool { return $this->type === 'drawing'; } public function image(): ?string { if ($this->isImage()) { $image = Image::find($this->shape['uuid']); if ($image) { return $image->url(); } } elseif ($this->isEntity()) { $entity = Entity::find($this->shape['entity_id']); if ($entity) { return Avatar::entity($entity)->size(256)->fallback()->thumbnail(); } } return null; } } ================================================ FILE: app/Models/WhiteboardStroke.php ================================================ belongsTo(WhiteboardShape::class); } public function unpack(int $scale = 1000): array { $points = []; $len = strlen($this->points); for ($i = 0; $i < $len; $i += 16) { $x = unpack('q', substr($this->points, $i, 8))[1]; $y = unpack('q', substr($this->points, $i + 8, 8))[1]; $points[] = $x / $scale; $points[] = $y / $scale; } return $points; } } ================================================ FILE: app/Notifications/Header.php ================================================ key = $key; $this->colour = $colour; $this->icon = $icon; $this->params = $params; } /** * Get the notification's delivery channels. * * @return array */ public function via($notifiable) { return ['database']; } /** * Get the array representation of the notification. * * @return array */ public function toArray($notifiable) { return [ 'key' => $this->key, 'colour' => $this->colour, 'icon' => $this->icon, 'params' => $this->params, ]; } } ================================================ FILE: app/Observers/AdminInviteObserver.php ================================================ check()) { // @phpstan-ignore-next-line $model->created_by = auth()->user()->id; } } public function updating(Model $model): void { // Some models don't have an updated_by. if (Arr::exists($model->getAttributes(), 'updated_by') && auth()->check()) { // @phpstan-ignore-next-line $model->updated_by = auth()->user()->id; } } public function deleted(Model $model): void { if (Arr::exists($model->getAttributes(), 'deleted_by') && auth()->check()) { // @phpstan-ignore-next-line $model->deleted_by = auth()->user()->id; if ( method_exists($model, 'useSoftDeletes') && $model->isDirty() ) { // Using softDeletes makes an update() on the model but only on the deleted_at field, // so we need to do another write to the db because it's dumb. $model->updateQuietly(); } } } } ================================================ FILE: app/Observers/BookmarkObserver.php ================================================ position)) { $model->position = Bookmark::max('position') + 1; } else { $model->position = (int) $model->position; } // Handle the entity type or direct entity if (! empty($model->entity_type_id)) { $model->entity_id = null; // $model->tab = null; $model->menu = ''; } else { $model->entity_type_id = null; $model->filters = null; } // Only allow certain keys in the options array $options = $model->options; if (! empty($options)) { $model->options = array_intersect_key($model->options, array_flip($model->optionsAllowedKeys)); } // Is private hook for non-admin (who can't set is_private) if (! isset($model->is_private)) { $model->is_private = false; } } } ================================================ FILE: app/Observers/CalendarObserver.php ================================================ isDirty(['date'])) { /** @var Calendar $model */ CalendarsClearElapsed::dispatch($model); } } } ================================================ FILE: app/Observers/CampaignDashboardObserver.php ================================================ user()); } public function updated(CampaignDashboard $campaignDashboard) { DashboardUpdated::dispatch($campaignDashboard, auth()->user()); } public function deleted(CampaignDashboard $campaignDashboard) { DashboardDeleted::dispatch($campaignDashboard, auth()->user()); } } ================================================ FILE: app/Observers/CampaignDashboardRoleObserver.php ================================================ dashboard, auth()->user()); } public function updated(CampaignDashboardRole $campaignDashboardRole): void { DashboardUpdated::dispatch($campaignDashboardRole->dashboard, auth()->user()); } public function deleted(CampaignDashboardRole $campaignDashboardRole): void { DashboardUpdated::dispatch($campaignDashboardRole->dashboard, auth()->user()); } } ================================================ FILE: app/Observers/CampaignDashboardWidgetObserver.php ================================================ position = 0; $last = CampaignDashboardWidget::onDashboard($model->dashboard) ->with('entity') ->orderBy('position', 'desc') ->first(); if (! empty($last)) { $model->position = $last->position + 1; } } } ================================================ FILE: app/Observers/CampaignDescriptionObserver.php ================================================ getAttributes(); $campaign = $description->campaign; foreach (['description', 'excerpt'] as $field) { if (! array_key_exists($field, $attributes)) { continue; } $description->$field = $this->purify( $this->saveService ->campaign($campaign) ->user(auth()->user()) ->text($description->$field) ->save() ); } } public function saved(CampaignDescription $description): void { if ($description->isClean('description')) { return; } $campaign = $description->campaign; if ($campaign && method_exists($campaign, 'mentions')) { EntityMappingJob::dispatch($campaign); } } } ================================================ FILE: app/Observers/CampaignExportObserver.php ================================================ user()); } } ================================================ FILE: app/Observers/CampaignFollowerObserver.php ================================================ token = sha1(Str::random(50)) . time() . uniqid(); $campaignInvite->is_active = true; } public function created(CampaignInvite $campaignInvite) { InviteCreated::dispatch($campaignInvite, auth()->user()); } public function deleted(CampaignInvite $campaignInvite) { InviteDeleted::dispatch($campaignInvite, auth()->user()); } } ================================================ FILE: app/Observers/CampaignObserver.php ================================================ isDirty('is_prioritised') && $campaign->is_prioritised && auth()->check()) { $user = auth()->user(); if (! $user->isElemental()) { $campaign->is_prioritised = false; return; } $adminCampaignIds = $user->campaignRoles()->where('is_admin', true)->pluck('campaign_id'); $alreadyPrioritised = Campaign::where('is_prioritised', true) ->where('id', '!=', $campaign->id) ->whereIn('id', $adminCampaignIds) ->exists(); if ($alreadyPrioritised) { $campaign->is_prioritised = false; } } } public function creating(Campaign $campaign) { $campaign->entity_visibility = false; $campaign->entity_personality_visibility = false; } public function saved(Campaign $campaign) { Saved::dispatch($campaign, auth()->user()); } public function updated(Campaign $campaign) { Updated::dispatch($campaign, auth()->user()); } public function deleted(Campaign $campaign) { Deleted::dispatch($campaign, auth()->user()); } } ================================================ FILE: app/Observers/CampaignPluginObserver.php ================================================ user()); $campaignPlugin->campaign->touchQuietly(); } public function deleted(CampaignPlugin $campaignPlugin) { PluginDeleted::dispatch($campaignPlugin, auth()->user()); $campaignPlugin->campaign->touchQuietly(); } } ================================================ FILE: app/Observers/CampaignRoleObserver.php ================================================ user()); } public function deleted(CampaignRole $campaignRole) { RoleDeleted::dispatch($campaignRole, auth()->user()); } public function updated(CampaignRole $campaignRole) { RoleUpdated::dispatch($campaignRole, auth()->user()); } } ================================================ FILE: app/Observers/CampaignRoleUserObserver.php ================================================ user()); } public function deleted(CampaignRoleUser $campaignRoleUser) { RoleUserRemoved::dispatch($campaignRoleUser, auth()->user()); } } ================================================ FILE: app/Observers/CampaignSettingObserver.php ================================================ campaign, auth()->user()); } } ================================================ FILE: app/Observers/CampaignStyleObserver.php ================================================ /i', '', $campaignStyle->content); $campaignStyle->content = str_replace(['>', '{{', '}}'], ['>', '', ''], $content); } public function creating(CampaignStyle $campaignStyle) { $last = $campaignStyle->campaign->styles()->max('order'); $campaignStyle->order = (int) $last + 1; } public function created(CampaignStyle $campaignStyle) { StyleCreated::dispatch($campaignStyle, $campaignStyle->campaign, auth()->user()); } public function updated(CampaignStyle $campaignStyle) { StyleUpdated::dispatch($campaignStyle, $campaignStyle->campaign, auth()->user()); } public function deleted(CampaignStyle $campaignStyle) { StyleDeleted::dispatch($campaignStyle, $campaignStyle->campaign, auth()->user()); } } ================================================ FILE: app/Observers/CampaignUserObserver.php ================================================ user_id) ->where('campaign_id', $campaignUser->campaign_id) ->first(); if ($follow) { $follow->delete(); } // Update the campaign members cache when a user was added to the campaign CampaignCache::campaign($campaignUser->campaign)->clear(); } public function deleted(CampaignUser $campaignUser) { // Update the campaign members cache when a user was deleted if ($campaignUser->campaign === null) { return; } CampaignCache::campaign($campaignUser->campaign)->clear(); } } ================================================ FILE: app/Observers/ChildEntityObserver.php ================================================ createEntity(); } public function deleted(MiscModel $model) { // Soft-delete the entity if ($model->entity) { $model->entity->delete(); } // If soft deleting, don't really delete the image // @phpstan-ignore-next-line if ($model->trashed()) { return; } Images::model($model)->cleanup(); } public function saved(MiscModel $model) { EntityLogger::model($model); } } ================================================ FILE: app/Observers/Concerns/HasMany.php ================================================ $relationName as $rel) { // If it already exists, we have an issue if (! empty($existing[$rel->$relationID])) { $recreate[$rel->$relationID] = $rel->$relationID; $model->$relation()->detach($rel->$relationID); continue; } $existing[$rel->$relationID] = $rel->$relationID; $unique[$rel->$relationID] = $rel->$relationID; } if (! empty($recreate)) { $model->$relation()->attach($recreate); } $newModels = []; $find = new $classname; foreach ($values as $id) { // Existing race, do nothing if (! empty($existing[$id])) { unset($existing[$id]); continue; } // If already managed, again, ignore if (! empty($unique[$id])) { continue; } $found = $find::find($id); if (empty($found)) { continue; } $newModels[] = $found->id; EntityLogger::dirty($relation, null); } $model->$relation()->attach($newModels); // Detach the remaining if (! empty($existing)) { $model->$relation()->detach($existing); EntityLogger::dirty($relation, null); } } } ================================================ FILE: app/Observers/ConversationMessageObserver.php ================================================ conversation->touch(); } } ================================================ FILE: app/Observers/ConversationObserver.php ================================================ isDirty('target')) { /** @var Conversation $model */ $model->participants()->delete(); } } } ================================================ FILE: app/Observers/DiceRollObserver.php ================================================ system = 'standard'; } } ================================================ FILE: app/Observers/DiceRollResultObserver.php ================================================ results = $this->diceRollerService->roll($model->diceRoll); } } ================================================ FILE: app/Observers/EntityAbilityObserver.php ================================================ position !== null) { $entityAbility->position = (int) $entityAbility->position; } } public function saved(EntityAbility $entityAbility) { // Position isn't empty, move the rest if ($entityAbility->position !== null) { $position = $entityAbility->position; $abilities = EntityAbility::select('entity_abilities.*') ->with(['ability', 'ability.entity']) ->has('ability') ->join('abilities as a', 'a.id', 'entity_abilities.ability_id') ->leftJoin('entities as ae', function (JoinClause $join) { $join ->on('ae.entity_id', '=', 'a.id') ->where('ae.type_id', '=', config('entities.ids.ability')); }) ->where(function ($query) use ($entityAbility) { $query->where('ae.id', $entityAbility->entity_id) ->orWhereNull('ae.id'); }) ->where('entity_abilities.entity_id', $entityAbility->entity_id) ->where('entity_abilities.id', '<>', $entityAbility->id) ->where('position', '>=', $position) ->defaultOrder() ->get(); /** @var EntityAbility $next */ foreach ($abilities as $next) { // No access, skip if (! $next->ability || ! $entityAbility->ability) { continue; } // Check the ability's parent to only move stuff in the same "group" if ($next->ability->ability_id != $entityAbility->ability->ability_id) { continue; } $position++; $next->position = $position; $next->saveQuietly(); } } // When adding or changing an entity ability to an entity, we want to update the // last updated date to reflect changes in the dashboard. if ($entityAbility->entity) { $entityAbility->entity->touchQuietly(); } } public function deleted(EntityAbility $entityAbility) { // When deleting an entity ability, we want to update the entity's last update // for the dashboard. Careful of this when deleting an entity, we could be // entering a non-ending loop. if ($entityAbility->entity) { $entityAbility->entity->touchQuietly(); } } } ================================================ FILE: app/Observers/EntityAssetObserver.php ================================================ entity) { $entityAsset->entity->touchQuietly(); } } public function deleting(EntityAsset $entityAsset): void { if ($entityAsset->isFile() && ! $entityAsset->image) { Images::model($entityAsset)->field('imagePath')->cleanup(); } } public function deleted(EntityAsset $entityAsset): void { if ($entityAsset->entity) { $entityAsset->entity->touchQuietly(); } } } ================================================ FILE: app/Observers/EntityLogObserver.php ================================================ create(); } public function updated(Entity $entity) { // Don't log updates if just did one (typically when creating, restoring or bulk editing) if (! empty($entity->getOriginal('deleted_at'))) { return; } EntityLogger::entity($entity)->update(); } public function deleted(Entity $entity) { // Not soft deleting? Nothing more to do if (! $entity->trashed()) { return; } EntityLogger::entity($entity)->delete(); } public function restored(Entity $entity) { EntityLogger::entity($entity)->restore(); } } ================================================ FILE: app/Observers/EntityObserver.php ================================================ user()->can('view', $entity)) { $this->grant($entity, Permission::View->value); } if (! auth()->user()->can('update', $entity)) { $this->grant($entity, Permission::Update->value); } // Refresh the model because adding permissions to the child means we have a new relation if (Permissions::granted() && $entity->hasChild()) { $entity->unsetRelation('child'); $entity->reloadChild(); } if ($entity->campaign->premium()) { EntityWebhookJob::dispatch($entity, auth()->user(), WebhookAction::CREATED->value); } } protected function grant(Entity $entity, int $action): CampaignPermission { $permission = new CampaignPermission; $permission->entity_id = $entity->id; $permission->entity_type_id = $entity->type_id; $permission->campaign_id = $entity->campaign_id; $permission->user_id = auth()->user()->id; $permission->action = $action; $permission->access = true; $permission->save(); Permissions::grant($entity); return $permission; } /** * Queue a few jobs whenever an entity gets updated */ public function updated(Entity $entity) { EntityUpdatedJob::dispatch($entity); if ($entity->campaign->premium()) { EntityWebhookJob::dispatch($entity, auth()->user(), WebhookAction::EDITED->value); } } public function deleted(Entity $entity) { // When an entity is soft-deleted, we just want some webhooks to trigger, // not actually delete the entity and its image. if ($entity->trashed()) { if ($entity->campaign->premium()) { EntityWebhookJob::dispatch($entity, auth()->user(), WebhookAction::DELETED->value); } return; } // Todo: Why is this not handled by the database? $entity->permissions()->delete(); $entity->widgets()->delete(); } public function restored(Entity $entity) { EntityRestored::dispatch($entity, auth()->user()); } } ================================================ FILE: app/Observers/EntityTypeObserver.php ================================================ user()); } public function updated(EntityType $entityType) { EntityTypeUpdated::dispatch($entityType, auth()->user()); } public function deleted(EntityType $entityType) { EntityTypeDeleted::dispatch($entityType, auth()->user()); } } ================================================ FILE: app/Observers/EntryObserver.php ================================================ getAttributes(); // @phpstan-ignore-next-line if (! array_key_exists($model->entryFieldName(), $attributes)) { return; } /** @var SaveService $service */ $service = app()->make(SaveService::class); $campaign = CampaignLocalization::getCampaign(); // When creating a campaign, there is no campaign yet if ($campaign) { $service->campaign($campaign); } // @phpstan-ignore-next-line $model->{$model->entryFieldName()} = $this->purify( $service ->user(auth()->user()) // @phpstan-ignore-next-line ->text($model->{$model->entryFieldName()}) ->save() ); // dd($model->{$model->entryFieldName()}); // Word count if (! Arr::exists($attributes, 'words')) { return; } // @phpstan-ignore-next-line $model->words = str_word_count(strip_tags($model->{$model->entryFieldName()})); } public function saved(Model $model) { // @phpstan-ignore-next-line if ($model->isClean([$model->entryFieldName(), $model->tooltipFieldName()])) { return; } if (method_exists($model, 'mentions')) { EntityMappingJob::dispatch($model); } } } ================================================ FILE: app/Observers/FamilyTreeObserver.php ================================================ family->entity)->updatedFamilyTree(); } } ================================================ FILE: app/Observers/FeatureObserver.php ================================================ isFolder()) { return; } // Delete any images in the folder first foreach ($image->images as $img) { $img->delete(); } } public function deleted(Image $image) { Storage::disk(config('images.disk')) ->delete($image->path); CampaignCache::campaign($image->campaign)->clear(); } public function saved(Image $image) { CampaignCache::campaign($image->campaign)->clear(); } } ================================================ FILE: app/Observers/ImageableObserver.php ================================================ getImageFields() as $field) { Images::model($model) // @phpstan-ignore-next-line ->folder($model->imageStoragePath()) ->field($field) ->handle(); } $model->saveQuietly(); } public function deleted(Model $model): void { // @phpstan-ignore-next-line foreach ($model->getImageFields() as $field) { Images::model($model) ->field($field) ->cleanup(); } } } ================================================ FILE: app/Observers/InventoryObserver.php ================================================ copy_item_entry && empty($inventory->item)) { $inventory->copy_item_entry = false; } } public function saved(Inventory $inventory) { // When adding or changing an inventory to an entity, we want to update the // last updated date to reflect changes in the dashboard. if ($inventory->entity) { $inventory->entity->touchQuietly(); } } public function deleted(Inventory $inventory) { // When deleting an inventory, we want to update the entity's last update // for the dashboard. Careful of this when deleting an entity, we could be // entering a non-ending loop. if ($inventory->entity) { $inventory->entity->touchQuietly(); } } } ================================================ FILE: app/Observers/MapGroupObserver.php ================================================ position)) { $mapGroup->position = (int) $mapGroup->position; } else { $lastGroup = $mapGroup->map->groups()->max('position'); if ($lastGroup) { $mapGroup->position = (int) $lastGroup + 1; } else { $mapGroup->position = 1; } } } public function deleted(MapGroup $mapGroup) { $mapGroup->map->touchSilently(); } public function saved(MapGroup $mapGroup) { $this->reorder($mapGroup); $mapGroup->map->touchSilently(); } } ================================================ FILE: app/Observers/MapLayerObserver.php ================================================ position)) { $mapLayer->position = (int) $mapLayer->position; } else { /** @var ?MapLayer $lastLayer */ $lastLayer = $mapLayer->map->layers()->orderByDesc('position')->first(); if ($lastLayer) { $mapLayer->position = (int) $lastLayer->position + 1; } else { $mapLayer->position = 1; } } // Trying to cheat the options if ($mapLayer->type_id > 2) { $mapLayer->type_id = null; } } public function deleted(MapLayer $mapLayer) { Images::model($mapLayer)->cleanup(); $mapLayer->map->touchSilently(); } public function saved(MapLayer $mapLayer) { $this->reorder($mapLayer); $mapLayer->map->touchSilently(); } } ================================================ FILE: app/Observers/MapMarkerObserver.php ================================================ opacity = round($mapMarker->opacity, 1); $mapMarker->custom_icon = $this->sanitizeCustomIcon($mapMarker); if ($mapMarker->size_id != 6) { $mapMarker->circle_radius = null; } } public function saved(MapMarker $mapMarker) { $mapMarker->map->touchSilently(); } public function deleted(MapMarker $mapMarker) { $mapMarker->map->touchSilently(); } /** * Sanitize the custom icon (i or svg html element) * * @return string|null */ protected function sanitizeCustomIcon(MapMarker $mapMarker) { if (empty($mapMarker->custom_icon)) { return null; } if (Str::startsWith($mapMarker->custom_icon, ['sanitize($mapMarker->custom_icon); if ($cleanSvg !== false) { return $cleanSvg; } else { return null; } } elseif (Str::startsWith($mapMarker->custom_icon, ['purify($mapMarker->custom_icon); } return null; } } ================================================ FILE: app/Observers/MapObserver.php ================================================ grid = (int) $map->grid; } } ================================================ FILE: app/Observers/OrganisationMemberObserver.php ================================================ settings; if (request()->has('settings[collapse]')) { if ((bool) request()->get('settings[collapse]')) { $settings['collapse'] = true; } else { unset($settings['collapse']); } } $post->settings = $settings; } public function created(Post $post) { PostCreated::dispatch($post, auth()->user()); } public function updated(Post $post) { // Don't log updates if just did one (typically when creating, restoring or bulk editing) if ($post->isDirty('deleted_at')) { return; } PostUpdated::dispatch($post, auth()->user()); } public function saved(Post $post) { if (request()->filled('position')) { $this->reorder($post); } // When adding or changing a post to an entity, we want to update the // last updated date to reflect changes in the dashboard. $post->entity->touchSilently(); } public function deleted(Post $post) { PostDeleted::dispatch($post, auth()->user()); // When deleting a post, we want to update the entity's last update // for the dashboard. Careful of this when deleting an entity, we could be // entering a non-ending loop. if ($post->entity) { $post->entity->touchSilently(); } } public function restored(Post $post) { PostRestored::dispatch($post, auth()->user()); } } ================================================ FILE: app/Observers/PresetObserver.php ================================================ config; foreach ($config as $key => $value) { if ($value === null) { unset($config[$key]); } } $preset->config = $config; } } ================================================ FILE: app/Observers/PurifiableObserver.php ================================================ getPurifiableFields(); foreach ($fields as $field) { $model->{$field} = $this->purify( $model->{$field} ); } } } ================================================ FILE: app/Observers/PurifiableTrait.php ================================================ service = $service; } public function saved(Model $model) { // @phpstan-ignore-next-line $this->service->request(request())->save($model); } } ================================================ FILE: app/Observers/ReminderObserver.php ================================================ is_recurring = ! empty($reminder->recurring_periodicity); if (! $reminder->is_recurring) { $reminder->recurring_until = null; } } public function updating(Reminder $reminder) { // When updating and elapsed isn't dirty (calculated on the overview), reset it if ($reminder->isDirty(['year', 'month', 'day', 'calendar_id'])) { $reminder->elapsed = null; } } public function updated(Reminder $reminder) { // Go touch linked entity and its child $reminder->remindable->touchSilently(); } public function deleted(Reminder $reminder) { // Go touch linked entity and its child $reminder->remindable->touchSilently(); } } ================================================ FILE: app/Observers/ReorderTrait.php ================================================ map->groups()->orderBy('position')->get() as $group) { $group->position = $position; $group->updateQuietly(); $position = $position + 1; } } elseif ($model instanceof MapLayer) { foreach ($model->map->layers()->orderBy('position')->get() as $layer) { $layer->position = $position; $layer->updateQuietly(); $position = $position + 1; } } elseif ($model instanceof TimelineElement) { foreach ($model->era->elements()->orderBy('position')->orderBy('updated_at', 'DESC')->get() as $element) { $element->position = $position; $element->updateQuietly(); $position = $position + 1; } } elseif ($model instanceof Post) { $this->reorderPosts($model); } } protected function reorderPosts(Post $model) { // Placing it first, this makes it a bit complicated $position = $model->position; // If it's placed after the entry (positive position) if ($model->position > 0) { /** @var Post[] $posts */ $posts = $model->entity->posts() ->where('position', '>', 0) ->whereNot('id', $model->id) ->orderBy('position') ->get(); foreach ($posts as $post) { // Ignore things that come "before". Could be moved into the query if ($post->position < $model->position) { continue; } $position++; $post->position = $position; $post->updateQuietly(); } } else { /** @var Post[] $posts */ $posts = $model->entity->posts() ->where('position', '<', 0) ->whereNot('id', $model->id) ->orderByDesc('position') ->get(); foreach ($posts as $post) { // Ignore things that come "after". Could be moved into the query if ($post->position > $model->position) { continue; } $position--; $post->position = $position; $post->updateQuietly(); } } } } ================================================ FILE: app/Observers/SanitizedObserver.php ================================================ getAttributes(); // @phpstan-ignore-next-line foreach ($model->getSanitizable() as $field) { if (Str::contains($field, '.')) { $segments = explode('.', $field); if (! isset($attributes[$segments[0]])) { continue; } $array = $model->{$segments[0]}; if (! isset($array[$segments[1]])) { continue; } $array[$segments[1]] = $this->purify($model->{$segments[0]}[$segments[1]]); $model->{$segments[0]} = $array; } else { if (! isset($attributes[$field])) { continue; } $model->$field = $this->purify($model->$field); } } } protected function purify(?string $text): ?string { return mb_trim(strip_tags($text ?? '')); } } ================================================ FILE: app/Observers/SlugObserver.php ================================================ slug = Str::slug($model->name, ''); } } ================================================ FILE: app/Observers/SuggestionObserver.php ================================================ clearSuggestions($model); } public function deleted(Model $model) { $this->clearSuggestions($model); } protected function clearSuggestions(Model $model): void { // Clear the cache suggestion for the entity type if ($model instanceof Entity) { EntityCache::clearSuggestion($model->entityType); } // @phpstan-ignore-next-line foreach ($model->getSuggestions() as $class => $call) { $cache = app($class); $cache::{$call}(); } } } ================================================ FILE: app/Observers/TaggableObserver.php ================================================ saveTags($model); } /** * @return void */ protected function saveTags(Model $model) { /** @var Bookmark $model */ if (! request()->has('save_tags')) { return; } // Only save tags if we are in a form. $ids = (array) request()->post('tags', []); // Only use tags the user can actually view. This way admins can // have tags on entities that the user doesn't know about. $existing = []; foreach ($model->tags as $tag) { $existing[$tag->id] = $tag->name; } $new = []; foreach ($ids as $id) { if (! empty($existing[$id])) { unset($existing[$id]); } else { /** @var Tag $tag */ $tag = Tag::findOrFail($id); $new[] = $tag->id; } } $model->tags()->attach($new); // Detach the remaining if (! empty($existing)) { $model->tags()->detach(array_keys($existing)); } } } ================================================ FILE: app/Observers/TimelineElementObserver.php ================================================ position) || $timelineElement->position < 1) { $timelineElement->position = 1; /** @var ?TimelineElement $last */ $last = $timelineElement->era->elements()->orderByDesc('position')->first(); if ($last) { $timelineElement->position = $last->position + 1; } } if (empty($timelineElement->colour)) { $timelineElement->colour = ''; } } public function saved(TimelineElement $timelineElement) { $this->reorder($timelineElement); } } ================================================ FILE: app/Observers/TimelineEraObserver.php ================================================ is_collapsed = (bool) $timelineEra->is_collapsed; } public function creating(TimelineEra $timelineEra) { // Give it the last position $lastGroup = $timelineEra->timeline->eras()->max('position'); if ($lastGroup) { $timelineEra->position = (int) $lastGroup + 1; } else { $timelineEra->position = 1; } } } ================================================ FILE: app/Observers/UserLogObserver.php ================================================ ip = request()->ip(); // In prod, requests come from our load balancers, so the real user ip is provided by Cloudflare. $ip = request()->server('HTTP_CF_CONNECTING_IP'); if (! empty($ip)) { $userLog->ip = $ip; // While we're at it, track the user's country. All of this data is purged after 30 days. $userLog->country = mb_substr(request()->server('HTTP_CF_IPCOUNTRY'), 0, 6); } } } ================================================ FILE: app/Observers/UserObserver.php ================================================ profile['bio'])) { $profile = $user->profile; try { $profile['bio'] = mb_substr(strip_tags($profile['bio']), 0, 301); $user->profile = $profile; } catch (Exception $e) { // An invalid profile, like emojis in text $profile['bio'] = ''; $user->profile = $profile; } } // Purify Billing info if (! empty($user->profile['billing'])) { $profile = $user->profile; try { $profile['billing'] = mb_substr(strip_tags($profile['billing']), 0, 1024); $user->profile = $profile; } catch (Exception $e) { // invalid billing info, like emojis in text $profile['billing'] = ''; $user->profile = $profile; } } } public function updated(User $user) { // Tell mailchimp about the user's new email if (! $user->wasRecentlyCreated && $user->isDirty('email') && $user->hasNewsletter()) { UpdateEmail::dispatch($user); EmailChanged::dispatch($user, $user->getOriginal('email')); } if ($user->isDirty('name')) { UserCache::user($user)->clearName(); } // Todo: move to the controller if ($user->isDirty('email')) { $user->log(UserAction::emailUpdate); } elseif ($user->isDirty('provider')) { $user->log(UserAction::socialSwitch); } elseif ($user->isDirty('password')) { $user->log(UserAction::passwordUpdate); } } public function creating(User $user) { $user->locale = LaravelLocalization::getCurrentLocale(); $settings = []; if (session()->has('tracking')) { $settings['tracking'] = session()->get('tracking'); session()->remove('tracking'); } if (session()->has('invite_token')) { $settings['invited'] = true; } if (count($settings) > 0) { $user->settings = $settings; } } public function created(User $user) { if (! app()->environment('testing')) { WelcomeEmailJob::dispatch($user, app()->getLocale()); } session()->put('user_registered', true); if (request()->filled('newsletter')) { $user ->updateSettings(['mail_release' => 1]) ->save(); MailSettingsChangeJob::dispatch($user, true); } } /** * When a user is deleted, we need to clean up their avatar (only on production to avoid Jay doing silly things), * their newsletter status, cache, and later we also need to delete their stripe data after 3 months. */ public function deleted(User $user) { // Log::info('Deleted user', ['user' => $user->id]); UserCache::user($user) ->clearName() ->clear(); // If the user was subscribed to the newsletter, unsubscribe them if (app()->isProduction() && ! empty($user->hasNewsletter())) { UnsubscribeUser::dispatch($user->email); } } } ================================================ FILE: app/Observers/VisibilityObserver.php ================================================ visibility_id)) { // @phpstan-ignore-next-line $model->visibility_id = Visibility::All; } } } ================================================ FILE: app/Observers/WebhookObserver.php ================================================ user()); } public function updated(Webhook $webhook) { WebhookUpdated::dispatch($webhook, auth()->user()); } public function deleted(Webhook $webhook) { WebhookDeleted::dispatch($webhook, auth()->user()); } } ================================================ FILE: app/Observers/WhiteboardShapeObserver.php ================================================ scale_x)) { $shape->width *= $shape->scale_x; unset($shape->scale_x); } if (! empty($shape->scale_y)) { $shape->height *= $shape->scale_y; unset($shape->scale_y); } if ($shape->isDirty('x')) { $shape->x = (int) round($shape->x * self::SCALE); } if ($shape->isDirty('y')) { $shape->y = (int) round($shape->y * self::SCALE); } if ($shape->isDirty('width')) { $shape->width = (int) round($shape->width * self::SCALE); } if ($shape->isDirty('height')) { $shape->height = (int) round($shape->height * self::SCALE); } if ($shape->isDirty('rotation')) { $shape->rotation = (int) round($shape->rotation * 1_000_000); } } } ================================================ FILE: app/Policies/AbilityPolicy.php ================================================ isAdmin() || $this->checkPermission(Permission::Bookmarks->value, $user); } public function view(User $user, Bookmark $bookmark): bool { return $user->isAdmin() || $this->checkPermission(Permission::Bookmarks->value, $user); } public function create(User $user): bool { return $user->isAdmin() || EntityPermission::user($user)->hasPermission(0, Permission::Bookmarks->value); } public function update(User $user, Bookmark $bookmark): bool { return $this->view($user, $bookmark); } public function delete(User $user, Bookmark $bookmark): bool { return $this->view($user, $bookmark); } protected function checkPermission(int $action, User $user): bool { return EntityPermission::user($user)->hasPermission(0, $action); } } ================================================ FILE: app/Policies/CalendarPolicy.php ================================================ user_id === $user->id; } } ================================================ FILE: app/Policies/CampaignPluginPolicy.php ================================================ isAdmin($user); } } ================================================ FILE: app/Policies/CampaignPolicy.php ================================================ access($user, $campaign); } public function member(User $user, Campaign $campaign): bool { return CampaignCache::campaign($campaign) ->members() ->where('id', $user->id)->count() == 1; } /** * Determine whether the user can access the campaign */ public function access(User $user, Campaign $campaign): bool { return true; } public function admin(User $user, Campaign $campaign): bool { return $user->isAdmin($campaign); } /** * Can't create a campaign while impersonating another user. Should be handled in the controller? */ public function create(User $user): bool { return ! Identity::isImpersonating(); } /** * Determine whether the user can update the campaign. */ public function update(User $user, Campaign $campaign): bool { return $this->member($user, $campaign) && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_MANAGE, $user, $campaign) ); } /** * Determine whether the user can manage the roles of the campaign. */ public function roles(User $user, Campaign $campaign): bool { return $this->member($user, $campaign) && ( $user->isAdmin() ); } /** * Determine whether the user can manage the webhooks of the campaign. */ public function webhooks(User $user, Campaign $campaign): bool { return $this->recover($user, $campaign); } /** * Determine whether the user can manage the webhooks of the campaign. */ public function logs(User $user, Campaign $campaign): bool { return $this->recover($user, $campaign); } /** * Determine whether the user can delete the campaign. */ public function delete(User $user, Campaign $campaign): bool { return $this->member($user, $campaign) && $user->isAdmin() && CampaignCache::campaign($campaign)->members()->count() == 1; } public function invite(User $user, Campaign $campaign): bool { return $this->member($user, $campaign) && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_MEMBERS, $user, $campaign) ); } public function setting(User $user, Campaign $campaign): bool { return $user->isAdmin(); } public function recover(User $user, Campaign $campaign): bool { return $user->isAdmin($campaign); } public function history(User $user, Campaign $campaign): bool { return $this->recover($user, $campaign); } public function dashboard(User $user, Campaign $campaign): bool { return $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_DASHBOARD, $user, $campaign); } public function stats(User $user, Campaign $campaign): bool { return $this->member($user, $campaign); } public function search(User $user, Campaign $campaign): bool { return $user->isAdmin(); } public function import(User $user, Campaign $campaign): bool { return $user->isWyvern() || $user->isElemental() || $user->hasRole('admin'); } /** * Determine whether the user can leave the campaign */ public function leave(User $user, Campaign $campaign): bool { if (Identity::isImpersonating()) { return false; } if (! $this->member($user, $campaign)) { return false; } // If we are not the owner if (! $user->isAdmin()) { return true; } // If there are other admins return $campaign->roles() ->admin() ->first() ->users ->count() > 1; } /** * Determine if a user can follow a campaign */ public function follow(User $user, Campaign $campaign): bool { if (! $campaign->isPublic()) { return false; } return ! $this->member($user, $campaign); } /** * Determine if the campaign setup is complete enough to be opened for applications */ public function canOpen(User $user, Campaign $campaign): bool { return $campaign->filters->count() === count(CampaignFilterType::cases()) && $campaign->systems->isNotEmpty() && $campaign->genres->isNotEmpty() && $campaign->playstyles->isNotEmpty() && ! empty($campaign->locale); } /** * Determine if a user can apply to a campaign */ public function apply(User $user, Campaign $campaign): bool { if ($campaign->isPrivate() || ! $campaign->is_open) { return false; } return ! $this->member($user, $campaign); } /** * Permission to view the members of a campaign */ public function members(User $user, Campaign $campaign): bool { return ($user->isAdmin($campaign) || $this->checkPermission(CampaignPermission::ACTION_MEMBERS, $user, $campaign)) || ! ($campaign->boosted() && $campaign->hide_members); } /** * Permission to view the campaign applications */ public function applications(?User $user): bool { return $user && $user->isAdmin(); } public function gallery(?User $user, Campaign $campaign): bool { return $user && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_GALLERY, $user, $campaign) || $this->checkPermission(CampaignPermission::ACTION_GALLERY_BROWSE, $user, $campaign) ); } public function relations(?User $user): bool { return $user && $user->isAdmin(); } public function mapPresets(?User $user): bool { return $user && $user->isAdmin(); } /** * Check if a user can unboost a campaign */ public function unboost(?User $user, Campaign $campaign): bool { $boost = $campaign->boosts->first(); return $user && $boost && $boost->user_id === $user->id; } public function galleryManage(?User $user, Campaign $campaign): bool { return $user && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_GALLERY, $user, $campaign) ); } public function galleryBrowse(?User $user, Campaign $campaign): bool { return $user && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_GALLERY, $user, $campaign) || $this->checkPermission(CampaignPermission::ACTION_GALLERY_BROWSE, $user, $campaign) ); } public function galleryUpload(?User $user, Campaign $campaign): bool { return $user && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_GALLERY, $user, $campaign) || $this->checkPermission(CampaignPermission::ACTION_GALLERY_UPLOAD, $user, $campaign) ); } protected function checkPermission(int $action, User $user, ?Campaign $campaign = null): bool { return EntityPermission::campaign($campaign)->user($user)->hasPermission(0, $action, null); } /** * Determine if the user can use templates on the campaign */ public function useTemplates(?User $user, Campaign $campaign): bool { return true; } /** * Determine if the user can set templates on the campaign */ public function setTemplates(?User $user, Campaign $campaign): bool { return $this->isAdmin($user) || $this->checkPermission(CampaignPermission::ACTION_TEMPLATES, $user, $campaign); } /** * Determine if the user can set post templates on the campaign */ public function setPostTemplates(?User $user, Campaign $campaign): bool { return $this->isAdmin($user) || $this->checkPermission(CampaignPermission::ACTION_POST_TEMPLATES, $user, $campaign); } public function export(User $user, Campaign $campaign): bool { if (! app()->isProduction()) { return true; } if ($user->hasRole('admin')) { return true; } return $campaign->campaignExports() ->whereDate('created_at', today()) ->where('status', '!=', CampaignExportStatus::failed) ->doesntExist(); } public function galleryWidget(?User $user, Campaign $campaign): bool { return $campaign->premium(); } public function whiteboards(User $user, Campaign $campaign): bool { if (app()->hasDebugModeEnabled() || app()->environment('qa')) { return true; } return $campaign->premium() && ($campaign->isWyvern() || $campaign->isElemental()); } } ================================================ FILE: app/Policies/CampaignRolePolicy.php ================================================ campaign_id === $campaign->id && $user->isAdmin(); } public function create(User $user) { return $user->isAdmin(); } public function update(User $user, CampaignRole $campaignRole) { return $user->isAdmin(); } public function delete(User $user, CampaignRole $campaignRole) { return ! $campaignRole->isAdmin() && ! $campaignRole->isPublic() && $user->isAdmin(); } public function user(User $user, CampaignRole $campaignRole) { return $user->isAdmin(); } /** * Only allow removing users from the admin role is there is more than one user in it */ public function removeUser(User $user, CampaignRole $campaignRole) { if (! $this->user($user, $campaignRole)) { return false; } // Non-admin role? Yep the user can modify the member return (bool) (! $campaignRole->isAdmin()); } public function permission(User $user, CampaignRole $campaignRole) { return ! $campaignRole->isAdmin() && $user->isAdmin(); } } ================================================ FILE: app/Policies/CampaignRoleUserPolicy.php ================================================ campaignRole->campaign_id === $campaign->id && $user->isAdmin(); } public function create(User $user, Campaign $campaign): bool { return $this->isAdmin($user); } public function update(User $user, CampaignRoleUser $campaignRoleUser): bool { return $this->isAdmin($user); } public function delete(User $user): bool { return $user->isAdmin(); } } ================================================ FILE: app/Policies/CampaignStylePolicy.php ================================================ isAdmin() && ! $style->is_enabled; } public function disable(User $user, CampaignStyle $style): bool { return $user->isAdmin() && $style->is_enabled; } } ================================================ FILE: app/Policies/CampaignUserPolicy.php ================================================ campaign_id === $campaign->id && $user->isAdmin(); } public function update(User $user, CampaignUser $campaignUser): bool { // Don't allow updating if we are currently impersonating if (Identity::isImpersonating()) { return false; } // If user isn't in admin if (! $user->isAdmin()) { return false; } if ($user->id === $campaignUser->user_id) { return false; } // User isn't an admin, easy peasy if (! $campaignUser->user->isAdmin()) { return true; } // Check if the user was added to the admin role recently $adminRole = UserCache::adminRole(); $role = $campaignUser->user->campaignRoleUser->where('campaign_role_id', $adminRole['id'])->first(); return $role->created_at->diffInMinutes() <= 15; } public function delete(User $user, CampaignUser $campaignUser): bool { return $this->update($user, $campaignUser); } public function switch(User $user, CampaignUser $campaignUser): bool { if (Identity::isImpersonating()) { return false; } return $user->isAdmin() && ! $campaignUser->user->isAdmin() && ! $campaignUser->user->isBanned(); } } ================================================ FILE: app/Policies/CharacterPolicy.php ================================================ is_personality_visible || $user->isAdmin(); } } ================================================ FILE: app/Policies/ClientPolicy.php ================================================ user_id === $user->id; } } ================================================ FILE: app/Policies/CommunityEventEntryPolicy.php ================================================ id == $communityEventEntry->created_by) && $communityEventEntry->event->isOngoing(); } } ================================================ FILE: app/Policies/CommunityVotePolicy.php ================================================ status(); if ($status == CommunityVote::STATUS_PUBLISHED) { return true; } // If it's not published and we aren't logged in, nope nope nope if (empty($user)) { return false; } if ($status == CommunityVote::STATUS_VOTING) { return $user->isGoblin() || $user->hasRole('admin'); } // Scheduled and Draft are limited to admins return $user->hasRole('admin'); } /** * Determine if a user can participate in a vote */ public function vote(User $user, CommunityVote $communityVote): bool { $status = $communityVote->status(); if ($status == CommunityVote::STATUS_PUBLISHED) { return true; } elseif ($status == CommunityVote::STATUS_VOTING) { return $user->isGoblin(); } // Not in a voting phase return false; } } ================================================ FILE: app/Policies/ConversationMessagePolicy.php ================================================ created_at->diffInHours(Carbon::now()); return $message->created_by === $user->id && ($elapsedHours < 1); } public function edit(?User $user, ConversationMessage $message): bool { return $this->delete($user, $message); } } ================================================ FILE: app/Policies/ConversationPolicy.php ================================================ can(Permission::View); } public function update(User $user, Entity $entity): bool { return EntityPermission::entity($entity)->user($user)->can(Permission::Update); } public function attributes(?User $user, Entity $entity): bool { if ($entity->exists === false) { return true; } return ! $entity->is_attributes_private || $user && $user->isAdmin(); } public function viewAttributes(?User $user, Entity $entity, Campaign $campaign): bool { if (! $campaign->enabled('entity_attributes')) { return false; } if (! $entity->is_attributes_private) { return true; } return $user && $user->isAdmin(); } public function privacy(User $user): bool { return $user->isAdmin(); } public function history(User $user, Entity $entity, Campaign $campaign): bool { return $user->isAdmin() || ! ($campaign->boosted() && $campaign->hide_history); } public function move(User $user, Entity $entity): bool { return $this->update($user, $entity); } public function inventory(User $user, Entity $entity): bool { return $this->update($user, $entity); } public function relation(User $user, Entity $entity): bool { return $this->update($user, $entity); } public function permissions(User $user, Entity $entity): bool { return $this->update($user, $entity) && EntityPermission::entity($entity)->user($user)->can(Permission::Permissions); } public function post(User $user, Entity $entity, ?string $action = null, ?Post $post = null): bool { return $this->update($user, $entity) || EntityPermission::entity($entity)->user($user)->can(Permission::Posts) || ($action == 'edit' ? $this->checkPostPermission($user, $post) : false); } public function delete(User $user, Entity $entity): bool { return EntityPermission::entity($entity)->user($user)->can(Permission::Delete); } public function reminders(User $user, Entity $entity): bool { return $this->update($user, $entity); } protected function checkPostPermission(User $user, Post $post): bool { $roleIds = UserCache::roles()->pluck('id')->toArray(); $perms = $post->permissions->where('permission', 1); return $perms->where('user_id', $user->id)->count() == 1 || $perms->whereIn('role_id', $roleIds)->count() == 1; } public function addFile(User $user, Entity $entity, Campaign $campaign): bool { return $campaign->isWyvern() || $campaign->isElemental() || $entity->assets()->file()->count() < Limit::campaign($campaign)->entityFiles(); } } ================================================ FILE: app/Policies/EntityTypePolicy.php ================================================ guest()) { return false; } if (! $entityType->isEnabled()) { return false; } if ($entityType->isCustom() && ! $campaign->premium()) { return false; } if ($entityType->code === 'bookmark') { return auth()->user()->can('create', new Bookmark); } return EntityPermission::campaign($campaign)->user($user)->entityType($entityType)->can(Permission::Create); } public function update(User $user, EntityType $entityType, Campaign $campaign) { return $entityType->campaign_id === $campaign->id; } public function delete(User $user, EntityType $entityType, Campaign $campaign) { return $entityType->campaign_id === $campaign->id && $entityType->isCustom(); } public function deleteEntities(User $user, EntityType $entityType, Campaign $campaign) { return EntityPermission::campaign($campaign)->user($user)->entityType($entityType)->can(Permission::Delete); } public function permissions(User $user, EntityType $entityType): bool { return EntityPermission::entityType($entityType)->user($user)->can(Permission::Permissions); } } ================================================ FILE: app/Policies/EventPolicy.php ================================================ checkPermission(CampaignPermission::ACTION_ADD, $user); } } ================================================ FILE: app/Policies/FamilyPolicy.php ================================================ hasRole('admin')) { return true; } return Feature::where('created_by', auth()->user()->id) ->whereDate('created_at', Carbon::today()) ->count() < 10; } public function vote(User $user): bool { return $user->isSubscriber(); } } ================================================ FILE: app/Policies/ImagePolicy.php ================================================ isAdmin() || $this->checkPermission(CampaignPermission::ACTION_GALLERY, $user, $campaign) || $this->checkPermission(CampaignPermission::ACTION_GALLERY_BROWSE, $user, $campaign) ); } public function create(?User $user, Campaign $campaign): bool { return $user && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_GALLERY, $user, $campaign) || $this->checkPermission(CampaignPermission::ACTION_GALLERY_UPLOAD, $user, $campaign) ); } public function view(?User $user, Image $image, Campaign $campaign): bool { return $user && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_GALLERY, $user, $campaign) || $this->checkPermission(CampaignPermission::ACTION_GALLERY_BROWSE, $user, $campaign) || ($this->checkPermission(CampaignPermission::ACTION_GALLERY_UPLOAD, $user, $campaign) && $image->created_by === $user->id) ); } public function edit(?User $user, Image $image, Campaign $campaign): bool { return $user && ( $user->isAdmin() || $this->checkPermission(CampaignPermission::ACTION_GALLERY, $user, $campaign) || ($this->checkPermission(CampaignPermission::ACTION_GALLERY_UPLOAD, $user, $campaign) && $image->created_by === $user->id) ); } public function visibility(User $user, Image $image): bool { if ( (in_array($image->visibility_id, [Visibility::Admin, Visibility::AdminSelf]) && ! auth()->user()->isAdmin()) && (in_array($image->visibility_id, [Visibility::Self, Visibility::AdminSelf]) && ! ($image->created_by == auth()->user()->id)) ) { return false; } if ($image->visibility_id === Visibility::AdminSelf && auth()->user()->isAdmin() && $image->created_by !== auth()->user()->id) { return false; } return true; } public function delete(?User $user, Image $image, Campaign $campaign): bool { return $this->edit($user, $image, $campaign); } /** * @return bool */ protected function checkPermission(int $action, User $user, ?Campaign $campaign = null) { return EntityPermission::campaign($campaign)->user($user)->hasPermission(0, $action, null); } } ================================================ FILE: app/Policies/ItemPolicy.php ================================================ can('update', $mapGroup->map); } public function delete(?User $user, MapGroup $mapGroup) { return $user && $user->can('update', $mapGroup->map); } } ================================================ FILE: app/Policies/MapLayerPolicy.php ================================================ can('update', $mapLayer->map); } public function delete(?User $user, MapLayer $mapLayer) { return $user && $user->can('update', $mapLayer->map); } } ================================================ FILE: app/Policies/MapMarkerPolicy.php ================================================ can('update', $mapMarker->map->entity); } public function delete(?User $user, MapMarker $mapMarker) { return $user && $user->can('update', $mapMarker->map->entity); } } ================================================ FILE: app/Policies/MapPolicy.php ================================================ boosted()) { $max = config('limits.campaigns.maps.groups.premium'); } return $map->groups->count() < $max; } public function addLayer(User $user, Map $map, Campaign $campaign): bool { $max = config('limits.campaigns.maps.layers.standard'); if ($campaign->boosted()) { $max = config('limits.campaigns.maps.layers.premium'); } return $map->layers->count() < $max; } } ================================================ FILE: app/Policies/MiscPolicy.php ================================================ user($user) // @phpstan-ignore-next-line ->hasPermission($this->entityTypeID(), $action, $entity); } } ================================================ FILE: app/Policies/NotePolicy.php ================================================ guest()) { return false; } return auth()->user()->can('update', $entity->organisation->entity) || auth()->user()->can('update', $entity->character->entity); } /** * Determine whether the user can update the entity. */ public function delete(User $user, OrganisationMember $entity) { if (auth()->guest()) { return false; } return auth()->user()->can('delete', $entity->organisation->entity) || auth()->user()->can('delete', $entity->character->entity); } } ================================================ FILE: app/Policies/OrganisationPolicy.php ================================================ can('update', $organisation->entity); } } ================================================ FILE: app/Policies/PluginPolicy.php ================================================ isAdmin(); } public function update(User $user, Plugin $plugin): bool { return $user->isAdmin() && $plugin->hasUpdate($plugin->created_by === $user->id); } public function changelog(User $user, Plugin $plugin): bool { return $user->isAdmin() && ! $plugin->hasUpdate($plugin->created_by === $user->id); } public function enable(User $user, Plugin $plugin): bool { // @phpstan-ignore-next-line return $user->isAdmin() && $plugin->isTheme() && ! $plugin->pivot->is_active; } public function disable(User $user, Plugin $plugin): bool { // @phpstan-ignore-next-line return $user->isAdmin() && $plugin->isTheme() && $plugin->pivot->is_active; } public function import(User $user, Plugin $plugin): bool { return $user->isAdmin() && $plugin->isContentPack(); } } ================================================ FILE: app/Policies/PostPolicy.php ================================================ visibility_id, [Visibility::Admin, Visibility::AdminSelf]) && ! $user->isAdmin() && $post->created_by != $user->id ) { return false; } elseif ($post->visibility_id === Visibility::AdminSelf && $post->created_by !== $user->id) { // If the post has its visibility set to admin-self, but they didn't create it, they can't edit its visibility return false; } // In all other cases, a user who can edit the post can edit the visibility, probably. return true; } } ================================================ FILE: app/Policies/QuestPolicy.php ================================================ can('relations', $campaign); } /** * Determine whether the user can update the relation. * * @return bool */ public function update(User $user, Relation $relation) { if (empty($relation->owner) || $relation->owner->isMissingChild()) { return false; } return $user->can('relation', $relation->owner); } /** * Determine whether the user can delete the relation. * * @return bool */ public function delete(User $user, ?Relation $relation) { // If the relation is empty, this call is coming from the bulk delete check if (empty($relation) || empty($relation->id)) { $campaign = CampaignLocalization::getCampaign(); return $user->can('relations', $campaign); } if (empty($relation->owner) || $relation->owner->isMissingChild()) { return false; } return $user->can('relation', $relation->owner); } } ================================================ FILE: app/Policies/ReminderPolicy.php ================================================ remindable_type === Entity::class && $reminder->remindable_id === $entity->id; } public function update(?User $user, Reminder $reminder) { return $user && $user->can('update', $reminder->calendar->entity); } public function delete(?User $user, Reminder $reminder) { return $user && $user->can('update', $reminder->calendar->entity); } } ================================================ FILE: app/Policies/TagPolicy.php ================================================ can('update', $timelineEra->timeline); } public function delete(?User $user, TimelineEra $timelineEra) { return $user && $user->can('update', $timelineEra->timeline); } } ================================================ FILE: app/Policies/TimelinePolicy.php ================================================ user_id === $user->id; } } ================================================ FILE: app/Policies/UserPolicy.php ================================================ isAdmin(); } public function boost(?User $user) { if (empty($user) || request()->get('_boost') === '0') { return false; } return $user->isGoblin() || ! empty($user->booster_count); } public function freeTrial(User $user) { return session()->get('kanka.freeTrial') && ! $user->isSubscriber(); } public function renewPaypalSubscription(User $user): bool { if (! $user->hasPayPal()) { return false; } $subscription = $user->subscription('kanka'); if (! $subscription) { return false; } return $subscription->ends_at->lte(now()->addDays(15)); } } ================================================ FILE: app/Policies/WebhookPolicy.php ================================================ isAdmin() && ! $webhook->isActive(); } public function disable(User $user, Webhook $webhook): bool { return $user->isAdmin() && $webhook->isActive(); } public function delete(User $user, Webhook $webhook): bool { return $user->isAdmin(); } } ================================================ FILE: app/Policies/WhiteboardPolicy.php ================================================ extend('passport', fn ($app, $name, array $config) => tap( new PassportTokenGuard( app(ResourceServer::class), new PassportUserProvider(Auth::createUserProvider($config['provider']), $config['provider']), app(ClientRepository::class), app(Encrypter::class), app('request'), ), fn ($guard) => app()->refresh('request', $guard, 'setRequest') )); }); $this->registerDevelopWarning(); $this->registerWebObservers(); Cashier::useCustomerModel(User::class); if (config('app.lazy')) { Model::preventLazyLoading(); } Validator::resolver(function ($translator, $data, $rules, $messages) { return new HashValidator($translator, $data, $rules, $messages); }); } /** * Register any application services. * * @return void */ public function register() {} protected function registerDevelopWarning() { if (! app()->runningInConsole()) { return; } if (config('app.ignore_develop_warning')) { return; } $path = base_path(); $command = 'git symbolic-ref -q --short HEAD || git describe --tags --exact-match'; if (class_exists('\Symfony\Component\Process\Process')) { try { // @phpstan-ignore-next-line if (method_exists(Process::class, 'fromShellCommandline')) { $process = Process::fromShellCommandline($command, $path); } else { $process = new Process([$command], $path); } $process->mustRun(); $output = $process->getOutput(); } catch (Exception $e) { // Silence errors } } if (! empty($output) && Str::startsWith($output, 'develop')) { throw new InvalidOptionException( "CONFIGURATION WARNING\n" . "You are currently running Kanka on the unstable @develop branch. This is unstable and WILL RESULT IN DATA LOSS.\n" . "If this isn't a mistake, add `APP_IGNORE_DEVELOP_WARNING=true` to your .env file." ); } } /** * Register web observers (ie not running in console) * Kanka uses a lot of observers because they offer some magic, but * they also make a lot of stuff break. */ protected function registerWebObservers() { // When in console (queue, commands), we don't want observers to trigger. // We probably do, but we're so far down the rabbit hole that we no // longer want them. if (app()->runningInConsole() && ! app()->runningUnitTests()) { return; } // In production, we want URLs to be HTTPS for pagination and redirects if ($this->app->isProduction() || $this->app->environment('qa')) { \URL::forceScheme('https'); } // Model observers. Lots of magic. AdminInvite::observe(AdminInviteObserver::class); Calendar::observe(CalendarObserver::class); Campaign::observe(CampaignObserver::class); CampaignDescription::observe(CampaignDescriptionObserver::class); CampaignUser::observe(CampaignUserObserver::class); CampaignRole::observe('App\Observers\CampaignRoleObserver'); CampaignRoleUser::observe('App\Observers\CampaignRoleUserObserver'); CampaignInvite::observe('App\Observers\CampaignInviteObserver'); CampaignDashboard::observe('App\Observers\CampaignDashboardObserver'); CampaignDashboardRole::observe('App\Observers\CampaignDashboardRoleObserver'); CampaignDashboardWidget::observe('App\Observers\CampaignDashboardWidgetObserver'); CampaignFollower::observe('App\Observers\CampaignFollowerObserver'); CampaignPlugin::observe('App\Observers\CampaignPluginObserver'); CampaignSetting::observe('App\Observers\CampaignSettingObserver'); Application::observe('App\Observers\ApplicationObserver'); CampaignStyle::observe('App\Observers\CampaignStyleObserver'); Conversation::observe('App\Observers\ConversationObserver'); ConversationMessage::observe('App\Observers\ConversationMessageObserver'); DiceRoll::observe('App\Observers\DiceRollObserver'); DiceRollResult::observe('App\Observers\DiceRollResultObserver'); Entity::observe('App\Observers\EntityObserver'); EntityType::observe('App\Observers\EntityTypeObserver'); EntityAbility::observe('App\Observers\EntityAbilityObserver'); EntityAsset::observe('App\Observers\EntityAssetObserver'); Post::observe('App\Observers\PostObserver'); Reminder::observe('App\Observers\ReminderObserver'); FamilyTree::observe(FamilyTreeObserver::class); Image::observe('App\Observers\ImageObserver'); Inventory::observe('App\Observers\InventoryObserver'); Map::observe('App\Observers\MapObserver'); MapLayer::observe('App\Observers\MapLayerObserver'); MapGroup::observe('App\Observers\MapGroupObserver'); MapMarker::observe('App\Observers\MapMarkerObserver'); Bookmark::observe('App\Observers\BookmarkObserver'); OrganisationMember::observe(OrganisationMemberObserver::class); Preset::observe('App\Observers\PresetObserver'); TimelineEra::observe('App\Observers\TimelineEraObserver'); TimelineElement::observe('App\Observers\TimelineElementObserver'); User::observe(UserObserver::class); UserLog::observe('App\Observers\UserLogObserver'); Webhook::observe('App\Observers\WebhookObserver'); QuestElement::observe('App\Observers\QuestElementObserver'); if (request()->has('_debug_perm') && config('app.debug')) { // Add in boot function DB::listen(function ($query) { $sql = $query->sql; foreach ($query->bindings as $key => $binding) { $sql = preg_replace('/\?/', "'{$binding}'", $sql, 1); } dump($sql); }); } } } ================================================ FILE: app/Providers/AttributesServiceProvider.php ================================================ app->singleton(AttributeMentionService::class, function () { return new AttributeMentionService; }); $this->app->alias(AttributeMentionService::class, 'attributes'); } } ================================================ FILE: app/Providers/AuthServiceProvider.php ================================================ runningInConsole()) { // We don't have api grants so allow anyone with a token to do everything // Todo: this might not actually be needed? Passport::enableImplicitGrant(); } } /** * The policy mappings for the application. * Laravel auto-discoveres policies if the Model is named \App\Models\XYZ and the police is \App\Policies\XYZPolicy */ protected $policies = [ Token::class => TokenPolicy::class, Client::class => ClientPolicy::class, ]; } ================================================ FILE: app/Providers/AvatarServiceProvider.php ================================================ app->singleton(AvatarService::class, function () { $service = new AvatarService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } if (auth()->check()) { $service->user(auth()->user()); } return $service; }); $this->app->alias(AvatarService::class, 'avatar'); } } ================================================ FILE: app/Providers/BreadcrumbServiceProvider.php ================================================ app->singleton(BreadcrumbService::class, function () { return new BreadcrumbService; }); $this->app->alias(BreadcrumbService::class, 'breadcrumb'); } } ================================================ FILE: app/Providers/BroadcastServiceProvider.php ================================================ app->singleton(EntityCacheService::class, function () { $service = new EntityCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->singleton(CampaignCacheService::class, function () { $service = new CampaignCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->singleton(UserCacheService::class, function () { $service = new UserCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } if (auth()->check()) { $service->user(auth()->user()); } return $service; }); $this->app->singleton(SingleUserCacheService::class, function () { return new SingleUserCacheService; }); $this->app->singleton(ReleaseCacheService::class, function () { return new ReleaseCacheService; }); $this->app->singleton(CharacterCacheService::class, function () { $service = new CharacterCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->singleton(QuestCacheService::class, function () { $service = new QuestCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->singleton(MapMarkerCacheService::class, function () { $service = new MapMarkerCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->singleton(EntityAssetCacheService::class, function () { $service = new EntityAssetCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->singleton(BookmarkCacheService::class, function () { $service = new BookmarkCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->singleton(TimelineElementCacheService::class, function () { $service = new TimelineElementCacheService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->singleton(MarketplaceCacheService::class, function () { return new MarketplaceCacheService; }); $this->app->alias(EntityCacheService::class, 'entitycache'); $this->app->alias(CampaignCacheService::class, 'campaigncache'); $this->app->alias(UserCacheService::class, 'usercache'); $this->app->alias(SingleUserCacheService::class, 'singleusercache'); $this->app->alias(ReleaseCacheService::class, 'releasecache'); $this->app->alias(CharacterCacheService::class, 'charactercache'); $this->app->alias(QuestCacheService::class, 'questcache'); $this->app->alias(AdCacheService::class, 'adcache'); $this->app->alias(MapMarkerCacheService::class, 'mapmarkercache'); $this->app->alias(EntityAssetCacheService::class, 'entityassetcache'); $this->app->alias(BookmarkCacheService::class, 'bookmarkcache'); $this->app->alias(TimelineElementCacheService::class, 'timelineelementcache'); $this->app->alias(MarketplaceCacheService::class, 'marketplacecache'); } } ================================================ FILE: app/Providers/CampaignLocalizationServiceProvider.php ================================================ app->singleton(LocalisationService::class, function ($app) { $service = new LocalisationService; $service->request($app->make(Request::class)); return $service; }); $this->app->alias(LocalisationService::class, 'campaignlocalization'); } } ================================================ FILE: app/Providers/DashboardServiceProvider.php ================================================ app->singleton(DashboardService::class, function () { return new DashboardService; }); $this->app->alias(DashboardService::class, 'dashboard'); } } ================================================ FILE: app/Providers/DatagridRendererProvider.php ================================================ app->singleton(DatagridRenderer2::class, function () { $service = new DatagridRenderer2; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->alias(DatagridRenderer2::class, 'datagrid'); } } ================================================ FILE: app/Providers/DatalayerServiceProvider.php ================================================ app->singleton(DatalayerService::class, function ($app) { $service = new DatalayerService; $service->request($app->make(Request::class)); return $service; }); $this->app->alias(DatalayerService::class, 'datalayer'); } } ================================================ FILE: app/Providers/DomainServiceProvider.php ================================================ app->singleton(DomainService::class, function ($app) { $service = new DomainService; $service->request($app->make(Request::class)); return $service; }); $this->app->alias(DomainService::class, 'domain'); } } ================================================ FILE: app/Providers/EntitySetupServiceProvider.php ================================================ app->singleton(LoggerService::class, function () { $s = new LoggerService; if (auth()->check()) { $s->user(auth()->user()); } return $s; }); $this->app->alias(LoggerService::class, 'entitylogger'); } /** * Bootstrap services. * * @return void */ public function boot() {} } ================================================ FILE: app/Providers/EventServiceProvider.php ================================================ [ LoginViaRemember::class, ], AdminInviteCreated::class => [ SendAdminInviteNotification::class, ], FeatureCreated::class => [ SendFeatureNotification::class, ], SpotlightSubmitted::class => [ SendSpotlightNotification::class, ], RoleUserAdded::class => [ RunRoleUserJob::class, ClearUserCache::class, LogUserRoleChanged::class, ], RoleUserRemoved::class => [ RunRoleUserJob::class, LogUserRoleChanged::class, ClearUserCache::class, ClearCampaignCache::class, ], InviteCreated::class => [ LogInvite::class, ], InviteDeleted::class => [ LogInvite::class, ], PluginUpdated::class => [ LogPlugin::class, ClearThemeCache::class, ], PluginDeleted::class => [ LogPlugin::class, ClearThemeCache::class, ], PluginImported::class => [ LogPlugin::class, ], StyleCreated::class => [ LogStyle::class, ClearStylesCache::class, ClearCampaignCache::class, ], StyleUpdated::class => [ LogStyle::class, ClearStylesCache::class, ClearCampaignCache::class, ], StyleDeleted::class => [ LogStyle::class, ClearStylesCache::class, ClearCampaignCache::class, ], SettingsSaved::class => [ ClearCampaignCache::class, ], Accepted::class => [ LogApplication::class, ClearCampaignCache::class, ClearUserCache::class, ], Rejected::class => [ LogApplication::class, ClearCampaignCache::class, ], Saved::class => [ ClearCampaignUsersSaved::class, ], Updated::class => [ LogCampaign::class, ], Deleted::class => [ ClearCampaignUsersSaved::class, ], DashboardCreated::class => [ LogDashboard::class, ClearCampaignCache::class, ], DashboardUpdated::class => [ LogDashboard::class, ClearCampaignCache::class, ], DashboardDeleted::class => [ LogDashboard::class, ClearCampaignCache::class, ], WebhookCreated::class => [ LogWebhook::class, ], WebhookUpdated::class => [ LogWebhook::class, ], WebhookDeleted::class => [ LogWebhook::class, ], WebhookTested::class => [ LogWebhook::class, ], RoleCreated::class => [ LogRole::class, ClearCampaignCache::class, ], RoleUpdated::class => [ LogRole::class, ClearCampaignCache::class, ], RoleDeleted::class => [ LogRole::class, ClearCampaignCache::class, ], ExportCreated::class => [ LogExport::class, ], SidebarSaved::class => [ LogSidebar::class, ], SidebarReset::class => [ LogSidebar::class, ], ThumbnailCreated::class => [ LogThumbnail::class, ClearCampaignCache::class, ], ThumbnailDeleted::class => [ LogThumbnail::class, ClearCampaignCache::class, ], ThumbnailsDeleted::class => [ LogThumbnails::class, ClearCampaignCache::class, ], UserJoined::class => [ Notify::class, ClearUserCache::class, LogMember::class, ], UserLeft::class => [ Notify::class, ClearUserCache::class, LogMember::class, ], Switched::class => [ LogMember::class, ], EntityRestored::class => [ LogEntity::class, ], PostRestored::class => [ LogPost::class, ], PostCreated::class => [ LogPost::class, ], PostUpdated::class => [ LogPost::class, ], PostDeleted::class => [ LogPost::class, ], EntityTypeCreated::class => [ LogEntityType::class, ], EntityTypeUpdated::class => [ LogEntityType::class, ], EntityTypeToggled::class => [ LogEntityType::class, ClearCampaignCache::class, ], EntityTypeDeleted::class => [ LogEntityType::class, ], Boost::class => [ LogPremium::class, ], Premium::class => [ LogPremium::class, ], Upgrade::class => [ LogPremium::class, ], AutoRemove::class => [ LogPremium::class, ], SuperBoost::class => [ LogPremium::class, ], Disable::class => [ LogPremium::class, ], EmailChanged::class => [ SendEmailUpdate::class, ], ]; /** * The subscriber classes to register. * * @var array */ protected $subscribe = [ 'App\Listeners\UserEventSubscriber', ]; /** * Register any events for your application. * * @return void */ public function boot() { parent::boot(); } } ================================================ FILE: app/Providers/FormCopyServiceProvider.php ================================================ app->singleton(FormCopyService::class, function () { return new FormCopyService; }); // $this->app->alias(FormCopyService::class, 'formcopy'); } } ================================================ FILE: app/Providers/IdentityServiceProvider.php ================================================ app->singleton(IdentityManager::class, function ($app) { return new IdentityManager($app); }); $this->app->alias(IdentityManager::class, 'identity'); } } ================================================ FILE: app/Providers/ImgServiceProvider.php ================================================ app->singleton(ImgService::class, function () { return new ImgService; }); $this->app->singleton(ImagesService::class, function ($app) { $service = new ImagesService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } $service->request($app->make(Request::class)); return $service; }); $this->app->alias(ImgService::class, 'img'); $this->app->alias(ImagesService::class, 'images'); } } ================================================ FILE: app/Providers/ImporterServiceProvider.php ================================================ app->singleton(ImportIdMapper::class, function () { return new ImportIdMapper; }); $this->app->alias(ImportIdMapper::class, 'importidmapper'); } } ================================================ FILE: app/Providers/LimitServiceProvider.php ================================================ app->singleton(LimitService::class, function () { $service = new LimitService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } if (auth()->check()) { $service->user(auth()->user()); } return $service; }); $this->app->alias(LimitService::class, 'limit'); } } ================================================ FILE: app/Providers/Logs/ApiLogServiceProvider.php ================================================ app->singleton(ApiLogService::class, function () { $service = new ApiLogService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } if (auth()->check()) { $service->user(auth()->user()); } return $service; }); $this->app->alias(ApiLogService::class, 'api_log'); } } ================================================ FILE: app/Providers/MentionsServiceProvider.php ================================================ app->singleton(MentionsService::class, function () { $service = new MentionsService( app()->make(MarkupFixer::class), app()->make(NewService::class) ); if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->alias(MentionsService::class, 'mentions'); } } ================================================ FILE: app/Providers/ModuleServiceProvider.php ================================================ app->singleton(ModuleService::class, function () { $service = new ModuleService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->alias(ModuleService::class, 'module'); } } ================================================ FILE: app/Providers/PermissionsServiceProvider.php ================================================ registerMain() // ->registerUser() ->registerRole() ->registerEntity(); } protected function registerMain(): self { $this->app->singleton(PermissionService::class, function () { $service = new PermissionService; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->alias(PermissionService::class, 'permissions'); return $this; } protected function registerEntity(): self { $this->app->singleton(EntityPermission::class, function () { /** @var EntityPermission $service */ $service = new EntityPermission; if (CampaignLocalization::hasCampaign()) { $service->campaign(CampaignLocalization::getCampaign()); } return $service; }); $this->app->alias(EntityPermission::class, 'entitypermission'); return $this; } protected function registerRole(): self { $this->app->singleton(RolePermission::class, function () { return new RolePermission; }); $this->app->alias(RolePermission::class, 'rolepermission'); return $this; } } ================================================ FILE: app/Providers/RouteServiceProvider.php ================================================ firstOrFail(); }); Route::model('entityType', EntityType::class); Route::model('userValidation', UserValidation::class); } /** * Define the routes for the application. */ public function map() { $this->api() ->web() ->campaign() ->settings() ->auth() ->webhooks() ->vendor(); } /** * Define the general "web" routes for the application. * * These routes all receive session state, CSRF protection, etc. */ protected function web(): self { Route::middleware(['web', 'adless']) ->namespace($this->namespace) ->group(base_path('routes/web.php')); Route::middleware(['web', 'adless']) ->namespace($this->namespace) ->group(base_path('routes/web-i18n.php')); return $this; } /** * Define the "api" routes for the application. */ protected function api(): self { $domain = Domain::isApi() ? Domain::api() : ''; $prefix = Domain::isApi() ? null : 'api'; $prefixName = Domain::isApi() ? null : 'api.'; Route::domain($domain) ->prefix($prefix) ->namespace($this->namespace) ->get('/health', [HealthController::class, 'index']); Route::domain($domain) ->name($prefixName) ->prefix($prefix) ->middleware('api') ->namespace($this->namespace) ->group(base_path('routes/api.php')); return $this; } /** * Define the "campaign" routes for everything in a campaign */ protected function campaign(): self { $domain = Domain::isApi() ? Domain::app() : null; Route::domain($domain) ->middleware(['web', LastCampaign::class]) ->namespace($this->namespace) ->group(base_path('routes/campaigns/campaign.php')); Route::domain($domain) ->middleware(['web', LastCampaign::class]) ->namespace($this->namespace) ->group(base_path('routes/campaigns/entities.php')); Route::domain($domain) ->middleware(['web', LastCampaign::class]) ->namespace($this->namespace) ->group(base_path('routes/campaigns/bulks.php')); Route::domain($domain) ->middleware(['web', LastCampaign::class]) ->namespace($this->namespace) ->group(base_path('routes/campaigns/search.php')); return $this; } /** * Define the "profile" routes of a user */ protected function settings(): self { Route::middleware(['web', 'adless']) ->prefix('settings') ->namespace($this->namespace) ->group(base_path('routes/settings.php')); return $this; } /** * Define the "auth" routes for login, logout, lost password etc */ protected function auth(): self { Route::middleware(['web', 'adless']) ->namespace('App\Http\Controllers') ->group(base_path('routes/auth.php')); return $this; } protected function vendor(): self { Route::middleware(['minimum']) ->namespace('\App\Http\Controllers') ->group(base_path('routes/vendor.php')); return $this; } protected function webhooks(): self { Route::middleware(['webhooks']) ->namespace($this->namespace) ->group(base_path('routes/webhooks.php')); return $this; } } ================================================ FILE: app/Renderers/CalendarRenderer.php ================================================ buildCurrentSegments(); return $this; } /** * Get previous month link */ public function previous(bool $title = false): string { $month = $this->getMonth(-1); $year = $this->getYear(); $months = $this->calendar->months(); // Yearly navigation if ($this->isYearlyLayout()) { $year--; if (! $this->calendar->hasYearZero() && $year == 0) { $year--; } if ($title) { return (string) $year; } else { return route( 'entities.show', [$this->campaign, 'entity' => $this->calendar->entity, 'layout' => 'year', 'year' => $year] ); } } if ($month <= 0) { $year--; if (! $this->calendar->hasYearZero() && $year == 0) { $year--; } $month = count($months); } if ($title) { return $months[$month - 1]['name'] . " {$year}"; } $routeOptions = [$this->campaign, 'entity' => $this->calendar->entity, 'month' => $month, 'year' => $year]; if ($this->calendar->defaultLayout() === 'year') { // @phpstan-ignore-next-line $routeOptions['layout'] = $this->isYearlyLayout() ? 'year' : 'month'; } return route( 'entities.show', $routeOptions ); } /** * Build a link to a year */ public function linkToYear(bool $next = true): string { $month = $this->getMonth(); $year = $this->getYear($next ? 1 : -1); if (! $this->calendar->hasYearZero() && $year == 0) { if ($next) { $year++; } else { $year--; } } $options = [ 'campaign' => $this->campaign, 'entity' => $this->calendar->entity, 'month' => $month, 'year' => $year, ]; if ($this->isYearlyLayout()) { if (! $this->calendar->yearlyLayout()) { $options['layout'] = 'year'; } unset($options['month']); } elseif ($this->calendar->yearlyLayout()) { $options['layout'] = 'month'; } return route( 'entities.show', $options ); } /** * Get the title to a year */ public function titleToYear(bool $next = true): string { $month = $this->getMonth(); $year = $this->getYear($next ? 1 : -1); if (! $this->calendar->hasYearZero() && $year == 0) { if ($next) { $year++; } else { $year--; } } if ($this->isYearlyLayout()) { return (string) $year; } $months = $this->calendar->months(); return $months[$month - 1]['name'] . " {$year}"; } /** * Get current year-month */ public function currentMonthName(): string { $months = $this->calendar->months(); $month = $months[$this->getMonth(-1)]; $monthName = e(Arr::get($month, 'name', '')); $monthName = '' . $monthName . ''; return $this->isYearlyLayout() ? '' : $monthName; } public function currentYearName(): string { // Year name $year = $this->getYear(); $names = $this->calendar->years(); $yearText = $year; if (isset($names[$year])) { $safeName = e($names[$year]); $yearText = "{$safeName}"; } return $yearText; } public function monthAlias(): string { $months = $this->calendar->months(); $month = $months[$this->getMonth(-1)]; $alias = Arr::get($month, 'alias', ''); if (empty($alias)) { return ''; } // Month alias is already escaped on saving so let's skip it here return $alias; } /** * Get next month link */ public function next($title = false): string { $month = $this->getMonth(1); $year = $this->getYear(); $months = $this->calendar->months(); // Yearly navigation if ($this->isYearlyLayout()) { $year++; if (! $this->calendar->hasYearZero() && $year == 0) { $year++; } if ($title) { return (string) $year; } else { return route( 'entities.show', [$this->campaign, 'entity' => $this->calendar->entity, 'layout' => 'year', 'year' => $year] ); } } if ($month > count($months)) { $year++; if (! $this->calendar->hasYearZero() && $year == 0) { $year++; } $month = 1; } if ($title) { return $months[$month - 1]['name'] . " {$year}"; } $routeOptions = [$this->campaign, 'entity' => $this->calendar->entity, 'month' => $month, 'year' => $year]; if ($this->calendar->yearlyLayout()) { // @phpstan-ignore-next-line $routeOptions['layout'] = $this->isYearlyLayout() ? 'year' : 'month'; } return route( 'entities.show', $routeOptions ); } /** * Build the calendar events for a month view * * @return array */ public function buildForMonth() { // Number of weeks in this month? $weekdays = $this->calendar->weekdays(); $months = $this->calendar->months(); $month = $months[$this->getMonth() - 1]; $data = []; // If weeks reset on the first day of the week, skip the offset $offset = 0; if (empty($this->calendar->reset) || ($this->calendar->reset === 'year' && $this->getMonth() != 1)) { $offset = $this->weekStartoffset(); if ($this->calendar->start_offset > 0) { $offset += $this->calendar->start_offset; } elseif ($this->calendar->start_offset < 0) { $offset += count($weekdays) + $this->calendar->start_offset; } } $events = $this->events(); if (Arr::get($month, 'type') == 'intercalary') { $offset = 0; } // Check if this month is a leap month if ($this->calendar->has_leap_year) { if ($this->calendar->leap_year_month == $this->getMonth()) { // Is this the starting year, or an increment of the offset? $handle = $this->getYear() - $this->calendar->leap_year_start; if ($handle % max(1, $this->calendar->leap_year_offset) === 0) { $month['length'] += $this->calendar->leap_year_amount; } } } // If the offset is higher than a week, let's scale it down a week to avoid an empty week row if ($offset >= count($weekdays)) { $offset -= count($weekdays); } // Define the week number from the start of the year $weekNumber = $this->startingWeekNumber(); // dump("starting week number: " . $weekNumber); $monthLength = $month['length']; $weekLength = 0; $week = []; $this->remainingRecurring = []; // Calc julian based on previous months $julian = 1; for ($passedMonth = 1; $passedMonth < $this->getMonth(); $passedMonth++) { $passedMonthData = $months[$passedMonth - 1]; $julian += $passedMonthData['length']; } for ($day = 1; $day <= $monthLength; $day++) { if ($offset > 0) { $week[] = null; $offset--; $day--; } else { $exact = $this->getYear() . '-' . $this->getMonth() . '-' . $day; $this->dayData = [ 'day' => $day, 'year' => $this->getYear(), 'month' => $this->getMonth(), 'events' => [], 'date' => $exact, 'isToday' => false, 'julian' => $julian, ]; if (isset($events[$exact])) { $this->dayData['events'] = $events[$exact]; } if ($exact == $this->calendar->date) { $this->dayData['isToday'] = true; } if ($this->moonService->has($day)) { $this->dayData['moons'] = $this->moonService->get($day); } if ($this->weatherService->has($exact)) { $this->dayData['weather'] = $this->weatherService->get($exact); } $monthday = $this->getMonth() . '-' . $day; if ($this->seasonService->has($monthday)) { $this->dayData['season'] = $this->seasonService->get($monthday); } // Add recurring events that span multiple days from the previous call $this->recurringReminders(); $this->addMoonReminders($day); $week[] = $this->dayData; $julian++; } $weekLength++; if (count($week) >= count($weekdays)) { $data[$weekNumber] = $week; $week = []; $weekNumber++; } } // Fill in the last week? $lastWeekDiff = count($week) - count($weekdays); if ($lastWeekDiff < 0) { if (abs($lastWeekDiff) >= count($weekdays)) { $lastWeekDiff += count($weekdays); } for ($day = $lastWeekDiff; $day < 0; $day++) { $week[] = null; } } $data[$weekNumber] = $week; return $data; } /** * Build the calendar for the yearly view */ public function buildForYear(): array { // Number of weeks in this month? $weekdays = $this->calendar->weekdays(); $months = $this->calendar->months(); $julian = 1; $data = []; $events = $this->events(); $offset = 0; if (empty($this->calendar->reset) || ($this->calendar->reset === 'year' && $this->getMonth() != 1)) { $offset = $this->weekStartoffset(); if ($this->calendar->start_offset > 0) { $offset += $this->calendar->start_offset; } elseif ($this->calendar->start_offset < 0) { $offset += count($weekdays) + $this->calendar->start_offset; } } // Add empty days for the beginning of the year for ($i = $offset; $i > 0; $i--) { $data[] = null; } $weekLength = count($weekdays); $monthNumber = 1; $weekNumber = $offset > 0 && empty($this->calendar->reset) ? 2 : 1; // dump('Starting week number: ' . $weekNumber); $totalDay = 1; $weekday = 1; foreach ($months as $month) { // dump('Month: ' . $month['name']); // if ($weekNumber) $month = $months[$monthNumber - 1]; $this->month = $monthNumber; // Check if this month is a leap month if ($this->calendar->has_leap_year) { if ($this->calendar->leap_year_month == $monthNumber) { // Is this the starting year, or an increment of the offset? $handle = $this->getYear() - $this->calendar->leap_year_start; if ($handle % max(1, $this->calendar->leap_year_offset) === 0) { $month['length'] += $this->calendar->leap_year_amount; // If it also happens to add days on an intercalary day, we need to influence the number of days // added at the end of the month. } } } $monthLength = $month['length']; $week = []; $currentPosition = 0; // If the month is intercalary, we need to offset to the "beginning" of the week if (Arr::get($month, 'type') == 'intercalary') { $totalDays = count($data); $emptyDaysToFill = $weekLength - ($totalDays % $weekLength); $currentPosition = $weekLength - $emptyDaysToFill; // On which week day we currently are // Don't fill a whole empty week if ($emptyDaysToFill == $weekLength) { $emptyDaysToFill = 0; } for ($d = 0; $d < $emptyDaysToFill; $d++) { $data[] = []; } } // Add each day of the month to the day thing $this->remainingRecurring = []; $endedWeek = false; for ($day = 1; $day <= $monthLength; $day++) { $endedWeek = false; $exact = $this->getYear() . '-' . $monthNumber . '-' . $day; // if ($weekNumber < 13) dump('new day ' . $weekday . ', ' . $totalDay . ', ' . $exact); $this->dayData = [ 'day' => $day, 'events' => [], 'date' => $exact, 'year' => $this->getYear(), 'julian' => $julian, 'isToday' => false, 'month' => $month['name'], 'week' => Arr::get($month, 'type') == 'intercalary' ? null : $weekNumber, ]; if (isset($events[$exact])) { $this->dayData['events'] = $events[$exact]; } if ($exact == $this->calendar->date) { $this->dayData['isToday'] = true; } if ($this->moonService->has($totalDay)) { $this->dayData['moons'] = $this->moonService->get($totalDay); } if ($this->weatherService->has($exact)) { $this->dayData['weather'] = $this->weatherService->get($exact); } $this->recurringReminders(); $this->addMoonReminders($day); $monthday = $monthNumber . '-' . $day; if ($this->seasonService->has($monthday)) { $this->dayData['season'] = $this->seasonService->get($monthday); } $data[] = $this->dayData; $totalDay++; // If we're hitting the end of the week, go down if ($weekday % $weekLength == 0 && Arr::get($month, 'type') != 'intercalary') { // if ($weekNumber < 13) dump('end of the week: week ' . $weekNumber . ' is over, going to ' . ($weekNumber+1)); $weekNumber++; $endedWeek = true; $weekday = 1; } else { $weekday++; } // Increase the julian date by one $julian++; } // if ($weekNumber < 13) dump('finished the month. Did we end the week on the last day? ' . ($endedWeek ? 'yes' : 'no')); // If the month is intercalary, we need to fill out the rest of the "week" until where it starts again // Or iff we have resets on the end of the month, we need to fill in some empty days if (Arr::get($month, 'type') == 'intercalary' || $this->calendar->reset === 'month') { // if ($weekNumber < 13) dump('there is a reset going on here'); $totalDays = count($data); $emptyDaysToFill = $weekLength - ($totalDays % $weekLength); // Don't fill a whole empty week if ($emptyDaysToFill == $weekLength) { $emptyDaysToFill = 0; } for ($d = 0; $d < $emptyDaysToFill; $d++) { $data[] = []; } // Fill out the next month beginning if needed // Only add at the beginning if we don't reset on first day of the week if (! $this->calendar->reset == 'month') { for ($d = 0; $d < $currentPosition; $d++) { $data[] = []; } } elseif (! $endedWeek) { // Only increase the week number if the reset didn't happen on the last day of the previous month, // otherwise we are skipping a week number. // if ($weekNumber < 13) dump('reset: week ' . $weekNumber . ' is over, going to ' . ($weekNumber+1)); $weekNumber++; $weekday = 1; } } $monthNumber++; } return $data; } public function currentMonthId() { return $this->getMonth(); } /** * Determine if the "today" button is disabled. */ public function todayButtonIsDisabled(): bool { $calendarYear = $this->calendar->currentDate('year'); $calendarMonth = $this->calendar->currentDate('month'); return (bool) ($this->year == $calendarYear && $this->month == $calendarMonth); } public function isIntercalaryMonth(): bool { $month = $this->currentMonthId() - 1; foreach ($this->calendar->months() as $k => $m) { if ($k == $month && Arr::get($m, 'type') == 'intercalary') { return true; } } return false; } /** * Build the current month and year segments */ protected function buildCurrentSegments(): void { if (isset($this->segments)) { return; } $this->segments = true; $segments = $this->splitDate($this->calendar->date); $this->setMonth((int) $segments[1]) ->setYear($segments[0]); if ($this->request->filled('month')) { $this->setMonth((int) $this->request->input('month')); } if ($this->request->filled('year')) { $this->setYear((int) $this->request->input('year')); } if (empty($this->getMonth())) { $this->setMonth(1); } // If the month is too big? Then use the max if ($this->getMonth() > count($this->calendar->months())) { $this->setMonth(count($this->calendar->months())); } // Yearly layout does things a bit differently, reset month to first $this->layout = $this->request->get('layout', $this->calendar->defaultLayout()) ?? 'month'; if ($this->isYearlyLayout()) { $this->setMonth(1); } $this->buildFullmoons(); $this->buildSeasons(); $this->buildWeeks(); $this->buildWeather(); } /** * Calculate the month starting offset */ protected function weekStartOffset(): int { $totalDays = $this->daysToDate(false); $negativeYear = $totalDays < 0; $weekLength = count($this->calendar->weekdays()); if ($weekLength == 0) { $weekLength = 1; } // If the calendar resets on the first day of the year, we can switch this around if ($this->calendar->reset == 'year') { $totalDays = $this->daysToDateForYear(); } $totalDays = $negativeYear ? abs($totalDays) : $totalDays; $offset = (int) floor($totalDays % $weekLength); // If we are in a negative year, we need to reduce the offset from the week length, as we want the last // month before the calendar really "starts" to end on the last day of the week. return $negativeYear && $this->calendar->reset != 'year' ? $weekLength - $offset : $offset; } /** * Load events of the year and month */ protected function events(): array { $this->events = []; $reminders = $this->getReminders($this->calendar); $this->parseReminders($reminders); if ($this->calendar->entity->parent) { $reminders = $this->getReminders($this->calendar->entity->parent->calendar); $this->parseReminders($reminders); } // dd($this->events); return $this->events; } /** * @return \Illuminate\Database\Eloquent\Collection */ protected function getReminders(Calendar $calendar) { // @phpstan-ignore-next-line return $calendar->calendarEvents() ->whereHas('remindable') ->with([ 'remindable' => function ($morphTo) { $morphTo->morphWith([ Entity::class => ['entityType', 'tags', 'image'], Post::class => ['tags', 'entity'], ]); }, 'death']) ->where(function ($query) { $query // Where it's the current year , or current year and current month ->where(function ($sub) { $sub->where('year', $this->getYear()); if (! $this->isYearlyLayout()) { $sub->where('month', $this->getMonth()); } }) // Or where the event is recurring, or recurring on this month ->orWhere(function ($sub) { if ($this->isYearlyLayout()) { $sub->where('is_recurring', true); } else { $sub->where('month', $this->getMonth()) ->where('is_recurring', true); } }) ->orWhere(function ($sub) { if ($this->calendar->show_birthdays) { $sub->where('year', '<=', $this->getYear()) ->whereIn('type_id', [EntityEventTypes::birth, EntityEventTypes::death]); if (! $this->isYearlyLayout()) { $sub->where('month', $this->getMonth()); } } }) // Events from previous year or month that spill over ->orWhere(function ($sub) { $previousYear = $this->getYear(-1); if (! $this->calendar->hasYearZero() && $previousYear == 0) { $previousYear--; } $sub->whereIn('year', [$previousYear, $this->getYear()]) ->where('length', '>', 1); }) // Events from previous month that spill over // ->orWhere(function ($sub) { // list($year, $month) = $this->subMonth($this->getYear(), $this->getMonth()); // $sub->where('year', $year) // ->where('month', $month) // ->where('length', '>', 1); // }) // Monthly recurring events ->orWhere(function ($sub) { $sub->where('is_recurring', true); }); }) ->get(); } protected function parseReminders(Collection $reminders): void { $totalMonths = count($this->calendar->months()); if (! $this->isYearlyLayout()) { $totalMonths = $this->getMonth(); } /** @var Reminder $event */ foreach ($reminders as $event) { if ($event->isBirth() && $event->death && $event->death->isPastDate($this->getYear(), $event->month, $event->day)) { continue; } $date = $event->year . '-' . $event->month . '-' . $event->day; // If the event is recurring, get the year to make sure it should start showing. This was previously // done in the query, but it didn't work on all systems. if ($event->is_recurring || $event->isBirth()) { if ($event->year > $this->getYear()) { continue; } // Over max reoccurring year? if (! empty($event->recurring_until) && $event->recurring_until < $this->getYear()) { continue; } $date = $this->getYear() . '-' . $event->month . '-' . $event->day; } if (! isset($this->events[$date])) { $this->events[$date] = []; } // Make sure the user can actually see the requested event if (empty($event->remindable) || ($event->remindable instanceof Entity && $event->remindable->isMissingChild()) || ($event->remindable instanceof Post && ! $event->remindable->entity)) { continue; } // If the event reoccurs each month, let's add it everywhere if ($event->recurringMonthly()) { $startingMonth = $event->year == $this->getYear() ? $event->month : 1; for ($month = $startingMonth; $month <= $totalMonths; $month++) { $recurringDate = $this->getYear() . '-' . $month . '-' . $event->day; $this->events[$recurringDate][] = $event; $this->addMultidayEvent($event, $recurringDate); } } elseif ($event->is_recurring && $event->recurring_periodicity !== 'year') { // If we haven't passed the max date for this event, show it in the recurring blocks if (empty($event->recurring_until) || $this->getYear() < $event->recurring_until) { $this->recurring[$event->recurring_periodicity][] = $event; } } else { // Only add it once $this->events[$date][] = $event; $this->addMultidayEvent($event, $date); } } foreach ($this->births as $key => $birth) { if (! isset($this->deaths[$key]) || ($this->deaths[$key]->month > $birth->month || ($this->deaths[$key]->month == $birth->month && $this->deaths[$key]->day > $birth->day))) { $date = $this->getYear() . '-' . $birth->month . '-' . $birth->day; $this->events[$date][] = $birth; } } // should end the first day of the month } /** * For multi-day event, add it to each day the event lasts */ protected function addMultidayEvent(Reminder $reminder, string $date) { // Does the day go over a few days? if ($reminder->length == 1) { return; } // Flag this reminder's start date to show (start) in the UI $this->eventStart[$reminder->id][] = $date; // For each length of the reminder, add it to the UI $extraDate = $date; for ($extra = 1; $extra < $reminder->length; $extra++) { $extraDate = $this->addDay($extraDate); [$y, $m, $d] = $this->splitDate($extraDate); if (! $this->calendar->hasYearZero() && $y == 0) { $extraDate = '1-' . $m . '-' . $d; } $this->events[$extraDate][] = $reminder; } // Finished adding all the reminder's days, flag it to show (end) in the UI $this->eventEnd[$reminder->id][] = $extraDate; } /** * Add an extra day to a date. */ protected function addDay(string $date): string { [$year, $month, $day] = $this->splitDate($date); $day++; // Day longer than month? $months = $this->calendar->months(); $monthKey = max($month - 1, 0); // If we're showing a reminder from the parent calendar, but the month doesn't exist in this calendar, // we need to do this dirty hack where we fake the previous month as being the last month of the previous year if (! isset($months[$monthKey])) { $monthKey = count($months) - 1; // $year; $day = 999999; // Force it to the last day of the previous month so that it can be incremented by one } $currentMonth = $months[$monthKey]; // $previousMonth = $month > 1 ? $months[$month-1] : last($months); if ($day > $currentMonth['length']) { $day = 1; $month++; } // Month longer than max year? if ($month > count($months)) { $month = 1; $year++; } return "{$year}-{$month}-{$day}"; } protected function subMonth(int $year, int $month): array { $months = $this->calendar->months(); $month--; if ($month <= 0) { $month = count($months); $year--; } return [$year, $month]; } /** * Get the current year */ protected function getYear(?int $add = 0): int { if (! $this->calendar->hasYearZero() && $this->year == 0) { return intval($this->year + 1 + $add); } // We need intval for people asking for a number that is > 32bit converting to floats return intval((int) $this->year + $add); } /** * Get the current month */ protected function getMonth(?int $add = 0): int { return intval($this->month + $add); } protected function setYear(int $year): self { $this->year = $year; return $this; } protected function setMonth(int $month): self { $this->month = $month; return $this; } /** * Split the date into segments. Handle negative years */ protected function splitDate(string $date): array { $segments = explode('-', mb_ltrim($date, '-')); if (Str::startsWith($date, '-')) { $segments[0] = '-' . $segments[0]; } return $segments; } public function isYearlyLayout(): bool { return $this->layout === 'year'; } public function currentYear(): int { return (int) $this->year; } public function isNamedWeek(int $week): bool { return ! empty($this->weeks[$week]) && ! $this->isIntercalaryMonth(); } public function namedWeek(int $week): string { return $this->weeks[$week] . ''; } protected function buildFullmoons(): void { // dump('full moons go brr'); // Calculate the number of days since 0000-01-01 $totalDays = $this->daysToDate(); // We'll need this later to know how many full moons to add $daysInAYear = 0; foreach ($this->calendar->months() as $count => $month) { $length = $month['length']; $daysInAYear += $length; } $this->moonService ->calendar($this->calendar) ->build($totalDays, $daysInAYear); } /** * Build the weather for the year */ public function buildWeather(): void { $this->weatherService ->calendar($this->calendar) ->currentYear($this->currentYear()) ->build(); } /** * Get the total number of days since the beginning * * @return float|int|mixed */ protected function daysToDate(bool $includeIntercalary = true) { return $this->daysService ->calendar($this->calendar) ->intercalary($includeIntercalary) ->month($this->getMonth()) ->year($this->getYear()) ->daysToDate(); } /** * Prepare each of the calendar's seasons */ protected function buildSeasons(): void { $this->seasonService ->calendar($this->calendar) ->build(); } /** * Prepare each of the calendar's yearly weeks */ protected function buildWeeks(): void { foreach ($this->calendar->weeks() as $number => $week) { if ($number <= 0) { continue; } $this->weeks[$number] = $week; } } /** * Calculate the starting week number */ protected function startingWeekNumber(): int { $months = $this->calendar->months(); $weekdays = $this->calendar->weekdays(); $daysInAYear = 0; $weekNumber = 1; $weekdaysCount = count($weekdays); foreach ($months as $monthNumber => $monthData) { // If we've reached the current month, break if ($monthNumber == $this->getMonth() - 1) { break; } if (Arr::get($monthData, 'type') == 'intercalary') { continue; } // If we reset months on the week, we need if ($this->calendar->reset === 'month') { // dump('month ' . $monthNumber . ' resets. length: ' . $monthData['length'] . ' / ' . $weekdaysCount . ' + 1'); // dump('reset, adding ' . (ceil($monthData['length'] / $weekdaysCount)) . ' week'); $weekNumber += ceil($monthData['length'] / $weekdaysCount); } $daysInAYear += $monthData['length']; } // If we reset months on the week, we need if ($this->calendar->reset === 'month') { return $weekNumber; } // We have the total number of days from the previous months, so we can figure out when the current // week starts $weekNumber = floor($daysInAYear / $weekdaysCount) + 1; return (int) $weekNumber; } /** * Get the number of days since the beginning of the year. This is used to calculate the month start offset on * calendars with first days resetting on the year. */ protected function daysToDateForYear(): int { $offset = 0; $daysInAYear = $days = $leapDays = 0; foreach ($this->calendar->months() as $count => $month) { if (Arr::get($month, 'type') == 'intercalary') { continue; } $length = $month['length']; $daysInAYear += $length; // If the month has already passed, add it to the days for this year if ($count < $this->getMonth() - 1) { $days += $length; } } return $days; } /** * Checks if date is the start of an event */ public function isEventStartDate(Reminder $reminder, string $date): bool { return isset($this->eventStart[$reminder->id]) && in_array($date, $this->eventStart[$reminder->id]); } /** * Checks if date is the end of an event */ public function isEventEndDate(Reminder $reminder, string $date): bool { return isset($this->eventEnd[$reminder->id]) && in_array($date, $this->eventEnd[$reminder->id]); } protected function recurringReminders(): void { // Add recurring events that span multiple days from the previous call $newRemaining = []; foreach ($this->remainingRecurring as $recurring) { if ($recurring['remaining'] == 1) { $this->eventEnd[$recurring['event']->id][] = $this->dayData['date']; } $this->dayData['events'][] = $recurring['event']; if ($recurring['remaining'] > 1) { $newRemaining[] = ['remaining' => $recurring['remaining'] - 1, 'event' => $recurring['event']]; } } $this->remainingRecurring = $newRemaining; } protected function addMoonReminders(int $day): void { // Add recurring events if the moon stuff fits if (empty($this->dayData['moons'])) { return; } foreach ($this->dayData['moons'] as $moon) { $key = $moon['id'] . '_' . $moon['type'][0]; if (! isset($this->recurring[$key])) { continue; } /** @var Reminder $event */ // dump('found events for ' . $key); foreach ($this->recurring[$key] as $event) { if (! $event->isPastDate($this->getYear(), $this->getMonth(), $day)) { continue; } $this->dayData['events'][] = $event; if ($event->length > 1) { $this->eventStart[$event->id][] = $this->dayData['date']; $this->remainingRecurring[] = ['remaining' => $event->length - 1, 'event' => $event]; } } } } } ================================================ FILE: app/Renderers/CharacterSheets/Blade.php ================================================ campaignPlugin->version->content; $html = str_replace(['<', '>', '&&'], ['<', '>', '&&'], $html); // Get all the referenced attributes in the character sheet so that they are set to null if an entity // doesn't have the attribute $html = preg_replace_callback('`\{\{(.*?[^(\!])\}\}`i', function ($matches) { $attribute = mb_trim((string) $matches[1]); // If it's a comment, we can safely ignore it if (Str::startsWith($attribute, '--') && Str::endsWith($attribute, '--')) { return '{{' . $attribute . '}}'; } // Flag this as an attribute that is referenced $name = Str::after($attribute, '$'); $this->templateAttributes[$name] = null; return '{{ ' . $attribute . ' }}'; }, $html); $html = preg_replace_callback('`\{\!\!(.*?[^(\!])\!\!\}`i', function ($matches) { $attribute = mb_trim((string) $matches[1]); $name = Str::after($attribute, '$'); // Flag this as an attribute that is referenced $this->templateAttributes[$name] = null; return '{!! ' . $attribute . ' !!}'; }, $html); // Blacklisted commands $html = str_replace([ '@php', '@dd', '@inject', '@yield', '@section', '@session', '@env', '@once', '@push', '@csrf', '@use', '@include', '\Illuminate\\', ], [ '', '', '', '', '', '', '', '', '', '', '', '', '', ], $html); // Remove more blacklisted stuff than can go unnoticed $html = preg_replace('`dd\((.*?)\)`i', '', $html); $html = preg_replace('`config\((.*?)\)`i', '', $html); // First loop to replace i18n with ()) in the texts $regexp = '`\@i18n(\((?:[^)(]++|(?1))*\))`i'; $html = preg_replace_callback($regexp, function ($matches) { return '{{ trans' . $matches[1] . ' }}'; }, $html); // Next loop on the easy non complicated i18n calls without () $regexp = '`\@i18n\((.*?)\)`i'; $html = preg_replace_callback($regexp, function ($matches) { return '{{ trans' . $matches[1] . ' }}'; }, $html); $this->loadTranslations(); $html = \Illuminate\Support\Facades\Blade::compileString($html); [$data, $ids, $checkboxes] = $this->prepareEntityData(); $html = preg_replace_callback('`\@liveAttribute\(\'(.*?[^)])\'\)`i', function ($matches) use ($data, $ids, $checkboxes) { $attr = mb_trim((string) $matches[1]); if (! isset($data[$attr])) { return $matches[0]; } $value = $data[$attr]; if (in_array($attr, $checkboxes)) { if ($data[$attr] === 'on' || $data[$attr] === '1') { $value = ''; } else { $value = ''; } } return '' . $value . ''; }, $html); $obLevel = ob_get_level(); ob_start() && extract($data, EXTR_SKIP); $errors = null; try { eval('?' . '>' . $html); $blade = ob_get_clean(); return $blade; } catch (Exception $e) { while (ob_get_level() > $obLevel) { ob_end_clean(); } $errors = $e->getMessage(); // throw $e; } catch (\Throwable $e) { while (ob_get_level() > $obLevel) { ob_end_clean(); } $errors = $e->getMessage(); // throw new FatalThrowableError($e); } return '
' . __('attributes/templates.errors.marketplace.rendering') . (! empty($errors) ? '

' . __('attributes/templates.errors.marketplace.hint') . ': ' . $errors . ' (line ' . $e->getLine() . ')' : null) . '
' . $this->debug($data); } /** * Build a html list of all variables * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ protected function debug(mixed $data): string { $html = '

Debug info - the following variables are available

'; foreach ($data as $key => $val) { if (! is_array($val) && ! is_object($val)) { $html .= '$' . $key . ' ' . (empty($val) ? null : e($val)) . '
'; } elseif (is_array($val)) { $html .= '$' . $key . ''; if (empty($val)) { $html .= 'NULL
'; } else { $html .= '
    '; foreach ($val as $k => $v) { if (is_array($v)) { $html .= '
  • ' . $k . '
  • '; continue; } $html .= '
  • ' . $k . ' ' . $v . '
  • '; } $html .= '
'; } } } return $html . '
'; } } ================================================ FILE: app/Renderers/CharacterSheets/Custom.php ================================================ entityAttributes = $this->entity->allAttributes; $html = preg_replace_callback('`\{(.*?)\}`i', function ($matches) { $name = (string) $matches[1]; return $this->attribute($name); }, $this->campaignPlugin->version->content); // Replace < and > in logical blocks // $html = str_replace(['<', '>'], ['<', '>'], $html); // dump($html); // return $html; // If-Else condition $html = preg_replace_callback('`@if\((.*?)\)(.*?)@endif`si', function ($matches) { return $this->ifBlock($matches); }, $html); $html = preg_replace_callback('`@if\((.*?)\)(.*?)@else(.*?)@endif`si', function ($matches) { return $this->ifElseBlock($matches); }, $html); $html = preg_replace_callback('`@empty\((.*?)\)(.*?)@endempty`si', function ($matches) { return (string) $this->emptyBlock($matches); }, $html); $html = preg_replace_callback('`@notempty\((.*?)\)(.*?)@endnotempty`si', function ($matches) { return (string) ! $this->emptyBlock($matches); }, $html); return $html; } protected function attribute(string $name): string { /** @var Attribute|null $attr */ $attr = $this->entityAttributes->where('name', $name)->first(); if (! empty($attr)) { if ($attr->isText()) { return nl2br($attr->mappedValue()); } return $attr->mappedValue(); } return '' . $name . ''; } /** * If Else block */ protected function ifElseBlock(array $matches) { // Test on a missing attribute always returns false $trimmed = mb_trim($matches[1]); if (Str::contains($trimmed, '')) { return $matches[3]; } // Strip tags to remove html brs on multilines $condition = strip_tags(mb_trim($matches[1])); if (Str::contains($condition, ['=', '>', '<'])) { if ($this->evaluateCondition($condition)) { return $matches[2]; } return null; } if (! empty($condition)) { return $matches[2]; } else { return $matches[3]; } } /** * If block * * @return mixed|null */ protected function ifBlock(array $matches) { // If there is an else in the block, let the if-else block handle it later if (Str::contains($matches[2], '@else')) { return $matches[0]; } // Test on a missing attribute always returns false $trimmed = mb_trim($matches[1]); if (Str::contains($trimmed, '')) { return null; } // Strip tags to remove html brs on multilines $condition = strip_tags(mb_trim($matches[1])); if (Str::contains($condition, ['=', '>', '<', '<', '>'])) { if ($this->evaluateCondition($condition)) { return $matches[2]; } return null; } if (! empty($condition)) { return $matches[2]; } return null; } /** * Evaluate a condition */ protected function evaluateCondition(string $condition): bool { // >= if (Str::contains($condition, '>=')) { $segments = explode('>=', $condition); return (int) mb_trim($segments[0]) >= (int) mb_trim($segments[1]); } elseif (Str::contains($condition, '<=')) { $segments = explode('<=', $condition); return (int) mb_trim($segments[0]) <= (int) mb_trim($segments[1]); } elseif (Str::contains($condition, '>')) { $segments = explode('>', $condition); return (int) mb_trim($segments[0]) > (int) mb_trim($segments[1]); } elseif (Str::contains($condition, '<')) { $segments = explode('<', $condition); return (int) mb_trim($segments[0]) < (int) mb_trim($segments[1]); } elseif (Str::contains($condition, '=')) { $segments = explode('=', $condition); return mb_trim($segments[0]) == mb_trim($segments[1]); } return false; } protected function emptyBlock(array $matches) { $condition = mb_trim($matches[1]); if (Str::contains($condition, '')) { return false; } return ! (empty($condition)); } } ================================================ FILE: app/Renderers/CharacterSheets/Renderer.php ================================================ campaignPlugin = $campaignPlugin; return $this; } /** * Add the plugin's translations to memory */ protected function loadTranslations(): void { // Always add the user's locale + en as a fallback $userLocale = app()->getLocale(); $locales = [$userLocale, 'en']; foreach ($this->campaignPlugin->version->getTranslationsAttribute() as $translation) { if (! in_array($translation['locale'], $locales)) { continue; } $lines['*.' . $translation['base']] = $translation['translation']; app('translator')->addLines($lines, $userLocale); } } /** * Prepare all the attributes of the entity to be accessible in blade */ protected function prepareEntityData(): array { $data = []; $ids = []; $checkboxes = []; $this->entityAttributes = $this->entity->allAttributes; $allAttributes = []; foreach ($this->entityAttributes as $attr) { $name = $attr->exposedName(false); $data[$name] = $attr->mappedValue(); $ids[$name] = $attr->id; if ($attr->isText()) { $data[$name] = nl2br($data[$name]); } elseif ($attr->isCheckbox()) { $checkboxes[] = $name; } // dump('mapping ' . $name . ' to ' . $attr->mappedValue()); // Clean up the name for ranged values $allAttributes[$name] = $data[$name]; unset($this->templateAttributes[$name]); } // We need this for some blade directives like foreach $data['__env'] = app(Factory::class); $data['attributes'] = $allAttributes; $data['_abilities'] = $this->abilities(); // Share some attributes to plugin developers $data['_locale'] = app()->getLocale(); $data['_entity_name'] = $this->entity->name; $data['_entity_type'] = $this->entity->type; $data['_entity_type_name'] = $this->entity->entityType->code; if ($this->entity->isCharacter()) { /** @var Character $character */ $character = $this->entity->child; $data['_character_title'] = $character->title; $data['_character_gender'] = $character->sex; $data['_character_age'] = $character->age; $data['_character_pronouns'] = $character->pronouns; $appearances = $character->appearances; $data['_character_appearances'] = $appearances->pluck('entry', 'name')->toArray(); $traits = $character->personality; $data['_character_traits'] = $traits->pluck('entry', 'name')->toArray(); } $tags = []; foreach ($this->entity->tags as $tag) { $tags[$tag->slug] = $tag->name; } $data['_tags'] = $tags; $data['_superboosted'] = $this->campaign->superboosted(); $data['_premium'] = $this->campaign->premium(); // Add any missing attributes to be accessible in blade foreach ($this->templateAttributes as $name => $val) { if (isset($data[$name])) { continue; } $data[$name] = $val; } if (! isset($data['openLI'])) { $data['openLI'] = '
  • '; } if (! isset($data['closeLI'])) { $data['closeLI'] = '
  • '; } if (! isset($data['openOL'])) { $data['openOL'] = '
      '; } if (! isset($data['closeOL'])) { $data['closeOL'] = '
    '; } if (! isset($data['openUL'])) { $data['openUL'] = '
      '; } if (! isset($data['closeUL'])) { $data['closeUL'] = '
    '; } return [$data, $ids, $checkboxes]; } /** * Load abilities of the entity and make them available to blade */ protected function abilities(): array { $abilities = $this->entity ->abilities() ->has('ability') ->has('ability.entity') ->with(['ability', 'ability.entity', 'ability.entity.parent', 'ability.entity.image', 'ability.entity.tags']) ->get(); $data = []; /** @var EntityAbility $abi */ foreach ($abilities as $abi) { $tags = []; foreach ($abi->ability->entity->tags as $tag) { $tags[] = $tag->slug; } $parent = null; if (! empty($abi->ability->entity->parent)) { $parent = [ 'name' => $abi->ability->entity->parent->name, 'slug' => Str::slug($abi->ability->entity->parent->name), ]; } $ability = [ 'id' => $abi->id, 'ability_id' => $abi->ability_id, 'name' => $abi->ability->name, 'slug' => Str::slug($abi->ability->name), 'type' => $abi->ability->type, 'entry' => $abi->ability->entity->parsedEntry(), 'charges' => $abi->ability->charges, 'note' => Mentions::mapAny($abi, 'note'), 'note_raw' => $abi->note, 'used_charges' => $abi->charges, 'thumb' => '
    ', 'link' => '' . $abi->ability->name . '', 'tags' => $tags, 'parent' => $parent, ]; $data[] = $ability; } return $data; } } ================================================ FILE: app/Renderers/DatagridRenderer.php ================================================ columns = $columns; return $this; } public function options(array $options): self { $this->options = $options; return $this; } public function bookmark(Bookmark $bookmark): self { $this->bookmark = $bookmark; return $this; } public function models(Collection|LengthAwarePaginator $models): self { $this->models = $models; return $this; } public function service($service): self { $this->filterService = $service; return $this; } /** * @param array $columns * @param array $data * @param array $options */ public function render( FilterService $filterService, $columns = [], $data = [], $options = [] ): self { $this->columns = $columns; $this->models = $data; $this->options = $options; $this->filterService = $filterService; return $this; } public function __toString(): string { $html = ''; $html .= ''; $html .= $this->renderColumns(); $html .= ''; $html .= ''; $html .= $this->renderRows(); $html .= '
    '; return $html; } /** * Render the header columns */ private function renderColumns() { $html = ''; // Checkbox for delete if (auth()->check()) { $html .= ''; } foreach ($this->columns as $column) { $html .= $this->renderHeadColumn($column); } // Admin column $html .= '' . $this->renderFilters() . ''; return $html; } /** * @param string|array $column */ private function renderHeadColumn($column) { // Easy mode: A string. We want to return it directly since it's so easy. if (is_string($column)) { if ($column == 'name') { return "" . $this->route($column) . "\n"; } else { return "hidden . "'>" . $this->route($column) . "\n"; } } // Check visibility if (isset($column['visible']) && $column['visible'] == false) { return null; } $html = null; $class = null; if (! empty($column['type'])) { // We have a type so we know what to do $type = $column['type']; $class = $column['type']; if ($type == 'avatar') { $class = (! empty($column['parent']) ? $this->hidden : $class) . ' w-14'; } elseif ($type == 'location') { $class .= ' ' . $this->hidden; $label = Arr::get($column, 'label', Module::singular(config('entities.ids.location'), __('entities.location'))); $html = $this->route('location.name', $label); } elseif ($type == 'entityLocations') { $class .= ' ' . $this->hidden; $label = Arr::get($column, 'label', Module::plural(config('entities.ids.location'), __('entities.locations'))); $html = $this->route('locations', $label); } elseif ($type == 'organisation') { $class .= ' ' . $this->hidden; $label = Arr::get($column, 'label', Module::singular(config('entities.ids.organisation'), __('entities.organisation'))); $html = $this->route('organisation.name', $label); } elseif ($type == 'character') { $class .= ' ' . $this->hidden; $label = Arr::get($column, 'label', Module::singular(config('entities.ids.character'), __('entities.character'))); $html = $this->route( 'character.name', $label ); } elseif ($type == 'entity') { $class .= ' ' . $this->hidden; $html = $this->route( 'entity.name', ! empty($column['label']) ? $column['label'] : __('fields.entry.label') ); } elseif ($type == 'parent') { $class .= ' ' . $this->hidden; if (! empty($this->nestedFilter)) { return null; } $html = $this->route( Arr::get($column, 'field', 'parent.name'), ! empty($column['label']) ? $column['label'] : __('crud.fields.parent') ); } elseif ($type == 'is_private') { // Viewers can't see private if (! isset($this->user) || ! $this->user->isAdmin()) { return null; } $html = $this->route( 'is_private', ' ' . __('crud.fields.is_private') . '' ); $class = 'w-14 text-center'; } elseif ($type == 'reminder') { $class .= ' ' . $this->hidden; $html = $this->route('calendar_date', __('crud.fields.calendar_date')); } else { // No idea what is expected $html = null; } } else { // Now the 'fun' starts $class .= Arr::get($column, 'class', ' ' . $this->hidden); if (! empty($column['label'])) { $label = $column['label']; // We have a label, that's nice. If we have a custom render, we probably can't orderBy if (! empty($column['disableSort'])) { $html = $label; } else { // So we have a label and no renderer, so we can order by. We just need a field $html = $this->route($column['field'], $label); } if (Str::contains($label, '{$html}\n"; } private function route(?string $field = null, ?string $label = null): string { // Field is label if (empty($label)) { $label = $this->trans($field); } // If we are in public mode (bots) don't make this links if (! auth()->check()) { return $label; } $routeOptions = [ 'campaign' => $this->campaign, 'order' => $field, 'page' => $this->request->get('page'), ]; if (isset($this->bookmark)) { $routeOptions['bookmark'] = $this->bookmark; } if ($this->request->get('_from', false) == 'bookmark') { $routeOptions['_from'] = 'bookmark'; } if (! empty($this->nestedFilter)) { $val = $this->request->get($this->nestedFilter, null); if (! empty($val)) { $routeOptions[$this->nestedFilter] = $val; } } // Order by $order = $this->filterService->order(); $orderImg = ' '; if (! empty($order) && isset($order[$field])) { $direction = 'down'; if ($order[$field] != 'DESC') { $routeOptions['desc'] = true; $direction = 'up'; } $orderImg = ' '; } return "" . $label . $orderImg . ''; } private function renderRows() { $html = ''; $rows = 0; foreach ($this->models as $model) { $rows++; $html .= $this->renderRow($model); if ($rows % 7 === 0) { $html .= $this->renderAdRow($rows); } } // Render an empty row if ($rows == 0) { $html .= '' . __('crud.datagrid.empty') . ''; } return $html; } private function renderRow(Model $model): string { /** @var MiscModel|Entity|Location $model */ $useEntity = $this->getOption('disableEntity') !== true; // Should never happen... if ($useEntity && empty($model->entity)) { return ''; } $html = 'type) ? 'data-type="' . Str::slug($model->type) . '" ' : null) . ($useEntity ? 'data-entity-id="' . $model->entity->id . '" data-entity-type="' . $model->entity->entityType->code . '"' : null); /*if (!empty($this->options['row']) && !empty($this->options['row']['data'])) { foreach ($this->options['row']['data'] as $name => $data) { $html .= ' ' . $name . '="' . $data($model) . '"'; } }*/ if (! empty($this->nestedFilter) && method_exists($model, 'children')) { $html .= ' data-children="' . $model->children_count . '"'; } $html .= '>'; // Bulk if (auth()->check()) { $html .= ''; } foreach ($this->columns as $column) { $html .= $this->renderColumn($column, $model); } $html .= $model instanceof MiscModel ? $this->renderEntityActionRow($model) : $this->renderActionRow($model); return $html . ''; } protected function renderAdRow(int $rows): string { if (! $this->showAds()) { return ''; } $colspan = count($this->columns) + (auth()->check() ? 2 : 0); return '' . Blade::render('ads.table', ['campaign' => $this->campaign, 'rows' => $rows]) . ''; } protected function showAds(): bool { if (isset($this->showAds)) { return $this->showAds; } if (! config('ads.nitro.enabled')) { return $this->showAds = false; } if ($this->request->has('_showads')) { return $this->showAds = true; } if (isset($this->user)) { // Subscribed users don't have ads if ($this->user->isSubscriber()) { return $this->showAds = false; } // User has been created less than 24 hours ago if ($this->user->created_at->diffInHours(Carbon::now()) < 24) { return $this->showAds = false; } } // Premium campaigns don't have ads displayed to their members return $this->showAds = ! empty($this->campaign) && ! $this->campaign->boosted(); } /** * @param MiscModel|Journal|Location $model * @return string|null */ private function renderColumn(string|array $column, $model) { $class = null; $content = null; // Easy mode: String. Just return it, no need to make it complicated. if (is_string($column)) { // Just for name, a link to the view if ($column == 'name') { if (isset($this->bookmark)) { // Need to embed the _from=bookmark&bookmark= in the url somehow $content = $this->entityLink($model); } else { $content = $this->entityLink($model); } } elseif ($column === 'type') { $content = $model instanceof Entity ? $model->type : $model->entity->type; } else { // Handle boolean values (has, is) if ($this->isBoolean($column)) { $content = $model->{$column} ? '' : ''; } else { $content = ($model->{$column}); } $class = $this->hidden; } return '' . $content . ''; } // Check visibility if (isset($column['visible']) && $column['visible'] == false) { return null; } // Start with a pre-defined "type" if (! empty($column['type'])) { $type = $column['type']; if ($type == 'avatar') { $who = ! empty($column['parent']) ? $model->{$column['parent']} : $model; if ($who instanceof Entity) { Avatar::entity($who)->child($who->child); $who = $who->child; } else { Avatar::entity($who->entity)->child($who); } $class = ! empty($column['parent']) ? $this->hidden : $class; if (! empty($who)) { $bookmarkId = isset($this->bookmark) ? $this->bookmark->id : null; $route = $who->entity ? $who->entity->url('show', $bookmarkId ? ['bookmark' => $bookmarkId] : []) : $who->getLink(); $content = ''; } } elseif ($type == 'location') { $class = $this->hidden; // @phpstan-ignore-next-line if (method_exists($model, 'location') && $model->location && $model->location->entity) { $content = $this->entityLink($model->location->entity); } } elseif ($type == 'entityLocations') { $class = $this->hidden; $locations = []; if ($model->entity->locations->isNotEmpty()) { foreach ($model->entity->locations as $location) { $locations[] = $this->entityLink($location->entity); } } $content = implode(', ', $locations); } elseif ($type == 'character') { $class = $this->hidden; // @phpstan-ignore-next-line if (method_exists($model, 'character') && $model->character && $model->character->entity) { $content = $this->entityLink($model->character->entity); } } elseif ($type == 'organisation') { $class = $this->hidden; // @phpstan-ignore-next-line if (method_exists($model, 'organisation') && $model->organisation && $model->organisation->entity) { $content = $this->entityLink($model->organisation->entity); } } elseif ($type == 'entity') { $class = $this->hidden; if ($model->entity) { $content = $this->entityLink($model->entity); } } elseif ($type == 'parent') { $class = $this->hidden; if (! empty($this->nestedFilter)) { return null; } // @phpstan-ignore-next-line if ($model->parent && $model->parent->entity) { $content = $this->entityLink($model->parent->entity); } } elseif ($type == 'is_private') { // Viewer can't see private if (! isset($this->user) || ! $this->user->isAdmin()) { return null; } $content = $model->is_private ? ' ' . __('crud.is_private') . '' : null; $class = ' text-center'; } elseif ($type == 'reminder') { $class = $this->hidden . ' col-calendar-date'; /** @var Journal $model */ if ($model->entity->calendarDate && $model->entity->calendarDate->calendar && $model->entity->calendarDate->calendar->entity) { $reminder = $model->entity->calendarDate; $content = '' . $reminder->readableDate() . ''; } } else { // Exception $content = 'ERR_UNKNOWN_TYPE'; } } elseif (! empty($column['render'])) { // If it's not a type, do we have a renderer? $content = $column['render']($model, $column); $class = Arr::get($column, 'class', $this->hidden); } elseif (! empty($column['field'])) { // A field was given? This could be when a field needs another label than anticipated. $content = $model->{$column['field']}; $class = $this->hidden; } else { // I have no idea. $content = 'ERR_UNKNOWN'; } return '' . $content . ''; } /** * @param string $option * @return mixed|null */ private function getOption($option) { if (! empty($this->options[$option])) { return $this->options[$option]; } return null; } /** * @return string */ private function trans(string $field = '') { $crudFields = ['name', 'type']; if (in_array($field, $crudFields)) { return __('crud.fields.' . $field); } $trans = $this->getOption('trans'); if (! empty($trans)) { return __(mb_rtrim($trans, '.') . '.' . $field); } // No idea what to do! return $field; } private function renderEntityActionRow(MiscModel $model): string { $content = ''; $actions = $model->datagridActions($this->campaign); if (! empty($actions)) { $content = Blade::render('cruds.datagrids._row-actions', ['campaign' => $this->campaign, 'model' => $model, 'actions' => $actions]); } return '' . $content . ''; } private function renderActionRow($model): string { $actions = ''; if (isset($this->user) && $this->user->can('update', $model)) { $actions .= ' '; } return '' . $actions . ''; } /** * Determin if a column is a boolean column * * @return bool */ private function isBoolean(string $column) { return Str::startsWith($column, ['is_', 'has_']); } /** * Render the filter * * @return string */ protected function renderFilters() { return ''; } /** * Tell the rendered that this is a nested view */ public function nested(string $key = 'parent_id'): self { $this->nestedFilter = $key; return $this; } protected function entityLink(Model $model): string { $bookmarkId = isset($this->bookmark) ? $this->bookmark->id : null; if ($model instanceof Entity) { return Blade::renderComponent( new EntityLink($model, $this->campaign, bookmark: $bookmarkId) ); } elseif ($model->entity) {// @phpstan-ignore-line return Blade::renderComponent( new EntityLink($model->entity, $this->campaign, bookmark: $bookmarkId) ); } // @phpstan-ignore-next-line return '' . $model->name . ''; } } ================================================ FILE: app/Renderers/DatagridRenderer2.php ================================================ campaign($this->campaign); } $this->layout = $layout; return $this; } public function route(string $route, ?array $options = null): self { $this->routeName = $route; $this->routeOptions = $options; return $this; } public function actionParams(?array $options = null): self { $this->actionParams = $options; return $this; } /** * Set which element needs to be highlighted */ public function highlight(Closure $highlight): self { $this->highlight = $highlight; return $this; } public function getActionParams(): array { return $this->actionParams; } /** * @return Header[] */ public function headers(): array { $headers = []; $header = null; if ($this->hasBulks()) { $headers[] = (new Header('bulk'))->campaign($this->campaign); } foreach ($this->layout->visibleColumns() as $key => $col) { $headers[] = (new Header($col))->campaign($this->campaign); } if ($this->hasActions()) { $headers[] = (new Header([]))->campaign($this->campaign); } return $headers; } /** * @return array|Column[] */ public function columns(Model $model): array { $columns = []; if ($this->hasBulks()) { $columns[] = (new Checkbox($model, []))->campaign($this->campaign); } foreach ($this->layout->visibleColumns() as $key => $col) { $columns[] = (new Standard($model, $col))->campaign($this->campaign); } if ($this->hasActions() && auth()->check()) { $action = new Action($model, $this->layout->actions(), $this->permissions); $action->params($this->actionParams); $action->campaign($this->campaign); if ($action->hasDelete()) { $this->deleteForms[] = $model; } $columns[] = $action; } return $columns; } public function hasBulks(): bool { return ! empty($this->layout->bulks()) && auth()->check() && auth()->user()->isAdmin(); } public function bulks(): array { if (isset($this->bulks)) { return $this->bulks; } $this->bulks = []; $bulks = $this->layout->bulks(); foreach ($bulks as $bulk) { if (is_array($bulk)) { if (empty($bulk['can'])) { $this->bulks[] = $bulk; continue; } $can = $bulk['can']; // General campaign permission if (Str::startsWith($can, 'campaign:')) { $action = Str::afterLast($can, 'campaign:'); if (auth()->check() && auth()->user()->can($action, $this->campaign)) { $this->bulks[] = $bulk; } continue; } // More specific use cases? } elseif ($bulk === Layout::ACTION_DELETE) { if (auth()->check() && auth()->user()->isAdmin()) { $this->bulks[] = $bulk; } } elseif ($bulk === Layout::ACTION_EDIT) { $this->bulks[] = $bulk; } } return $this->bulks; } public function hasActions(): bool { return ! empty($this->layout->actions()); } public function deleteForms(): array { return $this->deleteForms; } public function permissions(bool $permissions): self { $this->permissions = $permissions; return $this; } /** * @return string|null */ public function routeName() { if (isset($this->routeName)) { return $this->routeName; } /** @var Route $route */ $route = request()->route(); return $route->getName(); } public function routeOptions(): array { return $this->routeOptions; } /** * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function paginationFilters(): array { $options = $this->routeOptions; foreach ($this->routeOptions as $k => $v) { if (Str::endsWith($k, '_id')) { continue; } elseif ($k === 'all') { continue; } unset($options[$k]); } if (request()->has('o')) { $options['o'] = request()->get('o'); } if (request()->has('k')) { $options['k'] = request()->get('k'); } if (request()->has('m')) { $options['m'] = request()->get('m'); } return $options; } /** * Allow the ajax init to have custom ordering * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ public function initOptions(array $config): array { if (request()->has('o')) { $config['o'] = request()->get('o'); } if (request()->has('k')) { $config['k'] = request()->get('k'); } return $config; } /** * Highlight a row if it matches the highlight closure */ public function isHighlighted(mixed $row): bool { if (! isset($this->highlight)) { return false; } return $this->highlight->call($row); } /** * Create a list of data-attributes from the row for the row */ public function rowAttributes(mixed $row): string { $attributes = []; foreach ($row->rowAttributes() as $attr => $val) { if ($val instanceof UnitEnum) { // @phpstan-ignore-next-line $val = $val->value; } $attributes[] = 'data-' . $attr . '="' . $val . '"'; } return implode(' ', $attributes); } protected function entityLink(Model $model): string { if ($model instanceof Entity) { return Blade::renderComponent( new EntityLink($model, $this->campaign) ); } // @phpstan-ignore-next-line return '' . $model->name . ''; } } ================================================ FILE: app/Renderers/Layouts/Ability/Ability.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.ability'), __('entities.ability')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Ability $model) { return $model->entity->type; }, ], 'ability' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('parent_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Ability/Entity.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => __('crud.fields.name'), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type_id', 'label' => __('crud.fields.category'), 'render' => function (EntityAbility $model) { return $model->entity->entityType->name(); }, ], 'visibility' => [ 'label' => __('crud.fields.visibility'), 'render' => Standard::VISIBILITY, ], 'tags' => [ 'render' => Standard::TAGS, 'with' => 'entity', ], ]; } /** * Available actions on each row */ public function actions(): array { return [ 'abilities.entities.actions.delete', ]; } } ================================================ FILE: app/Renderers/Layouts/Calendar/Reminder.php ================================================ [ 'render' => Standard::IMAGE, ], 'entity' => [ 'key' => 'entity.name', 'label' => __('fields.entry.label'), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type_id', 'label' => __('campaigns/categories.tab'), 'render' => function ($model) { return $model->remindable instanceof Post ? __('entities.post') . ' (' . $model->remindable->entity->entityType->name() . ')' : $model->remindable->entityType->name(); }, ], 'date' => [ 'key' => 'date', 'label' => __('events.fields.date'), 'render' => 'readableDate()', ], 'length' => [ 'key' => 'length', 'label' => __('calendars.fields.length'), 'render' => 'readableLength()', ], 'comment' => [ 'label' => '', 'render' => function ($model) { if (empty($model->comment)) { return ''; } return ''; }, ], 'recurring' => [ 'label' => '', 'render' => function ($model) { if (empty($model->is_recurring)) { return ''; } return ''; }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [self::ACTION_EDIT_DIALOG, self::ACTION_DELETE]; } public function bulks(): array { return [self::ACTION_EDIT, self::ACTION_DELETE]; } } ================================================ FILE: app/Renderers/Layouts/Campaign/CampaignImport.php ================================================ [ 'key' => 'user.name', 'label' => __('campaigns.members.fields.name'), 'render' => function (\App\Models\CampaignImport $model) { if (! $model->user_id) { return ''; } $html = '' . $model->user->name . ''; return $html; }, ], 'updated_at' => [ 'key' => 'updated_at', 'label' => __('campaigns/import.fields.updated'), 'render' => function (\App\Models\CampaignImport $model) { $html = '' . $model->updated_at->diffForHumans() . ''; return $html; }, ], 'status' => [ 'key' => 'status_id', 'label' => __('campaigns/plugins.fields.status'), 'render' => function (\App\Models\CampaignImport $model) { if ($model->status_id === CampaignImportStatus::FAILED) { if ($model->isCsv()) { return ' ' . __('campaigns/import.status.invalid') . ''; } return ' ' . __('campaigns/import.status.failed') . ''; } elseif ($model->status_id == CampaignImportStatus::QUEUED) { return ' ' . __('campaigns/import.status.queued') . ''; } elseif ($model->status_id == CampaignImportStatus::FINISHED) { return ' ' . __('campaigns/import.status.finished') . ''; } elseif ($model->status_id == CampaignImportStatus::READY) { return ' ' . __('campaigns/import.status.ready') . ''; } elseif ($model->status_id == CampaignImportStatus::VALIDATING) { return ' ' . __('campaigns/import.status.validating') . ''; } elseif ($model->status_id == CampaignImportStatus::PROCESSING) { return ' ' . __('campaigns/import.status.processing') . ''; } return ' ' . __('campaigns/import.status.running') . ''; }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ ]; } public function bulks(): array { return [ ]; } } ================================================ FILE: app/Renderers/Layouts/Campaign/CampaignRole.php ================================================ [ 'key' => 'name', 'label' => __('campaigns.roles.fields.name'), 'render' => function ($model) { /** @var \App\Models\CampaignRole $model */ $html = '' . $model->name . '
    '; return $html; }, ], 'users' => [ 'label' => __('campaigns.roles.fields.users'), 'render' => function ($model) { return Number::format($model->users_count ?? 0); }, ], 'type' => [ 'key' => 'is_admin', 'label' => __('campaigns.roles.fields.type'), 'render' => function ($model) { /** @var \App\Models\CampaignRole $model */ $html = __( 'campaigns.roles.types.' . ($model->isAdmin() ? 'owner' : ($model->isPublic() ? 'public' : 'standard')), ); return $html; }, ], 'permissions' => [ 'label' => __('campaigns.roles.fields.permissions'), 'render' => Standard::VIEW, 'with' => 'campaigns.roles.rows.permissions', ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ 'update' => [ 'label' => 'campaigns.roles.actions.rename', 'icon' => 'fa-regular fa-edit', 'can' => 'update', 'type' => 'dialog-ajax', 'route' => 'campaign_roles.edit', ], 'show' => [ 'label' => 'campaigns.roles.actions.permissions', 'icon' => 'fa-regular fa-cog', 'route' => 'campaign_roles.show', ], 'duplicate' => [ 'label' => 'campaigns.roles.actions.duplicate', 'icon' => 'fa-regular fa-copy', 'can' => 'update', 'type' => 'dialog-ajax', 'route' => 'campaign_roles.duplicate', ], Layout::ACTION_DELETE, ]; } public function bulks(): array { return [self::ACTION_DELETE]; } } ================================================ FILE: app/Renderers/Layouts/Campaign/CampaignUser.php ================================================ [ 'label' => '', 'render' => function (\App\Models\CampaignUser $model) { if ($model->user->hasAvatar()) { return '
    '; } return '
    ' . $model->user->initials() . '
    '; }, ], 'name' => [ 'key' => 'user.name', 'label' => __('campaigns.members.fields.name'), 'render' => function (\App\Models\CampaignUser $model) { $html = '' . $model->user->name . ''; if ($model->user->isBanned()) { $html .= ''; } return $html; }, ], 'roles' => [ 'key' => 'user.roles', 'label' => __('campaigns.members.fields.roles'), 'render' => function (\App\Models\CampaignUser $model) { /** @var CampaignRole[] $roles */ $roles = $model->user->campaignRoles->where( 'campaign_id', $this->campaign->id, ); $roleLinks = []; foreach ($roles as $role) { if (auth()->user()->isAdmin()) { $roleLinks[] = '' . $role->name . ''; } else { $roleLinks[] = $role->name; } } $html = (string) implode(', ', $roleLinks); if (auth()->user()->can('update', $model)) { $html .= ' '; } return $html; }, ], 'created_at' => [ 'key' => 'created_at', 'label' => __('campaigns.members.fields.joined'), 'render' => Standard::SINCE, ], 'last_login' => [ 'key' => 'user.last_login', 'label' => __('campaigns.members.fields.last_login'), 'render' => function (\App\Models\CampaignUser $model) { if ( $model->user->has_last_login_sharing && ! empty($model->user->last_login_at) ) { return Blade::renderComponent( new Since( date: $model->user->last_login_at, withTime: false, ), ); } return ''; }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ 'switch' => [ 'label' => 'campaigns.members.actions.switch', 'icon' => 'fa-regular fa-sign-in-alt', 'can' => 'switch', 'route' => 'identity.switch', ], 'delete' => [ 'label' => 'campaigns.members.actions.remove', 'icon' => 'fa-regular fa-trash-can', 'can' => 'delete', 'type' => 'dialog-ajax', 'route' => 'campaign_users.delete', 'css' => 'text-error-content hover:bg-error', ], ]; } public function bulks(): array { return []; } } ================================================ FILE: app/Renderers/Layouts/Campaign/Plugin.php ================================================ [ 'key' => 'name', 'label' => __('campaigns/plugins.fields.name'), 'render' => function (\App\Models\Plugin $model) { return '' . $model->name . ''; }, ], 'update' => [ 'key' => 'has_update', 'label' => __('campaigns/plugins.info.updates'), 'render' => function ($model) { $base = ''; if ($model->obsolete()) { $base = ''; } if (! $model->has_update) { return $base; } if (! auth()->check() || ! auth()->user()->can('recover', $this->campaign)) { return $base; } return '' . __('campaigns/plugins.actions.update_available') . ' ' . $base; }, ], 'type' => [ 'key' => 'type_id', 'label' => __('campaigns/plugins.fields.type'), 'render' => function ($model) { return __('campaigns/plugins.types.' . $model->type()); }, ], 'status' => [ 'key' => 'pivot_is_active', 'label' => __('campaigns/plugins.fields.status'), 'render' => function ($model) { if (! $model->isTheme()) { return '' . __('campaigns/plugins.status.always') . ''; } if ($model->pivot->is_active) { return '' . __('campaigns/plugins.status.enabled') . ''; } return '' . __('campaigns/plugins.status.disabled') . ''; }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ 'update' => [ 'label' => 'campaigns/plugins.actions.update', 'icon' => 'fa-regular fa-download', 'can' => 'update', 'type' => 'dialog-ajax', 'route' => 'campaign_plugins.update-info', ], 'changelog' => [ 'label' => 'campaigns/plugins.actions.changelog', 'icon' => 'fa-regular fa-list', 'can' => 'changelog', 'type' => 'dialog-ajax', 'route' => 'campaign_plugins.update-info', ], 'disable' => [ 'can' => 'disable', 'route' => 'campaign_plugins.disable', 'label' => 'campaigns/plugins.actions.disable', 'icon' => 'fa-regular fa-ban', ], 'enable' => [ 'can' => 'enable', 'route' => 'campaign_plugins.enable', 'label' => 'campaigns/plugins.actions.enable', 'icon' => 'fa-regular fa-check', ], 'import' => [ 'can' => 'import', 'route' => 'campaign_plugins.confirm-import', 'type' => 'dialog-ajax', 'label' => 'campaigns/plugins.actions.import', 'icon' => 'fa-regular fa-clone', ], Layout::ACTION_DELETE, ]; } public function bulks(): array { return [ [ 'action' => 'enable', 'label' => 'campaigns/plugins.actions.bulks.enable', 'icon' => 'fa-regular fa-check', 'can' => 'campaign:recover', ], [ 'action' => 'disable', 'label' => 'campaigns/plugins.actions.bulks.disable', 'icon' => 'fa-regular fa-ban', 'can' => 'campaign:recover', ], [ 'action' => 'update', 'label' => 'campaigns/plugins.actions.bulks.update', 'icon' => 'fa-regular fa-download', 'can' => 'campaign:recover', ], self::ACTION_DELETE, ]; } } ================================================ FILE: app/Renderers/Layouts/Campaign/PostRecovery.php ================================================ [ 'key' => 'name', 'label' => __('crud.fields.name'), ], 'deleted' => [ 'key' => 'deleted_at', 'label' => __('campaigns/recovery.fields.deleted'), 'class' => self::ONLY_DESKTOP, 'render' => function ($post) { return $post->deleted_at->diffForHumans(); }, ], ]; return $columns; } /** * Bulk actions */ public function bulks(): array { return [ [ 'action' => 'recover', 'label' => 'campaigns/recovery.actions.recover', 'icon' => 'fa-regular fa-history', 'can' => 'campaign:recover', ], ]; } } ================================================ FILE: app/Renderers/Layouts/Campaign/Recovery.php ================================================ [ 'class' => 'avatar w-14', 'label' => '', 'render' => function ($entity) { $child = $entity->child()->withTrashed()->first(); if (empty($child)) { return ''; } return '
    '; }, ], 'name' => [ 'key' => 'name', 'label' => __('crud.fields.name'), ], 'type' => [ 'key' => 'type_id', 'label' => __('campaigns/categories.tab'), 'render' => function ($entity) { return $entity->entityType->singular(); }, ], 'deleted' => [ 'key' => 'deleted_at', 'label' => __('campaigns/recovery.fields.deleted'), 'class' => self::ONLY_DESKTOP, 'render' => function ($model) { return $model->deleted_at->diffForHumans(); }, ], ]; return $columns; } /** * Bulk actions */ public function bulks(): array { return [ [ 'action' => 'recover', 'label' => 'campaigns/recovery.actions.recover', 'icon' => 'fa-regular fa-history', 'can' => 'campaign:recover', ], ]; } } ================================================ FILE: app/Renderers/Layouts/Campaign/Theme.php ================================================ [ 'key' => 'order', 'label' => __('campaigns/styles.fields.order'), 'render' => function ($model) { return $model->order ? '#' . $model->order : null; }, ], 'name' => [ 'key' => 'name', 'label' => __('campaigns/styles.fields.name'), 'render' => function ($model) { return '' . $model->name . ''; }, ], 'length' => [ 'label' => __('campaigns/styles.fields.length'), 'class' => self::ONLY_DESKTOP, 'render' => 'length()', ], 'modified' => [ 'key' => 'updated_at', 'label' => __('campaigns/styles.fields.modified'), 'class' => self::ONLY_DESKTOP, 'render' => function ($model) { return $model->updated_at->diffForHumans(); }, ], 'enabled' => [ 'key' => 'is_enabled', 'label' => __('campaigns/styles.fields.is_enabled'), 'render' => function (CampaignStyle $model) { return $model->is_enabled ? '' . __('campaigns/styles.fields.is_enabled') . '' : null; }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ 'disable' => [ 'can' => 'disable', 'route' => 'campaign_styles.toggle', 'label' => 'campaigns/styles.actions.disable', 'icon' => 'fa-regular fa-ban', ], 'enable' => [ 'can' => 'enable', 'route' => 'campaign_styles.toggle', 'label' => 'campaigns/styles.actions.enable', 'icon' => 'fa-regular fa-check', ], self::ACTION_EDIT, self::ACTION_DELETE, ]; } public function bulks(): array { return [ [ 'action' => 'enable', 'label' => 'campaigns/styles.actions.enable', 'icon' => 'fa-regular fa-check', ], [ 'action' => 'disable', 'label' => 'campaigns/styles.actions.disable', 'icon' => 'fa-regular fa-ban', ], self::ACTION_DELETE, ]; } } ================================================ FILE: app/Renderers/Layouts/Campaign/Webhook.php ================================================ [ 'key' => 'action', 'label' => __('campaigns/webhooks.fields.event'), 'render' => function ($model) { /** @var \App\Models\Webhook $model */ return $model->actionKey(); }, ], 'type' => [ 'key' => 'type', 'label' => __('campaigns/webhooks.fields.type'), 'render' => function ($model) { return $model->typeKey(); }, ], 'message' => [ 'label' => __('campaigns/webhooks.fields.message'), 'render' => function ($model) { /** @var \App\Models\Webhook $model */ return '
    ' . Str::limit(strip_tags($model->message ?? ''), 30) . '
    '; }, ], 'url' => [ 'label' => __('campaigns/webhooks.fields.url'), 'render' => function ($model) { return '
    ' . $model->shortUrl() . '
    '; }, ], 'status' => [ 'key' => 'status', 'label' => __('campaigns/webhooks.fields.enabled'), 'render' => function (\App\Models\Webhook $model) { if ($model->status) { return '' . __('campaigns/webhooks.fields.enabled') . ''; } return ''; }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ 'update' => [ 'label' => 'crud.update', 'icon' => 'fa-regular fa-edit', 'route' => 'webhooks.edit', ], 'test' => [ 'label' => 'campaigns/webhooks.actions.test', 'icon' => 'fa-regular fa-webhook', 'route' => 'webhooks.test', ], 'disable' => [ 'can' => 'disable', 'route' => 'webhooks.toggle', 'label' => 'campaigns/webhooks.actions.bulks.disable', 'icon' => 'fa-regular fa-ban', ], 'enable' => [ 'can' => 'enable', 'route' => 'webhooks.toggle', 'label' => 'campaigns/webhooks.actions.bulks.enable', 'icon' => 'fa-regular fa-check', ], Layout::ACTION_DELETE, ]; } public function bulks(): array { return [ [ 'action' => 'enable', 'label' => 'campaigns/webhooks.actions.bulks.enable', 'icon' => 'fa-regular fa-check', 'can' => 'campaign:update', ], [ 'action' => 'disable', 'label' => 'campaigns/webhooks.actions.bulks.disable', 'icon' => 'fa-regular fa-ban', 'can' => 'campaign:update', ], self::ACTION_DELETE, ]; } } ================================================ FILE: app/Renderers/Layouts/Character/Organisation.php ================================================ [ 'render' => Standard::IMAGE, 'with' => ['target' => 'organisation'], ], 'organisation' => [ 'key' => 'organisation.name', 'label' => Module::singular( config('entities.ids.organisation'), __('entities.organisation'), ), 'render' => Standard::ENTITYLINK, 'with' => 'organisation', ], 'role' => [ 'key' => 'role', 'label' => __('organisations.members.fields.role'), 'render' => function ($model) { $icon = ''; if ($model->inactive()) { $icon = ''; } elseif ($model->unknown()) { $icon = ''; } $private = ''; if ($model->is_private) { $private = ' '; } return $icon . $private . $model->role; }, ], 'locations' => [ 'key' => 'locations.name', 'label' => Module::plural( config('entities.ids.location'), __('entities.locations'), ), 'render' => function ($model) { $locations = []; if ( $model->organisation?->entity?->locations?->isNotEmpty() ) { foreach ( $model->organisation->entity->locations as $location ) { if ($location->entity) { $locations[] = '' . e($location->name) . ''; } } } return implode(', ', $locations); }, ], 'pinned' => [ 'label' => '' . __('organisations.members.fields.pinned') . '', 'render' => function ($model) { if (! $model->pinned()) { return ''; } if ($model->pinnedToCharacter()) { return ''; } elseif ($model->pinnedToOrganisation()) { return ''; } return ''; }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [self::ACTION_EDIT_DIALOG, self::ACTION_DELETE]; } } ================================================ FILE: app/Renderers/Layouts/Columns/Action.php ================================================ 'calendar'] for workflow */ protected array $params = []; public function __construct(Model $model, array $config, bool $permissions) { parent::__construct($model, $config); // Validate actions? foreach ($this->config as $action) { if (in_array($action, [Layout::ACTION_EDIT, Layout::ACTION_COPY, Layout::ACTION_EDIT_DIALOG])) { if (! $permissions) { $this->actions[] = $action; } elseif (auth()->user()->can('update', $this->model)) { $this->actions[] = $action; } } elseif ($action == Layout::ACTION_DELETE) { if (! $permissions) { $this->actions[] = $action; } elseif (auth()->user()->can('delete', $this->model)) { $this->actions[] = $action; } } elseif (is_string($action) && view()->exists($action)) { $this->actions[] = $action; } elseif (is_array($action)) { // Custom, to do? $this->import($action); } } } public function params(array $params): self { $this->params = $params; return $this; } public function __toString(): string { $html = view('layouts.datagrid.actions') ->with('actions', $this->actions) ->with('model', $this->model) ->with('params', $this->params) ->with('campaign', $this->campaign) ->render(); return $html; } public function css(): ?string { return 'text-center table-actions w-8 h-8'; } public function hasDelete(): bool { return in_array(Layout::ACTION_DELETE, $this->actions); } protected function import(array $action): self { // No auth check? We good. if (! Arr::has($action, 'can')) { $this->actions[] = $action; return $this; } if (auth()->user()->can($action['can'], $this->model)) { $this->actions[] = $action; } return $this; } } ================================================ FILE: app/Renderers/Layouts/Columns/Checkbox.php ================================================ model->id . '" />'; } } ================================================ FILE: app/Renderers/Layouts/Columns/Column.php ================================================ model = $model; $this->config = $config; } public function __toString(): string { return ''; } public function css(): ?string { $default = null; if (Arr::get($this->config, 'render') === Standard::IMAGE) { $default = 'avatar w-14'; } if (empty($this->config['class'])) { return $default; } return (string) $this->config['class']; } } ================================================ FILE: app/Renderers/Layouts/Columns/Standard.php ================================================ config['render']) && isset($this->config['renter'])) { return 'Misspelled _render_'; } if (! isset($this->config['render'])) { return (string) $this->model->{$this->config['key']}; } $render = $this->config['render']; if ($render instanceof Closure) { return (string) $render($this->model); } elseif ($this->defined($render)) { return $this->view($render, Arr::get($this->config, 'with')); } $method = mb_substr($render, 0, -2); if (Str::endsWith($render, '()') && method_exists($this->model, $method)) { return (string) $this->model->$method(); } return $render . '???'; } /** * If this is a defined view */ protected function defined(string $render): bool { return in_array($render, [ self::CHARACTER, self::IMAGE, self::TAGS, self::ENTITYLINK, self::LOCATION, self::ENTITY_LOCATIONS, self::ENTITYLIST, self::ParentLink, self::VISIBILITY, self::VISIBILITY_PIVOT, self::DATE, self::MENTION_LINK, self::VIEW, self::SINCE, ]); } /** * Render a defined view */ protected function view(string $view, mixed $extra = null): string { return view('layouts.datagrid.rows.' . $view) ->with('model', $this->model) ->with('with', $extra) ->with('key', $this->config['key'] ?? '') ->with('campaign', $this->campaign) ->render(); } } ================================================ FILE: app/Renderers/Layouts/Creature/Creature.php ================================================ [ 'render' => Standard::IMAGE, ], 'creature_id' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.creature'), __('entities.creature')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Creature $model) { return $model->entity->type; }, ], 'creature' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('parent_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Entity/Children.php ================================================ [ 'render' => Standard::IMAGE, ], 'race_id' => [ 'key' => 'name', 'label' => $this->entityType->name(), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), ], 'parent' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return request()->get('m') != 0; }, ], 'children' => [ 'label' => __('tags.fields.children'), 'render' => function ($model) { return $model->children->count(); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Entity/Relation.php ================================================ [ 'key' => 'relation', 'label' => __('entities/relations.fields.role'), 'render' => function ($relation) { $icon = ''; if ($relation->isPinned()) { $icon = ' '; } $text = e($relation->relation); if ( auth()->check() && auth()->user()->can('update', $relation) ) { $url = route( $relation->url('edit'), $relation->routeParams([ 'campaign' => $relation->campaign_id, ]), ); $text = '' . $text . ''; } return $icon . $text; }, ], 'target' => [ 'key' => 'target.name', 'label' => __('fields.entry.label'), 'render' => Standard::ENTITYLINK, 'with' => 'target', ], 'location' => [ 'label' => __('entities.location'), 'render' => Standard::LOCATION, 'with' => 'target', ], 'attitude' => [ 'key' => 'attitude', 'label' => __('entities/relations.fields.attitude'), 'class' => 'hidden-xs hidden-sm', 'render' => function ($relation) { /** @var \App\Models\Relation $relation */ $icon = ''; if (empty($relation->colour)) { return $relation->attitude; } $html = '
    '; $icon = '
    '; return $html . $icon . '
    ' . $relation->attitude . '
    '; }, ], 'visibility' => [ 'label' => '', 'render' => Standard::VISIBILITY, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [self::ACTION_EDIT_DIALOG, self::ACTION_DELETE]; } } ================================================ FILE: app/Renderers/Layouts/Entity/Reminder.php ================================================ [ 'key' => 'calendar.name', 'label' => __('entities.calendar'), 'render' => Standard::ENTITYLINK, 'with' => 'calendar', ], 'date' => [ 'key' => 'date', 'label' => __('events.fields.date'), 'render' => function (Model $reminder) { $params = '?year=' . $reminder->year . '&month=' . $reminder->month; return '' . $reminder->readableDate() . ''; }, ], 'length' => [ 'key' => 'length', 'label' => __('calendars.fields.length'), 'render' => function (Model $reminder) { return trans_choice('calendars.fields.length_days', $reminder->length, ['count' => $reminder->length]); }, ], 'comment' => [ 'key' => 'comment', 'label' => '' . __('calendars.fields.comment') . '', 'render' => function (Model $reminder) { return '

    ' . $reminder->comment . '

    '; }, ], 'recurring' => [ 'label' => '' . __('calendars.fields.is_recurring') . '', 'render' => function (Model $reminder) { if ($reminder->is_recurring) { return ''; } if ($reminder->isBirth()) { return ''; } elseif ($reminder->isDeath()) { return ''; } elseif ($reminder->isFounded()) { return ''; } }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ self::ACTION_EDIT_DIALOG, self::ACTION_DELETE, ]; } } ================================================ FILE: app/Renderers/Layouts/Event/Event.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.event'), __('entities.event')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Event $model) { return $model->entity->type; }, ], 'date' => [ 'key' => 'date', 'label' => __('events.fields.date'), ], 'event' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('parent_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Family/Character.php ================================================ [ 'render' => Standard::IMAGE, ], 'character_id' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.character'), __('entities.character')), 'render' => Standard::CHARACTER, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Character $model) { return $model->entity->type; }, ], 'locations' => [ 'label' => Module::plural(config('entities.ids.location'), __('entities.locations')), 'render' => Standard::ENTITY_LOCATIONS, ], 'families' => [ 'label' => Module::plural(config('entities.ids.family'), __('entities.families')), 'render' => Standard::ENTITYLIST, 'with' => ['characterFamilies', 'family'], 'visible' => function () { return ! request()->has('family_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Family/Family.php ================================================ [ 'render' => Standard::IMAGE, ], 'family_id' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.family'), __('entities.family')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Family $model) { return $model->entity->type; }, ], 'location' => [ 'key' => 'location.name', 'label' => Module::singular(config('entities.ids.location'), __('entities.location')), 'render' => Standard::LOCATION, ], 'family' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('parent_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Header.php ================================================ data = $data; } public function __toString(): string { if (empty($this->data)) { return ''; } if (empty($this->data['label'])) { if (Arr::get($this->data, 'render') === Standard::IMAGE) { return ''; } if (Arr::get($this->data, 'render') === Standard::TAGS) { return __('entities.tags'); } return ! isset($this->data['label']) ? 'no label' : ''; } if (! $this->sortable()) { return $this->data['label']; } // Prepare some data $this->orderField = request()->get('k'); $this->orderDir = request()->get('o'); // We have some HTML going on, let blade render it try { return view('layouts.datagrid._head') ->with('head', $this) ->render(); } catch (Exception $e) { throw $e; // return $e->getMessage(); } } public function css(): ?string { $default = null; if (Arr::get($this->data, 'render') === Standard::IMAGE) { $default = 'avatar w-14'; } if (empty($this->data['class'])) { return $default; } return $this->data['class']; } public function bulk(): bool { return ! is_array($this->data) && $this->data === 'bulk'; } public function sortable(): bool { return ! empty($this->data['key']) && auth()->check() && ! empty(request()->route()); } public function icon(): ?string { if ($this->orderField != $this->data['key']) { return ''; } if ($this->orderDir == 'asc') { return 'fa-regular fa-arrow-up-a-z'; } return 'fa-regular fa-arrow-down-z-a'; } public function route(): string { $route = Datagrid::routeName(); $options = [ 'campaign' => $this->campaign, 'k' => $this->data['key'], 'o' => 'asc', ]; if ($this->orderField == $this->data['key']) { // Already desc? we want to reset if ($this->orderDir == 'desc') { $options = ['campaign' => $this->campaign]; } else { $options['o'] = 'desc'; } } $options = array_merge($options, Datagrid::routeOptions()); if (request()->has('page')) { $options['page'] = (int) request()->get('page'); } try { return route($route, $options); } catch (Exception $e) { throw $e; // return 'invalid'; } } public function label(): string { return $this->data['label']; } } ================================================ FILE: app/Renderers/Layouts/Item/Entity.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => 'fields.entry.label', 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type_id', 'label' => 'campaigns/categories.tab', 'render' => function ($model) { return $model->entityType->name(); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Item/Item.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.item'), __('entities.item')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Item $model) { return $model->entity->type; }, ], 'price' => [ 'key' => 'price', 'label' => __('items.fields.price'), ], 'size' => [ 'key' => 'size', 'label' => __('items.fields.size'), ], 'weight' => [ 'key' => 'weight', 'label' => __('items.fields.weight'), ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Journal/Journal.php ================================================ [ 'render' => Standard::IMAGE, ], 'journal' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.journal'), __('entities.journal')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Journal $model) { return $model->entity->type; }, ], 'date' => [ 'key' => 'date', 'label' => __('journals.fields.date'), 'render' => function ($model) { return Blade::renderComponent( new Date(date: $model->date) ); }, ], 'author' => [ 'key' => 'author.name', 'label' => __('journals.fields.author'), 'render' => Standard::ENTITYLINK, 'with' => 'author', ], 'parent' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('parent_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Layout.php ================================================ visibleColumns !== false) { return $this->visibleColumns; } $this->visibleColumns = []; foreach ($this->columns() as $key => $column) { if (! isset($column['visible'])) { $this->visibleColumns[] = $column; continue; } $condition = $column['visible'](); if (! $condition) { continue; } $this->visibleColumns[] = $column; } return $this->visibleColumns; } protected function entityLink(Model|MiscModel|Entity $model): string { if ($model instanceof Entity) { return Blade::renderComponent( new EntityLink($model, $this->campaign) ); } // @phpstan-ignore-next-line return '' . $model->name . ''; } } ================================================ FILE: app/Renderers/Layouts/Location/Character.php ================================================ [ 'render' => Standard::IMAGE, ], 'character_id' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.character'), __('entities.character')), 'render' => Standard::CHARACTER, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Character $model) { return $model->entity->type; }, ], 'locations' => [ 'key' => 'locations.name', 'label' => Module::plural(config('entities.ids.location'), __('entities.locations')), 'render' => Standard::ENTITY_LOCATIONS, 'visible' => function () { return ! request()->has('location_id'); }, ], 'families' => [ 'label' => Module::plural(config('entities.ids.family'), __('entities.families')), 'render' => Standard::ENTITYLIST, 'with' => ['characterFamilies', 'family'], ], 'races' => [ 'label' => Module::plural(config('entities.ids.race'), __('entities.races')), 'class' => self::ONLY_DESKTOP, 'render' => Standard::ENTITYLIST, 'with' => ['characterRaces', 'race'], ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Location/Event.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular( config('entities.ids.event'), __('entities.event') ), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Event $model) { return $model->entity->type; }, ], 'date' => [ 'key' => 'date', 'label' => __('events.fields.date'), ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Location/Location.php ================================================ [ 'render' => Standard::IMAGE, ], 'location_id' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.location'), __('entities.location')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Location $model) { return $model->entity->type; }, ], 'location' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('parent_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Location/Quest.php ================================================ [ 'key' => 'name', 'label' => __('crud.fields.name'), 'render' => function ($model) { return '' . $model->name . ''; }, ], 'position' => [ 'key' => 'position', 'label' => __('maps/groups.fields.position'), ], 'shown' => [ 'label' => __('maps/groups.fields.is_shown'), 'render' => function ($model) { if ($model->is_shown) { return ''; } return ''; }, ], 'parent' => [ 'label' => __('maps/groups.fields.parent'), 'key' => 'parent_id', 'render' => function ($model) { if ($model->parent) { return '' . $model->parent->name . ''; } return ''; }, ], 'visibility' => [ 'label' => __('crud.fields.visibility'), 'render' => Standard::VISIBILITY, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ self::ACTION_EDIT_DIALOG, self::ACTION_DELETE, ]; } public function bulks(): array { return [ self::ACTION_EDIT_DIALOG, self::ACTION_DELETE, ]; } } ================================================ FILE: app/Renderers/Layouts/Map/Layer.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => __('crud.fields.name'), 'render' => function ($model) { return '' . $model->name . ''; }, ], 'position' => [ 'key' => 'position', 'label' => __('maps/layers.fields.position'), ], 'type' => [ 'label' => __('maps/layers.fields.type'), 'render' => function ($model) { return __('maps/layers.short_types.' . $model->typeName()); }, ], 'visibility' => [ 'label' => __('crud.fields.visibility'), 'render' => Standard::VISIBILITY, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ self::ACTION_EDIT, self::ACTION_DELETE, ]; } public function bulks(): array { return [ self::ACTION_EDIT, self::ACTION_DELETE, ]; } } ================================================ FILE: app/Renderers/Layouts/Map/Map.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.map'), __('entities.map')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Map $model) { return $model->entity->type; }, ], 'map' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('map_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Map/Marker.php ================================================ [ 'key' => 'name', 'label' => __('crud.fields.name'), 'render' => function ($model) { return $model->markerLink(); }, ], 'entity_id' => [ 'label' => __('fields.description.label'), 'render' => Standard::ENTITYLINK, ], 'groups' => [ 'label' => __('maps/markers.fields.group'), 'render' => function ($model) { return $model->group?->name; }, ], 'type' => [ 'label' => __('crud.fields.type'), 'render' => function ($model) { return $model->typeLabel(); }, ], 'icon' => [ 'label' => __('maps/markers.fields.icon'), 'render' => function ($model) { return $model->datagridMarkerIcon(); }, ], 'visibility' => [ 'label' => __('crud.fields.visibility'), 'render' => Standard::VISIBILITY, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ self::ACTION_EDIT, self::ACTION_COPY, self::ACTION_DELETE, ]; } public function bulks(): array { return [ self::ACTION_EDIT, self::ACTION_DELETE, ]; } } ================================================ FILE: app/Renderers/Layouts/Mention/Mention.php ================================================ [ 'key' => 'name', 'label' => __('entities/mentions.fields.element'), 'render' => Standard::MENTION_LINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (EntityMention $model) { if ($model->isCampaign()) { return __('entities.campaign'); } $base = __('crud.hidden'); if ($model->entity) { $base = $model->entity->entityType->name(); } if ($model->isTimelineElement()) { return $base . ' (' . __('entities.timeline_element') . ')'; } elseif ($model->isQuestElement()) { return $base . ' (' . __('entities.quest_element') . ')'; } elseif ($model->isPost()) { return $base . ' (' . __('entities.post') . ')'; } return $base; }, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Note/Note.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.note'), __('entities.note')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Note $model) { return $model->entity->type; }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Organisation/Member.php ================================================ [ 'render' => Standard::IMAGE, 'with' => ['target' => 'character'], ], 'character' => [ 'key' => 'character.name', 'label' => Module::singular( config('entities.ids.character'), __('entities.character'), ), 'render' => Standard::CHARACTER, ], 'role' => [ 'key' => 'role', 'label' => __('organisations.members.fields.role'), 'render' => function ($model) { $icon = ''; if ($model->inactive()) { $icon = ''; } elseif ($model->unknown()) { $icon = ''; } $private = ''; if ($model->is_private) { $private = ' '; } return $icon . $private . $model->role; }, ], 'superior' => [ 'key' => 'parent_id', 'label' => __('organisations.members.fields.parent'), 'render' => Standard::ENTITYLINK, 'with' => 'superior', ], 'locations' => [ 'label' => Module::plural( config('entities.ids.location'), __('entities.locations'), ), 'class' => self::ONLY_DESKTOP, 'render' => Standard::ENTITY_LOCATIONS, 'with' => 'character.entity', ], 'pinned' => [ 'label' => '', 'render' => function ($model) { if (! $model->pinned()) { return ''; } if ($model->pinnedToCharacter()) { return ''; } elseif ($model->pinnedToOrganisation()) { return ''; } return ''; }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [self::ACTION_EDIT_DIALOG, self::ACTION_DELETE]; } } ================================================ FILE: app/Renderers/Layouts/Organisation/Organisation.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.organisation'), __('entities.organisation')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Organisation $model) { return $model->entity->type; }, ], 'organisation' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('parent_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Quest/Quest.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.quest'), __('entities.quest')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Quest $model) { return $model->entity->type; }, ], 'date' => [ 'key' => 'date', 'label' => __('quests.fields.date'), 'render' => Standard::DATE, ], 'completed' => [ 'key' => 'status', 'label' => __('quests.fields.status'), 'render' => function (\App\Models\Quest $model) { if ($model->entity->status) { return ''; } return ''; }, ], 'location' => [ 'key' => 'location.name', 'label' => Module::singular(config('entities.ids.location'), __('entities.location')), 'render' => Standard::LOCATION, 'visible' => function () { return ! request()->has('location_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Race/Character.php ================================================ [ 'render' => Standard::IMAGE, ], 'character_id' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.character'), __('entities.character')), 'render' => Standard::CHARACTER, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Character $model) { return $model->entity->type; }, ], 'locations' => [ 'label' => Module::plural(config('entities.ids.location'), __('entities.locations')), 'render' => Standard::ENTITY_LOCATIONS, ], 'races' => [ 'label' => Module::plural(config('entities.ids.race'), __('entities.races')), 'class' => self::ONLY_DESKTOP, 'render' => Standard::ENTITYLIST, 'with' => ['characterRaces', 'race'], 'visible' => function () { return ! request()->has('race_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; } } ================================================ FILE: app/Renderers/Layouts/Race/Race.php ================================================ [ 'render' => Standard::IMAGE, ], 'race_id' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.race'), __('entities.race')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Race $model) { return $model->entity->type; }, ], 'race' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, ], 'characters' => [ 'label' => Module::plural(config('entities.ids.character'), __('entities.characters')), 'render' => function ($model) { return $model->characters->count(); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Tag/Entity.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => __('fields.entry.label'), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), ], 'module' => [ 'key' => 'type_id', 'label' => __('campaigns/categories.tab'), 'render' => function ($model) { return $model->entityType->name(); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Tag/Post.php ================================================ [ 'render' => Standard::IMAGE, ], 'entity' => [ 'key' => 'entity.name', 'label' => __('fields.entry.label'), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type_id', 'label' => __('campaigns/categories.tab'), 'render' => function ($model) { return $model->entity->entityType->name(); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Tag/Tag.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.tag'), __('entities.tag')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Tag $model) { return $model->entity->type; }, ], 'colour' => [ 'key' => 'colour', 'label' => __('crud.fields.colour'), 'render' => function (\App\Models\Tag $tag) { if (! $tag->hasColour()) { return ''; } return '
    '; }, ], 'tag' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('tag_id'); }, ], ]; return $columns; } } ================================================ FILE: app/Renderers/Layouts/Timeline/Era.php ================================================ [ 'key' => 'name', 'label' => __('crud.fields.name'), 'render' => function (TimelineEra $model) { return '' . $model->name . ''; }, ], 'abbreviation' => [ 'key' => 'abbreviation', 'label' => __('timelines/eras.fields.abbreviation'), ], 'position' => [ 'key' => 'position', 'label' => __('maps/groups.fields.position'), ], 'start_year' => [ 'key' => 'start_year', 'label' => __('timelines/eras.fields.start_year'), 'render' => function (TimelineEra $era) { if (empty($era->start_year)) { return ''; } return Number::format($era->start_year ?? 0); }, ], 'end_year' => [ 'key' => 'end_year', 'label' => __('timelines/eras.fields.end_year'), 'render' => function (TimelineEra $era) { if (empty($era->end_year)) { return ''; } return Number::format($era->end_year); }, ], 'is_collapsed' => [ 'key' => 'is_collapsed', 'label' => __('timelines/eras.fields.is_collapsed'), 'render' => function (TimelineEra $model) { if ($model->is_collapsed) { return ''; } return ''; }, ], ]; return $columns; } /** * Available actions on each row */ public function actions(): array { return [ self::ACTION_EDIT, self::ACTION_DELETE, ]; } public function bulks(): array { return [ self::ACTION_DELETE, ]; } } ================================================ FILE: app/Renderers/Layouts/Timeline/Timeline.php ================================================ [ 'render' => Standard::IMAGE, ], 'name' => [ 'key' => 'name', 'label' => Module::singular(config('entities.ids.timeline'), __('entities.timeline')), 'render' => Standard::ENTITYLINK, ], 'type' => [ 'key' => 'type', 'label' => __('crud.fields.type'), 'render' => function (\App\Models\Timeline $model) { return $model->entity->type; }, ], 'timeline' => [ 'key' => 'parent.name', 'label' => __('crud.fields.parent'), 'render' => Standard::ParentLink, 'visible' => function () { return ! request()->has('parent_id'); }, ], 'tags' => [ 'render' => Standard::TAGS, ], ]; return $columns; } } ================================================ FILE: app/Rules/AccountEmail.php ================================================ ', 'https', 'http://', 'www.', 'Ђ', ' Illuro']) && Str::length($value) < 31) { $fail('Invalid account name.'); } } } ================================================ FILE: app/Rules/ApiUniqueAttributeNames.php ================================================ get('month_length'); if (! is_array($lengths) || count($lengths) === 0) { $fail(__('calendars.validators.moon_offset')); } $max = $lengths[0]; $min = 0 - $max; foreach ($value as $offset) { if ($offset > $max || $offset < $min) { $fail(__('calendars.validators.moon_offset')); } } } } ================================================ FILE: app/Rules/CampaignDelete.php ================================================ 'delete'])); } } } ================================================ FILE: app/Rules/Confirm.php ================================================ 'delete'])); } } } ================================================ FILE: app/Rules/EntityField.php ================================================ entityTypeId); if (! is_array($value)) { $value = [$value]; } foreach ($value as $id) { if (is_numeric($id)) { if ($this->modelClass::find($id)) { continue; } $fail(__('crud.dynamic.unknown', ['module' => $module->name()])); return; } $name = Str::startsWith($id, 'new:') ? Str::substr($id, 4) : $id; if (empty(mb_trim($name))) { continue; } $campaign = CampaignLocalization::getCampaign(); if (! auth()->user()->can('create', [$module, $campaign])) { $fail(__('crud.dynamic.permission', ['module' => $module->name()])); } } } } ================================================ FILE: app/Rules/EntityFile.php ================================================ isValid()) { $fail(__('validation.mimes', ['values' => $this->formats])); } // Block any hacking shenanigans if ($this->shouldBlockPhpUpload($value, [])) { $fail(__('validation.mimes', ['values' => $this->formats])); } if (empty($value->getPath())) { $fail(__('validation.mimes', ['values' => $this->formats])); } $validExtensions = explode(',', 'jpeg,png,jpg,gif,webp,pdf,xls,xlsx,mp3'); if (in_array($value->guessExtension(), $validExtensions)) { return; // $fail(__('validation.mimes', ['values' => 'jpg, jpeg, png, gif, webp, pdf, xls(x), csv, mp3, ogg, json'])); } // It wasn't an image, maybe it's an audio file if (empty($value->getClientOriginalExtension())) { $fail(__('validation.mimes', ['values' => $this->formats])); } if (in_array($value->getClientOriginalExtension(), ['mp3', 'ogg', 'json', 'csv'])) { return; } $fail(__('validation.mimes', ['values' => $this->formats])); } protected function shouldBlockPhpUpload($value, $parameters) { if (in_array('php', $parameters)) { return false; } $phpExtensions = [ 'php', 'php3', 'php4', 'php5', 'phtml', ]; return ($value instanceof UploadedFile) ? in_array(mb_trim(mb_strtolower($value->getClientOriginalExtension())), $phpExtensions) : in_array(mb_trim(mb_strtolower($value->getExtension())), $phpExtensions); } } ================================================ FILE: app/Rules/EntityLink.php ================================================ first(); if (empty($campaign) || ! $campaign->isPublic()) { $fail(__('validation.entity_link')); } // Are we targeting an entity or a misc? $entity = null; if ($segments[3] === 'entities') { /** @var ?Entity $entity */ // @phpstan-ignore-next-line $entity = Entity::where('id', (int) $segments[4]) ->where('campaign_id', $campaign->id) ->allCampaigns() ->withInvisible() ->first(); } else { $entityTypeID = config('entities.ids.' . Str::singular($segments[3])); if (empty($entityTypeID)) { $fail(__('validation.entity_link')); } // @phpstan-ignore-next-line $entity = Entity::where('entity_id', (int) $segments[4]) ->allCampaigns() ->withInvisible() ->where('type_id', $entityTypeID) ->where('campaign_id', $campaign->id) ->first(); } if (empty($entity) || $entity->is_private) { $fail(__('validation.entity_link')); } // Figuring out if the entity is visible to the public role is going to be tricky, so let's start doing some magic. $publicRole = $campaign->roles()->public()->first(); if (empty($publicRole)) { $fail(__('validation.entity_link')); } $permission = $publicRole->permissions() ->where(function ($sub) use ($entity) { return $sub->where('entity_id', $entity->id) ->orWhere('entity_type_id', $entity->typeId()); }) ->where('access', 1) ->where('action', Permission::View->value) ->first(); // We don't check for the public role have deny as a permission, this is good enough if (empty($permission)) { $fail(__('validation.entity_link')); } } } ================================================ FILE: app/Rules/FontAwesomeIcon.php ================================================ 'fa-solid fa-skull'])); } } } ================================================ FILE: app/Rules/GallerySize.php ================================================ make(StorageService::class); $available = $service->campaign(CampaignLocalization::getCampaign())->available(); if (! is_object($value)) { $fail('File isn\'t a stream'); return; } try { $size = (int) floor($value->getSize() / 1024); if ($size > $available) { $available = $this->human($available); $fail(__('campaigns/gallery.errors.storage', ['available' => $available]) . ' (storage_full)'); } } catch (Exception $e) { $available = $this->human($available); $fail(__('campaigns/gallery.errors.storage', ['available' => $available]) . ' (storage_full)'); } } public function human(int $value): string { if ($value > 1000000) { return floor($value / (1024 * 1024)) . ' GB'; } elseif ($value > 1000) { return floor($value / 1024) . ' MB'; } return $value . ' KB'; } } ================================================ FILE: app/Rules/GoodBye.php ================================================ 'goodbye'])); } } } ================================================ FILE: app/Rules/Lessless.php ================================================ $attribute, 'letter' => '<'])); } } } ================================================ FILE: app/Rules/Location.php ================================================ $module->name()])); } if (empty(mb_trim($value))) { return; } $campaign = CampaignLocalization::getCampaign(); if (! auth()->user()->can('create', [$module, $campaign])) { $fail(__('crud.dynamic.permission', ['module' => $module->name()])); } } } ================================================ FILE: app/Rules/Nested.php ================================================ self)) { return; } /** @var ?Entity $parent */ $parent = Entity::where('id', $value)->first(); if (! $parent) { return; } $bloodline = $parent->ancestorsAndSelf()->pluck('id')->toArray(); if (in_array($this->self->id, $bloodline)) { $fail('validation.nested_loop')->translate(['parent' => $parent->name]); } } } ================================================ FILE: app/Rules/Recaptcha.php ================================================ config('auth.recaptcha.secret'), 'response' => $value, ]; $res = Http::asForm()->post('https://www.google.com/recaptcha/api/siteverify', $data); // Log::info('Recaptcha request', $data); if (! $res->json('success')) { // Log::info('Recaptcha request', $res->json()); $fail(__('Invalid request, please try again.')); } } } ================================================ FILE: app/Rules/SocialLogin.php ================================================ first(); if ($user && $user->isSocialLogin()) { $fail(__('validation.social_login', ['provider' => Str::upper($user->provider)])); } } } ================================================ FILE: app/Rules/UniqueAttributeNames.php ================================================ $attribute])); } if (! preg_match('`[a-zA-Z]+`', $value)) { $fail(__('campaigns/vanity.rule', ['field' => $attribute])); } } } ================================================ FILE: app/Sanitizers/CalendarSanitizer.php ================================================ cleanMonths(); $this ->cleanWeekdays() ->cleanYearNames() ->cleanWeekNames() ->cleanMoons() ->cleanSeasons() ->cleanDate(); // Leap year $this->cleanLeap($months); return $this->data; } protected function cleanMonths(): array { $months = []; $monthNames = (array) $this->request->post('month_name', []); $monthLengths = (array) $this->request->post('month_length', []); $monthAliases = (array) $this->request->post('month_alias', []); $monthTypes = (array) $this->request->post('month_type', []); foreach ($monthNames as $name) { if (empty($name)) { continue; } // We want a month length of at least 1 day $length = (int) $monthLengths[$this->monthCount]; $months[] = [ 'name' => $this->purify($name), 'length' => max($length, 1), 'type' => $monthTypes[$this->monthCount] ?? 'standard', 'alias' => $this->purify($monthAliases[$this->monthCount] ?? ''), ]; $this->monthCount++; } $this->data['months'] = json_encode($months); return $months; } protected function cleanWeekdays(): self { $weekdays = []; $weekdayNames = (array) $this->request->post('weekday', []); foreach ($weekdayNames as $name) { if (empty($name)) { continue; } $weekdays[] = $this->purify($name); } $this->data['weekdays'] = json_encode($weekdays); return $this; } protected function cleanYearNames(): self { $years = []; $yearCount = 0; $yearValues = (array) $this->request->post('year_number', []); $yearNames = (array) $this->request->post('year_name', []); if (! empty($yearValues)) { foreach ($yearValues as $year) { if (empty($year)) { continue; } // Save the leap year $years[$year] = $this->purify($yearNames[$yearCount] ?? $year); $yearCount++; } } $this->data['years'] = json_encode($years); return $this; } protected function cleanWeekNames(): self { $weeks = []; $weekCount = 0; $weekValues = (array) $this->request->post('week_number', []); $weekNames = (array) $this->request->post('week_name', []); if (! empty($weekValues)) { foreach ($weekValues as $week) { if (empty($week)) { continue; } // Save the leap year $weeks[$week] = $this->purify($weekNames[$weekCount]); $weekCount++; } } $this->data['week_names'] = json_encode($weeks); return $this; } protected function cleanMoons(): self { $moons = []; $moonCount = 0; $moonValues = (array) $this->request->post('moon_fullmoon', []); $moonNames = (array) $this->request->post('moon_name', []); $moonOffsets = (array) $this->request->post('moon_offset', []); $moonColours = (array) $this->request->post('moon_colour', []); $moonIds = (array) $this->request->post('moon_id', []); // Get the highest moon id $autoMoonId = 0; foreach ($moonIds as $id) { if (! empty($id) && $id > $autoMoonId) { $autoMoonId = $id; } } $autoMoonId++; if ($moonValues) { foreach ($moonValues as $moon) { if (empty($moon)) { continue; } $moonId = $moonIds[$moonCount]; if (empty($moonId)) { $moonId = $autoMoonId; $autoMoonId++; } $moons[] = [ 'name' => $this->purify($moonNames[$moonCount]), 'fullmoon' => round((float) $moon, 10), 'offset' => (int) $moonOffsets[$moonCount], 'colour' => $this->purify($moonColours[$moonCount]), 'id' => (int) $moonId, ]; $moonCount++; } } $this->data['moons'] = json_encode($moons); return $this; } protected function cleanSeasons(): self { $seasons = []; $seasonCount = 0; $seasonNames = (array) $this->request->post('season_name', []); $seasonMonths = (array) $this->request->post('season_month', []); $seasonDays = (array) $this->request->post('season_day', []); foreach ($seasonNames as $name) { if (empty($name)) { continue; } // We want a season length of at least 1 day $month = (int) $seasonMonths[$seasonCount]; $day = (int) $seasonDays[$seasonCount]; $seasons[] = [ 'name' => $this->purify($name), 'month' => $month < 1 ? 1 : $month, 'day' => $day, ]; $seasonCount++; } $this->data['seasons'] = json_encode($seasons); return $this; } protected function cleanDate(): self { // Calculate date /** @var ?int $year */ $year = $this->request->post('current_year', '1'); $month = mb_ltrim($this->request->post('current_month', '1'), '0'); $day = mb_ltrim($this->request->post('current_day', '1'), '0'); $monthLengths = (array) $this->request->post('month_length', []); // Empty values and skipping year 0 if ($year === null || ($this->request->skip_year_zero && $year == 0)) { $year = 1; } if (empty($month)) { $month = 1; } if (empty($day)) { $day = 1; } if ($month > ($this->monthCount)) { $month = $this->monthCount; } if (isset($monthLengths[$month - 1])) { if ($day > $monthLengths[$month - 1]) { $day = $monthLengths[$month - 1]; } } $this->data['date'] = "{$year}-{$month}-{$day}"; return $this; } protected function cleanLeap(array $months): self { if (! $this->request->filled('has_leap_year')) { return $this; } if ($this->request->leap_year_month < 1) { $this->data['leap_year_month'] = 1; } elseif ($this->request->leap_year_month > count($months)) { $this->data['leap_year_month'] = count($months); } return $this; } } ================================================ FILE: app/Sanitizers/MiscSanitizer.php ================================================ request = $request; return $this; } public function sanitize(): array { return $this->data; } } ================================================ FILE: app/Sanitizers/SvgAllowedAttributes.php ================================================ [], 'meta' => [], ]; protected array $groups = []; /** * Build a list of entities grouped by their parent */ public function get(): array { $abilities = $this->entity->abilities() ->select('entity_abilities.*') ->with(['ability', // entity 'ability.entity', 'ability.entity.image', 'ability.entity.attributes', 'ability.entity.attributes.entity', 'ability.entity.tags', // parent 'ability.entity.parent', 'ability.entity.parent.tags', 'ability.entity.parent.image', ]) ->join('abilities as a', 'a.id', 'entity_abilities.ability_id') ->leftJoin('entities as ae', function (JoinClause $join) { $join ->on('ae.entity_id', '=', 'a.id') ->where('ae.type_id', '=', config('entities.ids.ability')); }) ->defaultOrder() ->get(); /** @var EntityAbility $ability */ foreach ($abilities as $ability) { // Can't read the ability? skip if (empty($ability->ability) || empty($ability->ability->entity)) { continue; } // If this ability has a parent ability, save it there $this->add($ability); } // Reorder parents $this->abilities['groups'] = $this->groups; usort($this->abilities['groups'], function ($a, $b) { return strcmp(mb_strtoupper($a['name']), mb_strtoupper($b['name'])); }); // Meta $this->abilities['meta'] = [ 'add_url' => route('entities.entity_abilities.create', [$this->campaign, $this->entity]), 'user_id' => $this->user->id ?? 0, 'is_admin' => isset($this->user) && $this->user->isAdmin(), ]; return $this->abilities; } protected function add(EntityAbility $entityAbility): void { $ability = $entityAbility->ability; $parent = $ability->entity->parent; $groupKey = $parent->id ?? 'unorganised'; if (empty($this->groups[$groupKey])) { if (empty($parent)) { $this->groups[$groupKey] = [ 'id' => 0, 'name' => __('entities/abilities.groups.unorganised'), 'type' => __('entities/abilities.types.unorganised'), 'abilities' => [], ]; } else { $type = empty($parent->type) ? Str::limit(strip_tags($parent->entry), 200) : $parent->type; $this->groups[$groupKey] = [ 'id' => $parent->id, 'name' => $parent->name, 'type' => $type, 'image' => Avatar::entity($parent)->size(192)->thumbnail(), 'has_image' => $parent->hasImage(), 'entry' => $parent->parsedEntry(), 'url' => $parent->url(), 'abilities' => [], ]; } } // Add to their parent's abilities $this->groups[$groupKey]['abilities'][] = $this->formatAbility($entityAbility); } /** * Prepare the entity ability into a json object that can be used on the frontend */ protected function formatAbility(EntityAbility $entityAbility): array { $classes = []; $tags = $entityAbility->ability->entity->visibleTags(); foreach ($tags as $tag) { $classes[] = ' kanka-tag-' . $tag->id; $classes[] = ' kanka-tag-' . $tag->slug; if ($tag->tag_id) { $classes[] = ' kanka-tag-' . $tag->tag_id; } } // implode(' ', $classes); $note = nl2br((string) $this->mapAttributes( Mentions::mapAny($entityAbility, 'note'), false )); if (! empty($note)) { $note = '' . __('entities/abilities.fields.note') . ': ' . $note; } $data = [ 'id' => $entityAbility->id, 'ability_id' => $entityAbility->ability_id, 'name' => $entityAbility->ability->name, 'entry' => $this->parseEntry($entityAbility->ability), 'type' => $entityAbility->ability->entity->type, 'charges' => $this->parseCharges($entityAbility->ability), 'used_charges' => $entityAbility->charges, 'class' => $classes, 'note' => $note, 'tags' => $this->formatTags($tags), 'visibility_id' => $entityAbility->visibility_id, 'visibility' => $entityAbility->visibilityName(), 'created_by' => $entityAbility->created_by, 'attributes' => $this->attributes($entityAbility->ability->entity), 'images' => [ 'has' => ! empty($entityAbility->ability->entity->image_path) || $entityAbility->ability->entity->image, 'thumb' => Avatar::entity($entityAbility->ability->entity)->size(192)->thumbnail(), 'url' => Avatar::entity($entityAbility->ability->entity)->original(), ], 'actions' => [ 'edit' => route('entities.entity_abilities.edit', [$this->campaign, $this->entity, $entityAbility]), 'update' => route('entities.entity_abilities.update', [$this->campaign, $this->entity, $entityAbility]), 'delete' => route('entities.entity_abilities.destroy', [$this->campaign, $this->entity, $entityAbility]), 'view' => route('entities.show', [$this->campaign, $entityAbility->ability->entity]), ], 'i18n' => [ 'edit' => __('crud.update'), 'left' => __('entities/abilities.charges.left'), ], 'entity' => [ 'id' => $entityAbility->ability->entity->id, 'tooltip' => route('entities.tooltip', [$this->campaign, $entityAbility->ability->entity->id]), ], ]; if (! empty($entityAbility->ability->charges)) { $data['actions']['use'] = route('entities.entity_abilities.use', [$this->campaign, $this->entity, $entityAbility]); } return $data; } protected function attributes(Entity $entity): array { $attributes = []; /** @var Attribute $attr */ foreach ($entity->attributes->sortBy('default_order') as $attr) { $attributes[] = [ 'id' => $attr->id, 'name' => $attr->name(), 'value' => Mentions::mapAttribute($attr), 'type' => $attr->type, ]; } return $attributes; } protected function formatTags(Collection $tags): array { $formatted = []; /** @var Tag $tag */ foreach ($tags as $tag) { $formatted[] = [ 'id' => $tag->id, 'name' => $tag->name, 'url' => $tag->getLink(), 'tooltip' => route('entities.tooltip', [$this->campaign, $tag->entity]), 'class' => $tag->colourClass(), 'style' => $tag->colourStyle(), ]; } return $formatted; } } ================================================ FILE: app/Services/Abilities/BaseAbilityService.php ================================================ charges)) { return null; } if (is_int($ability->charges)) { return $ability->charges; } try { return $this->mapAttributes($ability->charges); } catch (Exception $e) { return null; } } /** * @return float|int|mixed */ protected function parseEntry(Ability $ability) { $entry = $ability->entity->parsedEntry(); try { return $this->mapAttributes($entry, false); } catch (Exception $e) { return $entry; } } /** * @return float|int|string|null * * @throws ContainerException * @throws NotFoundException */ protected function mapAttributes(string $haystack, bool $calc = true) { // Replace {} with entity attributes $mappedText = preg_replace_callback('`\{(.*?)\}`i', function ($matches) { // dd($matches); $text = $matches[1]; if ($this->entityAttributes()->has($text)) { return $this->entityAttributes()->get($text); } return 0; }, $haystack); if (! $calc) { return $mappedText; } $calculator = new StringCalc; return $calculator->calculate($mappedText); } /** * @return array|Collection */ protected function entityAttributes() { if (isset($this->attributes)) { return $this->attributes; } $this->attributes = new Collection; /** @var Attribute $attribute */ foreach ($this->entity->attributes as $attribute) { $this->attributes->put($attribute->name, $attribute->mappedValue()); } return $this->attributes; } } ================================================ FILE: app/Services/Abilities/ChargeService.php ================================================ ability = $ability; return $this; } /** * Set an ability's charge as used */ public function use(int $used): bool { // Check that we are not above the parent if ($used > $this->parseCharges($this->ability->ability)) { return false; } $this->ability->charges = $used; $this->ability->saveQuietly(); return true; } /** * Reset the ability charges on the entity */ public function reset(): self { $usedAbilities = $this->entity->abilities()->where('charges', '>', 0)->get(); /** @var Ability $ability */ foreach ($usedAbilities as $ability) { $ability->charges = null; $ability->saveQuietly(); } return $this; } } ================================================ FILE: app/Services/Abilities/ImportService.php ================================================ entity->isCharacter()) { throw new Exception('not_character'); } /** @var Character $character */ $character = $this->entity->child; if (empty($character->characterRaces)) { throw new Exception('no_race'); } $count = 0; // Existing abilities $abilities = $this->entity->abilities()->with('ability')->get(); $existingIds = []; foreach ($abilities as $ability) { // The ability is soft-deleted so we can skip it if (empty($ability) || empty($ability->ability)) { continue; } $existingIds[] = $ability->ability_id; } $characterRaces = $character->characterRaces()->with('race', 'race.entity', 'race.entity.abilities')->get(); foreach ($characterRaces as $race) { /** @var EntityAbility[] $abilities */ $abilities = $race->race->entity->abilities; $count = 0; foreach ($abilities as $ability) { // If it's deleted or already on this entity, skip if (empty($ability->ability) || in_array($ability->ability_id, $existingIds)) { continue; } $new = $ability->replicate(['entity_id']); $new->entity_id = $this->entity->id; $new->save(); $count++; } } return $count; } } ================================================ FILE: app/Services/Abilities/ReorderService.php ================================================ get('ability'); if (empty($ids)) { return false; } $position = 1; foreach ($ids as $id) { /** @var ?EntityAbility $ability */ $ability = EntityAbility::find($id); if ($ability === null || $ability->entity_id !== $this->entity->id) { continue; } $ability->position = $position; $ability->saveQuietly(); $position++; } return true; } } ================================================ FILE: app/Services/Account/DeletionService.php ================================================ subscription(); DeleteUser::dispatch($this->user); auth()->logout(); $this->request->session()->invalidate(); $this->request->session()->regenerateToken(); // We also need to flush the session (campaign_id and other things) since this could cause // unexpected behaviour if the user registers a new account. $this->request->session()->flush(); return true; } /** * Remove the user from stripe */ protected function subscription(): void { if (! $this->user->hasStripeId()) { return; } // If the user has no active or invalid payment $sub = $this->user->subscription('kanka'); if (empty($sub) || $sub->canceled()) { return; } // If their sub was failing if ($sub->hasIncompletePayment()) { $sub->cancel(); } } } ================================================ FILE: app/Services/Api/ApiPermissionService.php ================================================ cachedPermissions)) { return $this->cachedPermissions; } $permissions = ['user' => [], 'role' => []]; /** @var CampaignPermission $perm */ foreach (CampaignPermission::where('entity_id', $entity->id)->get() as $perm) { $key = (! empty($perm->user_id) ? 'user' : 'role'); $subkey = (! empty($perm->user_id) ? $perm->user_id : $perm->campaign_role_id); $permissions[$key][$subkey][$perm->action] = $perm; } return $this->cachedPermissions = $permissions; } /** * @param Request $request */ public function saveEntity($request, Entity $entity) { // First, let's get all the stuff for this entity $permissions = $this->entityPermissions($entity); $model = []; // Next, start looping the data foreach ($request->all() as $permission) { if (! empty($permission['campaign_role_id'])) { $key = 'role'; $key2 = 'campaign_role_id'; } else { $key = 'user'; $key2 = 'user_id'; } if (empty($permissions[$key][$permission[$key2]][$permission['action']])) { $permission['campaign_id'] = $entity->campaign_id; $permission['entity_type_id'] = $entity->type_id; $permission['entity_id'] = $entity->id; array_push($model, CampaignPermission::create($permission)); } } return $model; } /** * @param Request $request */ public function entityPermissionTest($request, Campaign $campaign): array { $previousUser = 0; $permissionTest = []; foreach ($request->all() as $test) { $entityTypeId = null; /** @var Entity|MiscModel|null $entity */ $entity = null; $entityId = null; if (! isset($user) || $user != $previousUser) { $user = User::find($test['user_id']); EntityPermission::resetPermissions(); } if (isset($test['entity_type_id'])) { $entityTypeId = $test['entity_type_id']; } else { $entity = Entity::find($test['entity_id']); $entityTypeId = $entity->type_id; $entityId = $entity->id; } $permission = EntityPermission::campaign($campaign)->user($user)->hasPermission($entityTypeId, $test['action'], $entity); $permissionTest[] = ([ 'entity_type_id' => $entityTypeId, 'entity_id' => $entityId, 'user_id' => $test['user_id'], 'action' => $test['action'], 'can' => $permission, ]); $previousUser = $user; } return $permissionTest; } } ================================================ FILE: app/Services/Api/ApiService.php ================================================ randomService = $randomService; } public function deleteOld(bool $deleteOld = true): self { $this->deleteOld = $deleteOld; return $this; } /** * Add form attributes to an entity * * @throws Exception */ public function save(array $attributes): self { // First, let's get all the stuff for this entity $existingAttributes = $this->entity->attributes() // Need with() for saving to meilisearch ->with('entity') ->get(); foreach ($existingAttributes as $att) { $this->existing[$att->id] = $att; $this->existingNames[$att->id] = $att->name; } $this->purifyConfig(); foreach ($attributes as $attribute) { $this->saveAttribute($attribute); } if ($this->deleteOld) { // Remaining existing attributes have been deleted foreach ($this->existing as $id => $attribute) { $this->touched = true; $attribute->delete(); } } return $this; } protected function saveAttribute(array $attributeArray): self { try { // If they set an id we check if its a valid one, then fetch that attribute to edit it if (isset($attributeArray['id'])) { /** @var Attribute $attribute */ $attribute = Arr::get($this->existing, $attributeArray['id']); // If its an existing attribute we remove it from the existing names list. if (! empty($attribute)) { unset($this->existingNames[$attribute->id]); } } /** @var Attribute $attr */ $attr = new Attribute($attributeArray); if (empty($attr->name)) { return $this; } // If patching an attribute with the same name as one existing, delete the old one. if (! $this->deleteOld && in_array($attr->name, $this->existingNames)) { $key = array_search($attr->name, $this->existingNames); /** @var Attribute $attribute */ $attribute = Arr::get($this->existing, $key); $this->touched = true; $attribute->delete(); } $name = Purify::config($this->purifyConfig)->clean($attr->name); $value = Purify::config($this->purifyConfig)->clean($attr->value ?? ''); // Save empty strings as null $value = $value === '' ? null : $value; if (! isset($attribute)) { $attribute = new Attribute; } // If the linked entity isn't an attribute template, we might be dealing with a random value if (! $this->entity->isAttributeTemplate()) { [$attr->type_id, $value] = $this->randomService->randomAttribute($attr->type_id, $value); } $attribute->name = $name; $attribute->setValue($value); $attribute->is_private = $attr->is_private ?? 0; $attribute->is_pinned = $attr->is_pinned ?? 0; $attribute->type_id = $attr->type_id; // Some fields can only be defined on creation if (! $attribute->exists) { $attribute->entity_id = $this->entity->id; $attribute->is_hidden = $attr->is_hidden ?? 0; $attribute->origin_attribute_id = $attr->source_id ?? null; } $attribute->default_order = $this->order; if ($attribute->isDirty() || ! $attribute->exists) { $this->touched = true; } $attribute->save(); // Remove it from the list of existing ids, so that it doesn't get deleted unset($this->existing[$attribute->id]); // We add the new name to the list $this->existingNames[$attribute->id] = $attribute->name; $this->order++; } catch (Exception $e) { if (app()->isProduction()) { Log::error($e->getMessage()); } else { throw $e; } } return $this; } } ================================================ FILE: app/Services/Api/BulkEntityCreatorService.php ================================================ data = $data; return $this; } public function create(): Entity { if ($this->entityType->isCustom()) { return $this->createEntity(); } $this->new = $this->entityType->getMiscClass(); $this->new->fill($this->data); $this->new->campaign_id = $this->campaign->id; $this->new->save(); $this->entity = $this->new->entity; $this->entitySaveService->save($this->entity, $this->data); return $this->new->entity; } protected function createEntity(): Entity { $this->entity = new Entity($this->data); $this->entity->type_id = $this->entityType->id; $this->entity->campaign_id = $this->campaign->id; $this->entity->save(); $this->entitySaveService->save($this->entity, $this->data); return $this->entity; } } ================================================ FILE: app/Services/Api/CampaignService.php ================================================ filters() ->showcased() ->data; } public function search(): array { return $this->campaigns() ->data; } protected function filters(): self { $this->data['filters'] = [ 'is_open' => [ 'title' => 'Looking for players', ], 'is_boosted' => [ 'title' => 'Premium campaigns', ], 'language' => [ 'title' => 'Language', 'options' => [ 'en' => 'English', 'pt-BR' => 'Brazilian Portuguese', 'fr' => 'French', 'de' => 'German', 'es' => 'Spanish', 'ru' => 'Russian', 'it' => 'Italian', 'pl' => 'Polish', 'sk' => 'Slovak', ], ], 'system[]' => [ 'title' => 'System', 'options' => $this->systemsOptions(), ], 'genre' => [ 'title' => 'Genre', 'options' => $this->genreService->getGenres(), ], ]; return $this; } /** * Build a list of featured campaigns */ protected function showcased(): self { $this->data['featured'] = Campaign::public(false) ->showcased() ->get() ->map(fn ($campaign) => new CampaignResource($campaign)); return $this; } /** * Build a list of public campaigns */ protected function campaigns(): self { $this->data['campaigns'] = []; if ($this->usesDefaultFilters()) { $this->data['campaigns'] = $this->cachedCampaigns(); } else { $campaigns = Campaign::public() ->front((int) $this->request->get('sort_field_name')) ->filterPublic($this->request->only(['language', 'system', 'is_boosted', 'is_open', 'genre'])) ->paginate(); $this->data['campaigns'] = CampaignResource::collection($campaigns); } $this->campaignsMeta(); return $this; } /** * Determine if the request comes with any filters */ protected function usesDefaultFilters(): bool { return ! $this->request->anyFilled('sort_field_name', 'language', 'system', 'is_boosted', 'is_open', 'genre', 'page'); } /** * Cache the first page of campaigns with no filters for a day */ protected function cachedCampaigns(int $hours = 24): AnonymousResourceCollection { return Cache::remember('public-campaigns-page-1', 24 * 3600, function () { $campaigns = Campaign::public() ->front() ->filterPublic([]) ->paginate(); return CampaignResource::collection($campaigns); }); } /** * Add some pagination data to the response */ protected function campaignsMeta(): void { /** @var LengthAwarePaginator $paginator */ $paginator = $this->data['campaigns']->resource; $this->data['pagination'] = [ 'per_page' => $paginator->perPage(), 'current_page' => $paginator->currentPage(), 'total' => $paginator->total(), 'has_pages' => $paginator->hasPages(), 'next' => $paginator->nextPageUrl(), 'previous' => $paginator->previousPageUrl(), ]; } protected function systemsOptions(): array { return Cache::remember('campaign_systems', 24 * 3600, function () { return GameSystem::withCount('campaignSystem') ->orderByDesc('campaign_system_count') ->limit(20) ->pluck('name', 'id') ->toArray(); }); } } ================================================ FILE: app/Services/Api/FilterService.php ================================================ $type->code, 'url' => url('/filters/' . $type->id), ]; } return $endpoints; } public function filters(): array { $model = $this->entityType->getClass(); if (method_exists($model, 'getFilterableColumns')) { return $model->getFilterableColumns(); } return []; } } ================================================ FILE: app/Services/Api/KbService.php ================================================ ordered()->with(['faqs'])->get(); /** @var FaqCategory $category */ foreach ($categories as $category) { $questions = []; /** @var Faq $faq */ foreach ($category->sortedFaqs() as $faq) { $questions[] = [ 'id' => $faq->id, 'q' => $faq->question(), 'a' => $faq->answer(), 'slug' => $faq->slug(), ]; } if (empty($questions)) { continue; } $data[] = [ 'id' => $category->id, 'name' => $category->title, 'questions' => $questions, ]; } return $data; } } ================================================ FILE: app/Services/Api/ShowcaseService.php ================================================ campaigns() ->data; } /** * Build a list of public campaigns */ protected function campaigns(): self { $this->data['campaigns'] = []; if ($this->usesDefaultFilters() && ! app()->hasDebugModeEnabled()) { $this->data['campaigns'] = $this->cachedCampaigns(); } else { $campaigns = $this->baseQuery() ->paginate(); $this->data['campaigns'] = CampaignResource::collection($campaigns); } $this->campaignsMeta(); return $this; } /** * Determine if the request comes with any filters */ protected function usesDefaultFilters(): bool { return ! $this->request->anyFilled('page'); } /** * Cache the first page of campaigns with no filters for a day */ protected function cachedCampaigns(int $hours = 24): AnonymousResourceCollection { return Cache::remember('showcase-campaigns-page-1', $hours * 3600, function () { $campaigns = $this ->baseQuery() ->paginate(); return CampaignResource::collection($campaigns); }); } protected function baseQuery(): Builder { return Campaign::public(false) ->showcased(null) ->with(['systems', 'spotlight']); } /** * Add some pagination data to the response */ protected function campaignsMeta(): void { /** @var LengthAwarePaginator $paginator */ $paginator = $this->data['campaigns']->resource; $this->data['pagination'] = [ 'per_page' => $paginator->perPage(), 'current_page' => $paginator->currentPage(), 'total' => $paginator->total(), 'has_pages' => $paginator->hasPages(), 'next' => $paginator->nextPageUrl(), 'previous' => $paginator->previousPageUrl(), ]; } } ================================================ FILE: app/Services/Api/VoteService.php ================================================ orderBy('visible_at', 'DESC') ->paginate(15); foreach ($votes as $vote) { $data[] = [ ]; } return $data; } } ================================================ FILE: app/Services/AttributeMentionService.php ================================================ validField((string) $attribute->$field)) { return (string) $attribute->$field; } if (! isset($this->loadedEntity) || $this->loadedEntity->id != $attribute->entity_id) { // Referencing an attribute linked to an entity the user can't access if (empty($attribute->entity)) { return (string) $attribute->$field; } $this->loadedEntity = $attribute->entity; } try { // Prepare all the attributes and calculates them $this->entityAttributes(); $data = [ 'name' => $attribute->name, 'value' => $attribute->$field, ]; $value = $this->calculateAttributeValue($data); return $value; } catch (Exception $e) { return $this->$field; } } public function parse(Attribute $attribute, string $field = 'value'): string { if (! $this->validField((string) $attribute->$field)) { return (string) $attribute->$field; } if (! isset($this->loadedEntity) || $this->loadedEntity->id != $attribute->entity_id) { if (empty($attribute->entity)) { return (string) $attribute->$field; } $this->loadedEntity = $attribute->entity; } try { $calculated = $this->entityAttributes()->get($attribute->name); return (string) $calculated['final']; } catch (Exception $e) { // throw $e; return (string) $attribute->$field; } } /** * Determine if the text contains a valid attribute mention using {} * Also ignore anything with html like mentions, as the calculator * won't know what to do with such tags. */ protected function validField(?string $value = null): bool { if (! Str::contains($value, ['{', '}'])) { return false; } return ! (Str::contains($value, ['<', '>'])); } /** * Load all the entity attributes and pre-calculate the values */ protected function entityAttributes(): Collection { if (isset($this->loadedAttributes[$this->loadedEntity->id])) { return $this->loadedAttributes[$this->loadedEntity->id]; } $baseAttributes = $this->loadedEntity->attributes()->orderBy('default_order')->pluck('value', 'name'); $this->calculatedAttributes = new Collection; // Prepare our attributes with first level references foreach ($baseAttributes as $name => $value) { $references = []; preg_match_all('`\{(.*?)\}`i', $value, $references); // Cleanup attribute name to remove range stuff $name = preg_replace('`\[range:(.*)\]`i', '', $name); $this->calculatedAttributes->put($name, [ 'value' => $value, 'loop' => false, 'name' => $name, 'final' => null, 'references' => ! empty($references[1]) ? $references[1] : [], ]); } // Loop through the attributes and calculate the values foreach ($this->calculatedAttributes as $name => $attribute) { try { // @phpstan-ignore-next-line $this->calculatedAttributes[$name] = $this->calculateAttribute($attribute); } catch (Exception $e) { $attribute['loop'] = true; $attribute['final'] = $attribute['value']; $this->calculatedAttributes[$name] = $attribute; } } return $this->loadedAttributes[$this->loadedEntity->id] = $this->calculatedAttributes; } /** * Replace any attribute mentions in a string and result any math calculations in the resulting string * * @throws ContainerException * @throws NotFoundException */ protected function calculateAttributeValue(array $data, array $from = []): string { // If the final version is already calculated, use that // dump('parsing ' . $data['name'] . ' value ' . $data['value']); // First detect any loops going on here if (in_array($data['name'], $from)) { throw new Exception('loop detected on ' . $data['name']); } // Replace any attribute references $final = preg_replace_callback('`\{(.*?)\}`i', function ($matches) use ($data, $from) { $text = $matches[1]; // dump('checking for a reference called ' . $text); $ref = $this->calculatedAttributes->get($text); if ($ref) { // dump('has an attribute called it!'); if (! empty($ref['final'])) { // dump('has a final version too'); return $ref['final']; } elseif ($ref['loop']) { return 0; } // dump('calculating final version for ' . $text . ' with value ' . $ref['value']); $newFrom = $from; $newFrom[] = $data['name']; $ref['final'] = $this->calculateAttributeValue($ref, $newFrom); $this->calculatedAttributes[$text] = $ref; return $ref['final']; /*} catch (Exception $e) { $ref['loop'] = true; $ref['final'] = $ref['value']; $this->calculatedAttributes[$text] = $ref; return 0; }*/ } if ($text == 'name') { return (string) $this->loadedEntity->name; } return 0; }, $data['value']); try { $calculator = new StringCalc; $return = (string) $calculator->calculate($final); return $return; } catch (Exception $e) { return $final; } } /** * Calculate the value of an attribute by performing math on it */ protected function calculateAttribute(array $data): array { if (empty($data['references'])) { $data['final'] = $data['value']; return $data; } try { $data['final'] = $this->calculateAttributeValue($data, []); } catch (Exception $e) { // dump($e->getMessage()); // dd('oh these is a loop in here'); $data['final'] = 0; $data['loop'] = true; } return $data; } /** * Check if a given attribute is flagged as being in a loop */ public function isLoop(string $name): bool { if (! isset($this->calculatedAttributes) || $this->calculatedAttributes->isEmpty()) { return false; } $ref = $this->calculatedAttributes->get($name); if ($ref) { return $ref['loop']; } return false; } public function organise(Collection $attributes): array { $sections = []; $section = null; /** @var Attribute $attribute */ foreach ($attributes as $attribute) { if ($attribute->isSection()) { if ($section !== null) { $sections[] = $section; } $section = [ 'id' => $attribute->id, 'name' => $attribute->name(), 'is_private' => $attribute->is_private, 'attributes' => [], ]; continue; } elseif ($section === null) { $section = [ 'id' => 0, 'attributes' => [], ]; } $section['attributes'][] = $attribute; } $sections[] = $section; return $sections; } } ================================================ FILE: app/Services/AttributeService.php ================================================ randomService = $randomService; $this->templateService = $templateService; } /** * Apply a template to an entity * * @param int|string $templateId */ public function apply(Entity $entity, mixed $templateId): void { $this->templateService ->entity($entity) ->apply($templateId); } /** * Add form attributes to an entity * * @throws Exception */ public function save(array $attributes): self { // First, let's get all the stuff for this entity $existingAttributes = $this->entity->attributes() // Need with() for saving to meilisearch ->with('entity') ->get(); foreach ($existingAttributes as $att) { $this->existing[$att->id] = $att; } $this->purifyConfig(); foreach ($attributes as $attribute) { $this->saveAttribute($attribute); } // Remaining existing have been deleted foreach ($this->existing as $id => $attribute) { $this->touched = true; $attribute->delete(); } return $this; } protected function saveAttribute(string $attributeJson): self { try { /** @var Attribute $attr */ $attr = json_decode($attributeJson); if (empty($attr->name)) { return $this; } $name = Purify::config($this->purifyConfig)->clean($attr->name); $value = Purify::config($this->purifyConfig)->clean($attr->value ?? ''); // Save empty strings as null $value = $value === '' ? null : $value; /** @var Attribute $attribute */ $attribute = Arr::get($this->existing, $attr->id); if (empty($attribute)) { $attribute = new Attribute; } // If the linked entity isn't an attribute template, we might be dealing with a random value if (! $this->entity->isAttributeTemplate()) { [$attr->type, $value] = $this->randomService->randomAttribute(AttributeType::from($attr->type), $value); } $attribute->name = $name; $attribute->setValue($value); $attribute->is_private = $attr->is_private; $attribute->is_pinned = $attr->is_pinned; $attribute->type_id = $attr->type; // @phpstan-ignore-line // Some fields can only be defined on creation if (! $attribute->exists) { $attribute->entity_id = $this->entity->id; $attribute->is_hidden = $attr->is_hidden; $attribute->origin_attribute_id = $attr->source_id ?? null; } $attribute->default_order = $this->order; if ($attribute->isDirty() || ! $attribute->exists) { $this->touched = true; } $attribute->save(); // Remove it from the list of existing ids, so that it doesn't get deleted unset($this->existing[$attr->id]); $this->order++; } catch (Exception $e) { if (app()->isProduction()) { Log::error($e->getMessage()); } else { throw $e; } } return $this; } public function updateVisibility(bool $privateAttributes): self { // Only admins can update this value if (! $this->user->isAdmin()) { return $this; } $this->entity->is_attributes_private = $privateAttributes ? 1 : 0; // If the setting was changed, the entity is dirty and will need be be touched later if ($this->entity->isDirty('is_attributes_private')) { $this->touched = true; } return $this; } /** * Apply attribute templates to a new entity */ public function applyEntityTemplates(Entity $entity, int $order = 0): int { $typeId = $entity->typeId(); $templates = AttributeTemplate::has('entity')->enabled()->where(['entity_type_id' => $typeId])->get(); /** @var AttributeTemplate $template */ foreach ($templates as $template) { $order = $template->apply($entity, $order); } return $order; } /** * Deprecated? */ public function templates(Campaign $campaign): array { $templates = []; foreach (config('attribute-templates.templates') as $code => $class) { $template = new $class; $templates[$code] = $template->name(); } // Get templates from the plugins if ($campaign->boosted() && config('marketplace.enabled')) { foreach (CampaignPlugin::templates($campaign)->get() as $plugin) { if (empty($plugin->plugin)) { continue; } $templates[$plugin->plugin->uuid] = __('campaigns/plugins.templates.name', [ 'name' => $plugin->name, 'user' => $plugin->plugin->author(), ]); } } return $templates; } /** * Build a list of templates: * - First display attribute templates from the campaign * - Then display character sheets from the marketplace installed on the campaign */ public function templateList(): array { $templates = []; // Campaign templates $campaignTemplates = AttributeTemplate::has('entity') ->enabled() ->orderBy('name', 'ASC') ->pluck('name', 'id'); $key = __('entities.attribute_templates'); foreach ($campaignTemplates as $id => $name) { $templates[$key][$id] = $name; } // Kanka templates - deprecated as of 1.30 // $key = __('attributes/templates.list.kanka'); // foreach (config('attribute-templates.templates') as $code => $class) { // $template = new $class(); // $templates[$key][$code] = $template->name(); // } // If the campaign isn't boosted, or the marketplace isn't enable, end here if (! $this->campaign->boosted() || ! config('marketplace.enabled')) { return $templates; } // Marketplace campaigns $key = __('attributes/templates.list.sheets'); foreach (CampaignPlugin::templates($this->campaign)->with(['plugin', 'plugin.user'])->get() as $plugin) { if (empty($plugin->plugin)) { continue; } $templates[$key][$plugin->plugin->uuid] = __('campaigns/plugins.templates.name', [ 'name' => $plugin->name, 'user' => $plugin->plugin->author(), ]); } return $templates; } public function replaceMentions(int $sourceId): self { $source = Entity::findOrFail($sourceId); $sourceAttributes = []; foreach ($source->attributes as $attribute) { $sourceAttributes[Str::slug($attribute->name)] = $attribute->id; } $searchAttributes = $replaceAttributes = []; foreach ($this->entity->attributes as $attribute) { $slug = Str::slug($attribute->name); if (! isset($sourceAttributes[$slug])) { continue; } $searchAttributes[] = '{attribute:' . $sourceAttributes[$slug] . '}'; $replaceAttributes[] = '{attribute:' . $attribute->id . '}'; } if ($this->entity->hasEntry()) { $entry = Str::replace($searchAttributes, $replaceAttributes, $this->entity->entry); $this->entity->update(['entry' => $entry]); } foreach ($this->entity->posts as $post) { $post->entry = Str::replace($searchAttributes, $replaceAttributes, $post->entry); $post->updateQuietly(); } return $this; } } ================================================ FILE: app/Services/Attributes/ApiService.php ================================================ copy = true; return $this; } public function build(): array { $this->buildAttributes(); return [ 'attributes' => $this->attributes->toArray(), 'i18n' => $this->i18n(), 'meta' => $this->meta(), 'templates' => $this->templates(), ]; } protected function i18n(): array { return [ 'actions' => [ 'toggle' => __('entities/attributes.actions.toggle_privacy'), 'load' => __('entities/attributes.template.load.title'), 'help' => __('crud.actions.help'), 'search' => __('crud.search'), 'filters' => __('bookmarks.fields.filters'), ], 'columns' => [ 'attribute' => __('entities/attributes.fields.property'), 'value' => __('entities/attributes.fields.value'), 'pinned' => __('entities/attributes.fields.is_star'), 'private' => __('crud.fields.is_private'), 'delete' => __('crud.permissions.actions.delete'), 'preferences' => __('entities/attributes.fields.preferences'), ], 'types' => [ 'attribute' => __('entities/attributes.types.attribute'), 'multiline' => __('entities/attributes.types.text'), 'number' => __('entities/attributes.types.number'), 'section' => __('entities/attributes.types.section'), 'checkbox' => __('entities/attributes.types.checkbox'), 'random' => __('entities/attributes.types.random'), 'templates' => __('entities/attributes.types.kits'), ], 'filters' => [ 'show_hidden' => __('entities/attributes.actions.show_hidden'), 'no_results' => __('No results.'), ], 'placeholders' => [ 'name' => __('entities/attributes.labels.name'), 'checkbox_name' => __('entities/attributes.labels.checkbox'), 'section_name' => __('entities/attributes.labels.section'), 'multiline_name' => __('entities/attributes.placeholders.block'), 'value' => __('entities/attributes.placeholders.attribute'), 'number_value' => __('entities/attributes.placeholders.number'), ], 'toasts' => [ 'no_attributes_selected' => __('entities/attributes.errors.no_attribute_selected'), 'toggle_deleted' => __('entities/attributes.toasts.bulk_deleted'), 'toggled_privacy' => __('entities/attributes.toasts.bulk_privacy'), 'template' => __('entities/attributes.template.load.success'), 'max_reached' => __('entities/attributes.errors.too_many_v2', [ 'max' => Number::format($this->maxFields()), ]), ], 'templates' => [ 'title' => __('entities/attributes.template.load.title'), 'template' => __('entities/attributes.fields.kit'), 'load' => __('entities/attributes.actions.load'), 'helper' => __('entities/attributes.template.pitch', [ 'plugin' => '' . __('footer.plugins') . '', ]), ], ]; } protected function meta(): array { return [ 'has_hidden' => false, 'is_admin' => isset($this->user) && $this->user->isAdmin(), 'template' => route('templates.load-attributes', $this->campaign), 'mentions' => route('search.live', $this->campaign), 'max' => $this->maxFields(), ]; } protected function buildAttributes(): void { $this->attributes = new Collection; if (isset($this->entity)) { foreach ($this->entity->attributes()->ordered()->get() as $attribute) { $this->parseAttribute($attribute); } } $this->buildAutoTemplates(); $this->buildPlaceholders(); } protected function buildAutoTemplates(): void { if (! isset($this->entityType)) { return; } $templates = $this->entityType ->attributeTemplates() ->with(['entity', 'entity.attributes', 'entity.ancestors']) ->enabled() ->has('entity') ->get(); /** @var AttributeTemplate $template */ foreach ($templates as $template) { $this->addTemplate($template); /** @var AttributeTemplate $child */ foreach ($template->entity->ancestors()->with('attributeTemplate')->get() as $child) { /** @var Entity $child */ if (! $child->attributeTemplate->isEnabled()) { continue; } /*if (!in_array($child->id, $ids)) { $ids[] = $child->id; $attributeTemplates[] = $child; }*/ $this->addTemplate($child->attributeTemplate); } } } protected function buildPlaceholders(): void { /** @var ?Attribute $layout */ $layout = $this->attributes->where('name', '_layout')->first(); if (! $layout || ! Str::isUuid($layout['value'])) { return; } /** @var ?CampaignPlugin $plugin */ $plugin = CampaignPlugin::templates($this->campaign) ->select('campaign_plugins.*') ->leftJoin('plugin_versions as pv', 'pv.plugin_id', 'campaign_plugins.plugin_id') ->where('pv.uuid', $layout['value']) ->has('plugin') ->first(); // If the plugin is published, we're good. Otherwise, it's if (empty($plugin) || ! $plugin->renderable()) { return; } foreach ($plugin->version->attributes as $attribute) { if (! isset($attribute['placeholder']) || empty($attribute['placeholder'])) { continue; } $index = $this->attributes->search(function ($item) use ($attribute) { return $item['name'] === $attribute['name']; }); if ($index !== false) { $ex = $this->attributes->get($index); $ex['placeholder'] = $attribute['placeholder']; $this->attributes->put($index, $ex); } } } protected function addTemplate(AttributeTemplate $template): void { if (! $template->entity) { return; } $first = true; $count = $template->entity->attributes->count(); $this->template = true; foreach ($template->entity->attributes()->ordered()->get() as $attribute) { $this->parseAttribute($attribute, $first ? $template : null, $count); $first = false; } $this->template = false; // Update the helper text of the attribute } protected function parseAttribute(Attribute $attribute, ?AttributeTemplate $template = null, int $templateTotalAttributes = 0): void { // If an attribute with the same name already exists, don't add it again $existing = $this->attributes->where('name', $attribute->name)->first(); if ($existing) { return; } $formatted = [ 'id' => $this->copy ? null : $attribute->id, 'source_id' => $this->template ? $attribute->id : null, 'name' => $attribute->name, 'value' => $attribute->isCheckbox() ? (bool) $attribute->value : $attribute->value, 'is_section' => $attribute->isSection(), 'is_number' => $attribute->isNumber(), 'is_multiline' => $attribute->isText(), 'is_checkbox' => $attribute->isCheckbox(), 'is_random' => $attribute->isRandom(), 'is_private' => (bool) $attribute->is_private, 'is_pinned' => $attribute->isPinned(), 'is_hidden' => (bool) $attribute->is_hidden, 'is_checked' => false, 'is_deleted' => false, ]; if ($attribute->isList()) { $formatted['values'] = $attribute->listRange(); } if ($template) { $formatted['template'] = [ 'id' => $template->id, 'name' => $template->name, 'url' => $template->getLink(), 'text' => trans_choice( 'attribute_templates.hints.automatic', $templateTotalAttributes, [ 'link' => '' . $template->name . '', 'count' => "{$templateTotalAttributes}", ] ), ]; } $this->attributes->add($formatted); } protected function templates(): array { $templates = []; // Campaign templates $campaignTemplates = AttributeTemplate::has('entity') ->enabled() ->orderBy('name', 'ASC') ->pluck('name', 'id'); $key = __('entities.attribute_templates'); foreach ($campaignTemplates as $id => $name) { $templates[$key][$id] = $name; } // If the campaign isn't boosted, or the marketplace isn't enable, end here if (! $this->campaign->boosted() || ! config('marketplace.enabled')) { return $templates; } // Marketplace campaigns $key = __('attributes/templates.list.sheets'); foreach (CampaignPlugin::templates($this->campaign)->with(['plugin', 'plugin.user'])->get() as $plugin) { if (empty($plugin->plugin)) { continue; } $templates[$key][$plugin->plugin->uuid] = __('campaigns/plugins.templates.name', [ 'name' => $plugin->name, 'user' => $plugin->plugin->author(), ]); } return $templates; } /** * Get the max amount of fields a form can have */ protected function maxFields(): int { return app()->isProduction() ? ini_get('max_input_vars') : 200; } } ================================================ FILE: app/Services/Attributes/BaseAttributesService.php ================================================ touched) { return $this; } $this->entity->touch(); return $this; } /** * Prepare a custom HTML purifying config for attributes. We remove all custom fields that are added to purify.php * and in PurifySetupProvider. */ protected function purifyConfig(): self { $purifyConfig = config('purify.configs.default'); $purifyConfig['HTML.Allowed'] = preg_replace('`,a\[(.*?)\]`', '$2', $purifyConfig['HTML.Allowed']); $purifyConfig['HTML.Allowed'] = preg_replace('`,iframe\[(.*?)\]`', '$2', $purifyConfig['HTML.Allowed']); $purifyConfig['HTML.Allowed'] = preg_replace('`,summary\[(.*?)\]`', '$2', $purifyConfig['HTML.Allowed']); $purifyConfig['HTML.Allowed'] = preg_replace('`,table\[(.*?)\]`', '$2', $purifyConfig['HTML.Allowed']); $purifyConfig['HTML.Allowed'] = preg_replace('`,details\[(.*?)\]`', '$2', $purifyConfig['HTML.Allowed']); $purifyConfig['HTML.Allowed'] = preg_replace('`,figure\[(.*?)\]`', '$2', $purifyConfig['HTML.Allowed']); $purifyConfig['HTML.Allowed'] = preg_replace('`,figcaption\[(.*?)\]`', '$2', $purifyConfig['HTML.Allowed']); $this->purifyConfig = $purifyConfig; return $this; } } ================================================ FILE: app/Services/Attributes/RandomService.php ================================================ type = AttributeType::Standard; $this->value = $value; try { // List of strings separated by commas if (Str::contains($this->value, ',')) { return $this->fromList(); } elseif (Str::contains($this->value, '-')) { $this->type = AttributeType::Number; return $this->fromRange(); } } catch (Exception $e) { // Something went wrong, let's assume the random value is badly formatted return [$this->type, $this->value]; } return [$this->type, $this->value]; } /** * Split an attribute by comma, selecting a value from the list */ protected function fromList(): array { $values = explode(',', $this->value); $validValues = []; foreach ($values as $val) { $val = mb_trim($val); if (! empty($val)) { $validValues[] = $val; } } if (empty($validValues)) { return [$this->type, $this->value]; } elseif (count($validValues) == 1) { return [$this->type, Arr::first($validValues)]; } return [$this->type, Arr::random($validValues)]; } /** * Fine a value between a range */ protected function fromRange(): array { // Numerical value $values = explode('-', $this->value); if (count($values) !== 2) { return [$this->type, $this->value]; } $min = (int) mb_trim($values[0]); $max = (int) mb_trim($values[1]); return [$this->type, mt_rand($min, $max)]; } } ================================================ FILE: app/Services/Attributes/TemplateService.php ================================================ randomService = $randomService; } public function templateName(): string { return $this->templateName; } public function apply(mixed $templateId): bool { $templateIdInt = (int) $templateId; if (Str::isUuid($templateId)) { return $this->applyMarketplaceTemplate($templateId); } elseif (is_int($templateIdInt) && ! empty($templateIdInt)) { $attributeTemplate = $this->getAttributeTemplate($templateId); $attributeTemplate->apply($this->entity); $this->templateName = $attributeTemplate->name; return true; } return false; } /** * Apply a marketplace character sheet on an entity based on its uuid. This is called in the BULK interface * * @todo: move to a separate service */ public function applyMarketplaceTemplate(string $uuid): bool { $campaign = $this->entity->campaign; if (! $campaign->boosted()) { return false; } $plugin = $this->getMarketplacePlugin($uuid, $campaign); if (empty($plugin)) { return false; } $order = $this->entity->attributes()->count(); $existing = array_values($this->entity->attributes()->pluck('name')->toArray()); foreach ($plugin->version->attributes as $attribute) { // If the config is simply a name, we default to a small varchar if (! is_array($attribute)) { continue; } // Don't re-create existing attributes. $name = Arr::get($attribute, 'name', 'unknown'); if (in_array($name, $existing)) { continue; } $type = Arr::get($attribute, 'type', ''); $type = $this->mapAttributeTypeToID($type); $value = Arr::get($attribute, 'value', ''); [$type, $value] = $this->randomService->randomAttribute($type, $value); $order++; Attribute::create([ 'entity_id' => $this->entity->id, 'name' => $name, 'value' => $value, 'default_order' => $order, 'is_private' => false, 'type_id' => $type, 'is_pinned' => false, 'is_hidden' => Arr::get($attribute, 'is_hidden', false), ]); } // Layout attribute for rendering $layout = '_layout'; if (! in_array($layout, $existing)) { $order++; Attribute::create([ 'entity_id' => $this->entity->id, 'name' => '_layout', 'value' => $plugin->version->uuid, 'default_order' => $order, 'is_private' => false, 'is_pinned' => false, 'type_id' => AttributeType::Standard, ]); } $this->entity->touch(); $this->templateName = $plugin->plugin->name; return true; } /** * Get a character sheet marketplace plugin model from the db based on its uuid */ public function marketplaceTemplate(string $uuid): ?CampaignPlugin { if (! $this->campaign->boosted() || ! config('marketplace.enabled')) { return null; } if (! Str::isUuid($uuid)) { return null; } /** @var ?CampaignPlugin $plugin */ $plugin = CampaignPlugin::templates($this->campaign) ->select('campaign_plugins.*') ->leftJoin('plugin_versions as pv', 'pv.plugin_id', 'campaign_plugins.plugin_id') ->where('pv.uuid', $uuid) ->has('plugin') ->first(); // If the plugin is published, we're good. Otherwise, it's if (empty($plugin) || ! $plugin->renderable()) { return null; } return $plugin; } /** * Get an attribute template model from the campaign based on its ID */ protected function getAttributeTemplate(int $templateId): AttributeTemplate { if (isset($this->loadedTemplates[$templateId])) { return $this->loadedTemplates[$templateId]; } /** @var AttributeTemplate $attributeTemplate */ $attributeTemplate = AttributeTemplate::findOrFail($templateId); return $this->loadedTemplates[$templateId] = $attributeTemplate; } /** * Get a marketplace plugin's model based on its UUID * * @return CampaignPlugin|null */ protected function getMarketplacePlugin(string $pluginUuid, Campaign $campaign) { if (isset($this->loadedPlugins[$pluginUuid])) { return $this->loadedPlugins[$pluginUuid]; } return $this->loadedPlugins[$pluginUuid] = CampaignPlugin::templates($campaign) ->select('campaign_plugins.*') // ->leftJoin('plugins as p', 'p.id', 'plugin_id') ->where('p.uuid', $pluginUuid) ->first(); } /** * Map an attribute type from its string representation to an ID (as saved in the DB) * * @param string|null $type the string type of attribute to be converted to an int */ protected function mapAttributeTypeToID(?string $type = null): AttributeType { if (empty($type) || $type === 'attribute') { return AttributeType::Standard; } $mapping = [ 'text' => AttributeType::Block, 'checkbox' => AttributeType::Checkbox, 'section' => AttributeType::Section, 'block' => AttributeType::Section, 'random' => AttributeType::Random, 'number' => AttributeType::Number, 'list' => AttributeType::List, ]; if (isset($mapping[$type])) { return $mapping[$type]; } dd('missing mapping for ' . $type); } /** * Deprecated as of 1.30 * Get a community template base on its name to render properly * * @return bool|Template */ public function communityTemplate(string $template) { $templates = config('attribute-templates.templates'); if (Arr::exists($templates, $template)) { /** @var Template $template */ // @phpstan-ignore-next-line return new $templates[$template]; } return false; } public function api(string|int $template): array { $templateIdInt = (int) $template; if (Str::isUuid($template)) { return $this->loadCharacterSheet($template); } elseif (is_int($templateIdInt) && ! empty($templateIdInt)) { return $this->loadCampaignTemplate($template); } return []; } protected function loadCharacterSheet(string $template): array { $plugin = $this->getMarketplacePlugin($template, $this->campaign); if (empty($plugin)) { return []; } $attributes = []; foreach ($plugin->version->attributes as $attribute) { // If the config is simply a name, we default to a small varchar if (! is_array($attribute)) { continue; } // Don't re-create existing attributes. $name = Arr::get($attribute, 'name', 'unknown'); $type = Arr::get($attribute, 'type', ''); $type = $this->mapAttributeTypeToID($type); $value = Arr::get($attribute, 'value', ''); [$type, $value] = $this->randomService->randomAttribute($type, $value); $attributes[] = [ 'name' => $name, 'value' => $value, 'is_private' => false, 'is_pinned' => false, 'is_hidden' => (bool) Arr::get($attribute, 'is_hidden', false), 'is_checked' => false, 'is_deleted' => false, 'is_checkbox' => $type === AttributeType::Checkbox, 'is_multiline' => $type === AttributeType::Block, 'is_section' => $type === AttributeType::Section, 'is_number' => $type === AttributeType::Number, 'placeholder' => Arr::get($attribute, 'placeholder', ''), ]; } // Layout attribute for rendering $attributes[] = [ 'name' => '_layout', 'value' => $plugin->version->uuid, 'is_private' => false, 'is_pinned' => false, 'is_hidden' => false, 'is_checked' => false, 'is_deleted' => false, ]; return $attributes; } protected function loadCampaignTemplate(int $templateId): array { $template = AttributeTemplate::findOrFail($templateId); $attributes = []; /** @var Attribute $attribute */ foreach ($template->entity->attributes()->orderBy('default_order', 'ASC')->get() as $attribute) { [$type, $value] = $this->randomService->randomAttribute($attribute->type_id, $attribute->value); $attribute->type_id = $type; $attributes[] = [ 'name' => $attribute->name, 'value' => $value, 'is_private' => (bool) $attribute->is_private, 'is_pinned' => $attribute->isPinned(), 'is_hidden' => false, 'is_checked' => false, 'is_deleted' => false, 'source_id' => $attribute->id, 'is_checkbox' => $attribute->isCheckbox(), 'is_multiline' => $attribute->isText(), 'is_random' => $attribute->isRandom(), 'is_section' => $attribute->isSection(), 'is_number' => $attribute->isNumber(), ]; } return $attributes; } } ================================================ FILE: app/Services/Auth/LoginService.php ================================================ viaRemember() ? UserAction::autoLogin : UserAction::login; $userLogType = session()->get('kanka.userLog', $action); if ($this->user->isBanned()) { $userLogType = session()->get('kanka.userLog', UserAction::bannedLogin); } $this->user->log($userLogType); session()->remove('kanka.userLog'); return $this; } public function updateLastLoginTime(): self { $this->user->updateQuietly(['last_login_at' => Carbon::now()]); return $this; } public function clearInactivityFlag(): self { // Delete any flags to auto-delete the account based on inactivity UserFlag::where('user_id', $this->user->id) ->whereIn('flag', [UserFlags::firstWarning->value, UserFlags::secondWarning->value]) ->delete(); return $this; } public function loadFlags(): self { // Only bother users for up to 30 days about their free trial $flag = $this ->user ->flags() ->freeTrial() ->whereDate('created_at', '>=', Carbon::now()->subDays(30)) ->count() === 1; if ($flag) { // If the user "dismissed" the tutorial 2 or more days ago, cancel that $count = $this->user->tutorials() ->where('code', 'banner_free_trial') ->whereDate('created_at', '<', Carbon::now()->subDays(2)) ->delete(); if ($count === 1) { UserCache::user($this->user)->clear(); } session()->put('kanka.freeTrial', true); } return $this; } } ================================================ FILE: app/Services/Auth/SessionService.php ================================================ has('invite_token')) { return null; } try { $campaign = $this->inviteService ->user($this->user) ->useToken(session()->get('invite_token')) ->attribute() ->campaign(); $this->campaignService ->user($this->user) ->campaign($campaign) ->set(); return true; } catch (Exception $e) { // Silence errors here return false; } } public function handleFirstLogin(): ?bool { if (! session()->has('first_login')) { return null; } try { $this->starterService ->user($this->user) ->create(); session()->remove('first_login'); return true; } catch (Exception $e) { // Log exception or handle it properly return false; } } } ================================================ FILE: app/Services/BookmarkService.php ================================================ get('bookmark'); if (empty($ids)) { return false; } $position = 1; foreach ($ids as $id) { /** @var ?Bookmark $link */ $link = Bookmark::where('id', $id)->first(); if (empty($link)) { continue; } $link->position = $position; $link->saveQuietly(); $position++; } return true; } } ================================================ FILE: app/Services/Bookmarks/RandomEntityService.php ================================================ bookmark = $bookmark; return $this; } public function url(): ?string { $entityType = $this->bookmark->random_entity_type != 'any' ? $this->bookmark->random_entity_type : null; $entityTypeID = null; if (! empty($entityType)) { $entityTypeID = config('entities.ids.' . $entityType); } /** @var ?Entity $entity */ $entity = Entity::inTags($this->bookmark->tags->pluck('id')->toArray()) ->inTypes($entityTypeID) ->whereNotIn('entities.id', Dashboard::excluding()) ->orderByRaw('RAND()') ->first(); if (empty($entity) || $entity->isMissingChild()) { return null; } return $entity->url(); } } ================================================ FILE: app/Services/Bookmarks/RoutingService.php ================================================ bookmark = $bookmark; return $this; } /** * Get the route the bookmark points to */ public function url(): string { if ($this->bookmark->dashboard) { $dashboard = $this->bookmark->dashboard_id; if (Arr::get($this->bookmark->options, 'default_dashboard') === '1') { $dashboard = 'default'; } return route('dashboard', [$this->campaign, 'dashboard' => $dashboard, 'bookmark' => $this->bookmark->id]); } elseif ($this->bookmark->isRandom()) { return route('bookmarks.random', [$this->campaign, $this->bookmark->id]); } return ! empty($this->bookmark->entity_id) ? $this->getEntityRoute() : $this->getIndexRoute(); } /** * Generate a route for an entity's overview or subpage */ protected function getEntityRoute(): string { $plural = $this->bookmark->target->entityType->pluralCode(); if (empty($plural)) { return ''; } $route = 'entities.show'; $entity = true; if (! empty($this->bookmark->menu)) { $menuRoute = $this->bookmark->target->entityType->pluralCode() . '.' . $this->bookmark->menu; $entity = false; // Inventories use a different url buildup $routeOptions = [$this->campaign, $this->bookmark->target->id, 'bookmark' => $this->bookmark->id]; if ($this->bookmark->menu === 'inventory') { return route('entities.inventory', $routeOptions); } elseif ($this->bookmark->menu === 'relations') { return route('entities.relations.index', $routeOptions); } elseif ($this->bookmark->menu === 'abilities') { if ($this->bookmark->target->isAbility()) { $routeOptions = [$this->campaign, $this->bookmark->target->entity_id, 'bookmark' => $this->bookmark->id]; return route('abilities.abilities', $routeOptions); } return route('entities.entity_abilities.index', $routeOptions); } elseif ($this->bookmark->menu === 'assets') { return route('entities.entity_assets.index', $routeOptions); } elseif ($this->bookmark->menu === 'reminders') { return route('entities.reminders.index', $routeOptions); } elseif ($this->bookmark->menu === 'attributes') { return route('entities.attributes', $routeOptions); } if (Route::has($menuRoute)) { $route = $menuRoute; } } return route($route, $this->getRouteParams($entity)); } /** * Generate the route for a list of entities */ protected function getIndexRoute(): string { $filters = $this->bookmark->filters . '&_clean=true&_from=bookmark&bookmark=' . $this->bookmark->id; if (! empty($this->bookmark->options['is_nested']) && $this->bookmark->options['is_nested'] == '1') { $filters .= '&n=1'; } try { return route('entities.index', [$this->campaign, $this->bookmark->entityType, $filters]); } catch (Exception $e) { return '/invalid'; } } public function getRouteParams(bool $entity): array { $parameters = [ $this->campaign, $entity ? $this->bookmark->target : $this->bookmark->target->entity_id, 'bookmark' => $this->bookmark->id, ]; if (! empty($this->bookmark->menu)) { if ($this->bookmark->menu == 'all-members') { $parameters['all_members'] = 1; } if (isset($this->bookmark->options['subview_filter'])) { $parameters[] = $this->bookmark->options['subview_filter']; } } return $parameters; } } ================================================ FILE: app/Services/Bragi/BragiService.php ================================================ openAI = $service; } public function prepare(): array { $data = [ 'header' => __('bragi.intro', [ 'name' => 'Bragi', 'here' => '' . __('bragi.here') . '', ]), ]; if (! $this->user->hasTokens()) { return $this->renderError($data, 'invalid-sub'); } elseif ($this->user->availableTokens() <= 0) { return $this->renderError($data, 'out-of-tokens', ['date' => $this->user->tokenRenewalDate()]); } $data['texts'] = [ 'placeholder' => __('bragi.placeholders.prompt'), 'submit' => __('bragi.actions.generate'), 'insert' => __('bragi.actions.insert'), 'tokens' => __('bragi.token-limit', ['amount' => '']), 'loading' => __('bragi.loading'), ]; $data['limits'] = [ 'prompt' => config('bragi.limit.prompt'), ]; $data['tokens'] = $this->user->availableTokens(); return $data; } /** * Call the API and generate a result for the user. */ public function generate(BragiRequest $request): array { if (! $this->user->hasTokens()) { return $this->renderError([], 'invalid-sub'); } elseif ($this->user->availableTokens() <= 0) { return $this->renderError([], 'out-of-tokens', ['date' => $this->user->tokenRenewalDate()]); } $data = []; $prompt = $request->get('prompt'); $context = []; if ($request->filled('name')) { $context['name'] = $request->get('name'); } if ($request->filled('gender')) { $context['gender'] = $request->get('gender'); } if ($request->filled('pronouns')) { $context['pronouns'] = $request->get('pronouns'); } $openAI = $this ->openAI ->input($prompt, $context) ->campaign($this->campaign) ->generate(); try { $data['result'] = $this->openAI->result(); $logs = []; $logs = $openAI['usage']; // Log the result into the db for admins $this->log($prompt, $data['result'], $logs); } catch (OpenAiException $e) { $data['result'] = 'API error, please try again'; Log::warning('OpenAI error', $e->getContext()); } $data['tokens'] = $this->user->availableTokens(); if ($data['tokens'] <= 0) { $data['message'] = __('bragi.errors.out-of-tokens', ['date' => $this->user->tokenRenewalDate()]); } return $data; } /** * Log what was generated into the db * * @return void */ protected function log(string $prompt, string $result, array $data = []) { $log = new BragiLog; $log->user_id = $this->user->id; $log->campaign_id = $this->campaign->id; $log->prompt = $prompt; $log->result = $result; $log->data = $data; $log->save(); } /** * Prepare an error for the plugin */ protected function renderError(array $data, string $error, array $params = []): array { $data['error'] = $error; $data['message'] = __('bragi.errors.' . $error, $params); return $data; } } ================================================ FILE: app/Services/Bragi/OpenAiService.php ================================================ prompt = $prompt; foreach ($context as $key => $value) { $this->$key = $value; } return $this; } public function generate(): array { $token = config('openai.secret'); $openAi = new OpenAi($token); $openAi->setCustomURL(config('openai.custom_url')); // Creating prompt $prompt = $this->preparePrompt(); // Choosing model $engine = config('bragi.backstory.engine'); // Defining max tokens // 1 token is almost 0.75 word $maxTokens = config('bragi.backstory.tokens'); $complete = $openAi->chat([ 'model' => $engine, 'messages' => $prompt, 'temperature' => config('bragi.backstory.temperature'), 'max_tokens' => $maxTokens, 'frequency_penalty' => config('bragi.backstory.frequency_penalty'), 'presence_penalty' => config('bragi.backstory.presence_penalty'), ]); $this->output = json_decode($complete, true); return $this->output; } /** * Generate the prompt to send to ChatGTP */ protected function preparePrompt(): array { $prompts = [ [ 'role' => 'system', 'content' => __('bragi/backstory.system'), ], ]; $roles = []; if (! empty($this->name)) { $roles[] = __('bragi/backstory.setup.name', ['name' => $this->name]); } if (! empty($this->pronouns)) { $roles[] = __('bragi/backstory.setup.gender', ['gender' => $this->gender]); } if (! empty($this->gender)) { $roles[] = __('bragi/backstory.setup.pronouns', ['pronouns' => $this->pronouns]); } if ($this->campaign->systems) { $roles[] = __('bragi/backstory.setup.systems', ['systems' => $this->campaign->systems->pluck('name')->implode(', ')]); } if ($this->campaign->genres) { // Comma-separated list of campaign genres $list = new Collection; foreach ($this->campaign->genres as $genre) { $list->push(__('genres.' . $genre->slug)); } $roles[] = __('bragi/backstory.setup.genres', ['genres' => $list->implode(', ')]); } $roles[] = __('bragi/backstory.setup.prompt', ['prompt' => $this->prompt]); $roles[] = __('bragi/backstory.closing'); foreach ($roles as $role) { } $prompts[] = [ 'role' => 'user', 'content' => implode("\n", $roles), ]; return $prompts; } public function result(): string { if (! Arr::has($this->output, 'choices')) { $excep = new OpenAiException; $excep->setContext($this->output); throw $excep; } $return = ''; $texts = explode("\n", $this->output['choices'][0]['message']['content']); foreach ($texts as $text) { $striped = mb_trim(htmlentities($text)); if (empty($striped) || $striped == '.') { continue; } $return .= '

    ' . $text . '

    '; } // Disciple of Kankappy 0.x% if (mt_rand(1, 1000) <= config('bragi.kankappy')) { $return .= '

    ' . __('bragi.kankappy') . '

    '; } return $return; } } ================================================ FILE: app/Services/BreadcrumbService.php ================================================ campaign; if (isset($this->entityType) && $this->entityType->hasEntity()) { $params['entityType'] = $this->entityType; $entityIndexRoute = route('entities.index', $params); } elseif (isset($this->entity) && $this->entity->entityType->hasEntity()) { $params['entityType'] = $this->entity->entityType; $entityIndexRoute = route('entities.index', $params); } else { $entityIndexRoute = route($name . '.index', $params); } return $entityIndexRoute; } public function list(): array { return [ 'url' => $this->index(), 'label' => isset($this->entityType) ? $this->entityType->plural() : $this->entity->entityType->plural(), ]; } public function show(): array { return [ 'url' => route('entities.show', [$this->campaign, $this->entity]), 'label' => $this->entity->name, ]; } } ================================================ FILE: app/Services/BulkService.php ================================================ ids = $ids; return $this; } /** * Total updated entities submitted (can be different from the total that was updated) */ public function total(): int { return $this->total; } /** * Delete several entities * * @throws Exception */ public function delete(): int { $model = $this->getModel(); if (isset($this->entityType)) { if (! $this->entityType->isBookmark()) { $with = ['entityType', 'campaign']; if ($this->entityType->isNested()) { $with[] = 'children'; } $entities = Entity::with($with)->whereIn('id', $this->ids)->get(); } else { $entities = $model->whereIn('id', $this->ids)->get(); } } else { $entities = $model->with('mirror')->whereIn('id', $this->ids)->get(); } /** @var Entity|Relation $entity */ foreach ($entities as $entity) { if (! $this->can('delete', $entity)) { continue; } if ($this->request->get('delete_mirrored') && $entity->mirror) { $entity->mirror->delete(); $this->count++; } $entity->delete(); $this->count++; } return $this->count; } /** * @throws Exception */ public function export(): Collection { return Entity::whereIn('id', $this->ids)->get(); } /** * Set permissions for several entities * * @return int number of updated entities */ public function permissions(array $permissions = [], bool $override = true): int { $entities = Entity::whereIn('id', $this->ids)->get(); foreach ($entities as $entity) { if (! $this->can('update', $entity)) { continue; } $this->permissionService ->entity($entity) ->override($override) ->change($permissions); $this->count++; } return $this->count; } /** * @throws TranslatableException */ public function copyToCampaign(Campaign $campaign): int { // First we make sure we have access to the new campaign. // todo: move this to the request validator $check = $this->user->campaigns()->where('campaign_id', $campaign->id)->first(); if (empty($check)) { throw new TranslatableException('crud.move.errors.unknown_campaign'); } $this->moveService ->campaign($this->campaign) ->copy(true) ->to($campaign); $with = [ 'entityType', 'image', 'inventories', 'attributes', ]; $entities = Entity::with($with)->whereIn('id', $this->ids)->get(); foreach ($entities as $entity) { if (! $this->can('update', $entity)) { continue; } if ($this->moveService->entity($entity)->process()) { $this->count++; } } return $this->count; } public function transform(EntityType $entityType): int { $this->transformService ->entityType($entityType) ->campaign($this->campaign); $entities = Entity::whereIn('id', $this->ids)->get(); foreach ($entities as $entity) { if (! $this->can('update', $entity)) { continue; } $this->transformService->entity($entity); $this->transformService->transform(); $this->count++; } return $this->count; } /** * @throws Exception */ public function editing(array $fields, Bulk $bulk): int { $model = $this->getModel(); // Only get fields that can be bulk edited and with content $fillableFields = Arr::only($fields, $bulk->fields()); $filledFields = []; $filledForeigns = []; foreach ($fillableFields as $field => $value) { if (is_array($value) && ! empty($value)) { $filledFields[$field] = $value; } elseif (! empty($value) || ($value === '0' && Str::endsWith($field, '_id'))) { $filledFields[$field] = mb_trim($value); } } // Purify name if (Arr::has($filledFields, 'name')) { $filledFields['name'] = Purify::clean($filledFields['name']); } // Loop on boolean fields that can be true, false or null foreach ($bulk->booleans() as $field) { // Field wasn't provided in request, ignore if (! Arr::has($fields, $field)) { continue; } $value = Arr::get($fields, $field); // If the field is a boolean type is_ or has_ and the value is null, we skip updating it // Also skip colour when empty/null if (Str::startsWith($field, ['is_', 'has_']) && $value === null) { // Do nothing } elseif ($field === 'colour' && empty($value)) { // Do nothing } else { $filledFields[$field] = $value; } } // Loop on all the bulk fields that are foreign relations foreach ($bulk->foreignRelations() as $relation) { // Field wasn't provided in request, ignore if (! Arr::has($fields, $relation)) { continue; } foreach ($fields[$relation] as $foreignID) { if (! isset($filledForeigns[$relation])) { $filledForeigns[$relation] = []; } $filledForeigns[$relation][] = $foreignID; } } // Private if (isset($fields['is_private']) && $fields['is_private'] != null) { $filledFields['is_private'] = $fields['is_private'] === '0'; } // Active if (isset($fields['is_active']) && $fields['is_active'] != null) { $filledFields['is_active'] = $fields['is_active'] === '1'; } // List of fields that can have +/- math operations, like a character's age $maths = $bulk->maths(); // Handle tags differently unset($filledFields['tags']); $tagIds = Arr::get($fields, 'tags', []); // Handle locations differently unset($filledFields['locations']); $locationIds = Arr::get($fields, 'locations', []); // Handle creators differently unset($filledFields['creators']); $creatorIds = Arr::get($fields, 'creators', []); // Handle images differently if (isset($filledFields['entity_image'])) { $imageUuid = $filledFields['entity_image']; unset($filledFields['entity_image']); } if (isset($filledFields['entity_header'])) { $headerUuid = $filledFields['entity_header']; unset($filledFields['entity_header']); } if (isset($filledFields['type'])) { $type = $filledFields['type']; unset($filledFields['type']); } // Handle entity_type_id unset (value "0" means remove) $unsetEntityType = false; if (Arr::get($filledFields, 'entity_type_id') === '0') { $unsetEntityType = true; unset($filledFields['entity_type_id']); } // Handle status_id (entity-level field, "0" means remove) $unsetStatus = false; $newStatusId = null; if (Arr::has($filledFields, 'status_id')) { if (Arr::get($filledFields, 'status_id') === 'remove') { $unsetStatus = true; } else { $newStatusId = $filledFields['status_id']; } unset($filledFields['status_id']); } if (! isset($this->entityType)) { $mirrorOptions = []; $mirrorOptions['unmirror'] = (bool) Arr::get($fields, 'unmirror', '0'); $mirrorOptions['update_mirrored'] = (bool) Arr::get($fields, 'update_mirrored', '0'); return $this->updateRelations($filledFields, $mirrorOptions); } $with = $this->entityType->hasEntity() ? ['tags'] : []; $models = $model->with($with)->whereIn('id', $this->ids)->get(); foreach ($models as $entity) { $this->total++; if (! $this->can('update', $entity)) { continue; } $entityFields = $filledFields; // Handle math fields foreach ($maths as $math) { $mathField = Arr::get($entityFields, $math, false); if ($mathField !== false && Str::startsWith($mathField, ['+', '-']) && is_numeric($entity->{$math})) { if (Str::startsWith($mathField, '+')) { $entityFields[$math] = $entity->child->{$math} + (int) Str::after($mathField, '+'); } else { $entityFields[$math] = $entity->child->{$math} - (int) Str::after($mathField, '-'); } } } if ($this->entityType->hasEntity() && $this->entityType->isStandard()) { $entity->child->update($entityFields); } else { // Relations, bookmarks $entity->update($entityFields); } // Foreign belongsTo loop foreach ($filledForeigns as $relation => $ids) { if ($this->entityType->isStandard()) { $entity->child->{$relation}()->syncWithoutDetaching($ids); } else { $entity->{$relation}()->syncWithoutDetaching($ids); } } // We have to still update the entity object (except for bookmarks) if (isset($imageUuid)) { $entity->image_uuid = $imageUuid; // Changed the image, reset the focus $entity->focus_x = null; $entity->focus_y = null; } if (isset($headerUuid)) { $entity->header_uuid = $headerUuid; } if (isset($type)) { $entity->type = $type; } // Sync parent_id if provided, preventing self-referencing if (isset($entityFields['parent_id']) && intval($entityFields['parent_id']) != $entity->id) { $entity->parent_id = $entityFields['parent_id']; } if ($newStatusId !== null) { $entity->status_id = $newStatusId; } elseif ($unsetStatus) { $entity->status_id = null; } if ($this->entityType->hasEntity() && $this->entityType->isStandard()) { $entity->is_private = $entity->child->is_private; $entity->name = $entity->child->name; } $entity->update(); $this->count++; $locationsAction = Arr::get($fields, 'bulk-locations', 'add'); if ($locationsAction === 'remove') { $entity->locations()->detach($locationIds); } elseif (! empty($locationIds)) { $this->locationRelationsService->attach($entity, $locationIds); } // Handle creators (items only) if ($this->entityType->hasEntity() && $this->entityType->isStandard() && method_exists($entity->child, 'creators')) { if (Arr::get($fields, 'bulk-creators') === 'remove') { $entity->child->creators()->detach(); } elseif (! empty($creatorIds)) { $entity->child->creators()->syncWithoutDetaching($creatorIds); } } // Handle entity_type_id unset (attribute templates only) if ($unsetEntityType && $this->entityType->hasEntity() && $this->entityType->isStandard() && in_array('entity_type_id', $entity->child->getFillable())) { $entity->child->update(['entity_type_id' => null]); } // No tags? We're done if (empty($fields['tags'])) { continue; } $tagAction = Arr::get($fields, 'bulk-tagging', 'add'); if ($tagAction === 'remove') { $entity->tags()->detach($tagIds); } else { /** @var TagService $tagService */ $tagService = app()->make(TagService::class); $tagService ->user($this->user) ->entity($entity) ->withNew() ->add($tagIds); $entity->touch(); } } return $this->count; } /** * Bulk apply attribute templates * * @param string $template * * @throws BindingResolutionException */ public function templates(string|int $template): int { /** @var AttributeService $service */ $service = app()->make('App\Services\AttributeService'); $entities = Entity::whereIn('id', $this->ids)->get(); foreach ($entities as $entity) { if (! $this->can('update', $entity)) { continue; } $service->apply($entity, $template); $this->count++; } return $this->count; } /** * @throws Exception */ protected function getModel() { if (isset($this->entityType)) { if ($this->entityType->isBookmark()) { return new Bookmark; } return new Entity; } return new Relation; } protected function updateRelations(array $filledFields, $mirrorOptions): int { $relations = Relation::whereIn('id', $this->ids)->get(); // If the colour is empty, unset it if (empty($filledFields['colour'])) { unset($filledFields['colour']); } /** @var Relation $relation */ foreach ($relations as $relation) { $this->total++; if (! $this->user->can('update', $relation)) { // Can't update this? Technically not possible since bulk editing is only available // for admins, but better safe than sorry continue; } // Same owner and target? no bueno if ($relation->owner_id == Arr::get($filledFields, 'target_id') || ($relation->target_id == Arr::get($filledFields, 'owner_id'))) { continue; } if ($mirrorOptions['update_mirrored'] && $relation->mirror) { $mirrorFields = Arr::except($filledFields, ['target_id', 'owner_id']); $relation->mirror->update($mirrorFields); $this->count++; $this->total++; } if ($mirrorOptions['unmirror'] && $relation->mirror) { $relation->mirror->update(['mirror_id' => null]); $filledFields['mirror_id'] = null; if (! $mirrorOptions['update_mirrored']) { $this->count++; $this->total++; } } $relation->update($filledFields); $this->count++; } return $this->count; } protected function can(string $action, $entity): bool { if (! isset($this->entityType) || ! $this->entityType->hasEntity() || $entity instanceof Entity) { return $this->user->can($action, $entity); } return $this->user->can($action, $entity); } } ================================================ FILE: app/Services/Caches/AdCacheService.php ================================================ ads = false; return $this; } public function canHaveAds(): bool { return $this->ads; } public function get(): ?Ad { return $this->ad; } public function newId(bool $reset = false): self { if (! $reset && isset($this->currentId)) { $this->currentId++; } else { $this->currentId = 1; } return $this; } public function id(string $key): string { if ($this->currentId === 1) { return $key; } return $key . '_' . $this->currentId; } public function test(int $section, int $id): bool { $this->ad = Ad::select(['id', 'html'])->section($section)->where('id', $id)->first(); return ! empty($this->ad); } public function show(): string { return (string) $this->ad->html; } /** * Check if there is an ad to be displayed */ public function has(int $section): bool { $this->load($section); return (bool) (! empty($this->ad)); } /** * Load the ad in memory */ protected function load(int $section): self { if (! config('app.admin')) { return $this; } $key = $this->cacheKey($section); if (cache()->has($key)) { $this->ad = cache()->get($key); return $this; } $this->ad = Ad::select(['id', 'html'])->section($section)->where('is_active', true)->first(); // Save a "false" model in the db to avoid re-calling the db each time if (empty($this->ad)) { $this->ad = false; } cache()->put($key, $this->ad, 86400); return $this; } /** * Ad cache key for section */ protected function cacheKey(int $section): string { return 'ad_section_' . $section; } } ================================================ FILE: app/Services/Caches/BaseCache.php ================================================ class_basename($this), // 'key' => $key, // ]); return Cache::forget($key); } /** * Wrapper for the cache get method */ protected function get(string $key) { return Cache::get($key); } /** * Wrapper for the cache put method */ protected function put(string $key, $data, int $ttl): bool { Log::info(class_basename($this), [ 'put' => $key, ]); return Cache::put($key, $data, $ttl); } /** * Wrapper for the cache forever method. Don't actually store forever as data from inactive users doesn't * need to be kept somewhere. */ protected function forever(string $key, $data, int $days = 7): bool { Log::info(class_basename($this), [ 'forever' => $key, ]); return Cache::put($key, $data, $days * 86400); } /** * Wrapper for the cache has metho */ protected function has(string $key): bool { return Cache::has($key) && ! app()->environment('testing'); } } ================================================ FILE: app/Services/Caches/BookmarkCacheService.php ================================================ iconSuggestionKey(); return Cache::remember($key, 24 * 3600, function () { return Bookmark::where('campaign_id', $this->campaign->id) ->whereNotNull('icon') ->select(DB::raw('icon, MAX(created_at) as cmat')) ->groupBy('icon') ->orderBy('cmat', 'DESC') ->take(10) ->pluck('icon') ->toArray(); }); } public function clearSuggestion(): self { $this->forget( $this->iconSuggestionKey() ); return $this; } /** * Type suggestion cache key */ protected function iconSuggestionKey(): string { return 'campaign_' . $this->campaign->id . '_bookmark_suggestions'; } } ================================================ FILE: app/Services/Caches/CampaignCacheService.php ================================================ $this->formatSettings(), 'dashboards' => $this->formatDashboards(), 'members' => $this->formatMembers(), 'admin-role' => $this->formatAdminRole(), 'applications' => $this->formatApplications(), 'flags' => $this->formatFlags(), 'time' => time(), ]; } protected function primaryKey(): string { return 'campaign_' . $this->campaign->id; } public function clearSidebar(): self { $this->forget('campaign_' . $this->campaign->id . '_sidebar'); return $this; } } ================================================ FILE: app/Services/Caches/CharacterCacheService.php ================================================ genderSuggestionKey(); return Cache::remember($key, 24 * 3600, function () { return Character::select(DB::raw('sex, MAX(created_at) as cmat')) ->groupBy('sex') ->whereNotNull('sex') ->orderBy('cmat', 'DESC') ->take(10) ->pluck('sex') ->toArray(); }); } public function pronounSuggestion(): array { $key = $this->pronounSuggestionKey(); return Cache::remember($key, 24 * 3600, function () { return Character::select(DB::raw('pronouns, MAX(created_at) as cmat')) ->groupBy('pronouns') ->whereNotNull('pronouns') ->orderBy('cmat', 'DESC') ->take(10) ->pluck('pronouns') ->toArray(); }); } public function clearSuggestion(): self { $this->forget( $this->genderSuggestionKey() ); $this->forget( $this->pronounSuggestionKey() ); return $this; } /** * Type suggestion cache key */ protected function genderSuggestionKey(): string { return 'campaign_' . $this->campaign->id . '_character_gender_suggestions'; } /** * Type suggestion cache key */ protected function pronounSuggestionKey(): string { return 'campaign_' . $this->campaign->id . '_character_pronoun_suggestions'; } } ================================================ FILE: app/Services/Caches/EntityAssetCacheService.php ================================================ iconSuggestionKey(); return Cache::remember($key, 24 * 3600, function () { $default = [ 'fa-brands fa-d-and-d-beyond', 'ra ra-aura', ]; $data = []; $icons = EntityAsset::leftJoin('entities as e', 'e.id', 'entity_assets.entity_id') ->where('e.campaign_id', $this->campaign->id) ->select(DB::raw('JSON_UNQUOTE(JSON_EXTRACT(metadata, "$.icon")) as icon , MAX(entity_assets.created_at) as cmat')) ->groupBy('metadata') ->whereNotNull('metadata->icon') ->where('entity_assets.type_id', EntityAssetType::link) ->orderBy('cmat', 'DESC') ->take(10) ->pluck('icon') ->all(); return array_slice(array_unique(array_merge($icons, $default)), 0, 10); }); } public function clearSuggestion(): self { $this->forget( $this->iconSuggestionKey() ); return $this; } /** * Type suggestion cache key */ protected function iconSuggestionKey(): string { return 'campaign_' . $this->campaign->id . '_entity_asset_suggestions'; } } ================================================ FILE: app/Services/Caches/EntityCacheService.php ================================================ typeSuggestionKey($entityType->code), 24 * 3600, function () use ($entityType) { return Entity::select(DB::raw('type, MAX(created_at) as cmat')) ->groupBy('type') ->whereNotNull('type') ->where('type_id', $entityType->id) ->orderBy('cmat', 'DESC') ->take(20) ->pluck('type') ->all(); }); } public function clearSuggestion(EntityType $entityType): self { $this->forget( $this->typeSuggestionKey( $entityType->code ) ); return $this; } /** * @return MiscModel|mixed */ public function child(Entity $entity) { $key = $entity->type_id . '_' . $entity->entity_id; if (isset($this->entities[$key])) { return $this->entities[$key]; } // Why are we doing ->first? because child is a loop to this function. $child = $entity->child()->first(); return $this->entities[$key] = $child; } /** * Type suggestion cache key */ protected function typeSuggestionKey(string $type): string { return 'campaign_' . $this->campaign->id . '_' . $type . '_type_suggestions'; } } ================================================ FILE: app/Services/Caches/MapMarkerCacheService.php ================================================ iconSuggestionKey(); return Cache::remember($key, 24 * 3600, function () { $default = [ 'ra ra-tower', 'fa-solid fa-home', 'ra ra-capitol', 'fa-solid fa-skull', 'fa-solid fa-coins', 'ra ra-beer', 'fa-solid fa-map-marker-alt', 'fa-solid fa-thumbtack', 'ra ra-wooden-sign', 'fa-solid fa-map-pin', ]; $data = MapMarker::leftJoin('maps as m', 'm.id', 'map_markers.map_id') ->where('m.campaign_id', $this->campaign->id) ->select(DB::raw('custom_icon, MAX(map_markers.created_at) as cmat')) ->groupBy('custom_icon') ->whereNotNull('custom_icon') ->orderBy('cmat', 'DESC') ->take(10) ->pluck('custom_icon') ->all(); foreach ($default as $value) { if (! in_array($value, $data)) { $data[] = $value; } } return array_slice($data, 0, 10); }); } public function clearSuggestion(): self { $this->forget( $this->iconSuggestionKey() ); return $this; } /** * Type suggestion cache key */ protected function iconSuggestionKey(): string { return 'campaign_' . $this->campaign->id . '_map_marker_suggestions'; } } ================================================ FILE: app/Services/Caches/MarketplaceCacheService.php ================================================ countKey(); return Cache::remember($key, 24 * 3600, function () { $data = [ 1 => 0, 2 => 0, 3 => 0, ]; $bonus = config('app.debug') ? 22 : 1; $counts = Plugin::where('status_id', 3) ->groupBy('type_id') ->select('type_id', DB::raw('count(*) as tot')) ->get() ->toArray(); foreach ($counts as $type) { $data[$type['type_id']] = $type['tot'] * $bonus; } return $data; }); } public function clearCount(): self { $this->forget( $this->countKey() ); return $this; } /** * Type suggestion cache key */ protected function countKey(): string { return 'marketplace_counts'; } } ================================================ FILE: app/Services/Caches/QuestCacheService.php ================================================ roleSuggestionKey() . '2'; return Cache::remember($key, 24 * 3600, function () { return QuestElement::select(DB::raw('role, MAX(created_at) as cmat')) ->groupBy('role') ->whereNotNull('role') ->orderBy('cmat', 'DESC') ->take(10) ->pluck('role') ->toArray(); }); } public function clearSuggestion(): self { $this->forget( $this->roleSuggestionKey() ); return $this; } /** * Type suggestion cache key */ protected function roleSuggestionKey(): string { return 'campaign_' . $this->campaign->id . '_quest_element_role_suggestions'; } } ================================================ FILE: app/Services/Caches/ReleaseCacheService.php ================================================ whereRaw('id IN (select MAX(id) FROM releases WHERE published_at < \'' . $date . '\' AND (end_at IS NULL OR end_at > \'' . $date . '\') GROUP BY category_id)') // ->groupBy('category_id2') ->latest('published_at') ->get(); }); } public function clearLatest(): bool { return $this->forget('latest_releases'); } } ================================================ FILE: app/Services/Caches/SingleUserCacheService.php ================================================ user->id . '_entities_created_count'; if ($this->has($key)) { return (int) $this->get($key); } $data = DB::table('entities')->where('created_by', $this->user->id)->count(); $this->forever($key, $data, 1); return $data; } /** * Wrapper for the cache get method */ protected function get(string $key) { return Cache::get($key); } /** * Wrapper for the cache forever method. Don't actually store forever as data from inactive users doesn't * need to be kept somewhere. */ protected function forever(string $key, $data, int $days = 7): bool { Log::info(class_basename($this), [ 'forever' => $key, ]); return Cache::put($key, $data, $days * 86400); } /** * Wrapper for the cache has metho */ protected function has(string $key): bool { return Cache::has($key); } } ================================================ FILE: app/Services/Caches/TimelineElementCacheService.php ================================================ iconSuggestionKey(); return Cache::remember($key, 24 * 3600, function () { $default = [ 'ra ra-tower', 'fa-solid fa-home', 'ra ra-capitol', 'fa-solid fa-skull', 'fa-regular fa-coins', 'ra ra-beer', 'fa-solid fa-map-marker-alt', 'fa-solid fa-thumbtack', 'ra ra-wooden-sign', 'fa-solid fa-map-pin', ]; $data = TimelineElement::leftJoin('timelines as t', 't.id', 'timeline_elements.timeline_id') ->where('t.campaign_id', $this->campaign->id) ->select(DB::raw('icon, MAX(timeline_elements.created_at) as cmat')) ->groupBy('icon') ->whereNotNull('icon') ->orderBy('cmat', 'DESC') ->take(10) ->pluck('icon') ->all(); foreach ($default as $value) { if (! in_array($value, $data)) { $data[] = $value; } } return array_slice($data, 0, 10); }); } public function clearSuggestion(): self { $this->forget( $this->iconSuggestionKey() ); return $this; } /** * Type suggestion cache key */ protected function iconSuggestionKey(): string { return 'campaign_' . $this->campaign->id . '_timeline_element_suggestions'; } } ================================================ FILE: app/Services/Caches/Traits/Campaign/ApplicationCache.php ================================================ primary($this->campaign->id)->get('applications'); } protected function formatApplications(): int { return $this->campaign->applications->count(); } } ================================================ FILE: app/Services/Caches/Traits/Campaign/DashboardCache.php ================================================ primary($this->campaign->id)->get('dashboards'); } protected function formatDashboards(): array { $available = [ 'admin' => [], 'public' => [], ]; /** @var CampaignRole[] $roles */ $roles = $this->campaign->roles()->with(['dashboardRoles', 'dashboardRoles.dashboard'])->get(); foreach ($roles as $role) { $dashboards = $role->dashboardRoles; if ($dashboards->isEmpty()) { continue; } $key = 'role_' . $role->id; if ($role->is_admin) { $key = 'admin'; } elseif ($role->is_public) { $key = 'public'; } $available[$key] = $dashboards; } return $available; } } ================================================ FILE: app/Services/Caches/Traits/Campaign/FlagCache.php ================================================ primary($this->campaign->id)->get('flags')); } protected function formatFlags(): array { $flags = []; foreach ($this->campaign->flags as $flag) { $flags[$flag->flag->value] = $flag->value; } return $flags; } } ================================================ FILE: app/Services/Caches/Traits/Campaign/MemberCache.php ================================================ primary($this->campaign->id)->get('members')); } protected function formatMembers(): array { $data = []; foreach ($this->campaign->members as $member) { $data[] = [ 'id' => $member->user_id, ]; } return $data; } } ================================================ FILE: app/Services/Caches/Traits/Campaign/RoleCache.php ================================================ primary($this->campaign->id)->get('public-permissions'); } /** * Build a list of dashboards setup for the campaign * * @return array[] */ public function adminRole(): array { return $this->primary($this->campaign->id)->get('admin-role'); } protected function formatAdminRole(): array { $role = $this->campaign->roles->where('is_admin', 1)->first(); return [ 'id' => $role->id, 'name' => $role->name, ]; } } ================================================ FILE: app/Services/Caches/Traits/Campaign/SettingCache.php ================================================ primary($this->campaign->id)->get('modules')); } protected function formatSettings(): array { $settings = $this->campaign->setting->toArray(); unset($settings['id'], $settings['campaign_id'], $settings['created_at'], $settings['updated_at']); return $settings; } } ================================================ FILE: app/Services/Caches/Traits/Campaign/StyleCache.php ================================================ stylesKey(); return Cache::remember($key, 24 * 3600, function () { $css = "/**\n * Campaign Styles for campaign #" . $this->campaign->id . "\n */\n\n"; $styles = $this->campaign ->styles() ->enabled() ->defaultOrder() ->get(); foreach ($styles as $style) { /** @var CampaignStyle $style */ if ($style->isTheme()) { $css .= '/** Theme builder #' . $style->id . " */\n@layer theme {\n" . $style->content() . "\n}\n"; continue; } $css .= '/** Style ' . $style->name . '#' . $style->id . " */\n" . $style->content() . "\n"; } return $css; }); } public function stylesTimestamp(): int { return (int) $this->primary($this->campaign->id)->get('time'); } public function clearStyles(): self { $this->forget( $this->stylesKey() ); return $this; } protected function stylesKey(): string { return 'campaign_' . $this->campaign->id . '_styles'; } } ================================================ FILE: app/Services/Caches/Traits/Campaign/ThemeCache.php ================================================ themeKey(); return Cache::remember($key, 24 * 3600, function () { $theme = ''; // @phpstan-ignore-next-line $plugins = CampaignPlugin::leftJoin('plugins as p', 'p.id', 'plugin_id') ->where('campaign_id', $this->campaign->id) ->where('p.type_id', 1) ->where('is_active', true) ->with('version') ->has('plugin') ->has('plugin.user') ->get(); /** @var CampaignPlugin $plugin */ foreach ($plugins as $plugin) { if ($plugin->version->fonts) { $theme .= '/** plugin: ' . e($plugin->name) . ' #' . e($plugin->version->version) . " fonts **/\n"; $theme .= $plugin->version->fonts . "\n\n"; } } foreach ($plugins as $plugin) { $theme .= '/** plugin: ' . e($plugin->name) . ' #' . e($plugin->version->version) . " code **/\n"; $theme .= $plugin->version->content . "\n\n"; } return $theme; }); } public function clearTheme(): self { $this->forget( $this->themeKey() ); return $this; } protected function themeKey(): string { return 'campaign_' . $this->campaign->id . '_theme'; } } ================================================ FILE: app/Services/Caches/Traits/Campaign/ThumbnailCache.php ================================================ primary($this->campaign->id)->get('thumbnails', null); // Since thumbnails come from the db, we need to load them _after_ permissions/campaign roles have been loaded. // It's an extra back and forth with redis on the first init of a clear cache if (is_array($data)) { return $data; } return $this->append($this->campaign->id, 'thumbnails', $this->formatThumbnails()); } protected function formatThumbnails(): array { $defaults = $this->campaign->defaultImages(); $data = []; foreach ($defaults as $default) { $data[Str::singular($default['type'])] = $default['path']; } return $data; } } ================================================ FILE: app/Services/Caches/Traits/PrimaryCache.php ================================================ primary[$cache])) { return $this->primary[$cache]; } $key = $this->primaryKey(); if ($this->has($key)) { return $this->primary[$cache] = new Collection($this->get($key)); } $data = $this->primaryData(); $this->forever($key, $data); return $this->primary[$cache] = new Collection($data); } /** * Clear the primary cache */ public function clear(): self { $this->forget($this->primaryKey()); return $this; } /** * Some data needs to load "after the init", which can be done with this append function. */ protected function append(int $key, string $property, mixed $data): mixed { $this->primary[$key][$property] = $data; $this->forever($this->primaryKey(), $this->primary[$key]); return $data; } } ================================================ FILE: app/Services/Caches/Traits/User/CampaignCache.php ================================================ primary($this->user->id)->get('campaigns') ); } public function follows(): Collection { return new Collection( $this->primary($this->user->id)->get('follows') ); } } ================================================ FILE: app/Services/Caches/Traits/User/RoleCache.php ================================================ adminRole() !== null; } /** * Get the user's admin role in a current campaign */ public function adminRole(): ?array { foreach ($this->roles() as $role) { if ($role['is_admin']) { return $role; } } return null; } /** * Get the user roles */ public function roles(): Collection { $roles = $this->primary($this->user->id)->get('roles'); foreach ($roles as $campaignId => $campaignRoles) { if ($campaignId !== $this->campaign->id) { continue; } return new Collection($campaignRoles); } return new Collection; } } ================================================ FILE: app/Services/Caches/Traits/User/TutorialCache.php ================================================ primary($this->user->id)->get('tutorials')); } protected function prepareTutorials(): array { return $this->user ->tutorials ->pluck('code') ->toArray(); } } ================================================ FILE: app/Services/Caches/Traits/User/UserFlagCache.php ================================================ primary($this->user->id)->get('flags') ); } } ================================================ FILE: app/Services/Caches/UserCacheService.php ================================================ nameKey($userId); return Cache::remember($key, 3600 * 24, function () use ($userId) { /** @var ?User $user */ $user = User::select('name')->find($userId); return $user?->name; }); } public function clearName(): self { $key = $this->nameKey($this->user->id); $this->forget($key); return $this; } public function entitiesCreatedCount(): int { $key = 'user_' . $this->user->id . '_entities_created_count'; return Cache::remember($key, 24 * 3600, function () { return DB::table('entities')->where('created_by', $this->user->id)->count(); }); } protected function nameKey(int $userId): string { return 'user_' . $userId . '_name'; } protected function primaryData(): array { // Prepare the data. $data = [ 'campaigns' => [], 'follows' => [], 'roles' => [], 'tutorials' => [], 'flags' => [], ]; /** @var Campaign $campaign */ foreach ($this->user->campaigns()->userOrdered($this->user)->get() as $campaign) { $data['campaigns'][] = $this->formatCampaign($campaign); } /** @var Campaign $campaign */ foreach ($this->user->following()->public()->userOrdered($this->user)->get() as $campaign) { $data['follows'][] = $this->formatCampaign($campaign); } // Track the user's admin roles foreach ($this->user->campaignRoles as $role) { $data['roles'][$role->campaign_id][] = $this->formatRole($role); } foreach ($this->user->flags as $flag) { $data['flags'][$flag->flag->value] = $this->formatFlag($flag); } $data['tutorials'] = $this->prepareTutorials(); return $data; } /** * Format the campaign for the cache */ protected function formatCampaign(Campaign $campaign): array { return [ 'id' => $campaign->id, 'name' => $campaign->name, 'route' => route('dashboard', $campaign), 'image' => $campaign->image, 'boosted' => $campaign->boosted(), ]; } /** * Format the role for the cache */ protected function formatRole(CampaignRole $role): array { return [ 'id' => $role->id, 'name' => $role->name, 'is_admin' => $role->isAdmin(), 'is_public' => $role->isPublic(), ]; } /** * Format a flag for the cache */ protected function formatFlag(UserFlag $role): array { return [ 'amount' => $role->amount, ]; } protected function primaryKey(): string { return 'user_' . $this->user->id; } } ================================================ FILE: app/Services/CalendarService.php ================================================ calendar = $calendar; return $this; } /** * Add an event to a calendar, and return the new calendar_event model */ public function addEvent(array $data = []): Reminder { $entity = $this->entity($data); $link = new Reminder; $link->remindable_type = Entity::class; $link->remindable_id = $entity->id; $link->calendar_id = $this->calendar->id; $link->year = $data['year']; $link->month = $data['month']; $link->day = $data['day']; $link->length = $data['length']; $link->comment = Purify::clean($data['comment']); $link->is_recurring = Arr::get($data, 'is_recurring', false); $link->colour = Arr::get($data, 'colour', null); $link->recurring_until = Arr::get($data, 'recurring_until', null); $link->recurring_periodicity = Arr::get($data, 'recurring_periodicity', null); $link->visibility_id = Arr::get($data, 'visibility_id', 1); $link->save(); return $link; } /** * Save the weather on the requested date */ public function saveWeather(AddCalendarWeather $request): CalendarWeather { // Make sure we don't already have a weather effect on this date $weather = $this->findWeather( (int) $request->post('year'), (int) $request->post('month'), (int) $request->post('day') ); if (! $weather) { $weather = new CalendarWeather([ 'calendar_id' => $this->calendar->id, 'year' => $request->post('year'), 'month' => $request->post('month'), 'day' => $request->post('day'), 'visibility_id' => $request->post('visibility_id'), 'name' => $request->post('name'), ]); } $weather->fill([ 'weather' => $request->post('weather'), 'temperature' => $request->post('temperature'), 'precipitation' => $request->post('precipitation'), 'wind' => $request->post('wind'), 'effect' => $request->post('effect'), 'visibility_id' => $request->post('visibility_id'), 'name' => $request->post('name'), ]); $weather->save(); return $weather; } /** * Find the saved weather for a specific date */ public function findWeather(int $year, int $month, int $day) { return CalendarWeather::dated( $this->calendar->id, $year, $month, $day )->first(); } /** * Create a new event if it's just a name and no entity id. Otherwise, validate the entity * * @throws TranslatableException */ protected function entity(array $data = []): Entity { if (empty($data['entity_id']) && ! empty($data['name'])) { $entityType = EntityType::find(config('entities.ids.event')); if (! $this->user->can('create', [$entityType, $this->campaign])) { throw new TranslatableException(__('calendars.event.errors.missing_permissions')); } // Create an event $event = new Event; $event->name = Str::after($data['name'], 'new:'); $event->date = $data['year'] . '-' . $data['month'] . '-' . $data['day']; $event->campaign_id = $this->campaign->id; if ($event->save()) { return $event->entity; } } elseif (! empty($data['entity_id'])) { return Entity::findOrFail($data['entity_id']); } throw new TranslatableException(__('calendars.event.errors.invalid_entity')); } } ================================================ FILE: app/Services/Calendars/AdvancerService.php ================================================ calendar = $calendar; return $this; } /** * Advance a calendar's date by one day */ public function advance(): self { [$year, $month, $day] = $this->calendar->dateArray(); // Day is longer than month max length? $months = $this->calendar->months(); if (! empty($day)) { $day = ((int) $day) + 1; if ($day > $months[$month - 1]['length']) { $day = 1; $month++; } } else { $month++; } // Reset month and increment year if ($month > count($months)) { $month = 1; $year++; } if (! $this->calendar->hasYearZero() && $year == 0) { $year++; } $this->calendar->date = $year . '-' . $month . ($day !== false ? '-' . $day : null); $this->calendar->saveQuietly(); return $this; } /** * Retreat a calendar's date by one day */ public function retreat(): self { [$year, $month, $day] = $this->calendar->dateArray(); $day--; $months = $this->calendar->months(); if ($day < 1) { $month--; if ($month < 1) { $month = count($months); $year--; } $day = $months[$month - 1]['length']; } if (! $this->calendar->hasYearZero() && $year == 0) { $year--; } $this->calendar->date = $year . '-' . $month . '-' . $day; $this->calendar->save(); CalendarsClearElapsed::dispatch($this->calendar); return $this; } } ================================================ FILE: app/Services/Calendars/DaysService.php ================================================ intercalary = $intercalary; return $this; } public function month(int $month): self { $this->month = $month; return $this; } public function day(int $day): self { $this->day = $day; return $this; } public function year(int $year): self { $this->year = $year; return $this; } public function daysToDate(): int { // We assume that the 01 01 00 is a monday. // We need to know how many days elapsed since that day, to calculate the offset (total days / week length) $daysInAYear = $days = $leapDays = 0; foreach ($this->calendar->months() as $count => $month) { if (! $this->intercalary && Arr::get($month, 'type') == 'intercalary') { continue; } $length = $month['length']; $daysInAYear += $length; // If the month has already passed, add it to the days for this year if ($count < $this->month - 1) { $days += $length; } } if ($this->calendar->has_leap_year && $this->year >= $this->calendar->leap_year_start) { // If the leap month is intercalary, we don't need to offset anything. $months = $this->calendar->months(); $leapMonth = Arr::get($months, $this->calendar->leap_year_month - 1, false); if ($leapMonth && Arr::get($leapMonth, 'type') == 'intercalary') { // Nothing } else { // Calc the number of years that were leap years // dump("the current year (" . $this->getYear() . ") is >= to when the calendar leap year starts // (" . $this->calendar->leap_year_start . ")"); $yearDiffWithLeapStart = $this->year - $this->calendar->leap_year_start; $amountOfYears = ceil($yearDiffWithLeapStart / max(1, $this->calendar->leap_year_offset)); // dump ("the amount of leap years that has elapsed since the beginning is the following: $amountOfYears"); // dump ("the value is ceil((" . $this->getYear() . "-" . $this->calendar->leap_year_start . ") // / " . $this->calendar->leap_year_offset . ")"); if ($amountOfYears < 0) { $amountOfYears = 0; } $leapDays = $amountOfYears * $this->calendar->leap_year_amount; // dump ("total leap days elapsed: $leapDays"); // But if we are a leap year, we need to do the math if (($this->year - $this->calendar->leap_year_start) % max($this->calendar->leap_year_offset, 1) == 0) { if ($this->month > $this->calendar->leap_year_month) { // We've passed the leap month of the year $leapDays += $this->calendar->leap_year_amount; } } } } // Number of days since the beginning of the year if (! $this->calendar->hasYearZero() && $this->year > 0) { return ($daysInAYear * ($this->year - 1)) + $days + $leapDays; } return ($daysInAYear * $this->year) + $days + $leapDays; } } ================================================ FILE: app/Services/Calendars/MoonService.php ================================================ moons[$day]); } public function get(int $day): array { return $this->moons[$day] ?? []; } public function build(int $totalDays, int $daysInAYear): void { foreach ($this->calendar->moons() as $moon) { $fullmoon = $moon['fullmoon']; // Let's figure out how many full moons occurred until now $numberOfFullMoons = $totalDays / $fullmoon; // When was the last full moon? $lastFullMoon = floor($numberOfFullMoons) * $fullmoon; // Use that to see how many days it's been $daysSinceLastFullMoon = round($totalDays - $lastFullMoon, 2); // Next full moon? If it's 0, we want it today. $nextFullMoon = (1 + $moon['offset']) + ($fullmoon - ($daysSinceLastFullMoon == 0 ? $fullmoon : $daysSinceLastFullMoon)); // Previous cycles. Twice because sometimes the first full moon appears at the end of a long month, so // we need to fill up the month as much as we can. // With a big enough offset and fullmoon cycle, the user can end up with no moon on their first month of // the first year? $maxCycles = max(2, 3); for ($cycles = 1; $cycles <= $maxCycles; $cycles++) { $this->addPhases($nextFullMoon - ($moon['fullmoon'] * $cycles), $moon); } // Current cycles $this->addPhases($nextFullMoon, $moon); // Now the full moon will appear several times on this month/year. $fullMoonsPerYear = ceil($daysInAYear / $fullmoon); for ($i = 0; $i < $fullMoonsPerYear; $i++) { $nextFullMoon += $fullmoon; $this->addPhases($nextFullMoon, $moon); } } } protected function addPhases(float $start, array $moon): void { // Full & New Moon $this->addPhase($start, $moon, 'full', 'far fa-circle'); $newMoon = $start + ($moon['fullmoon'] / 2); $this->addPhase($newMoon, $moon, 'new', 'fa-solid fa-circle'); if ($moon['fullmoon'] <= 10) { return; } // Cycle is long enough for more phases to be displayed $quarterMonth = $moon['fullmoon'] / 4; $this->addPhase($newMoon - $quarterMonth, $moon, 'last_quarter', 'fa-solid fa-circle-half-stroke fa-flip-horizontal'); $this->addPhase($newMoon + $quarterMonth, $moon, '1first_quarter', 'fa-solid fa-circle-half-stroke'); } protected function addPhase(float $nextFullMoon, array $moon, string $type = 'full', string $class = 'fa-regular fa-circle'): void { // Moons can be float so we "floor" them $nextFullMoon = (int) floor($nextFullMoon); // If the next full moon is before year 0... What? if ($nextFullMoon < 0) { // return; } if (! isset($this->moons[$nextFullMoon])) { $this->moons[$nextFullMoon] = []; } $this->moons[$nextFullMoon][] = [ 'name' => $moon['name'], 'type' => $type, 'class' => $class, 'colour' => $this->colour(Arr::get($moon, 'colour', 'grey')), 'id' => Arr::get($moon, 'id', null), ]; } protected function colour(string $colour): string { switch ($colour) { case 'aqua': return '#3B82F6'; // blue-500 case 'black': return '#000000'; // black case 'brown': return '#7C2D12'; // orange-900 (closest brown) case 'green': return '#22C55E'; // green-500 case 'light-blue': return '#93C5FD'; // blue-300 case 'maroon': return '#9D174D'; // pink-800 (closest maroon) case 'navy': return '#1E3A8A'; // blue-900 case 'orange': return '#F97316'; // orange-500 case 'pink': return '#EC4899'; // pink-500 case 'purple': return '#A855F7'; // purple-500 case 'red': return '#EF4444'; // red-500 case 'teal': return '#14B8A6'; // teal-500 case 'yellow': return '#EAB308'; // yellow-500 case 'grey': return '#6B7280'; // gray-500 } return $colour; } } ================================================ FILE: app/Services/Calendars/ReminderService.php ================================================ calendar = $calendar; return $this; } /** * Look at events to calculate the most upcoming events for the calendar * dashboard widget. */ public function upcoming(int $needle = 10): Collection { // @phpstan-ignore-next-line $reminders = $this->calendar->calendarEvents() ->with(['remindable']) ->whereHasMorph( 'remindable', [Entity::class, Post::class], function (Builder $query, string $type) { if ($type === Post::class) { $query->whereHas('entity'); } }) ->where(function ($primary) { $primary->where(function ($sub) { $sub->where(function ($recurring) { $recurring ->where('is_recurring', true) ->whereIn('recurring_periodicity', ['year', 'month']) ->where(function ($recurringuntil) { $recurringuntil ->whereNull('recurring_until') // Events that end in the future are fine, they could be reoccurring on this month ->orWhere('recurring_until', '>=', $this->calendar->currentYear()); }); }); }) ->orWhere(function ($ondate) { // Not recurring $ondate ->where('is_recurring', false) ->where(function ($date) { // An event that happens before this year $date ->where('year', '>', $this->calendar->currentYear()) ->orWhere(function ($subdate) { // An event that happens this year but after this month $subdate ->where('year', $this->calendar->currentYear()) ->where('month', '>', $this->calendar->currentMonth()); }) ->orWhere(function ($subdate) { // An event that happens this year after this year $subdate ->where('year', $this->calendar->currentYear()) ->where('month', $this->calendar->currentMonth()) ->where('day', '>=', $this->calendar->currentDay()); }); }); }); }) // Skip events that were on months which no longer exist ->where('month', '<=', count($this->calendar->months())) ->orderBy('year', 'asc') ->orderBy('month', 'asc') ->orderBy('day', 'asc') ->take($needle) ->get(); // Order the past events in descending date to get the closest ones to the current date first return $reminders->sortBy(function (Reminder $reminder) { return $reminder ->setRelation('calendar', $this->calendar) ->nextUpcomingOccurrence( $this->calendar->currentYear(), $this->calendar->currentMonth(), $this->calendar->currentDay(), $this->calendar->months(), $this->calendar->daysInYear() ); }); } /** * Look at events to calculate the most recently occurring events for the calendar * dashboard widget. */ public function past(int $needle = 10): Collection { // @phpstan-ignore-next-line $reminders = $this->calendar->calendarEvents() ->with(['remindable']) ->whereHas('remindable') ->where(function ($primary) { $primary->where(function ($sub) { $sub->where(function ($recurring) { $recurring ->where('is_recurring', true) ->whereIn('recurring_periodicity', ['year', 'month']) ->where(function ($recurringuntil) { $recurringuntil ->whereNull('recurring_until') // Events that end in the future are fine, they could be reoccurring on this month ->orWhere('recurring_until', '>=', $this->calendar->currentYear()); }) ->where('year', '<=', $this->calendar->currentYear()); }); }) ->orWhere(function ($ondate) { // Not recurring $ondate ->where('is_recurring', false) ->where(function ($date) { // An event that happens before this year $date ->where('year', '<', $this->calendar->currentYear()) ->orWhere(function ($subdate) { // An event that happens this year but before this month $subdate ->where('year', $this->calendar->currentYear()) ->where('month', '<', $this->calendar->currentMonth()); }) ->orWhere(function ($subdate) { // An event that happens this year but before this day $subdate ->where('year', $this->calendar->currentYear()) ->where('month', $this->calendar->currentMonth()) ->where('day', '<', $this->calendar->currentDay()); }); }); }); }) // Skip events that were on months which no longer exist ->where('month', '<=', count($this->calendar->months())) ->orderBy('year', 'desc') ->orderBy('month', 'desc') ->orderBy('day', 'desc') ->take($needle) ->get(); // Order the past events in descending date to get the closest ones to the current date first return $reminders->sortBy(function (Reminder $reminder) { return $reminder ->setRelation('calendar', $this->calendar) ->mostRecentOccurrence( $this->calendar->currentYear(), $this->calendar->currentMonth(), $this->calendar->currentDay(), $this->calendar->months(), $this->calendar->daysInYear() ); }); } } ================================================ FILE: app/Services/Calendars/SeasonService.php ================================================ seasons[$day]); } public function get(string $day): ?string { return $this->seasons[$day] ?? null; } public function build(): void { foreach ($this->calendar->seasons() as $season) { $date = $season['month'] . '-' . $season['day']; $this->seasons[$date] = $season['name']; } } } ================================================ FILE: app/Services/Calendars/WeatherService.php ================================================ currentYear = $currentYear; return $this; } public function has(string $day): bool { return isset($this->effects[$day]); } public function get(string $day): CalendarWeather { return $this->effects[$day]; } public function build(): void { // First build parent weather, and override with local weather if ($this->calendar->calendar) { $weathers = $this->calendar->calendar->calendarWeather()->year($this->currentYear)->get(); /** @var CalendarWeather $weather */ foreach ($weathers as $weather) { $this->effects[$weather->year . '-' . $weather->month . '-' . $weather->day] = $weather; } } $weathers = $this->calendar->calendarWeather()->year($this->currentYear)->get(); /** @var CalendarWeather $weather */ foreach ($weathers as $weather) { $this->effects[$weather->year . '-' . $weather->month . '-' . $weather->day] = $weather; } } } ================================================ FILE: app/Services/Campaign/AchievementService.php ================================================ campaign->superboosted()) { return false; } $cacheKey = 'campaign_' . $this->campaign->id . '_achievements'; if (Cache::has($cacheKey) && app()->isProduction()) { return Cache::get($cacheKey); } // @phpstan-ignore-next-line $characters = $this->campaign->characters()->withInvisible()->count() + $this->random(); // @phpstan-ignore-next-line $locations = $this->campaign->locations()->withInvisible()->count() + $this->random(); // @phpstan-ignore-next-line $creatures = $this->campaign->creatures()->withInvisible()->count() + $this->random(); // @phpstan-ignore-next-line $families = $this->campaign->families()->withInvisible()->count() + $this->random(); // @phpstan-ignore-next-line $organisations = $this->campaign->organisations()->withInvisible()->count() + $this->random(); // @phpstan-ignore-next-line $dead = $this->campaign->characters()->withInvisible()->whereHas('entity', fn ($q) => $q->where('entities.status_id', 3))->count() + $this->random(10, 30); // @phpstan-ignore-next-line $calendars = $this->campaign->calendars()->withInvisible()->count() + $this->random(5, 15); // @phpstan-ignore-next-line $events = $this->campaign->events()->withInvisible()->count() + $this->random(); $tags = $this->taggedEntities() + $this->random(); $plugins = $this->plugins() + $this->random(2, 9); $connections = $this->connections() + $this->random(30, 60); $markers = $this->markers() + $this->random(30, 60); $this->loadModules(); /** @var ?Spotlight $spotlight */ $spotlight = $this->campaign->spotlight() ->where('status', SpotlightStatus::active) ->first(); $stats = [ 'spotlighted' => [ 'icon' => 'fa-duotone fa-stars', 'amount' => $spotlight ? 1 : 0, 'target' => 1, 'level' => $spotlight ? 5 : 0, 'history' => 'spotlighted', 'url' => $spotlight?->url, ], 'characters' => [ 'icon' => $this->modules['character']->icon(), 'amount' => $characters, 'target' => $this->target($characters), 'level' => $this->level($characters), 'module' => $this->moduleName('character'), ], 'locations' => [ 'icon' => $this->modules['location']->icon(), 'amount' => $locations, 'target' => $this->target($locations), 'level' => $this->level($locations), 'module' => $this->moduleName('location'), ], 'creatures' => [ 'icon' => $this->modules['creature']->icon(), 'amount' => $creatures, 'target' => $this->target($creatures), 'level' => $this->level($creatures), 'module' => $this->moduleName('creature'), ], 'families' => [ 'icon' => $this->modules['family']->icon(), 'amount' => $families, 'target' => $this->target($families, 2), 'level' => $this->level($families, 2), 'module' => $this->moduleName('family'), ], 'organisations' => [ 'icon' => $this->modules['organisation']->icon(), 'amount' => $organisations, 'target' => $this->target($organisations, 2), 'level' => $this->level($organisations, 2), 'module' => $this->moduleName('organisation'), ], 'calendars' => [ 'icon' => $this->modules['calendar']->icon(), 'amount' => $calendars, 'target' => $this->target($calendars, 3), 'level' => $this->level($calendars, 3), 'module' => $this->moduleName('calendar'), ], 'events' => [ 'icon' => $this->modules['event']->icon(), 'amount' => $events, 'target' => $this->target($events, 2), 'level' => $this->level($events, 2), 'module' => $this->moduleName('event'), ], 'dead' => [ 'icon' => 'fa-regular fa-skull', 'amount' => $dead, 'target' => $this->target($dead, 2), 'level' => $this->level($dead, 2), 'history' => 'dead', ], 'tags' => [ 'icon' => $this->modules['tag']->icon(), 'amount' => $tags, 'target' => $this->target($tags, 1), 'level' => $this->level($tags, 1), 'history' => 'tagged', ], 'plugins' => [ 'icon' => 'fa-duotone fa-store', 'amount' => $plugins, 'target' => $this->target($plugins, 3), 'level' => $this->level($plugins, 3), 'history' => 'plugins', ], 'markers' => [ 'icon' => 'fa-duotone fa-map-location', 'amount' => $markers, 'target' => $this->target($markers, 2), 'level' => $this->level($markers, 2), 'history' => 'markers', ], 'connections' => [ 'icon' => 'fa-duotone fa-heart', 'amount' => $connections, 'target' => $this->target($connections, 2), 'level' => $this->level($connections, 2), 'history' => 'connections', ], ]; // Calc progress foreach ($stats as $key => $stat) { $stats[$key]['progress'] = ($stat['amount'] / $stat['target']) * 100; } // Reorder uasort($stats, function ($a, $b) { if ($a['level'] == $b['level']) { return 0; } return ($a['level'] < $b['level']) ? 1 : -1; }); Cache::put($cacheKey, $stats, 86400); return $stats; } public function target(int $amount, int $level = 1): int { $targets = $level == 1 ? $this->primaryTargets : ($level == 2 ? $this->secondaryTargets : $this->tertiaryTargets); $last = 20; foreach ($targets as $target) { if ($amount < $target) { return $target; } $last = $target; } return $last; } public function level(int $amount, int $level = 1): int { $targets = $level == 1 ? $this->primaryTargets : ($level == 2 ? $this->secondaryTargets : $this->tertiaryTargets); $level = 0; foreach ($targets as $target) { if ($amount >= $target) { $level++; } } return $level; } public function title(int $amount, int $level = 1): string { $targets = $level == 1 ? $this->primaryTargets : ($level == 2 ? $this->secondaryTargets : $this->tertiaryTargets); foreach ($targets as $target) { if ($amount > $target) { $level++; } } return __('campaigns/stats.titles.' . ($level == 1 ? 'primary' : ($level == 2 ? 'secondary' : 'tertiary')) . '.' . $level); } public function achievements(): array { $dead = $this->campaign->characters()->whereHas('entity', fn ($q) => $q->where('entities.status_id', 3))->count(); $calendars = $this->campaign->calendars()->count(); $achievements = [ 'dead' => [ 'title' => __('campaigns/stats.achievements.murderer.title'), 'goal' => __('campaigns/stats.achievements.murderer.goal'), 'amount' => $dead, 'target' => 100, 'icon' => 'fa-regular fa-skull', ], 'calendars' => [ 'title' => __('campaigns/stats.achievements.calendars.title'), 'goal' => __('campaigns/stats.achievements.calendars.goal'), 'amount' => $calendars, 'target' => 3, 'icon' => 'fa-regular fa-calendar', ], ]; // Success rate foreach ($achievements as $key => $achievement) { $achievements[$key]['score'] = $achievement['amount'] > $achievement['target'] ? 100 : ($achievement['amount'] / $achievement['target']); } // Reorder by achieved or close usort($achievements, function ($a, $b) { return strcmp((string) $a['score'], (string) $b['score']); }); return $achievements; } protected function taggedEntities(): int { return EntityTag::leftJoin('entities as e', 'e.id', 'entity_tags.entity_id') ->where('e.campaign_id', $this->campaign->id) ->whereNull('e.deleted_at') ->count(); } protected function plugins(): int { return CampaignPlugin::where('campaign_id', $this->campaign->id) ->count(); } protected function connections(): int { return Relation::where('campaign_id', $this->campaign->id) ->whereNull('mirror_id') ->count(); } protected function markers(): int { return MapMarker::leftJoin('maps as m', 'm.id', 'map_markers.map_id') ->where('m.campaign_id', $this->campaign->id) ->whereNull('m.deleted_at') ->count(); } protected function moduleName(string $module): array { /** @var EntityType $entityType */ $entityType = $this->modules[$module]; return [ 'singular' => $entityType->name(), 'plural' => $entityType->plural(), ]; } protected function random(int $min = 50, int $max = 4000): int { if (app()->isProduction()) { return 0; } return mt_rand($min, $max); } protected function loadModules(): void { /** @var EntityType $module */ foreach (EntityType::default()->get() as $module) { $this->modules[$module->code] = $module; } } } ================================================ FILE: app/Services/Campaign/ApplicationService.php ================================================ application = $application; return $this; } public function apply(StoreCampaignApplication $request): self { $data = $request->validated(); // Attach relationship data $data['campaign_id'] = $this->campaign->id; $data['user_id'] = $this->user->id; // Create the record Application::create($data); CampaignCache::campaign($this->campaign)->clear(); NotifyAdmins::dispatch( $this->campaign, 'application.new', 'door-open', 'yellow', [ 'link' => route('applications.index', $this->campaign), 'campaign' => $this->campaign->name, ] ); return $this; } /** * @throws Exception */ public function process(array $data): string { $return = 'approved'; if (Arr::get($data, 'action') === 'reject') { $return = 'rejected'; // Notify the user $rejection = $this->purify(Arr::get($data, 'reason')); if ($rejection == '') { $key = 'campaign.application.rejected_no_message'; } else { $key = 'campaign.application.rejected'; } $this->application ->user ->notify( new Header($key, 'user', 'red', [ 'campaign' => $this->campaign->name, 'reason' => $rejection, ]) ); Rejected::dispatch($this->application, $this->campaign, $this->user); $this->application->update(['status' => ApplicationStatus::Rejected]); } else { $this->approve((int) Arr::get($data, 'role_id'), $this->purify(Arr::get($data, 'reason'))); $this->application->update(['status' => ApplicationStatus::Approved]); } return $return; } protected function approve(int $roleID, string $message = ''): self { // Add the user to the campaign CampaignUser::create([ 'user_id' => $this->application->user_id, 'campaign_id' => $this->campaign->id, ]); // Add the user to the role CampaignRoleUser::create([ 'user_id' => $this->application->user_id, 'campaign_role_id' => $roleID, ]); // $message = $this->purify(Arr::get($data, 'message')); if ($message == '') { $key = 'campaign.application.approved'; } else { $key = 'campaign.application.approved_message'; } // Notify the user $this->application ->user ->notify( new Header( $key, 'user', 'success', [ 'campaign' => $this->campaign->name, 'reason' => $message, 'link' => route('dashboard', $this->campaign), ] ) ); Accepted::dispatch($this->application, $this->campaign, $this->user); return $this; } } ================================================ FILE: app/Services/Campaign/BoostService.php ================================================ action = $action; return $this; } public function upgrade(): self { $this->upgrade = true; return $this; } /** * @throws AlreadyBoostedException * @throws ExhaustedBoostsException */ public function boost(): void { if ($this->campaign->boosted() && ! $this->upgrade) { throw new AlreadyBoostedException($this->campaign); } elseif ($this->user->availableBoosts() === 0) { throw new TranslatableException('settings/premium.exceptions.out-of-stock'); } if ($this->action == 'superboost' && $this->user->availableBoosts() < ($this->upgrade ? 2 : 3)) { throw new ExhaustedSuperboostsException; } // How many boosters we need to create in the table. This is silly and could use some refactoring. $amount = 1; if ($this->upgrade) { $amount = 2; Upgrade::dispatch($this->campaign, $this->user); } elseif ($this->action === 'superboost') { $amount = 3; SuperBoost::dispatch($this->campaign, $this->user); } else { Boost::dispatch($this->campaign, $this->user); } for ($i = 0; $i < $amount; $i++) { CampaignBoost::create([ 'campaign_id' => $this->campaign->id, 'user_id' => $this->user->id, ]); } $this->campaign->boost_count = $this->campaign->boosts()->count(); $this->campaign->saveQuietly(); $key = $this->action === 'superboost' ? 'boost.superboost' : 'boost.add'; $this->notify($key); } public function premium(): void { if ($this->campaign->premium()) { throw new TranslatableException('settings/premium.exceptions.already'); } elseif ($this->user->availableBoosts() < 1) { throw new TranslatableException('settings/premium.exceptions.out-of-stock'); } $amount = 4; Premium::dispatch($this->campaign, $this->user); for ($i = 0; $i < $amount; $i++) { CampaignBoost::create([ 'campaign_id' => $this->campaign->id, 'user_id' => $this->user->id, ]); } $this->campaign->boost_count = $this->campaign->boosts()->count(); $this->campaign->saveQuietly(); $this->notify('premium.add'); } /** * Unboost a campaign * * @throws \Exception */ public function unboost(CampaignBoost $campaignBoost): self { $campaignBoost->delete(); // Delete other boosts on the same campaign if the user is superboosting if (isset($this->user)) { foreach ($this->user->boosts()->where('campaign_id', $campaignBoost->campaign_id)->get() as $boost) { $boost->delete(); } Disable::dispatch($this->campaign, $this->user); } $boostCount = $this->campaign->boosts()->count(); $this->campaign->boost_count = $boostCount; // Revert back to public visibility if ($this->campaign->isUnlisted()) { $this->campaign->visibility_id = CampaignVisibility::public; } $this->campaign->saveQuietly(); if (isset($this->user)) { $key = $this->user->hasBoosterNomenclature() ? 'boost.remove' : 'premium.remove'; $this->notify($key); } return $this; } /** * Migrate a user away from the old boost concepts */ public function migrate(): void { if ($this->user->isLegacyPatron()) { throw new TranslatableException('As a Patreon supporter, your account cannot switch to the new premium campaigns system. Please switch to supporting Kanka directly through the app to migrate to premium campaigns.'); } $settings = $this->user->settings; if (! isset($settings['grandfathered_boost'])) { throw new TranslatableException('Your account has already switched to the premium campaign system.'); } unset($settings['grandfathered_boost']); $this->user->settings = $settings; $this->user->saveQuietly(); // Unboost everything foreach ($this->user->boosts()->with(['campaign', 'user'])->get() as $boost) { $this ->campaign($boost->campaign) ->unboost($boost); } } public function back(): void { $settings = $this->user->settings; $settings['grandfathered_boost'] = 1; $this->user->settings = $settings; $this->user->saveQuietly(); foreach ($this->user->boosts()->with(['campaign', 'user'])->get() as $boost) { $this ->campaign($boost->campaign) ->unboost($boost); } } /** * Dispatch a job to notify all campaign admins */ protected function notify(string $key): self { NotifyAdmins::dispatch( $this->campaign, $key, 'rocket', 'maroon', [ 'user' => $this->user->name, 'campaign' => $this->campaign->name, 'link' => route('dashboard', $this->campaign), ] ); return $this; } } ================================================ FILE: app/Services/Campaign/Connections/WebService.php ================================================ mirrored = new Collection; $this->loadConnections(); return [ 'entities' => $this->entities, 'nodes' => $this->nodes, 'i18n' => $this->i18n(), 'urls' => $this->urls(), ]; } protected function loadConnections(): void { $query = Relation::preparedWith(); if (! $this->campaign->boosted()) { $query->limit(config('limits.campaigns.web'))->latest(); } $connections = $query->get(); foreach ($connections as $connection) { $this->parseConnection($connection); } } protected function parseConnection(Relation $connection): void { $this->addEntity($connection->target); $this->addEntity($connection->owner); $this->addNode($connection); } protected function addEntity(Entity $entity): void { if (in_array($entity->id, $this->parsedEntities)) { return; } $this->entities[$entity->id] = (new EntityResource($entity))->campaign($this->campaign); } protected function addNode(Relation $connection): void { // Don't add mirrored relations $mirrorKey = $connection->mirror_id . '-' . $connection->id; if ($connection->isMirrored() and $this->mirrored->has($mirrorKey)) { return; } $node = [ 'id' => $connection->id, 'source' => $connection->owner_id, 'target' => $connection->target_id, 'text' => $connection->relation, 'colour' => $connection->colour, 'attitude' => $connection->attitude, 'type' => 'entity-relation', 'is_mirrored' => $connection->isMirrored(), 'shape' => $connection->isMirrored() && $connection->mirror && $connection->relation == $connection->mirror->relation ? 'none' : 'triangle', ]; if (isset($this->user) && $this->user->can('update', $connection->owner)) { $node['url'] = route('entities.relations.edit', [ 'campaign' => $this->campaign, 'entity' => $connection->owner_id, 'relation' => $connection, 'from' => 'web', ]); } $this->nodes[] = $node; if ($connection->isMirrored() && $connection->mirror && $connection->relation && $connection->relation == $connection->mirror->relation) { $this->mirrored->put($connection->id . '-' . $connection->mirror_id, true); } } protected function i18n(): array { return [ 'add' => __('connections/web.actions.add'), 'create' => __('crud.create'), 'print' => __('crud.actions.print'), 'download' => __('connections/web.actions.download'), 'download-png' => __('connections/web.actions.download-png'), 'download-pdf' => __('connections/web.actions.download-pdf'), 'qq-keyboard-shortcut' => __('header.qq.tooltip') . ' [ N ]', 'back' => __('connections/web.actions.back'), 'campaign' => $this->campaign->name, 'zoom-fit' => __('connections/web.actions.zoom-fit'), 'reset-layout' => __('connections/web.actions.reset-layout'), ]; } protected function urls(): array { return [ 'create' => route('relations.create', [$this->campaign, 'from' => 'web']), 'creator' => route('entity-creator.selection', $this->campaign), 'back' => route('relations.index', [$this->campaign]), ]; } } ================================================ FILE: app/Services/Campaign/Counters/VisibleEntityCountService.php ================================================ role() ->permissions() ->count() ->save() ->cleanup(); } protected function role(): self { $this->role = CampaignRole::where([ 'campaign_id' => $this->campaign->id, 'is_public' => true, ]) ->with('permissions') ->first(); return $this; } protected function permissions(): self { $this->types = $this->ids = []; /** @var CampaignPermission $permission */ foreach ($this->role->permissions as $permission) { if ($permission->isAction(Permission::View->value)) { if (! empty($permission->entity_id)) { $this->ids[] = $permission->entity_id; } else { $this->types[] = $permission->entity_type_id; } } } return $this; } protected function count(): self { // Now that we have the types and ids, we can count the number of visible entities in this campaign $this->count = Entity::where(['campaign_id' => $this->campaign->id]) ->where('is_private', false) ->where(function ($sub) { return $sub->inTypes($this->types) ->orWhereIn('id', $this->ids); }) ->count(); return $this; } protected function save(): self { $this->campaign->visible_entity_count = $this->count; $this->campaign->saveQuietly(); return $this; } protected function cleanup(): void { unset($this->count); unset($this->campaign); unset($this->role); unset($this->ids); unset($this->types); } } ================================================ FILE: app/Services/Campaign/CreateService.php ================================================ data = $data; return $this; } public function create(): Campaign { $data = $this->data ?? $this->request->all(); $entry = Arr::get($data, 'description'); $excerpt = Arr::get($data, 'excerpt'); DB::beginTransaction(); try { $this->campaign = Campaign::create($data); $this ->slug() ->notify() ->roles() ->settings() ->description($entry, $excerpt) ->log(); DB::commit(); } catch (Exception $e) { DB::rollBack(); throw $e; } return $this->campaign; } protected function notify(): self { $this->user->notify(new Header( 'campaign.created', 'check', 'success', [ 'campaign' => $this->campaign->name, 'link' => route('dashboard', ['campaign' => $this->campaign]), ] )); return $this; } protected function roles(): self { $role = new CampaignUser([ 'user_id' => $this->user->id, 'campaign_id' => $this->campaign->id, ]); $role->save(); $role = CampaignRole::create([ 'campaign_id' => $this->campaign->id, 'name' => __('campaigns.members.roles.owner'), 'is_admin' => true, ]); $readOnlyRoles = []; $readOnlyRoles[] = CampaignRole::create([ 'campaign_id' => $this->campaign->id, 'name' => __('campaigns.members.roles.public'), 'is_public' => true, ]); $readOnlyRoles[] = CampaignRole::create([ 'campaign_id' => $this->campaign->id, 'name' => __('campaigns.members.roles.player'), ]); $entityTypes = EntityType::default()->get(); foreach ($readOnlyRoles as $readOnlyRole) { foreach ($entityTypes as $entityType) { CampaignPermission::create([ 'campaign_role_id' => $readOnlyRole->id, 'access' => true, 'action' => CampaignPermission::ACTION_READ, 'entity_type_id' => $entityType->id, ]); } } $member = new CampaignRoleUser([ 'campaign_role_id' => $role->id, 'user_id' => $this->user->id, ]); $member->save(); return $this; } protected function slug(): self { $this->campaign->slug = (string) $this->campaign->id; $this->campaign->saveQuietly(); return $this; } protected function settings(): self { $setting = new CampaignSetting([ 'campaign_id' => $this->campaign->id, 'dice_rolls' => 0, 'conversations' => 0, ]); $setting->save(); return $this; } protected function description(?string $entry, ?string $excerpt): self { if ($entry !== null || $excerpt !== null) { CampaignDescription::create([ 'campaign_id' => $this->campaign->id, 'description' => $entry, 'excerpt' => $excerpt, ]); } return $this; } protected function log(): self { // Make sure we save the last campaign id to avoid infinite loops $this->campaignService ->user($this->user) ->campaign($this->campaign) ->set(); $this->user->log(UserAction::campaignNew, ['campaign' => $this->campaign->id]); UserCache::clear(); return $this; } } ================================================ FILE: app/Services/Campaign/DefaultImageService.php ================================================ campaign->default_images; if ($images === null) { $images = []; } if (Arr::has($images, $this->entityType->pluralCode())) { return false; } /** @var UploadedFile $source */ $source = $request->file('default_entity_image'); $image = new Image; $image->campaign_id = $this->campaign->id; $image->ext = $source->extension(); $image->size = (int) ceil($source->getSize() / 1024); // kb $image->name = mb_substr($source->getFileName(), 0, 45); $image->is_default = true; $image->save(); $source ->storePubliclyAs( $image->folder, $image->file ); $images[$this->entityType->pluralCode()] = $image->id; $this->campaign->default_images = $images; $this->campaign->saveQuietly(); ThumbnailCreated::dispatch($this->campaign, $this->entityType, $this->user); return true; } /** * Remove a default image */ public function destroy(): bool { $images = $this->campaign->default_images; if ($images === null) { $images = []; } if (! isset($images[$this->entityType->pluralCode()])) { return false; } /** @var ?Image $image */ $image = Image::find($images[$this->entityType->pluralCode()]); if (empty($image)) { return false; } $image->delete(); unset($images[$this->entityType->pluralCode()]); $this->campaign->default_images = $images; $this->campaign->saveQuietly(); ThumbnailDeleted::dispatch($this->campaign, $this->entityType, $this->user); return true; } /** * Remove all default images from a campaign */ public function destroyAll(): void { $images = $this->campaign->default_images ?? []; $imageModels = Image::find($images); foreach ($imageModels as $image) { $image->delete(); } $this->campaign->default_images = []; $this->campaign->saveQuietly(); ThumbnailsDeleted::dispatch($this->campaign, $this->user); } } ================================================ FILE: app/Services/Campaign/DeletionService.php ================================================ user->log(UserAction::campaignDelete, ['campaign' => $this->campaign->id]); $this->campaign->delete(); // Todo: queue this maybe $this->searchCleanupService->campaign($this->campaign)->cleanup(); // Delete boosters, so the user can use them on other campaigns. // If the person boosting isn't the current user, maybe we could warn them? $this->campaign->boosts()->delete(); // Log for the user foreach ($this->campaign->users as $member) { $member->notify(new Header( 'campaign.deleted', 'trash', 'yellow', [ 'campaign' => $this->campaign->name, ] )); } $this->campaignService->user($this->user)->next(); } public function cleanup(): void { // Since these are generally s3/minio storages, we need this mumbo jumbo $folders = ['campaign', 'campaigns', 'w']; foreach ($folders as $folder) { $path = $folder . '/' . $this->campaign->id; if (! Storage::directoryExists($path)) { continue; } $files = Storage::allFiles($path); if (! empty($files)) { Storage::delete($files); } Storage::deleteDirectory($path); } } } ================================================ FILE: app/Services/Campaign/ExportService.php ================================================ exportPath; } public function markdown($isMarkdown = false): self { $this->isMarkdown = $isMarkdown; return $this; } public function isJson(): bool { return ! $this->isMarkdown; } public function log(CampaignExport $campaignExport): self { $this->log = $campaignExport; return $this; } public function export(): self { try { $this ->prepare() ->info() ->campaignJson() ->campaignModules() ->customCampaignModules() ->entities() ->customEntities() ->gallery() ->finish() ->notify(); $this->log->update([ 'status' => CampaignExportStatus::finished, 'size' => $this->filesize(), 'path' => $this->exportPath(), ]); } catch (Exception $e) { $this->log ->update([ 'status' => CampaignExportStatus::failed, ]); if (isset($this->path)) { $this->cleanup(); } Log::error('Campaign export', ['action' => 'export', 'err' => $e->getMessage()]); throw $e; } return $this; } public function filesize(): int { return $this->filesize; } protected function campaignModules(): self { // If markdown export, skip if ($this->isMarkdown) { return $this; } $modules = []; $settings = $this->campaign->setting->toArray(); unset($settings['id'], $settings['campaign_id'], $settings['created_at'], $settings['updated_at']); $entities = config('entities.ids'); foreach ($settings as $name => $active) { $module = ['enabled' => $active]; try { if ($this->campaign->hasModuleName($entities[Str::singular($name)])) { $module['name_singular'] = $this->campaign->moduleName($entities[Str::singular($name)]); } if ($this->campaign->hasModuleName($entities[Str::singular($name)], true)) { $module['name_plural'] = $this->campaign->moduleName($entities[Str::singular($name)], true); } if ($this->campaign->hasModuleIcon($entities[Str::singular($name)])) { $module['icon'] = $this->campaign->moduleIcon($entities[Str::singular($name)]); } } catch (Exception $e) { } $modules[$name] = $module; } $this->archive->addFromString('settings/modules.json', json_encode($modules)); $this->files++; return $this; } protected function customCampaignModules(): self { // If markdown export, skip if ($this->isMarkdown) { return $this; } $settings = $this->campaign->entityTypes->where('is_special', 1)->select('id', 'code', 'is_enabled', 'singular', 'plural', 'icon')->toArray(); $this->archive->addFromString('settings/custom-modules.json', json_encode($settings)); $this->files++; return $this; } protected function prepare(): self { $this->version = config('app.version'); $this->exportPath = 'app/exports/'; $saveFolder = storage_path($this->exportPath); File::ensureDirectoryExists($saveFolder); // We want the full path for jobs running in the queue. $this->file = Str::slug($this->campaign->name) . '_' . date('Ymd_His') . '.zip'; Log::debug('Campaign export', ['action' => 'preparing', 'exportPath' => $this->exportPath, 'file' => $this->file]); CampaignCache::campaign($this->campaign); if ($this->isMarkdown) { CampaignCache::user($this->user); Mentions::campaign($this->campaign); Avatar::campaign($this->campaign); CampaignLocalization::forceCampaign($this->campaign); Module::campaign($this->campaign); $this->markdown->user($this->user); } $this->path = $saveFolder . $this->file; $this->archive = new ZipArchive; $creation = $this->archive->open($this->path, ZipArchive::CREATE | ZipArchive::OVERWRITE); if (! $creation) { throw new Exception('Could not create zip file'); } Log::debug('Campaign export', ['action' => 'zip created', 'path' => $this->path]); // Count the number of elements to export to get a rough idea of progress $this->totalElements = Entity::where('campaign_id', $this->campaign->id)->count() + 1; // Campaign json; if ($this->isJson()) { $this->totalElements = $this->totalElements + Image::where('campaign_id', $this->campaign->id)->count(); } $this->currentElements = 0; $cloudfront = config('filesystems.disks.cloudfront.url'); if ($cloudfront) { $this->cloudfront = true; } return $this; } protected function info(): self { if ($this->isMarkdown) { return $this; } $info = [ 'kanka_version' => config('app.version'), 'export_version' => $this->version, 'started' => date('Y-m-d H:i:s'), ]; $this->archive->addFromString('info.json', json_encode($info)); return $this; } protected function campaignJson(): self { // We don't want the whole model to be available to the export. // It would probably make more sense to have a resource for this. $hidden = [ 'boost_count', 'visible_entity_count', 'is_hidden', ]; if ($this->isMarkdown) { $this->archive->addFromString('campaign.md', $this->markdown->campaign($this->campaign)->campaignMarkdown()); } else { $this->archive->addFromString('campaign.json', $this->campaign->makeHidden($hidden)->toJson()); } $this->files++; $image = $this->campaign->image; if (! empty($image) && Str::contains($image, '?') && Storage::exists($image)) { $this->addImage($image, $image); } $image = $this->campaign->header_image; if (! empty($image) && Str::contains($image, '?') && Storage::exists($image)) { $this->addImage($image, $image); } $this->progress(); return $this; } protected function entities(): self { $entityWith = [ 'entity', 'entity.entityTags', 'entity.relationships', 'entity.posts', 'entity.posts.postTags', 'entity.abilities', 'entity.abilities.ability', 'entity.reminders', 'entity.image', 'entity.header', 'entity.assets', 'entity.files', 'entity.mentions', 'entity.inventories', 'entity.inventories.item', 'entity.entityAttributes', ]; $entities = config('entities.classes-plural'); foreach ($entities as $entity => $class) { if (! $this->campaign->enabled($entity) || ! method_exists($class, 'export')) { continue; } try { $property = Str::camel($entity); $smartWith = $this->smartWith($entityWith, $class); foreach ($this->campaign->$property()->with($smartWith)->has('entity')->get() as $model) { $this->process($entity, $model); } } catch (Exception $e) { Log::error('Campaign export', ['err' => $e->getMessage()]); // $saveFolder = storage_path($this->exportPath); // $this->archive->saveTo($saveFolder); throw new Exception( 'Missing campaign entity relation: ' . $entity . '-' . $class . '? ' . $e->getMessage() ); } } return $this; } protected function customEntities(): self { $entityWith = [ 'entityTags', 'relationships', 'posts', 'posts.postTags', 'abilities', 'abilities.ability', 'reminders', 'image', 'header', 'assets', 'files', 'mentions', 'inventories', 'inventories.item', 'entityAttributes', ]; $entityTypes = $this->campaign->entityTypes->where('is_special', 1)->all(); foreach ($entityTypes as $entityType) { if (! $entityType->isEnabled()) { continue; } try { $base = Entity::inTypes($entityType->id) ->with($entityWith) ->get(); $name = Str::camel(Str::slug($entityType->plural)) . '_' . $entityType->id; foreach ($base as $model) { $this->process($name, $model); } } catch (Exception $e) { Log::error('Campaign export', ['err' => $e->getMessage()]); // $saveFolder = storage_path($this->exportPath); // $this->archive->saveTo($saveFolder); throw new Exception( 'Missing campaign custom entity relation: ' . $entityType->singular . '? ' . $e->getMessage() ); } } return $this; } protected function smartWith(array $with, string $entityClass): array { /** @var MiscModel $class */ $class = app()->make($entityClass); // @phpstan-ignore-next-line foreach ($class->exportRelations() as $rel) { $with[] = $rel; } return $with; } protected function gallery(): self { if ($this->isMarkdown) { return $this; } foreach ($this->campaign->images()->with('imageFolder')->get() as $image) { try { /** @var Image $image */ $this->processImage($image); } catch (Exception $e) { // $saveFolder = storage_path($this->exportPath); // $this->archive->saveTo($saveFolder); throw new Exception( $e->getMessage() ); } } return $this; } protected function processImage(Image $image): self { try { $this->archive->addFromString('gallery/' . $image->id . '.json', $image->export()); $this->files++; } catch (Exception $e) { Log::warning('Campaign export', ['err' => 'Can\'t get gallery image', 'image' => $image->id]); } if (! $image->isFolder() && Storage::exists($image->path)) { $this->addImage($image->path, 'gallery/' . $image->id . '.' . $image->ext); } $this->progress(); return $this; } protected function process(string $module, $model): self { if ($model instanceof Entity) { $entity = $model; } else { $entity = $model->entity; } if ($this->isMarkdown) { $exportData = $this->markdown->campaign($this->campaign)->module($module)->entity($entity)->markdown(); $this->archive->addFromString($module . '/' . Str::slug($model->name) . '_' . $entity->id . '.md', $exportData); } else { $exportData = $model->export(); if ($model instanceof Entity) { $exportData = json_encode(['entity' => $exportData]); } $this->archive->addFromString($module . '/' . Str::slug($model->name) . '_' . $entity->id . '.json', $exportData); } $this->files++; $path = $entity->image_path; if (! empty($path) && ! Str::contains($path, '?') && Storage::exists($path)) { $this->addImage($path, $path); } $path = $entity->header_image; if (! empty($path) && ! Str::contains($path, '?') && Storage::exists($path)) { $this->addImage($path, $path); } /** @var EntityAsset $file */ foreach ($entity->files as $file) { if (! isset($file->metadata['path'])) { continue; } $path = $file->metadata['path']; if (! Storage::exists($path)) { continue; } $this->addImage($path, $path); } // Layers are now stored in the gallery so this no longer applies // if ($model instanceof Map) { // foreach ($model->layers as $layer) { // $path = $layer->image; // if (! $path || ! Storage::exists($path)) { // continue; // } // $this->addImage($path, $path); // } // } $this->progress(); return $this; } protected function notify(): self { $this->user->notify(new Header( 'campaign.export', 'download', 'success', [ 'link' => route('campaign.export', $this->campaign), 'time' => 120, 'campaign' => $this->campaign->name, ] )); return $this; } protected function finish(): self { // Save all the content. try { if ($this->isMarkdown) { $exportData = $this->markdown->exportIndex(); $this->archive->addFromString('index.md', $exportData); $this->files++; } $this->archive->close(); $path = 'exports/' . $this->campaign->id; $this->exportPath = $path . '/' . $this->file; Log::info('Campaign export', ['action' => 'finished generating zip', 'exportPath' => $this->exportPath, 'path' => $this->path, 'file' => $this->file]); Storage::disk('s3')->putFileAs($path, $this->path, $this->file, 'public'); $this->filesize = (int) floor(filesize($this->path) / pow(1024, 2)); Log::info('Campaign export', ['action' => 'saved to disk']); } catch (Exception $e) { Log::error('Campaign export', ['action' => 'finish', 'err' => $e->getMessage()]); // The export might fail if the zip is too big. $this->files = 0; throw $e; } $this->cleanup(); return $this; } protected function cleanup(): void { if (! isset($this->path)) { return; } // Don't delete zips on debug mode if (app()->hasDebugModeEnabled()) { return; } if (file_exists($this->path) && ! is_dir($this->path)) { unlink($this->path); } } /** * Track that the export had an issue */ public function fail(): self { // Notify the user that something went wrong $this->user->notify(new Header( 'campaign.export_error', 'circle-exclamation', 'red', [ 'campaign' => $this->campaign->name, ] )); $this->cleanup(); return $this; } /** * Each time an element is added to the zip, there is a chance that the progress is increased */ protected function progress(): void { $this->currentElements++; $total = round($this->currentElements / $this->totalElements, 2) * 100; // Only track in 1 percentage point increments if ($total < ($this->log->progress + 1)) { return; } $this->log->progress = $total; $this->log->save(); } protected function addImage(string $path, string $fileName): void { $maxRetries = 3; $retry = 0; while ($retry < $maxRetries) { try { if (! $this->cloudfront) { // In Laravel, Storage::get() will load the image in memory but not get rid of it until // garbage collection, so to avoid memory issues, we do it ourselves $stream = Storage::disk('s3')->readStream($path); } else { // In prod, s3 assets are behind cloudfront, so we can be even more efficient $stream = Storage::disk('cloudfront')->readStream($path); } $content = stream_get_contents($stream); fclose($stream); $this->archive->addFromString($fileName, $content); $this->files++; return; } catch (\Throwable $e) { $retry++; usleep(200_000 * $retry); // exponential backoff (200ms, 400ms, 600ms) } } Log::error('Campaign export', ['err' => 'S3 GetObject permanently failed', 'attempt' => $retry, 'path' => $path]); } } ================================================ FILE: app/Services/Campaign/Exports/QueueService.php ================================================ $this->campaign->id, 'created_by' => $this->user->id, 'type' => $this->type, 'status' => CampaignExportStatus::scheduled, ]); Export::dispatch($this->campaign, $this->user, $entitiesExport)->onQueue('heavy'); } public function type(int $type): self { $this->type = $type; return $this; } } ================================================ FILE: app/Services/Campaign/FollowService.php ================================================ campaign->isFollowing()) { return ! $this->remove(); } return $this->add(); } public function remove(): bool { /** @var ?CampaignFollower $follow */ $follow = CampaignFollower::where([ 'campaign_id' => $this->campaign->id, 'user_id' => $this->user->id, ])->first(); if (empty($follow)) { return false; } $follow->delete(); return true; } public function add(): bool { $follow = new CampaignFollower; $follow->campaign_id = $this->campaign->id; $follow->user_id = $this->user->id; return $follow->save(); } } ================================================ FILE: app/Services/Campaign/Gallery/BulkService.php ================================================ files = $files; return $this; } public function delete(): int { if (count($this->files) === 0) { return 0; } $count = Image::whereIn('id', $this->files)->delete(); return $count; } } ================================================ FILE: app/Services/Campaign/GenreService.php ================================================ campaign->genres as $genre) { $existing[$genre->id] = $genre->slug; } $new = []; foreach ($ids as $id) { if (! empty($existing[$id])) { unset($existing[$id]); } else { $genre = Genre::find($id); if (! empty($genre)) { $new[] = $genre->id; } } } $this->campaign->genres()->attach($new); // Detatch the remaining if (! empty($existing)) { $this->campaign->genres()->detach(array_keys($existing)); } } } ================================================ FILE: app/Services/Campaign/Import/ImportIdMapper.php ================================================ misc[$type][$old] = $new; return $this; } public function putEntity(int $old, int $new): self { $this->entities[$old] = $new; return $this; } public function putCustomEntityType(int $old, int $new): self { $this->customEntityTypes[$old] = $new; return $this; } public function putCustomEntityTypeName(string $old, string $oldPlural): self { $this->customEntityTypeNames[$old] = $oldPlural; return $this; } public function putGallery(string $old, string $new): self { $this->gallery[$old] = $new; return $this; } public function putPost(int $old, int $new): self { $this->posts[$old] = $new; return $this; } public function putQuestElement(int $old, int $new): self { $this->questElements[$old] = $new; return $this; } public function putTimelineElement(int $old, int $new): self { $this->timelineElements[$old] = $new; return $this; } public function get(string $type, int $old): int { return $this->misc[$type][$old]; } public function has(string $type, int $old): bool { return ! empty($this->misc[$type][$old]); } public function getEntity(int $old): int { return $this->entities[$old]; } public function hasEntity(int $old): bool { return ! empty($this->entities[$old]); } public function getCustomEntityType(int $old): int { return $this->customEntityTypes[$old]; } public function getCustomEntityTypes(): array { return $this->customEntityTypeNames; } public function hasOldEntityType(int $old): bool { return ! empty($this->customEntityTypes[$old]); } public function getGallery(string $old): string { return $this->gallery[$old]; } public function hasGallery(string $old): bool { return ! empty($this->gallery[$old]); } public function getPost(int $old): int { return $this->posts[$old]; } public function getTimelineElement(int $old): int { return $this->timelineElements[$old]; } public function getQuestElement(int $old): int { return $this->questElements[$old]; } } ================================================ FILE: app/Services/Campaign/Import/ImportMentions.php ================================================ $type) { $id = mb_substr($type, 17, -1); if (! in_array($id, $images)) { $images[$key] = $id; } } $this->imageMentions = []; foreach ($images as $uuid) { if (! ImportIdMapper::hasGallery($uuid)) { continue; } $newUuid = ImportIdMapper::getGallery($uuid); $text = Str::replace($uuid, $newUuid, $text); // Old folder structure $text = Str::replace( // @phpstan-ignore-next-line '/campaigns/' . $this->data['campaign_id'] . '/', '/w/' . $this->campaign->id . '/', $text ); // Newer folder structure $text = Str::replace( // @phpstan-ignore-next-line '/w/' . $this->data['campaign_id'] . '/', '/w/' . $this->campaign->id . '/', $text ); $this->imageMentions[] = $newUuid; } return $text; } public function imageMentions(): array { return $this->imageMentions; } protected function mapImageMentions(mixed $model): self { if (empty($this->imageMentions)) { return $this; } foreach ($this->imageMentions as $uuid) { $men = new ImageMention; // @phpstan-ignore-next-line $men->entity_id = $this->entity->id; $men->image_id = $uuid; if ($model instanceof Post) { $men->post_id = $model->id; } $men->save(); } return $this; } } ================================================ FILE: app/Services/Campaign/Import/ImportService.php ================================================ entityMappingService = $entityMappingService; } public function job(CampaignImport $job) { $this->job = $job; $this ->campaign($job->campaign) ->user($job->user); return $this; } public function run(): void { $this ->init() ->mappers() ->download() ->process() ->finish() ->cleanup(); } protected function init(): self { $this->job->status_id = CampaignImportStatus::RUNNING; $this->job->save(); return $this; } protected function mappers(): self { $this->mappers = []; $setup = [ 'tags' => TagMapper::class, 'calendars' => CalendarMapper::class, 'creatures' => CreatureMapper::class, 'notes' => NoteMapper::class, 'races' => RaceMapper::class, 'events' => EventMapper::class, 'items' => ItemMapper::class, 'journals' => JournalMapper::class, 'abilities' => AbilityMapper::class, 'families' => FamilyMapper::class, 'organisations' => OrganisationMapper::class, 'timelines' => TimelineMapper::class, 'quests' => QuestMapper::class, 'maps' => MapMapper::class, 'locations' => LocationMapper::class, 'characters' => CharacterMapper::class, 'custom' => CustomMapper::class, ]; foreach ($setup as $model => $mapperClass) { $this->logs[] = 'Init mapper ' . $model; $mapper = app()->make($mapperClass); $this->mappers[$model] = $mapper ->campaign($this->campaign) ->user($this->user) ->prepare(); } return $this; } /** * Download the files from s3 onto the local machine and unzip it */ protected function download(): self { $files = $this->job->config['files']; $path = '/campaigns/' . $this->campaign->id . '/imports/'; foreach ($files as $file) { // Log::info('Want to download ' . $file); $s3 = Storage::disk('export')->get($file); $local = $path . uniqid() . '.zip'; // Log::info('Will download from the export disk to local ' . $local); Storage::disk('local')->put($local, $s3); $this->archive = new ZipArchive; $zipPath = storage_path('app/' . $local); // Log::info('Want to open ' . $zipPath); $this->archive->open($zipPath); // Log::info('Opened ' . $local . ' file'); $this->extract(); $this->archive->close(); unlink($zipPath); } return $this; } protected function extract(): void { $this->dataPath = 'campaigns/' . $this->campaign->id . '/import-data'; $this->archive->extractTo(storage_path('/app/' . $this->dataPath)); } protected function process() { try { $this->importCampaign() ->moduleSettings() ->customModules() ->gallery() ->entities() ->secondCampaign(); $this->job->status_id = CampaignImportStatus::FINISHED; } catch (ImportException $e) { $this->errors = [$e->getMessage()]; Log::error('Import', ['where' => 'importException', 'error' => $e->getMessage()]); $this->job->status_id = CampaignImportStatus::FAILED; } catch (Exception $e) { // dump($e->getMessage()); // dump($e->getTrace()); $this->errors = [$e->getMessage()]; Log::error('Import', ['where' => 'exception', 'error' => $e->getMessage()]); $this->job->status_id = CampaignImportStatus::FAILED; $this->exception = $e; } return $this; } protected function finish(): self { Storage::disk('local')->deleteDirectory($this->dataPath); $this->job->logs = $this->logs; $this->job->save(); $key = 'failed'; $colour = 'red'; if (! $this->job->isFailed()) { $key = 'success'; $colour = 'success'; UserLog::create([ 'user_id' => $this->user->id, 'type_id' => UserAction::zipImport, 'campaign_id' => $this->campaign->id, 'data' => [ 'module' => 'import', 'action' => 'zip finished', 'count' => $this->entityCount, ], ]); } $this->campaign->notifyAdmins( new Header( 'campaign.import.' . $key, 'upload', $colour, [ 'campaign' => $this->campaign->name, 'link' => route('dashboard', $this->campaign), ] ) ); return $this; } protected function importCampaign(): self { // Open the campaign zip $data = $this->open('campaign.json'); if ($data == []) { throw new ImportException('No campaign.json found'); } /** @var CampaignMapper $mapper */ $mapper = app()->make(CampaignMapper::class); $this->campaign = $mapper ->path($this->dataPath) ->data($data) ->campaign($this->campaign) ->import(); $this->originalCampaignID = (int) $data['id']; return $this; } protected function gallery(): self { $this->gallery = app()->make(GalleryMapper::class); $this->gallery->campaign($this->campaign) ->prepare(); $path = $this->dataPath . '/gallery'; if (! Storage::disk('local')->exists($path)) { $this->logs[] = 'No gallery'; return $this; } Log::info('Campaign import', ['importing' => 'gallery']); $files = Storage::disk('local')->files($path); foreach ($files as $file) { if (! Str::endsWith($file, '.json')) { continue; } $filePath = Str::replace($this->dataPath, '', $file); $data = $this->open($filePath); $this->gallery ->path($path) ->data($data) ->import(); unset($data); } $this->gallery->tree()->clear(); return $this; } protected function moduleSettings(): self { // Open the campaign settings file $data = $this->open('settings/modules.json'); if (! $data) { return $this; } Log::info('Campaign import', ['importing' => 'module settings']); $moduleSettings = $this->campaign->setting; foreach ($data as $module => $settings) { if (isset($moduleSettings->$module)) { $moduleSettings->$module = $settings['enabled']; } } $moduleSettings->save(); // Since modules and custom names are cached, any changes to them need to invalidate any existing cache CampaignCache::campaign($this->campaign)->clear(); EntityCache::campaign($this->campaign); CharacterCache::campaign($this->campaign); TimelineElementCache::campaign($this->campaign); QuestCache::campaign($this->campaign); MapMarkerCache::campaign($this->campaign); EntityAssetCache::campaign($this->campaign); BookmarkCache::campaign($this->campaign); return $this; } protected function customModules(): self { // Open the campaign settings file $data = $this->open('settings/custom-modules.json'); if (! $data || ! $this->campaign->premium()) { return $this; } // Dont import more than the campaign is allowed to have. $limit = config('limits.campaigns.modules.premium'); if ($this->campaign->isWyvern()) { $limit = config('limits.campaigns.modules.wyvern'); } elseif ($this->campaign->isElemental()) { $limit = config('limits.campaigns.modules.elemental'); } $entityTypes = $this->campaign->entityTypes->count(); foreach ($data as $module) { if ($entityTypes >= $limit) { break; } // Create Custom Module $newModule = new EntityType; $newModule->campaign_id = $this->campaign->id; $newModule->is_special = true; $newModule->is_enabled = $module['is_enabled']; $newModule->singular = $module['singular']; $newModule->plural = $module['plural']; $newModule->icon = $module['icon']; $newModule->code = $module['code']; $newModule->save(); // Create its corresponding bookmark $bookmark = new Bookmark; $bookmark->campaign_id = $this->campaign->id; $bookmark->entity_type_id = $newModule->id; $bookmark->name = $newModule->plural; $bookmark->save(); ImportIdMapper::putCustomEntityType($module['id'], $newModule->id); ImportIdMapper::putCustomEntityTypeName($module['code'] . '_' . $module['id'], Str::camel(Str::slug($module['plural'])) . '_' . $module['id']); $entityTypes++; } return $this; } protected function entities(): self { $fileNames = ImportIdMapper::getCustomEntityTypes(); /** * @var string $model * @var mixed $mapper */ foreach ($this->mappers as $model => $mapper) { if ($model == 'custom') { Log::info('Campaign import', ['importing' => 'custom entities']); // We handle custom models differently. foreach ($fileNames as $fileName => $pluralFileName) { $this->logs[] = 'Processing ' . $fileName; $count = 0; foreach ($this->customFiles($fileName, $pluralFileName) as $file) { if (! Str::endsWith($file, '.json')) { continue; } $filePath = Str::replace($this->dataPath, '', $file); $data = $this->open($filePath); // Log::info('array: ' . json_encode($data)); // Log::info('array: ' . $filePath); $mapper ->path($this->dataPath) ->data($data) ->first(); $count++; unset($data); } $this->logs[] = $count; $this->entityCount += $count; $mapper->tree()->clear(); } } else { $this->logs[] = 'Processing ' . $model; Log::info('Campaign import', ['importing' => $model]); $count = 0; foreach ($this->files($model) as $file) { if (! Str::endsWith($file, '.json')) { continue; } $filePath = Str::replace($this->dataPath, '', $file); $data = $this->open($filePath); $mapper ->path($this->dataPath . '/') ->data($data) ->first(); $count++; unset($data); } $this->logs[] = $count; $this->entityCount += $count; $mapper->tree()->clear(); } } // Second parse foreach ($this->mappers as $model => $mapper) { if ($model == 'custom') { foreach ($fileNames as $fileName => $newId) { if (! method_exists($mapper, 'second')) { continue; } $this->logs[] = 'Second round ' . $fileName; $count = 0; foreach ($this->files($fileName) as $file) { if (! Str::endsWith($file, '.json')) { continue; } $filePath = Str::replace($this->dataPath, '', $file); $data = $this->open($filePath); // Add the original campaign id for gallery image mapping $data['campaign_id'] = $this->originalCampaignID; // @phpstan-ignore-next-line $mapper ->path($this->dataPath . '/') ->data($data) ->second(); $count++; unset($data); } $this->logs[] = $count; } } else { if (! method_exists($mapper, 'second')) { continue; } $this->logs[] = 'Second round ' . $model; $count = 0; foreach ($this->files($model) as $file) { if (! Str::endsWith($file, '.json')) { continue; } $filePath = Str::replace($this->dataPath, '', $file); $data = $this->open($filePath); // Add the original campaign id for gallery image mapping $data['campaign_id'] = $this->originalCampaignID; // @phpstan-ignore-next-line $mapper ->path($this->dataPath . '/') ->data($data) ->second(); $count++; unset($data); } $this->logs[] = $count; } } foreach ($this->mappers as $model => $mapper) { if ($model == 'custom') { foreach ($fileNames as $fileName => $newId) { if (! method_exists($mapper, 'third')) { continue; } $this->logs[] = 'Third round ' . $fileName; $count = 0; foreach ($this->files($fileName) as $file) { if (! Str::endsWith($file, '.json')) { continue; } $filePath = Str::replace($this->dataPath, '', $file); $data = $this->open($filePath); if (empty($data['entity']['mentions'])) { continue; } // @phpstan-ignore-next-line $mapper ->path($this->dataPath . '/') ->data($data) ->third(); $count++; unset($data); } $this->logs[] = '- ' . $count; } } else { if (! method_exists($mapper, 'third')) { continue; } $this->logs[] = 'Third round ' . $model; $count = 0; foreach ($this->files($model) as $file) { if (! Str::endsWith($file, '.json')) { continue; } $filePath = Str::replace($this->dataPath, '', $file); $data = $this->open($filePath); if (empty($data['entity']['mentions'])) { continue; } // @phpstan-ignore-next-line $mapper ->path($this->dataPath . '/') ->data($data) ->third(); $count++; unset($data); } $this->logs[] = '- ' . $count; } } return $this; } protected function files(string $model): array { $path = $this->dataPath . '/' . $model; if (! Storage::disk('local')->exists($path)) { $this->logs[] = 'No ' . $model; return []; } return Storage::disk('local')->files($path); } protected function customFiles(string $model, string $pluralModel): array { $path = $this->dataPath . '/' . $model; $pluralPath = $this->dataPath . '/' . $pluralModel; if (Storage::disk('local')->exists($path)) { return Storage::disk('local')->files($path); } elseif (Storage::disk('local')->exists($pluralPath)) { return Storage::disk('local')->files($pluralPath); } $this->logs[] = 'No ' . $model; return []; } protected function open(string $file): array { $path = $this->dataPath . '/' . $file; if (! Storage::disk('local')->exists($path)) { $this->logs[] = 'file ' . $path . ' doesnt exist'; return []; } $fullpath = Storage::disk('local')->path($path); $content = file_get_contents($fullpath); $data = json_decode($content, true); if (! is_array($data)) { Log::info('Failed to open ' . $path . ' into a proper json', ['data' => $data]); } return $data; } protected function secondCampaign(): self { $this->campaign->entry = $this->mentions($this->campaign->entry); $this->campaign->excerpt = $this->mentions($this->campaign->excerpt); $this->campaign->save(); $this->entityMappingService->silent()->with($this->campaign)->map(); return $this; } protected function cleanup(): self { $files = $this->job->config['files']; foreach ($files as $file) { Storage::disk('local')->delete($file); } // Finished with our core loop, now throw any exception for sentry to catch them if (isset($this->exception)) { throw $this->exception; } return $this; } public function fail(Throwable $e): self { $config = $this->job->config; if (! isset($config['logs'])) { $config['logs'] = []; } $this->job->errors = [$e->getMessage()]; $this->job->config = $config; $this->job->status_id = CampaignImportStatus::FAILED; $this->job->save(); if (app()->bound('sentry')) { app('sentry')->captureException($e); } Log::error('Import', ['where' => 'fail', 'error' => $e->getMessage()]); return $this->cleanup(); } } ================================================ FILE: app/Services/Campaign/Import/Mappers/AbilityMapper.php ================================================ prepareModel() ->trackMappings('ability_id'); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.ability')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/BaseEntityMapper.php ================================================ data, 'entity.image_uuid'); if (! empty($image) && ImportIdMapper::hasGallery($image)) { $this->entity->image_uuid = ImportIdMapper::getGallery($image); } $image = Arr::get($this->data, 'entity.header_uuid'); if (! empty($image) && ImportIdMapper::hasGallery($image)) { $this->entity->header_uuid = ImportIdMapper::getGallery($image); } return $this; } protected function posts(): self { if (empty($this->data['entity']['posts'])) { return $this; } $import = [ 'name', 'entry', 'visibility_id', 'position', 'settings', 'layout_id', ]; foreach ($this->data['entity']['posts'] as $data) { $post = new Post; $post->entity_id = $this->entity->id; foreach ($import as $field) { if (! array_key_exists($field, $data)) { continue; } $post->$field = $data[$field]; } if (! empty($data['location_id']) && ImportIdMapper::has('locations', $data['location_id'])) { $locationID = ImportIdMapper::get('locations', $data['location_id']); if (! empty($locationID)) { $post->location_id = $locationID; } } if (empty($post->position)) { $post->position = 0; } $post->entry = $this->mentions($post->entry); $post->created_by = $this->user->id; $post->save(); if (array_key_exists('postTags', $data)) { foreach ($data['postTags'] as $oldTag) { if (! ImportIdMapper::has('tags', $oldTag['tag_id'])) { continue; } $tagID = ImportIdMapper::get('tags', $oldTag['tag_id']); $postTag = new PostTag; $postTag->post_id = $post->id; $postTag->tag_id = $tagID; $postTag->save(); } } ImportIdMapper::putPost($data['id'], $post->id); $this->mapImageMentions($post); } return $this; } protected function assets(): self { if (empty($this->data['entity']['assets'])) { return $this; } $import = [ 'type_id', 'visibility_id', 'name', 'position', 'is_pinned', ]; foreach ($this->data['entity']['assets'] as $data) { $asset = new EntityAsset; $asset->entity_id = $this->entity->id; foreach ($import as $field) { if (! array_key_exists($field, $data)) { continue; } $asset->$field = $data[$field]; } if (! empty($data['metadata'])) { if (! empty($data['metadata']['path'])) { $img = $data['metadata']['path']; if (! Storage::disk('local')->exists($this->path . $img)) { // dd('image ' . $this->path . $img . ' doesnt exist'); continue; } $image = $this->migrateImage($img); $asset->image_uuid = $image->id; unset($data['metadata']['path']); $asset->metadata = $data['metadata']; } else { $asset->metadata = $data['metadata']; } } if (! empty($data['image_uuid']) && ImportIdMapper::hasGallery($data['image_uuid'])) { $asset->image_uuid = ImportIdMapper::getGallery($data['image_uuid']); } $asset->created_by = $this->user->id; $asset->save(); } return $this; } protected function attributes(): self { if (empty($this->data['entity']['entityAttributes'])) { return $this; } $import = [ 'name', 'value', 'is_private', 'default_order', 'is_pinned', 'type_id', 'is_hidden', ]; foreach ($this->data['entity']['entityAttributes'] as $data) { $attr = new Attribute; $attr->entity_id = $this->entity->id; foreach ($import as $field) { if (! array_key_exists($field, $data)) { continue; } $attr->$field = $data[$field]; } $attr->value = $this->mentions($attr->value); $attr->save(); } return $this; } protected function tags(): self { if (empty($this->data['entity']['entityTags'])) { return $this; } foreach ($this->data['entity']['entityTags'] as $data) { if (! ImportIdMapper::has('tags', $data['tag_id'])) { continue; } $tagID = ImportIdMapper::get('tags', $data['tag_id']); $entityTag = new EntityTag; $entityTag->entity_id = $this->entity->id; $entityTag->tag_id = $tagID; $entityTag->save(); } return $this; } protected function reminders(): self { if (empty($this->data['entity']['events'])) { return $this; } $fields = [ 'length', 'comment', 'is_recurring', 'recurring_until', 'recurring_periodicity', 'colour', 'day', 'month', 'year', 'type_id', 'visibility_id', ]; foreach ($this->data['entity']['events'] as $data) { if (! ImportIdMapper::has('calendars', $data['calendar_id'])) { continue; } $rem = new Reminder; $rem->remindable_id = $this->entity->id; $rem->remindable_type = Entity::class; $rem->calendar_id = ImportIdMapper::get('calendars', $data['calendar_id']); foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $rem->$field = $data[$field]; } $rem->created_by = $this->user->id; $rem->save(); } return $this; } protected function relations(): self { if (empty($this->data['entity']['relationships'])) { return $this; } $fields = [ 'relation', 'visibility_id', 'attitude', 'is_pinned', 'colour', 'marketplace_uuid', ]; foreach ($this->data['entity']['relationships'] as $data) { if (! ImportIdMapper::hasEntity($data['target_id'])) { continue; } $targetID = ImportIdMapper::getEntity($data['target_id']); $rel = new Relation; $rel->owner_id = $this->entity->id; $rel->target_id = $targetID; $rel->campaign_id = $this->campaign->id; $rel->created_by = $this->user->id; foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $rel->$field = $data[$field]; } $rel->save(); } return $this; } protected function abilities(): self { if (empty($this->data['entity']['abilities'])) { return $this; } $fields = [ 'visibility_id', 'charges', 'position', 'note', ]; foreach ($this->data['entity']['abilities'] as $data) { if (! ImportIdMapper::has('abilities', $data['ability_id'])) { continue; } $abilityID = ImportIdMapper::get('abilities', $data['ability_id']); if (empty($abilityID)) { continue; } $ab = new EntityAbility; $ab->entity_id = $this->entity->id; $ab->ability_id = $abilityID; $ab->created_by = $this->user->id; foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $ab->$field = $data[$field]; } $ab->save(); } return $this; } protected function inventory(): self { if (empty($this->data['entity']['inventories'])) { return $this; } $fields = [ 'name', 'amount', 'position', 'description', 'visibility_id', 'is_equipped', 'copy_item_entry', ]; foreach ($this->data['entity']['inventories'] as $data) { $inv = new Inventory; $inv->entity_id = $this->entity->id; if (! empty($data['item_id'])) { if (! ImportIdMapper::has('items', $data['item_id'])) { continue; } $itemID = ImportIdMapper::get('items', $data['item_id']); if (empty($itemID)) { continue; } $inv->item_id = $itemID; } $inv->created_by = $this->user->id; foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $inv->$field = $data[$field]; } $inv->save(); } return $this; } protected function entityThird(): void { $this ->foreignMentions(); } /** * Migrate old entity image system to the gallery. */ protected function images(): self { $oldImages = ['image_path', 'header_image']; foreach ($oldImages as $old) { $this->migrateToGallery($old); } return $this; } protected function migrateToGallery(string $old): self { $img = Arr::get($this->data, 'entity.' . $old); if (empty($img) || ! Storage::disk('local')->exists($this->path . $img)) { return $this; } $image = $this->migrateImage($img); if ($old == 'image_path') { $this->entity->image_uuid = ImportIdMapper::getGallery($image->id); } else { $this->entity->header_uuid = ImportIdMapper::getGallery($image->id); } return $this; } protected function migrateImage(string $img): Image { $imageExt = Str::after($img, '.'); // We need to create a new Image to migrate to the new system. $image = new Image; $image->campaign_id = $this->campaign->id; $image->ext = $imageExt; $image->name = $this->entity->name; $image->visibility_id = Visibility::All; $size = Storage::disk('local')->size($this->path . $img); $image->size = (int) ceil($size / 1024); // kb $image->save(); // Upload the file to s3 using streams Storage::writeStream($image->path, Storage::disk('local')->readStream($this->path . $img)); ImportIdMapper::putGallery($image->id, $image->id); return $image; } protected function foreignMentions(): self { if (empty($this->data['entity']['mentions'])) { return $this; } foreach ($this->data['entity']['mentions'] as $data) { if (! ImportIdMapper::hasEntity($data['target_id'])) { continue; } try { $men = new EntityMention; $men->entity_id = $this->entity->id; $men->target_id = ImportIdMapper::getEntity($data['target_id']); if (! empty($data['campaign_id'])) { $men->campaign_id = $this->campaign->id; } elseif (! empty($data['post_id'])) { $men->post_id = ImportIdMapper::getPost($data['post_id']); } elseif (! empty($data['timeline_element_id'])) { $men->timeline_element_id = ImportIdMapper::getTimelineElement($data['timeline_element_id']); } elseif (! empty($data['quest_element_id'])) { $men->quest_element_id = ImportIdMapper::getQuestElement($data['quest_element_id']); } $men->save(); } catch (Exception $e) { // Silence issues in parsing mentions } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/CalendarMapper.php ================================================ prepareModel() ->trackMappings('calendar_id'); } public function second(): void { // @phpstan-ignore-next-line $this->loadModel() ->weather() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.calendar')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } protected function weather(): self { if (empty($this->data['calendarWeather'])) { return $this; } $fields = [ 'weather', 'temperature', 'precipitation', 'wind', 'effect', 'name', 'day', 'month', 'year', 'visibility_id', ]; foreach ($this->data['calendarWeather'] as $data) { $el = new CalendarWeather; $el->calendar_id = $this->model->id; foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $el->$field = $data[$field]; } $el->created_by = $this->user->id; $el->save(); } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/CampaignMapper.php ================================================ campaign->getFillable(); foreach ($this->data as $property => $value) { if (in_array($property, $forbidden) && ! empty($this->campaign->$property)) { continue; } if (! is_array($value)) { if (! in_array($property, $fillable)) { continue; } $this->campaign->$property = $value; } } $this->image('image'); $this->image('header_image'); $arrays = ['settings', 'default_images', 'ui_settings']; foreach ($arrays as $key) { if (empty($this->data[$key])) { continue; } $this->campaign->$key = $this->data[$key]; } $this->campaign->save(); return $this->campaign; } protected function image(string $field): void { if (empty($this->data[$field]) || empty($this->campaign->$field)) { return; } // Let's see if the original exists on the s3 bucket to avoid a lot of pain $destination = 'w/' . $this->campaign->id . '/' . Str::afterLast($this->data[$field], '/'); if (Storage::exists($this->data[$field])) { Storage::copy($this->data[$field], $destination); $this->campaign->$field = $destination; return; } $path = $this->path . '/' . $this->data[$field]; if (! Storage::disk('local')->exists($path)) { return; } // Upload the file to s3 using streams Storage::writeStream($destination, Storage::disk('local')->readStream($path)); $this->campaign->$field = $destination; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/CharacterMapper.php ================================================ migrateOldStatus() ->prepareModel() ->trackMappings('character_id'); } /** * Backward compatibility: resolve old character status fields to entities.status_id. * Old is_dead boolean or child status_id enum (0=alive, 1=dead, 2=missing). */ protected function migrateOldStatus(): self { if (array_key_exists('status_id', $this->data['entity'] ?? [])) { return $this; } $map = [0 => 'alive', 1 => 'dead', 2 => 'missing']; // Old is_dead boolean → enum value if (array_key_exists('is_dead', $this->data) && ! array_key_exists('status_id', $this->data)) { $this->data['status_id'] = (int) $this->data['is_dead']; unset($this->data['is_dead']); } // Child-level status_id enum → resolve to category_statuses, then remove // so it doesn't get written back to the characters table's old enum column if (array_key_exists('status_id', $this->data)) { $oldValue = (int) $this->data['status_id']; unset($this->data['status_id']); if (isset($map[$oldValue])) { $this->resolveOldStatusToEntity('character', $map[$oldValue]); } } return $this; } public function second(): void { // @phpstan-ignore-next-line $this ->loadModel() ->pivot('characterFamilies', 'families', 'family_id') ->pivot('characterRaces', 'races', 'race_id') ->saveModel() ->traits() ->characterLocations() ->memberships() ->entitySecond(); } protected function traits(): self { if (empty($this->data['characterTraits'])) { return $this; } $fields = ['name', 'entry', 'is_private', 'section_id', 'default_order']; foreach ($this->data['characterTraits'] as $data) { $trait = new CharacterTrait; $trait->character_id = $this->model->id; foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $trait->$field = $data[$field]; } $trait->save(); } return $this; } protected function memberships(): self { if (empty($this->data['organisationMemberships'])) { return $this; } $parents = []; foreach ($this->data['organisationMemberships'] as $data) { $member = new CharacterOrganisation; $member->character_id = $this->model->id; $member->organisation_id = ImportIdMapper::get('organisations', $data['organisation_id']); $member->role = $data['role'] ?? null; $member->is_private = $data['is_private'] ?? false; $member->pin_id = $data['pin_id'] ?? null; $member->status_id = $data['status_id'] ?? null; if (! empty($data['parent_id']) && isset($parents[$data['parent_id']])) { $member->parent_id = $parents[$data['parent_id']]; } $member->save(); $parents[$data['id']] = $member->id; } return $this; } protected function characterLocations(): self { // Support old format if (! empty($this->data['location_id'])) { $locationID = ImportIdMapper::get('locations', $this->data['location_id']); if (! empty($locationID)) { $this->entity->locations()->attach($locationID); } } if (! Arr::has($this->data, 'entity.entityLocations')) { return $this; } // New 3.8 format foreach ($this->data['entity']['entityLocations'] as $location) { if (empty($location['location_id'])) { continue; } $locationID = ImportIdMapper::get('locations', $location['location_id']); if (empty($locationID)) { continue; } $this->entity->locations()->attach($locationID); } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/CreatureMapper.php ================================================ migrateOldStatus() ->prepareModel() ->trackMappings('creature_id'); } /** * Backward compatibility: resolve old boolean status fields to entities.status_id. */ protected function migrateOldStatus(): self { if (array_key_exists('status_id', $this->data['entity'] ?? [])) { return $this; } if (! empty($this->data['is_dead'])) { $this->resolveOldStatusToEntity('creature', 'dead'); } elseif (! empty($this->data['is_extinct'])) { $this->resolveOldStatusToEntity('creature', 'extinct'); } return $this; } public function second(): void { $this->loadModel() ->entityLocations() ->saveModel() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.creature')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/CustomEntityMapper.php ================================================ entityMappingService = $entityMappingService; } protected function prepareEntity(): self { $this->entity(); return $this; } protected function loadEntity(): self { $id = ImportIdMapper::getEntity($this->data['entity']['id']); $this->entity = Entity::where('id', $id)->firstOrFail(); return $this; } protected function trackMappings(?string $parent = null): void { $this->mapping[$this->data['entity']['id']] = $this->entity->id; ImportIdMapper::putEntity($this->data['entity']['id'], $this->entity->id); if ($parent && ! empty($this->data['entity'][$parent])) { $this->parents[$this->data['entity'][$parent]][] = $this->entity->id; } } protected function entity(): void { $entityMapping = ['name', 'is_private', 'tooltip', 'is_template', 'is_attributes_private', 'focus_x', 'focus_y', 'entry', 'type', 'status_id']; $this->entity = new Entity; $this->entity->created_by = $this->user->id; $this->entity->updated_by = $this->user->id; $this->entity->campaign_id = $this->campaign->id; // Log::info(ImportIdMapper::getCustomEntityTypes()); $this->entity->type_id = ImportIdMapper::getCustomEntityType($this->data['entity']['type_id']); foreach ($entityMapping as $field) { if (! array_key_exists($field, $this->data['entity'] ?? [])) { continue; } $this->entity->$field = $this->data['entity'][$field]; } $this ->images() ->gallery(); $this->entity->save(); EntityLogger::entity($this->entity)->create(); ImportIdMapper::putEntity($this->data['entity']['id'], $this->entity->id); $this ->assets() ->tags(); } public function second(): void { $this ->entitySecond(); } protected function entitySecond(): void { $this->entity->tooltip = $this->mentions($this->entity->tooltip); $this->entity->entry = $this->mentions($this->entity->entry); $this->entity->save(); $this->posts() ->attributes() ->relations() ->reminders() ->abilities() ->inventory() ->locations(); } protected function locations(): self { if (empty($this->data['entity']['entityLocations'])) { return $this; } foreach ($this->data['entity']['entityLocations'] as $data) { if (! ImportIdMapper::has('locations', $data['location_id'])) { continue; } $locID = ImportIdMapper::get('locations', $data['location_id']); $entityLoc = new EntityLocation; $entityLoc->entity_id = $this->entity->id; $entityLoc->location_id = $locID; $entityLoc->save(); } return $this; } public function tree(): self { foreach ($this->parents as $parent => $children) { if (! isset($this->mapping[$parent])) { continue; } // We need the nested trait to trigger for this so it's going to be inefficient $models = Entity::whereIn('id', $children)->get(); foreach ($models as $model) { $model->parent_id = $this->mapping[$parent]; $model->saveQuietly(); } } return $this; } protected function saveEntity(): self { $this->entity->save(); $this->mapImageMentions($this->entity); return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/CustomMapper.php ================================================ prepareEntity() ->trackMappings('parent_id'); } public function second(): void { $this ->loadEntity() ->saveEntity() ->entitySecond(); } public function third(): self { $this ->loadEntity() ->entityThird(); return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/EntityMapper.php ================================================ entityMappingService = $entityMappingService; } protected function prepareModel(): self { $this->model = app()->make($this->className); $this->model->campaign_id = $this->campaign->id; $columns = $this->model->getConnection()->getSchemaBuilder()->getColumnListing($this->model->getTable()); foreach ($this->data as $field => $value) { // @phpstan-ignore-next-line if (is_array($value) || in_array($field, $this->ignore) || ! in_array($field, $columns)) { continue; } $this->model->$field = $value; } $this->model->saveQuietly(); $this->entity(); return $this; } protected function loadModel(): self { $builder = app()->make($this->className); $id = ImportIdMapper::get($this->mappingName, $this->data['id']); $this->model = $builder->where('id', $id)->with('entity')->firstOrFail(); $this->entity = $this->model->entity; return $this; } protected function trackMappings(?string $parent = null): void { $this->mapping[$this->data['id']] = $this->model->id; ImportIdMapper::put($this->mappingName, $this->data['id'], $this->model->id); if ($parent && ! empty($this->data[$parent])) { $this->parents[$this->data[$parent]][] = $this->entity->id; } } protected function entity(): void { $mapping = ['name', 'is_private', 'campaign_id']; $entityMapping = ['tooltip', 'is_template', 'is_attributes_private', 'focus_x', 'focus_y', 'entry', 'type', 'type_id', 'status_id']; $this->entity = new Entity; $this->entity->entity_id = $this->model->id; $this->entity->created_by = $this->user->id; $this->entity->updated_by = $this->user->id; foreach ($mapping as $field) { $this->entity->$field = $this->model->$field; } // Old exports might not have this info so we call back on the model hardcoded ids if (empty($this->entity->type_id)) { $this->entity->type_id = $this->model->entityTypeId(); } foreach ($entityMapping as $field) { if (! array_key_exists($field, $this->data['entity'] ?? [])) { continue; } $this->entity->$field = $this->data['entity'][$field]; } if (isset($this->data['entity']['archived_at'])) { $this->entity->archived_at = Carbon::now(); } $this ->images() ->gallery(); $this->entity->save(); EntityLogger::entity($this->entity)->create(); ImportIdMapper::putEntity($this->data['entity']['id'], $this->entity->id); $this ->assets() ->tags(); } public function second(): void { $this ->loadModel() ->entitySecond(); } protected function entitySecond(): void { $this->entity = $this->model->entity; $this->entity->tooltip = $this->mentions($this->entity->tooltip); $this->entity->entry = $this->mentions($this->entity->entry); $this->entity->save(); $this->posts() ->attributes() ->relations() ->reminders() ->abilities() ->inventory(); } protected function foreign(string $model, string $field): self { if (empty($this->data[$field])) { return $this; } if ($model === 'entities') { if (! ImportIdMapper::hasEntity($this->data[$field])) { return $this; } $foreignID = ImportIdMapper::getEntity($this->data[$field]); } else { if (! ImportIdMapper::has($model, $this->data[$field])) { return $this; } $foreignID = ImportIdMapper::get($model, $this->data[$field]); } if (! $foreignID) { return $this; } $this->model->$field = $foreignID; return $this; } protected function pivot(string $relation, string $model, string $field): self { // Check if import has old location_id and migrate it to new locations pivot table system, currently only happens with organisations if ($relation == 'pivotLocations' && isset($this->data['location_id']) && ! in_array(['location_id' => $this->data['location_id']], $this->data[$relation])) { $this->data[$relation][] = ['location_id' => $this->data['location_id']]; } foreach ($this->data[$relation] as $pivot) { if (! ImportIdMapper::has($model, $pivot[$field])) { continue; } $foreignID = ImportIdMapper::get($model, $pivot[$field]); if (array_key_exists('is_private', $pivot)) { $this->model->{$model}()->attach($foreignID, ['is_private' => $pivot['is_private']]); } else { $this->model->{$model}()->attach($foreignID); } } return $this; } /** * Import locations through the entity_locations pivot table */ protected function entityLocations(): self { // Handle old location_id field from legacy exports if (isset($this->data['location_id']) && ImportIdMapper::has('locations', $this->data['location_id'])) { $foreignID = ImportIdMapper::get('locations', $this->data['location_id']); $this->entity->locations()->attach($foreignID); } // Handle pivotLocations data from exports if (isset($this->data['pivotLocations'])) { foreach ($this->data['pivotLocations'] as $pivot) { if (! ImportIdMapper::has('locations', $pivot['location_id'])) { continue; } $foreignID = ImportIdMapper::get('locations', $pivot['location_id']); // Avoid duplicates (e.g., if location_id was already handled above) if (! $this->entity->locations()->wherePivot('location_id', $foreignID)->exists()) { $this->entity->locations()->attach($foreignID); } } } // Handle entityLocations nested in entity data if (Arr::has($this->data, 'entity.entityLocations')) { foreach ($this->data['entity']['entityLocations'] as $location) { if (empty($location['location_id'])) { continue; } if (! ImportIdMapper::has('locations', $location['location_id'])) { continue; } $foreignID = ImportIdMapper::get('locations', $location['location_id']); if (! $this->entity->locations()->wherePivot('location_id', $foreignID)->exists()) { $this->entity->locations()->attach($foreignID); } } } return $this; } protected function saveModel(): self { $this->model->save(); $this->mapImageMentions($this->model); return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/EventMapper.php ================================================ prepareModel() ->trackMappings('event_id'); } public function second(): void { $this ->loadModel() ->entityLocations() ->saveModel() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.event')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/FamilyMapper.php ================================================ migrateOldStatus() ->prepareModel() ->trackMappings('family_id'); } /** * Backward compatibility: resolve old boolean status fields to entities.status_id. */ protected function migrateOldStatus(): self { if (array_key_exists('status_id', $this->data['entity'] ?? [])) { return $this; } if (! empty($this->data['is_extinct'])) { $this->resolveOldStatusToEntity('family', 'extinct'); } return $this; } public function second(): void { $this ->loadModel() ->foreign('locations', 'location_id') ->saveModel(); /*$this ->familyTree();*/ } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.family')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/GalleryMapper.php ================================================ mapping[$uuid]); } public function get(string $uuid): string { return $this->mapping[$uuid]; } public function mapping(): array { return $this->mapping; } public function prepare(): self { // $this->campaign->images()->delete(); return $this; } public function import(): void { // We allow uploading json files in the gallery, which will be ignored here for now. if (empty($this->data['id'])) { return; } $this->image = new Image; $this->image->campaign_id = $this->campaign->id; // Need to save to set the id otherwise it stores wrong data. $this->image->save(); $this->mapping[$this->data['id']] = $this->image->id; ImportIdMapper::putGallery($this->data['id'], $this->image->id); if (! empty($this->data['folder_id'])) { $this->parents[$this->data['folder_id']][] = $this->image->id; } $this->importFields(); $this->image->save(); } public function tree(): self { foreach ($this->parents as $parent => $children) { if (empty($this->mapping[$parent])) { continue; } $newParent = $this->mapping[$parent]; DB::update('UPDATE images SET folder_id = \'' . $newParent . '\' where id in (\'' . implode('\', \'', $children) . '\') limit ' . count($children)); } return $this; } protected function importFields(): void { $columns = $this->image->getConnection()->getSchemaBuilder()->getColumnListing($this->image->getTable()); foreach ($this->data as $field => $value) { if (in_array($field, $this->reset) || is_array($value) || ! in_array($field, $columns)) { continue; } $this->image->$field = $value; } $this->importImage(); } protected function importImage(): void { if (($this->data['is_folder'] ?? 0) === 1) { return; } if (empty($this->data['id']) || empty($this->data['ext'])) { return; } // An image needs the image saved locally $imagePath = $this->path . '/' . $this->data['id'] . '.' . $this->data['ext']; $destination = 'campaigns/' . $this->campaign->id . '/' . $this->image->id . '.' . $this->data['ext']; if (! Storage::disk('local')->exists($imagePath)) { Log::info('image ' . $imagePath . ' doesnt exist'); return; } // Upload the file to s3 using streams Storage::writeStream($destination, Storage::disk('local')->readStream($imagePath)); } } ================================================ FILE: app/Services/Campaign/Import/Mappers/ImageMapper.php ================================================ data[$field]) && ! empty($this->model->$field)) { return; } // Let's see if the original exists on the s3 bucket to avoid a lot of pain $destination = 'w/' . $this->campaign->id . '/' . Str::afterLast($this->data[$field], '/'); if (Storage::exists($this->data['image'])) { Storage::copy($this->data['image'], $destination); $this->campaign->image = $destination; return; } $path = $this->path . '/' . $this->data[$field]; if (! Storage::disk('local')->exists($path)) { return; } // Upload the file to s3 using streams Storage::writeStream($destination, Storage::disk('local')->readStream($path)); $this->campaign->$field = $destination; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/ImportMapper.php ================================================ data = $data; return $this; } public function path(string $path): self { $this->path = $path; return $this; } public function prepare(): self { return $this; } public function clear(): void { unset($this->path, $this->data); } } ================================================ FILE: app/Services/Campaign/Import/Mappers/ItemMapper.php ================================================ prepareModel() ->trackMappings('item_id'); } public function second(): void { // @phpstan-ignore method.notFound $this ->loadModel() ->foreign('locations', 'location_id') ->importCreators() ->saveModel() ->legacyCreator() ->entitySecond(); } protected function importCreators(): self { if (empty($this->data['itemCreators'])) { return $this; } foreach ($this->data['itemCreators'] as $pivot) { if (! ImportIdMapper::hasEntity($pivot['creator_id'])) { continue; } $foreignID = ImportIdMapper::getEntity($pivot['creator_id']); $creator = new ItemCreator; $creator->item_id = $this->model->id; $creator->creator_id = $foreignID; $creator->save(); } return $this; } /** * Backward compatibility: old exports have creator_id on the item instead of itemCreators pivot */ protected function legacyCreator(): self { if (empty($this->data['creator_id']) || ! empty($this->data['itemCreators'])) { return $this; } if (! ImportIdMapper::hasEntity($this->data['creator_id'])) { return $this; } $foreignID = ImportIdMapper::getEntity($this->data['creator_id']); $creator = new ItemCreator; $creator->item_id = $this->model->id; $creator->creator_id = $foreignID; $creator->save(); return $this; } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.item')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/JournalMapper.php ================================================ prepareModel() ->trackMappings('journal_id'); } public function second(): void { $this ->loadModel() ->foreign('locations', 'location_id') ->foreign('entities', 'author_id') ->saveModel() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.journal')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/LocationMapper.php ================================================ migrateOldStatus() ->prepareModel() ->trackMappings('location_id'); } /** * Backward compatibility: resolve old boolean status fields to entities.status_id. */ protected function migrateOldStatus(): self { if (array_key_exists('status_id', $this->data['entity'] ?? [])) { return $this; } if (! empty($this->data['is_destroyed'])) { $this->resolveOldStatusToEntity('location', 'destroyed'); } return $this; } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.location')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/MapMapper.php ================================================ prepareModel() ->trackMappings('map_id'); } public function second(): void { // @phpstan-ignore-next-line $this->loadModel() ->foreign('locations', 'location_id') ->groups() ->groupsParents() ->layers() ->markers() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.map')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } protected function groups(): self { $fields = [ 'name', 'position', 'visibility_id', 'is_shown', ]; $this->groups = []; foreach ($this->data['groups'] as $data) { $el = new MapGroup; $el->map_id = $this->model->id; foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $el->$field = $data[$field]; } $el->created_by = $this->user->id; $el->save(); $this->groups[$data['id']] = $el->id; } return $this; } protected function groupsParents(): self { foreach ($this->data['groups'] as $data) { if (isset($data['parent_id'])) { $group = MapGroup::where('id', $this->groups[$data['id']])->first(); $group->parent_id = $this->groups[$data['parent_id']]; $group->save(); } } return $this; } protected function layers(): self { $fields = [ 'name', 'position', 'image_uuid', 'image_path', 'height', 'width', 'entry', 'visibility_id', 'type_id', ]; $this->layers = []; foreach ($this->data['layers'] as $data) { $el = new MapLayer; $el->map_id = $this->model->id; foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $el->$field = $data[$field]; } $el->entry = $this->mentions($el->entry); $el->created_by = $this->user->id; // Move image $imageField = isset($data['image_path']) ? 'image' : 'image_path'; if (! empty($el->$imageField)) { $imageName = Str::afterLast($el->$imageField, '/'); $destination = 'w/' . $this->campaign->id . '/maps/' . $el->map_id . '/' . $imageName; if (! Storage::disk('local')->exists($this->path . $el->$imageField)) { Log::error('map layer image ' . $this->path . $el->$imageField . ' doesnt exist'); return $this; } // Upload the file to s3 using streams Storage::writeStream($destination, Storage::disk('local')->readStream($this->path . $el->$imageField)); $el->image_path = $destination; } else { if (empty($el->image_uuid) || ! ImportIdMapper::hasGallery($el->image_uuid)) { continue; } $el->image_uuid = ImportIdMapper::getGallery($el->image_uuid); } $el->save(); $this->layers[$data['id']] = $el->id; } return $this; } protected function markers(): self { $fields = [ 'pin_size', 'name', 'entry', 'longitude', 'latitude', 'colour', 'shape_id', 'size_id', 'icon', 'custom_icon', 'custom_shape', 'is_draggable', 'visibility_id', 'font_colour', 'circle_radius', 'polygon_style', 'opacity', 'css', ]; foreach ($this->data['markers'] as $data) { $marker = new MapMarker; $marker->map_id = $this->model->id; if (! empty($data['entity_id'])) { if (! ImportIdMapper::hasEntity($data['entity_id'])) { continue; } $marker->entity_id = ImportIdMapper::getEntity($data['entity_id']); } foreach ($fields as $field) { if (isset($data[$field])) { $marker->$field = $data[$field]; } } if (! empty($data['group_id'])) { $marker->group_id = $this->groups[$data['group_id']]; } $marker->created_by = $this->user->id; $marker->entry = $this->mentions($marker->entry); $marker->save(); } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/MiscMapper.php ================================================ campaign->{$this->mappingName}()->forceDelete(); return $this; } public function third(): self { $this ->loadModel() ->entityThird(); return $this; } public function tree(): self { return $this; } /** * Resolve an old child-level status field to a category_statuses.id * and inject it into $this->data['entity']['status_id'] for the EntityMapper. */ protected function resolveOldStatusToEntity(string $entityTypeCode, string $statusKey): void { if (array_key_exists('status_id', $this->data['entity'] ?? [])) { return; } $entityTypeId = config('entities.ids.' . $entityTypeCode); $status = DB::table('category_statuses') ->where('category_id', $entityTypeId) ->where('key', $statusKey) ->first(); if ($status) { $this->data['entity']['status_id'] = $status->id; } } } ================================================ FILE: app/Services/Campaign/Import/Mappers/NoteMapper.php ================================================ prepareModel() ->trackMappings('note_id'); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.note')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/OrganisationMapper.php ================================================ migrateOldStatus() ->prepareModel() ->trackMappings('organisation_id'); } /** * Backward compatibility: resolve old boolean status fields to entities.status_id. */ protected function migrateOldStatus(): self { if (array_key_exists('status_id', $this->data['entity'] ?? [])) { return $this; } if (! empty($this->data['is_defunct'])) { $this->resolveOldStatusToEntity('organisation', 'defunct'); } return $this; } public function second(): void { $this ->loadModel() ->entityLocations() ->saveModel() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.organisation')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/QuestMapper.php ================================================ migrateOldStatus() ->prepareModel() ->trackMappings('quest_id'); } /** * Backward compatibility: resolve old quest status fields to entities.status_id. * Old is_completed boolean or child status_id enum (0=not_started, 1=ongoing, 2=completed, 3=abandoned). */ protected function migrateOldStatus(): self { if (array_key_exists('status_id', $this->data['entity'] ?? [])) { return $this; } $map = [0 => 'not_started', 1 => 'ongoing', 2 => 'completed', 3 => 'abandoned']; // Old is_completed boolean → enum value if (array_key_exists('is_completed', $this->data) && ! array_key_exists('status_id', $this->data)) { $this->data['status_id'] = $this->data['is_completed'] ? 2 : 0; unset($this->data['is_completed']); } // Child-level status_id enum → resolve to category_statuses, then remove // so it doesn't get written back to the quests table's old enum column if (array_key_exists('status_id', $this->data)) { $oldValue = (int) $this->data['status_id']; unset($this->data['status_id']); if (isset($map[$oldValue])) { $this->resolveOldStatusToEntity('quest', $map[$oldValue]); } } return $this; } public function second(): void { // @phpstan-ignore-next-line $this->loadModel() ->foreign('locations', 'location_id') ->foreign('entities', 'instigator_id') ->saveModel() ->elements() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.quest')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } protected function elements(): self { $fields = [ 'role', 'entry', 'visibility_id', 'colour', 'name', ]; foreach ($this->data['elements'] as $data) { $el = new QuestElement; $el->quest_id = $this->model->id; if (! empty($data['entity_id'])) { if (! ImportIdMapper::hasEntity($data['entity_id'])) { continue; } $el->entity_id = ImportIdMapper::getEntity($data['entity_id']); } foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $el->$field = $data[$field]; } $el->entry = $this->mentions($el->entry); $el->save(); ImportIdMapper::putQuestElement($data['id'], $el->id); } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/RaceMapper.php ================================================ migrateOldStatus() ->prepareModel() ->trackMappings('race_id'); } /** * Backward compatibility: resolve old boolean status fields to entities.status_id. */ protected function migrateOldStatus(): self { if (array_key_exists('status_id', $this->data['entity'] ?? [])) { return $this; } if (! empty($this->data['is_extinct'])) { $this->resolveOldStatusToEntity('race', 'extinct'); } return $this; } public function second(): void { $this->loadModel() ->entityLocations() ->saveModel() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.race')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/TagMapper.php ================================================ Legacy named colour to hex mapping for old exports */ protected const LEGACY_COLOURS = [ 'red' => 'D93D33', 'yellow' => 'f39c12', 'brown' => 'a35831', 'aqua' => '00829B', 'light-blue' => '3A7CAD', 'green' => '058943', 'navy' => '001F3F', 'teal' => '2D8289', 'orange' => 'C85208', 'purple' => '605ca8', 'maroon' => 'D81B60', 'grey' => '797676', 'gray' => '797676', 'pink' => 'C822D7', 'black' => '111111', ]; protected array $ignore = ['id', 'entry', 'type', 'campaign_id', 'slug', 'image', '_lft', '_rgt', 'tag_id', 'created_at', 'updated_at']; protected string $className = Tag::class; protected string $mappingName = 'tags'; public function first(): void { $this->convertLegacyColour(); $this ->prepareModel() ->trackMappings('tag_id'); } /** * Convert legacy named colour values from old exports to hex */ protected function convertLegacyColour(): void { if (! empty($this->data['colour']) && isset(self::LEGACY_COLOURS[$this->data['colour']])) { $this->data['colour'] = self::LEGACY_COLOURS[$this->data['colour']]; } } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.tag')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } } ================================================ FILE: app/Services/Campaign/Import/Mappers/TimelineMapper.php ================================================ prepareModel() ->trackMappings('timeline_id'); } public function second(): void { // @phpstan-ignore-next-line $this->loadModel() ->eras() ->elements() ->entitySecond(); } public function tree(): self { foreach ($this->parents as $parent => $entityIds) { if (! isset($this->mapping[$parent])) { continue; } $parentEntity = Entity::where('entity_id', $this->mapping[$parent]) ->where('type_id', config('entities.ids.timeline')) ->first(); if (! $parentEntity) { continue; } $entities = Entity::whereIn('id', $entityIds)->get(); foreach ($entities as $entity) { $entity->parent_id = $parentEntity->id; $entity->saveQuietly(); } } return $this; } protected function eras(): self { $fields = [ 'name', 'abbreviation', 'start_year', 'end_year', 'entry', 'is_collapsed', 'position', ]; $this->eras = []; foreach ($this->data['eras'] as $data) { $er = new TimelineEra; $er->timeline_id = $this->model->id; foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $er->$field = $data[$field]; } $er->entry = $this->mentions($er->entry); $er->save(); $this->eras[$data['id']] = $er->id; } return $this; } protected function elements(): self { $fields = [ 'position', 'name', 'date', 'entry', 'colour', 'visibility_id', 'icon', 'is_collapsed', 'use_entity_entry', 'use_event_date', ]; foreach ($this->data['elements'] as $data) { $el = new TimelineElement; $el->timeline_id = $this->model->id; $el->era_id = $this->eras[$data['era_id']]; if (! empty($data['entity_id'])) { if (! ImportIdMapper::hasEntity($data['entity_id'])) { continue; } $el->entity_id = ImportIdMapper::getEntity($data['entity_id']); } foreach ($fields as $field) { if (! array_key_exists($field, $data)) { continue; } $el->$field = $data[$field]; } $el->entry = $this->mentions($el->entry); $el->save(); ImportIdMapper::putTimelineElement($data['id'], $el->id); } return $this; } } ================================================ FILE: app/Services/Campaign/Import/PrepareService.php ================================================ campaign->id) ->where('user_id', $this->user->id) ->whereNotIn('status_id', [CampaignImportStatus::FINISHED, CampaignImportStatus::FAILED, CampaignImportStatus::READY]) ->first(); if ($token) { return $token; } $token = new CampaignImport; $token->user_id = $this->user->id; $token->campaign_id = $this->campaign->id; $token->status_id = CampaignImportStatus::PREPARED; $token->save(); return $token; } } ================================================ FILE: app/Services/Campaign/LeaveService.php ================================================ campaign->id) ->where('user_id', $this->user->id) ->first(); if (empty($member)) { throw new Exception(__('campaigns.leave.error')); } // Delete the member $member->delete(); // Delete the member from all the roles in the campaign $roles = CampaignRoleUser::select('campaign_role_users.*') ->where('user_id', $this->user->id) ->leftJoin('campaign_roles as cr', 'cr.id', '=', 'campaign_role_users.campaign_role_id') ->where('cr.campaign_id', $this->campaign->id) ->get(); foreach ($roles as $role) { $role->delete(); } UserLeft::dispatch($this->campaign, $this->user); } } ================================================ FILE: app/Services/Campaign/LocalisationService.php ================================================ request->route('campaign'); return ! empty($campaign) && $campaign instanceof Campaign; } /** * Get the campaign */ public function getCampaign(): ?Campaign { if (isset($this->campaign)) { return $this->campaign; } // Load the campaign from the router return $this->campaign = $this->request->route('campaign'); } /** * Force the campaign. This is use for moving entities between campaigns. */ public function forceCampaign(Campaign $campaign): void { $this->campaign = $campaign; } public function getConsoleCampaign(): int { return $this->consoleCampaignId; } public function setConsoleCampaign(int $campaignId): self { $this->consoleCampaignId = $campaignId; return $this; } } ================================================ FILE: app/Services/Campaign/MemberService.php ================================================ userCampaignRole = $campaignRoleUser; return $this; } public function fromRequest(UpdateUserRole $request): self { $this ->loadUser($request->get('user_id')) ->loadCampaignRole($request->get('role_id')); return $this; } public function update(CampaignUser $user, array $roles): bool { $currentRoles = $user->roles->pluck('id')->toArray(); $roleUsers = CampaignRoleUser::where('user_id', $user->user_id) ->whereIn('campaign_role_id', $currentRoles) ->whereNotIn('campaign_role_id', $roles) ->with('campaignRole') ->get(); $deletedRoles = []; /** @var ?CampaignRoleUser $role */ foreach ($roleUsers as $role) { // Admin role being switched? Forget the cache if ($role->campaignRole->isAdmin()) { CampaignCache::campaign($this->campaign)->clear(); } // When trying to delete an admin role check if it was created recently if ($role->campaignRole->isAdmin() && ! $role->recentlyCreated()) { throw (new TranslatableException( 'campaigns.roles.users.errors.cant_kick_admins' ))->setOptions([ 'admin' => $role->campaignRole->name, 'discord' => 'Discord', 'email' => '' . config('app.email') . '', ]); } } // Now actually delete the roles foreach ($roleUsers as $role) { $deletedRoles[] = $role->id; $role->delete(); } $rolesToCreate = array_diff_key($roles, $currentRoles); foreach ($rolesToCreate as $role) { CampaignRoleUser::create([ 'campaign_role_id' => $role, 'user_id' => $user->user_id, ]); } return true; } /** * Add a user to a role */ public function add(): bool { if ( ! $this->checkUserInCampaign() || ! $this->checkRoleInCampaign() || $this->userIsInRole() ) { return false; } // If both are valid, add the user to the role $userRole = new CampaignRoleUser; $userRole->user_id = $this->user->id; $userRole->campaign_role_id = $this->campaignRole->id; $userRole->save(); return true; } /** * Remove a user from a campaign role */ public function remove(): bool { if ( ! $this->checkUserInCampaign() || ! $this->checkRoleInCampaign() || ! $this->userIsInRole() ) { return false; } $this->userCampaignRole->delete(); return true; } /** * @throws TranslatableException */ public function delete(): bool { // If the role isn't an admin, we can safely delete if (! $this->userCampaignRole->campaignRole->isAdmin()) { return $this->userCampaignRole->delete(); } // It's the admin role. Only allow if campaign author or self $userIsCreator = $this->userCampaignRole->campaignRole->campaign->created_by === $this->user->id; if ($this->userCampaignRole->user_id === $this->user->id || $userIsCreator) { $roleCount = $this ->user ->campaignRoles ->where('campaign_id', $this->userCampaignRole->campaignRole->campaign_id) ->count(); // Stop a user from having 0 roles if ($this->userCampaignRole->user_id === $this->user->id && $roleCount === 1) { throw (new TranslatableException( 'campaigns.roles.users.errors.needs_more_roles' ))->setOptions(['admin' => $this->userCampaignRole->campaignRole->name]); } } elseif (! $this->userCampaignRole->recentlyCreated()) { // If the user wasn't added to the admin role recently (ex by mistake), allow removing them throw (new TranslatableException( 'campaigns.roles.users.errors.cant_kick_admins' ))->setOptions([ 'admin' => $this->userCampaignRole->campaignRole->name, 'discord' => 'Discord', 'email' => '' . config('app.email') . '', ]); } return $this->userCampaignRole->delete(); } /** * Load a user */ protected function loadUser(int $userID): self { $this->user = User::find($userID); return $this; } /** * Load a campaign role */ protected function loadCampaignRole(int $roleID): self { $this->campaignRole = CampaignRole::find($roleID); return $this; } /** * Validate that the given user is in the correct campaign */ protected function checkUserInCampaign(): bool { return $this->campaign->users()->where('user_id', $this->user->id)->count() === 1; } /** * Validate that the given role is in the correct campaign */ protected function checkRoleInCampaign(): bool { return $this->campaignRole->campaign_id === $this->campaign->id; } /** * Validate that the user exists in the role */ protected function userIsInRole(): bool { $this ->userCampaignRole = CampaignRoleUser::where('user_id', $this->user->id) ->where('campaign_role_id', $this->campaignRole->id) ->first(); return ! empty($this->userCampaignRole); } } ================================================ FILE: app/Services/Campaign/ModuleEditService.php ================================================ campaign->settings; $key = $this->entityType->id; unset($settings['modules'][$key]['s'], $settings['modules'][$key]['p'], $settings['modules'][$key]['i']); if ($this->campaign->boosted()) { $singular = $plural = $icon = null; if ($request->filled('singular')) { $singular = $this->purify(mb_trim($request->get('singular'))); } if ($request->filled('plural')) { $plural = $this->purify(mb_trim($request->get('plural'))); } if ($request->filled('icon')) { $icon = $this->purify(mb_trim($request->get('icon'))); } if (! empty($singular)) { $settings['modules'][$key]['s'] = $singular; } if (! empty($plural)) { $settings['modules'][$key]['p'] = $plural; } if (! empty($icon)) { $settings['modules'][$key]['i'] = $icon; } } // Bookmarks can't have default images. if (($request->hasFile('default_entity_image') || $request->filled('remove-image')) && ! $this->entityType->isBookmark()) { $this->defaultImageService->campaign($this->campaign) ->user($this->user) ->entityType($this->entityType) ->destroy(); if ($request->hasFile('default_entity_image')) { $this->defaultImageService->save($request); } } $this->campaign->settings = $settings; $this->campaign->updateQuietly(); $this->campaign->setting->{$this->entityType->pluralCode()} = (int) $request->get('enabled'); $this->campaign->setting->save(); Cache::forget('campaign_' . $this->campaign->id . '_sidebar'); return $this; } /** * Remove the custom modules setup from the campaign */ public function reset(): self { $settings = $this->campaign->settings; unset($settings['modules']); if (is_array($settings)) { foreach ($settings as $name => $val) { if (Str::startsWith($name, 'modules.')) { unset($settings[$name]); } } } $this->campaign->settings = $settings; $this->campaign->saveQuietly(); CampaignCache::campaign($this->campaign)->clear(); Cache::forget('campaign_' . $this->campaign->id . '_sidebar'); $this->user->campaignLog($this->campaign->id, 'modules', 'reset'); return $this; } public function toggle(): bool { // Validate module $fillable = $this->campaign->setting->getFillable(); if (! in_array($this->entityType->pluralCode(), $fillable)) { throw new Exception; } $this->campaign->setting->{$this->entityType->pluralCode()} = ! $this->campaign->setting->{$this->entityType->pluralCode()}; $this->campaign->setting->saveQuietly(); Cache::forget('campaign_' . $this->campaign->id . '_sidebar'); EntityTypeToggled::dispatch($this->entityType, $this->user, $this->campaign); return (bool) $this->campaign->setting->{$this->entityType->pluralCode()}; } public function toggleFeature(string $module): bool { // Validate module $fillable = $this->campaign->setting->getFillable(); if (empty($module) || ! in_array($module, $fillable)) { throw new Exception; } $this->campaign->setting->{$module} = ! $this->campaign->setting->{$module}; $this->campaign->setting->saveQuietly(); CampaignCache::campaign($this->campaign)->clear(); Cache::forget('campaign_' . $this->campaign->id . '_sidebar'); return (bool) $this->campaign->setting->{$module}; } } ================================================ FILE: app/Services/Campaign/ModuleService.php ================================================ fallback = true; return $this; } public function singular(string|int $key, ?string $fallback = null): ?string { $id = $this->id($key); if ($this->campaign->hasModuleName($id)) { return $this->campaign->moduleName($id); } return $fallback; } public function plural(string|int $key, ?string $fallback = null): ?string { $id = $this->id($key); if ($this->campaign->hasModuleName($id, true)) { return $this->campaign->moduleName($id, true); } if (empty($fallback) && $this->fallback) { return $this->pluralFallback($id); } if (is_array(__($fallback))) { return $this->pluralFallback($id); } return __($fallback); } public function icon(string|int $key, ?string $fallback = null): ?string { $id = $this->id($key); if ($this->campaign->hasModuleIcon($id)) { return $this->campaign->moduleIcon($id); } return $fallback; } public function duoIcon(EntityType $entityType): string { if ($this->campaign->hasModuleIcon($entityType->id)) { return $this->campaign->moduleIcon($entityType->id); } return $this->defaultIcon($entityType); } public function defaultIcon(EntityType $entityType): string { if (config('fontawesome.kit')) { return 'fa-duotone ' . $entityType->icon; } return 'fa-regular ' . $entityType->icon; } /** * From a string or id, figure out the entity type number */ protected function id(mixed $key): int { // Ints are easy and what we hope for if (is_int($key)) { return $key; } // Already cached in this page execution? Easy! if (isset($this->cache[$key])) { return $this->cache[$key]; } // If it's a plural, and this is a bit bonkers, but we want to figure out the singular if (Str::endsWith($key, 's')) { $key = Str::singular($key); } $id = config('entities.ids.' . $key); if (empty($id)) { throw new Exception('Invalid entity type id key ' . $key); } return $this->cache[$key] = (int) $id; } protected function pluralFallback(int $key) { $flipped = array_flip(config('entities.ids')); return __('entities.' . Str::plural($flipped[$key])); } } ================================================ FILE: app/Services/Campaign/NotificationService.php ================================================ campaign->notifyAdmins( new Header('campaign.' . $key, $icon, $colour, $params) ); } } ================================================ FILE: app/Services/Campaign/Notifications/HideService.php ================================================ campaign->isHidden()) { $colour = 'yellow'; $icon = 'eye-slash'; $key = 'hidden'; } NotifyAdmins::dispatch( $this->campaign, $key, $icon, $colour, [ 'campaign' => $this->campaign->name, 'link' => route('dashboard', $this->campaign), ] ); } } ================================================ FILE: app/Services/Campaign/Notifications/ImageRemoveService.php ================================================ campaign, $key, $icon, $colour, [ 'entity' => $this->entity->name, 'link' => route('entities.show', [$this->entity, $this->campaign]), ] ); } } ================================================ FILE: app/Services/Campaign/PluginService.php ================================================ plugin = $plugin; return $this; } public function enable(): bool { $plugin = $this->campaignPlugin(); if ($plugin->canEnable()) { $plugin->is_active = true; $plugin->save(); return true; } return false; } public function disable(): bool { $plugin = $this->campaignPlugin(); if ($plugin->canDisable()) { $plugin->is_active = false; $plugin->save(); return true; } return false; } public function remove(): bool { // Find the campaign plugin $plugin = $this->campaignPlugin(); if (empty($plugin)) { throw new Exception(__('campaigns/plugins.errors.invalid_plugin')); } $plugin->delete(); return true; } protected function campaignPlugin(): ?CampaignPlugin { return CampaignPlugin::where('campaign_id', $this->campaign->id) ->where('plugin_id', $this->plugin->id) ->first(); } public function update(): bool { // Get latest $latest = $this->plugin->versions() ->publishedVersions($this->plugin->created_by === $this->user->id) ->orderBy('id', 'desc') ->first(); // The user could be submitting a plugin to update that was removed in another window if (empty($latest)) { return false; } /** @var CampaignPlugin $campaignPlugin */ $campaignPlugin = CampaignPlugin::where('campaign_id', $this->campaign->id) ->where('plugin_id', $this->plugin->id) ->first(); if ($campaignPlugin->plugin_version_id === $latest->id) { return false; } $campaignPlugin->plugin_version_id = $latest->id; $campaignPlugin->save(); return true; } } ================================================ FILE: app/Services/Campaign/PurgeService.php ================================================ ids; } public function real(): self { $this->dry = false; return $this; } public function count(): int { return $this->baseAll() ->count(); } public function purgeEmpty(): int { $this->baseAll() ->chunkById(500, function ($campaigns) { /** @var Campaign $campaign */ foreach ($campaigns as $campaign) { if (! $this->dry) { $campaign->forceDelete(); $this->deletionService->campaign($campaign)->cleanup(); Log::info('Services\Campaigns\PurgeService', ['campaign' => $campaign->id]); } $this->count++; } }); return $this->count; } public function purgeDeleted(): int { $delay = Carbon::now()->subHours(config('purge.hard_delete'))->toDateString(); Campaign::onlyTrashed() ->where('deleted_at', '<=', $delay) ->chunkById(500, function ($campaigns) { /** @var Campaign $campaign */ foreach ($campaigns as $campaign) { $this->ids[] = $campaign->id; $campaign->forceDelete(); $this->deletionService->campaign($campaign)->cleanup(); Log::info('Services\Campaigns\PurgeService', ['campaign' => $campaign->id]); $this->count++; } }); return $this->count; } protected function baseAll(): Builder { return Campaign::select('campaigns.id') ->leftJoin('campaign_user as cu', 'cu.campaign_id', 'campaigns.id') ->whereNull('cu.id'); } } ================================================ FILE: app/Services/Campaign/SearchCleanupService.php ================================================ getKeys(); $client->index('entities')->deleteDocuments(['filter' => 'campaign_id = ' . $this->campaign->id]); } } ================================================ FILE: app/Services/Campaign/ShareService.php ================================================ campaign->update(['visibility_id' => CampaignVisibility::public->value]); return $this; } } ================================================ FILE: app/Services/Campaign/Sidebar/SaveService.php ================================================ campaign->ui_settings; // First we want to figure out the new "order", and later we can worry about the "overrides". $order = []; $parent = null; foreach ($data['order'] as $field => $value) { if (Str::endsWith($field, '_start')) { $parent = Str::before($field, '_start'); $order[$parent] = []; continue; } elseif (Str::endsWith($field, '_end')) { $parent = null; continue; } if (! empty($parent)) { $order[$parent][$field] = $field; } else { $order[$field] = null; } } $ui['sidebar'] = [ 'order' => $order, ]; // Now let's build the config. $labels = []; $icons = []; foreach ($data as $field => $value) { if (empty($value)) { continue; } if (Str::endsWith($field, '_label')) { $labels[Str::before($field, '_label')] = Purify::clean(strip_tags($value)); continue; } elseif (Str::endsWith($field, '_icon')) { $icons[Str::before($field, '_icon')] = Purify::clean(strip_tags($value)); continue; } // Nothing of value } // Save the new data to the campaign config if (! empty($labels)) { $ui['sidebar']['labels'] = $labels; } elseif (isset($ui['sidebar']['labels'])) { // @phpstan-ignore-line unset($ui['sidebar']['labels']); } if (! empty($icons)) { $ui['sidebar']['icons'] = $icons; } elseif (isset($ui['sidebar']['icons'])) { unset($ui['sidebar']['icons']); } $this->campaign->ui_settings = $ui; $this->campaign->save(); SidebarSaved::dispatch($this->campaign, $this->user); $this->clearCache(); } public function reset(): void { $ui = $this->campaign->ui_settings; unset($ui['sidebar']); $this->campaign->ui_settings = $ui; $this->campaign->save(); SidebarReset::dispatch($this->campaign, $this->user); $this->clearCache(); } public function clearCache(): void { Cache::forget($this->cacheKey()); } protected function cacheKey(): string { return 'campaign_' . $this->campaign->id . '_sidebar'; } } ================================================ FILE: app/Services/Campaign/Sidebar/SetupService.php ================================================ null, 'bookmarks' => null, 'world' => [ // world 'characters', 'locations', 'maps', 'organisations', 'families', 'creatures', 'races', ], 'time' => [ 'calendars', 'timelines', 'events', 'journals', ], 'game' => [ 'quests', 'items', 'abilities', ], 'notes' => null, 'other' => [ 'tags', 'conversations', 'dice_rolls', 'relations', 'attribute_templates', 'whiteboards', ], 'gallery' => null, 'history' => null, 'settings' => null, // 'search' => null, ]; protected $modules = []; public function __construct() { $this->setupElements(); } protected function setupElements(): void { $this->elements = [ 'dashboard' => [ 'icon' => 'fa-duotone fa-house', 'label' => 'sidebar.dashboard', 'module' => false, 'route' => 'dashboard', 'fixed' => true, ], 'bookmarks' => [ 'type' => 'bookmark', 'fixed' => true, ], 'world' => [ 'icon' => 'fa-duotone fa-mountains', 'label' => 'sidebar.world', 'module' => false, 'fixed' => true, 'route' => false, ], 'characters' => [ 'type' => 'character', 'category_id' => config('entities.ids.character'), 'mode' => true, ], 'locations' => [ 'type' => 'location', 'category_id' => config('entities.ids.location'), 'mode' => true, ], 'maps' => [ 'mode' => true, 'category_id' => config('entities.ids.map'), 'type' => 'map', ], 'organisations' => [ 'mode' => true, 'category_id' => config('entities.ids.organisation'), 'type' => 'organisation', ], 'families' => [ 'mode' => true, 'category_id' => config('entities.ids.family'), 'type' => 'family', ], 'calendars' => [ 'mode' => true, 'category_id' => config('entities.ids.calendar'), 'type' => 'calendar', ], 'timelines' => [ 'mode' => true, 'category_id' => config('entities.ids.timeline'), 'type' => 'timeline', ], 'races' => [ 'mode' => true, 'category_id' => config('entities.ids.race'), 'type' => 'race', ], 'creatures' => [ 'mode' => true, 'category_id' => config('entities.ids.creature'), 'type' => 'creature', ], 'game' => [ 'icon' => 'fa-duotone fa-book', 'label' => 'sidebar.game', 'route' => false, 'fixed' => true, ], 'quests' => [ 'mode' => true, 'category_id' => config('entities.ids.quest'), 'type' => 'quest', ], 'journals' => [ 'mode' => true, 'category_id' => config('entities.ids.journal'), 'type' => 'journal', ], 'items' => [ 'mode' => true, 'category_id' => config('entities.ids.item'), 'type' => 'item', ], 'events' => [ 'mode' => true, 'category_id' => config('entities.ids.event'), 'type' => 'event', ], 'abilities' => [ 'mode' => true, 'category_id' => config('entities.ids.ability'), 'type' => 'ability', ], 'notes' => [ 'mode' => true, 'type' => 'note', ], 'other' => [ 'icon' => 'fa-duotone fa-database', 'label' => 'sidebar.other', 'module' => false, 'route' => false, 'fixed' => true, ], 'time' => [ 'icon' => 'fa-duotone fa-hourglass', 'label' => 'sidebar.time', 'module' => false, 'route' => false, 'fixed' => true, ], 'tags' => [ 'mode' => true, 'type' => 'tag', ], 'conversations' => [ 'type' => 'conversation', ], 'dice_rolls' => [ 'type' => 'dice_roll', ], 'relations' => [ 'icon' => 'fa-duotone fa-circle-nodes', 'label' => 'sidebar.relations', 'perm' => 'relations', 'module' => false, ], 'gallery' => [ 'icon' => 'fa-duotone fa-images', 'label' => 'sidebar.gallery', 'route' => 'gallery', 'perm' => 'gallery', 'module' => false, ], 'attribute_templates' => [ 'type' => 'attribute_template', ], 'whiteboards' => [ 'type' => 'whiteboard', ], 'settings' => [ 'icon' => 'fa-duotone fa-cog', 'label' => 'sidebar.settings', 'module' => false, 'fixed' => true, 'route' => 'overview', ], /*'search' => [ 'icon' => 'fa fa-search', 'label' => 'Search...', 'module' => false, 'route' => 'search', ],*/ 'history' => [ 'icon' => 'fa-duotone fa-clock-rotate-left', 'label' => 'sidebar.recent', 'perm' => 'recover', 'module' => false, 'fixed' => true, ], ]; } protected bool $withDisabled = false; public function withDisabled(): self { $this->withDisabled = true; return $this; } /** * Generate an array of the sidebar elements */ public function layout(): array { $key = $this->cacheKey(); if (! $this->withDisabled && Cache::has($key)) { if (! app()->hasDebugModeEnabled()) { return Cache::get($key); } } $this->loadModules(); $this->fillElements(); $layout = []; $layoutSetup = $this->customLayout(); $rewrite = []; foreach ($layoutSetup as $name => $children) { // We migrated to a new structure, but have the setup in json, so we need to "fix it" here if (in_array($name, $rewrite)) { continue; } if ($name === 'menu_links') { $name = 'bookmarks'; } elseif ($name === 'campaigns') { $name = 'world'; $rewrite[] = 'world'; } elseif ($name === 'campaign') { $name = 'game'; $rewrite[] = 'game'; } if (! isset($this->elements[$name])) { dd('E601 - cant find element ' . $name); } $element = $this->customElement($name); // Add a route if it should have one if (! isset($element['route'])) { if (! isset($element['type'])) { $element['route'] = $name . '.index'; } } // No children? Nothing more to do if (empty($children)) { // If this is a level 0 element like "Notes", the module still needs to be checked if (! isset($element['module']) && ! $this->withDisabled) { if (! $this->campaign->enabled($name)) { continue; } } $layout[$name] = $element; continue; } $layout[$name] = $element; $layout[$name]['children'] = []; foreach ($children as $childName) { $child = $this->customElement($childName); // Child has a module, check that the campaign has it enabled if (! isset($child['module'])) { if (! $this->campaign->enabled($childName)) { if ($this->withDisabled) { $child['disabled'] = true; } else { continue; } } } // Child has permission check? if (isset($child['perm']) && count($children) === 1) { $layout[$name]['perm'] = $child['perm']; } // Add route when none is set if (! isset($child['route']) && ! isset($child['type'])) { $child['route'] = $childName . '.index'; } // Add it $layout[$name]['children'][$childName] = $child; } } if (! $this->withDisabled) { Cache::put($key, $layout, 7 * 86400); } return $layout; } protected function customLayout(): array { // Only boosted campaigns can change the layout if (! $this->campaign->boosted()) { return $this->layout; } $layout = Arr::get($this->campaign->ui_settings, 'sidebar.order'); if (empty($layout)) { return $this->layout; } // We have a layout, let's see if anything is missing. There is probably a smarter way to do this. $definedElements = []; foreach ($layout as $name => $values) { $definedElements[] = $name; if (! is_array($values)) { continue; } foreach ($values as $key) { $definedElements[] = $key; } } // Find missing elements that are in the base layout but not in the custom one $missing = array_diff(array_keys($this->elements), $definedElements); // Loop through the missing elements and inject them where they are "expected" foreach ($missing as $element) { $found = false; // dump('Missing: ' . $element); // If it's a base level, add it there if (array_key_exists($element, $this->layout)) { $layout[$element] = null; continue; } foreach ($this->layout as $name => $children) { if (! is_array($children)) { continue; } $values = array_values($children); // dump(!in_array($element, $values)); if (! in_array($element, $values)) { continue; } $layout[$name][$element] = $element; // dump('Added it to ' . $name); $found = true; continue; } if (! $found) { dd('E637: Couldn\'t place sidebar element ' . $element); } } return $layout; } /** * Load custom element setup for boosted campaigns */ protected function customElement(string $key): array { $element = $this->elements[$key]; $element['label_key'] = $element['label'] ?? null; unset($element['label']); if (! $this->campaign->boosted()) { return $element; } // Module custom name if (! empty($element['type']) && ! $this->withDisabled) { /** @var ?EntityType $type */ $type = $this->modules[$element['type']] ?? null; if ($type) { if ($type->isCustom() || $this->campaign->hasModuleName($type->id, true)) { $element['custom_label'] = $type->plural(); } $element['custom_icon'] = $type->icon(); } } $label = Arr::get($this->campaign->ui_settings, 'sidebar.labels.' . $key); $icon = Arr::get($this->campaign->ui_settings, 'sidebar.icons.' . $key); if (! empty($label)) { $element['custom_label'] = $label; } if (! empty($icon)) { $element['custom_icon'] = $icon; } return $element; } protected function cacheKey(): string { return 'campaign_' . $this->campaign->id . '_sidebar'; } /** * Available parents for placing a quick link */ public function availableParents(): array { $labels = []; $this->loadModules(); foreach ($this->elements as $key => $element) { $labels[$key] = $this->elementName($element); } return $labels; } protected function loadModules(): void { $modules = $this->campaign->getEntityTypes(); /** @var EntityType $module */ foreach ($modules as $module) { $this->modules[$module->code] = $module; } } protected function fillElements(): void { foreach ($this->elements as $key => $element) { if (! isset($element['type'])) { continue; } /** @var ?EntityType $module */ $module = $this->modules[$element['type']] ?? null; if (! $module) { continue; } $element['icon'] = Module::defaultIcon($module); $element['label'] = $module->defaultPluralKey(); $this->elements[$key] = $element; } } protected function elementName(array $element): string { if (empty($element['type'])) { return __($element['label']); } /** @var ?EntityType $module */ $module = $this->modules[$element['type']]; if (! $module) { return __($element['label']); } return __($module->defaultPluralKey()); } } ================================================ FILE: app/Services/Campaign/StatService.php ================================================ campaign->id; if (Cache::has($key) && ! config('app.debug')) { return Cache::get($key); } $this->stats = []; // @phpstan-ignore-next-line $this->stats['entities'] = $this->campaign->entities()->withInvisible()->count(); $this->permissions() ->types() ->modules() ->words(); Cache::put($key, $this->stats, 3600 * 24); return $this->stats; } protected function types(): self { $stats = []; // @phpstan-ignore-next-line $res = $this->campaign ->entities() ->withInvisible() ->select(['type_id', DB::raw('count(*) as total')]) ->groupBy('type_id') ->get(); foreach ($res as $data) { $stats[$data->type_id] = $data->total; } arsort($stats); $this->stats['types'] = $stats; return $this; } protected function permissions(): self { $this->stats['permissions'] = []; $this->stats['permissions']['users'] = $this->campaign->users()->count(); $this->stats['permissions']['followers'] = $this->campaign->followers()->count(); $this->stats['permissions']['roles'] = $this->campaign->roles()->count(); arsort($this->stats['permissions']); return $this; } protected function modules(): self { $this->stats['modules'] = []; // @phpstan-ignore-next-line $this->stats['modules']['properties'] = Attribute::withPrivate()->leftJoin('entities', 'entities.id', 'attributes.entity_id')->where('entities.campaign_id', $this->campaign->id)->count(); // @phpstan-ignore-next-line $this->stats['modules']['articles'] = Post::withInvisible()->leftJoin('entities', 'entities.id', 'posts.entity_id')->where('entities.campaign_id', $this->campaign->id)->count(); // @phpstan-ignore-next-line $this->stats['modules']['abilities'] = EntityAbility::withPrivate()->leftJoin('entities', 'entities.id', 'entity_abilities.entity_id')->where('entities.campaign_id', $this->campaign->id)->count(); // @phpstan-ignore-next-line $this->stats['modules']['reminders'] = Reminder::withPrivate()->whereHasMorph( 'remindable', [Entity::class, Post::class], function (Builder $query, string $type) { if ($type === Entity::class) { $query->where('campaign_id', $this->campaign->id); } else { $query->leftJoin('entities as e', 'e.id', '=', 'posts.entity_id') ->where('e.campaign_id', $this->campaign->id); } })->count(); // @phpstan-ignore-next-line $this->stats['modules']['inventories'] = Inventory::withPrivate()->leftJoin('entities', 'entities.id', 'inventories.entity_id')->where('entities.campaign_id', $this->campaign->id)->count(); // @phpstan-ignore-next-line $this->stats['modules']['bookmarks'] = Bookmark::withPrivate()->where('campaign_id', $this->campaign->id)->count(); arsort($this->stats['modules']); return $this; } protected function words(): self { $this->stats['words'] = [ 'total' => 0, 'entities' => 0, 'posts' => 0, ]; // Count the `words` field in the entities // @phpstan-ignore-next-line $entityWords = $this->campaign ->entities() ->withInvisible() ->sum('words'); $this->stats['words']['entities'] = $entityWords; $this->stats['words']['total'] += $entityWords; // Count words from all posts of entities in the campaign // @phpstan-ignore-next-line $postWords = Post::withInvisible() ->leftJoin('entities', 'entities.id', 'posts.entity_id') ->where('entities.campaign_id', $this->campaign->id) ->sum('posts.words'); $this->stats['words']['total'] += $postWords; $this->stats['words']['posts'] = $postWords; // Count words from all quest and timeline elements in the campaign // @phpstan-ignore-next-line $questWords = QuestElement::withPrivate() ->leftJoin('quests', 'quests.id', 'quest_elements.quest_id') ->where('quests.campaign_id', $this->campaign->id) ->sum('quest_elements.words'); $this->stats['words']['total'] += $questWords; $this->stats['words']['elements'] = $questWords; // @phpstan-ignore-next-line $timelineWords = TimelineElement::withPrivate() ->leftJoin('timelines', 'timelines.id', 'timeline_elements.timeline_id') ->where('timelines.campaign_id', $this->campaign->id) ->sum('timeline_elements.words'); $this->stats['words']['total'] += $timelineWords; $this->stats['words']['elements'] += $timelineWords; $timelineEraWords = TimelineEra::leftJoin('timelines', 'timelines.id', 'timeline_eras.timeline_id') ->where('timelines.campaign_id', $this->campaign->id) ->sum('timeline_eras.words'); $this->stats['words']['total'] += $timelineEraWords; $this->stats['words']['elements'] += $timelineEraWords; // Map layers and markers $markerWords = MapMarker::leftJoin('maps', 'maps.id', 'map_markers.map_id') ->where('maps.campaign_id', $this->campaign->id) ->sum('map_markers.words'); $this->stats['words']['total'] += $markerWords; $this->stats['words']['elements'] += $markerWords; $layerWords = MapLayer::leftJoin('maps', 'maps.id', 'map_layers.map_id') ->where('maps.campaign_id', $this->campaign->id) ->sum('map_layers.words'); $this->stats['words']['total'] += $layerWords; $this->stats['words']['elements'] += $layerWords; // Character traits $traitWords = CharacterTrait::leftJoin('characters', 'characters.id', 'character_traits.character_id') ->where('characters.campaign_id', $this->campaign->id) ->sum('character_traits.words'); $this->stats['words']['total'] += $traitWords; $this->stats['words']['elements'] += $traitWords; return $this; } } ================================================ FILE: app/Services/Campaign/SystemService.php ================================================ campaign->systems as $system) { $existing[$system->id] = $system->name; } $new = []; foreach ($ids as $id) { if (! empty($existing[$id])) { unset($existing[$id]); } else { $genre = GameSystem::find($id); if (! empty($genre)) { $new[] = $genre->id; } } } $this->campaign->systems()->attach($new); // Detatch the remaining if (! empty($existing)) { $this->campaign->systems()->detach(array_keys($existing)); } } } ================================================ FILE: app/Services/Campaign/ThemeBuilderService.php ================================================ getStyle(); $style->content = $config; $style->save(); } protected function getStyle(): CampaignStyle { $style = CampaignStyle::theme()->first(); if ($style) { return $style; } $style = new CampaignStyle([ 'name' => 'Campaign theme', 'is_enabled' => true, ]); $style->is_theme = true; $style->campaign_id = $this->campaign->id; return $style; } } ================================================ FILE: app/Services/Campaign/Webhooks/SaveService.php ================================================ webhook = $webhook; return $this; } public function save(): Webhook { if (! isset($this->webhook)) { $this->create(); } $this->tags(); return $this->webhook; } protected function create(): void { $this->webhook = new Webhook($this->request->all()); $this->webhook->campaign_id = $this->campaign->id; $this->webhook->save(); } protected function tags(): void { // HTML forms will have 'save-tags', while the api will have a tag array if they want to make changes. if (! $this->request->has('tags') && ! $this->request->has('save-tags')) { return; } $ids = $this->request->post('tags', []); if (! is_array($ids)) { // People sent weird stuff through the API $ids = []; } $this->tagService ->user($this->user) ->campaign($this->campaign) ->model($this->webhook) ->sync($ids); } } ================================================ FILE: app/Services/ColourService.php ================================================ calculated[$hexColour])) { return $this->calculated[$hexColour]; } // hexColour RGB $R1 = hexdec(mb_substr($hexColour, 1, 2)); $G1 = hexdec(mb_substr($hexColour, 3, 2)); $B1 = hexdec(mb_substr($hexColour, 5, 2)); // Black RGB $blackColour = '#000000'; $R2BlackColour = hexdec(mb_substr($blackColour, 1, 2)); $G2BlackColour = hexdec(mb_substr($blackColour, 3, 2)); $B2BlackColour = hexdec(mb_substr($blackColour, 5, 2)); // Calc contrast ratio $L1 = 0.2126 * pow($R1 / 255, 2.2) + 0.7152 * pow($G1 / 255, 2.2) + 0.0722 * pow($B1 / 255, 2.2); $L2 = 0.2126 * pow($R2BlackColour / 255, 2.2) + 0.7152 * pow($G2BlackColour / 255, 2.2) + 0.0722 * pow($B2BlackColour / 255, 2.2); $contrastRatio = 0; if ($L1 > $L2) { $contrastRatio = (int) (($L1 + 0.05) / ($L2 + 0.05)); } else { $contrastRatio = (int) (($L2 + 0.05) / ($L1 + 0.05)); } // If contrast is more than 5, return black colour if ($contrastRatio > 5) { return $this->calculated[$hexColour] = '#000000'; } else { return $this->calculated[$hexColour] = '#FFFFFF'; } } } ================================================ FILE: app/Services/CommunityVoteService.php ================================================ remove($communityVote, $user); } else { $this->add($communityVote, $user, $option); } // Return the new % values $communityVote->refresh(); return $communityVote->voteStats(); } protected function remove(CommunityVote $communityVote, User $user): void { CommunityVoteBallot::where([ 'community_vote_id' => $communityVote->id, 'user_id' => $user->id, ])->delete(); } protected function add(CommunityVote $communityVote, User $user, string $option): void { // Validate the option $options = $communityVote->options(); if (! isset($options[$option])) { return; } // If we've already voted for this option, don't bother if ($communityVote->votedFor($option)) { return; } // Delete any previous option $this->remove($communityVote, $user); CommunityVoteBallot::create([ 'community_vote_id' => $communityVote->id, 'user_id' => $user->id, 'vote' => $option, ]); } } ================================================ FILE: app/Services/CountryService.php ================================================ getCountry(); $currency = 'usd'; $euroCountries = ['AT', 'BE', 'HR', 'CY', 'EE', 'FI', 'FR', 'DE', 'GR', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PT', 'SK', 'SI', 'ES', 'EZ']; if (in_array($country, $euroCountries)) { $currency = 'eur'; } return $currency; } } ================================================ FILE: app/Services/CsvImportService.php ================================================ job = $job; $this ->campaign($job->campaign) ->user($job->user); return $this; } public function entityType(EntityType $entityType) { $this->entityType = $entityType; return $this; } public function tags(array $tags) { $this->tags = $tags; return $this; } public function fieldMap(array $fieldMap) { $this->fieldMap = $fieldMap; return $this; } public function traits(array $appearances, array $personalities) { $this->appearances = $appearances; $this->personalities = $personalities; return $this; } public function run(): void { $this ->init() ->download() ->processCsv(); } protected function init(): self { $this->logs[] = 'Starting Import'; $this->job->status_id = CampaignImportStatus::RUNNING; $this->job->save(); return $this; } /** * Download the files from s3 onto the local machine and unzip it */ protected function download(): self { $files = $this->job->config['files']; $path = '/campaigns/' . $this->campaign->id . '/imports/'; foreach ($files as $file) { // Log::info('Want to download ' . $file); $s3 = Storage::disk('export')->get($file); $local = $path . uniqid() . '.csv'; // Log::info('Will download from the export disk to local ' . $local); Storage::disk('local')->put($local, $s3); $this->filePath = storage_path('app/' . $local); $this->logs[] = 'Downloaded csv to ' . $local; } return $this; } protected function getHeader(): array { $csv = new SplFileObject($this->filePath); $csv->setFlags( SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE ); foreach ($csv as $row) { // Skip empty lines / EOF if ($row === [null]) { continue; } return $row; } return []; } public function processCsv(): self { // Open the CSV file $this->logs[] = 'Processing CSV'; $csv = new SplFileObject($this->filePath); $csv->setFlags( SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE ); DB::beginTransaction(); try { // We're in the queue after all CampaignLocalization::forceCampaign($this->campaign); CampaignCache::campaign($this->campaign)->clear(); EntityCache::campaign($this->campaign); CharacterCache::campaign($this->campaign); TimelineElementCache::campaign($this->campaign); QuestCache::campaign($this->campaign); MapMarkerCache::campaign($this->campaign); EntityAssetCache::campaign($this->campaign); BookmarkCache::campaign($this->campaign); Limit::campaign($this->campaign); Limit::user($this->user); // Batch size controls how many rows are loaded into memory at once. $batchSize = 50; $batch = []; $count = 0; foreach ($csv as $rowIndex => $row) { // Skip header if needed if ($rowIndex === 0) { $this->headers = $row; continue; } // Skip empty rows if ($row === [null]) { continue; } $count++; $batch[] = $row; if (count($batch) === $batchSize) { $this->logs[] = 'Processing batch'; $this->processBatch($batch); $batch = []; // free memory immediately } } // Process remaining rows if (! empty($batch)) { $this->processBatch($batch); } DB::commit(); } catch (Exception $e) { DB::rollBack(); $this->fail($e); throw $e; } $this->job->status_id = CampaignImportStatus::FINISHED; $this->job->save(); UserLog::create([ 'user_id' => $this->user->id, 'type_id' => UserAction::csvImport, 'campaign_id' => $this->campaign->id, 'data' => [ 'module' => 'import', 'action' => 'CSV finished', 'count' => $this->entityCount, 'entity_type' => $this->entityType->code, ], ]); $this->user->notify(new Header('campaign.import.csv_success', 'upload', 'info', [ 'campaign' => $this->campaign->name, 'link' => route('dashboard', ['campaign' => $this->campaign]), 'count' => $count, ])); $this->logs[] = 'Finished processing CSV'; $this->cleanup(); return $this; } protected function processBatch(array $rows): void { foreach ($rows as $row) { if ($row === false) { continue; } $temp = []; foreach ($this->fieldMap as $field => $index) { if (Str::startsWith($field, 'is_')) { // Correctly handles "true", "false", "1", "0", "on", "off" $temp[$field] = filter_var($row[$index], FILTER_VALIDATE_BOOL); } else { $temp[$field] = $row[$index]; } } $mappedPersonalities = []; foreach ($this->personalities as $key) { $this->logs[] = 'Has personalities'; if (isset($row[$key])) { $mappedPersonalities[$this->headers[$key]] = $row[$key]; } } $mappedAppearances = []; foreach ($this->appearances as $key) { $this->logs[] = 'Has appearances'; if (isset($row[$key])) { $mappedAppearances[$this->headers[$key]] = $row[$key]; } } $temp['traits'] = ['personalities' => $mappedPersonalities, 'appearances' => $mappedAppearances]; $this->data = $temp; $this->create(); } // Log::info('Example CSV Data', ['data' => $data]); } public function create(): Entity { // Remove target as we need that for something else if (! empty($this->data['entry'])) { $this->data['entry'] = '

    ' . nl2br($this->data['entry'] . '

    '); } elseif ($this->entityType->id == config('entities.ids.note')) { $this->data['entry'] = ''; } if (empty($this->data['is_private'])) { $this->data['is_private'] = $this->campaign->entity_visibility; } $traits = $this->data['traits']; unset($this->data['traits']); if ($this->entityType->isCustom()) { $entity = $this->createEntity(); $this->entityCount++; return $entity; } // Prepare the validator $requestValidator = '\App\Http\Requests\Store' . ucfirst(Str::camel($this->entityType->code)); /** @var StoreCharacter $validator */ $validator = new $requestValidator; $this->validateEntity($this->data, $validator->rules()); $new = $this->entityType->getMiscClass(); $new->fill($this->data); $new->campaign_id = $this->campaign->id; $new->save(); $new->createEntity(); $entity = $new->entity; $entity->entry = $this->data['entry'] ?? ''; $entity->type = $this->data['type'] ?? ''; $entity->is_private = $this->data['is_private']; $entity->created_by = $this->user->id; $entity->saveQuietly(); $this->saveTags($entity); if ($this->entityType->isCharacter() && $new instanceof Character) { $this->saveTraits($new, $traits); } $this->entityCount++; return $new->entity; } protected function createEntity(): Entity { $requestValidator = StoreCustomEntity::class; $validator = new $requestValidator; $this->validateEntity($this->data, $validator->rules()); $entity = new Entity($this->data); $entity->type_id = $this->entityType->id; $entity->campaign_id = $this->campaign->id; $entity->entry = $this->data['entry'] ?? ''; $entity->type = $this->data['type'] ?? ''; $entity->is_private = $this->data['is_private']; $entity->created_by = $this->user->id; $entity->save(); $this->entitySaveService->save($entity, $this->data); return $entity; } /** * Validate an entity's request to make sure data doesn't contain erroneous info */ protected function validateEntity(array $data, array $rules) { return Validator::make( $data, $rules, )->validate(); } /** * Save the tags */ protected function saveTags(Entity $entity): void { if (empty($this->tags)) { return; } /** @var TagService $tagService */ $tagService = app()->make(TagService::class); $tagService->user($this->user) ->entity($entity) ->sync($this->tags); } /** * Save the character traits */ protected function saveTraits(Character $character, array $traits): void { foreach ($traits as $type => $entries) { $traitOrder = 0; foreach ($entries as $name => $entry) { if (empty($name)) { continue; } $model = new CharacterTrait; $model->character_id = $character->id; $model->section_id = $type == 'personalities' ? CharacterTrait::SECTION_PERSONALITY : CharacterTrait::SECTION_APPEARANCE; $model->name = $name; $model->entry = $entry; $model->default_order = $traitOrder; $model->save(); $traitOrder++; } } } protected function cleanup(): self { $files = $this->job->config['files']; $this->logs[] = 'Created ' . $this->entityCount . ' new entities'; $this->job->logs = $this->logs; $this->job->save(); foreach ($files as $file) { Storage::disk('export')->delete($file); } File::delete($this->filePath); return $this; } public function fail(Throwable $e): self { // Notify the user that something went wrong $this->user->notify(new Header( 'campaign.import.failed', 'circle-exclamation', 'red', [ 'campaign' => $this->campaign->name, 'link' => route('dashboard', ['campaign' => $this->campaign]), ] )); $config = $this->job->config; if (! isset($config['logs'])) { $config['logs'] = []; } $this->job->errors = [$e->getMessage()]; $this->job->config = $config; $this->job->status_id = CampaignImportStatus::FAILED; $this->job->save(); if (app()->bound('sentry')) { app('sentry')->captureException($e); } Log::error('CSV Import', ['where' => 'fail', 'error' => $e->getMessage()]); $this->logs[] = 'Processing Failed'; return $this->cleanup(); } } ================================================ FILE: app/Services/CsvValidatorService.php ================================================ job = $job; $this ->campaign($job->campaign) ->user($job->user); return $this; } public function run(): void { $this ->init() ->download() ->validate(); } public function toSelect(): array { return $this ->download() ->getHeader(); } public function preview(): array { return $this ->download() ->getHeaderAndFirstRows(); } protected function init(): self { $this->job->status_id = CampaignImportStatus::VALIDATING; $this->job->save(); return $this; } /** * Download the files from s3 onto the local machine and unzip it */ protected function download(): self { $files = $this->job->config['files']; $path = '/campaigns/' . $this->campaign->id . '/imports/'; foreach ($files as $file) { // Log::info('Want to download ' . $file); $s3 = Storage::disk('export')->get($file); $local = $path . uniqid() . '.csv'; // Log::info('Will download from the export disk to local ' . $local); Storage::disk('local')->put($local, $s3); $this->filePath = storage_path('app/' . $local); } return $this; } protected function cleanup(): self { File::delete($this->filePath); return $this; } protected function getHeader(): array { $csv = new SplFileObject($this->filePath); $csv->setFlags( SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE ); foreach ($csv as $row) { // Skip empty lines / EOF if ($row === [null]) { continue; } $this->cleanup(); return $row; } $this->cleanup(); return []; } /** * Return the header row and the first two data rows from a CSV */ protected function getHeaderAndFirstRows(int $rows = 2): array { $csv = new SplFileObject($this->filePath); $csv->setFlags( SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE ); $result = []; foreach ($csv as $row) { // Skip empty lines / EOF if ($row === [null]) { continue; } $result[] = $row; // Stop after header + N rows if (count($result) === $rows + 1) { break; } } // Reset pointer for later use $csv->rewind(); $this->cleanup(); return $result; } /** * Return headers for columns that have no empty values in any row */ protected function getFullyFilledColumnHeaders(SplFileObject $csv): array { $headers = null; $hasEmpty = []; foreach ($csv as $row) { // Skip empty lines / EOF if ($row === [null] || $row === false) { continue; } // First non-empty row = headers if ($headers === null) { $headers = $row; $hasEmpty = array_fill(0, count($headers), false); continue; } foreach ($row as $index => $value) { if (trim((string) $value) === '') { $hasEmpty[$index] = true; } } } // Reset pointer if needed later $csv->rewind(); if ($headers === null) { return []; } $result = []; foreach ($headers as $index => $header) { if ($hasEmpty[$index] === false) { $result[] = $header; } } return $result; } public function validate(): void { $csv = new SplFileObject($this->filePath); $csv->setFlags( SplFileObject::READ_CSV | SplFileObject::SKIP_EMPTY | SplFileObject::DROP_NEW_LINE ); $validHeaders = $this->getFullyFilledColumnHeaders($csv); if (empty($validHeaders)) { $this->cleanup(); throw new RuntimeException( __('campaigns/import.csv.validation_error') ); } $config = $this->job->config; $config['filled_columns'] = $validHeaders; $this->job->status_id = CampaignImportStatus::READY; $this->job->config = $config; $this->job->save(); $this->user->notify(new Header('campaign.import.csv_ready', 'upload', 'info', [ 'campaign' => $this->campaign->name, 'link' => route('campaign.import.csv', ['campaign' => $this->campaign, 'campaign_import' => $this->job]), ])); $this->cleanup(); } } ================================================ FILE: app/Services/DashboardService.php ================================================ dashboard = $dashboard; return $this; } /** * Get the current or default dashboard for the user */ public function getDashboard(?int $dashboard = null): ?CampaignDashboard { // Only available for boosted campaigns if (! $this->campaign->boosted()) { return null; } // If the campaign has no dashboards, just stop $available = $this->availableDashboards(); if (empty($available)) { return null; } // No dashboard given, let's see what the user can access if (empty($dashboard)) { return $this->defaultDashboard($available); } // Dashboard given, make sure the user has access return $this->validateDashboard($available, $dashboard); } /** * Get the available dashboards for the user * * @return array[]|CampaignDashboard[] */ public function getDashboards(): array { // Only available for boosted campaigns if (! $this->campaign->boosted()) { return []; } $available = $this->availableDashboards(); $dashboards = []; if (! isset($this->user) || ! $this->user->can('member', $this->campaign)) { foreach ($available['public'] as $role) { $dashboards[] = $role->dashboard; } return $dashboards; } // Admin? if ($this->user->isAdmin()) { foreach ($available['admin'] as $role) { $dashboards[] = $role->dashboard; } return $dashboards; } // Member of the campaign, check dashboards for roles of them $roles = UserCache::roles(); $dashboards = []; foreach ($roles as $role) { $key = 'role_' . $role['id']; if (! isset($available[$key])) { continue; } foreach ($available[$key] as $r) { $dashboards[] = $r->dashboard; } } return $dashboards; } public function add(Entity $entity): self { $this->displayedEntities[] = $entity->id; return $this; } public function excluding(): array { return $this->displayedEntities; } /** * Create a dashboard and it's permissions */ public function create(): CampaignDashboard { $this ->createDashboard() ->roles() ->copy(); return $this->dashboard; } protected function createDashboard(): self { $this->dashboard = CampaignDashboard::create([ 'campaign_id' => $this->campaign->id, 'created_by' => $this->user->id, 'name' => $this->request->post('name'), ]); return $this; } protected function roles(): self { // Loop through the permissions $roles = (array) $this->request->post('roles'); foreach ($roles as $roleId => $setting) { if (empty($setting)) { continue; } // Validate the role /** @var ?CampaignRole $role */ $role = $this->campaign->roles()->where('id', $roleId)->first(); if (empty($role)) { continue; } $dashboardRole = CampaignDashboardRole::create([ 'campaign_dashboard_id' => $this->dashboard->id, 'campaign_role_id' => $role->id, 'is_visible' => true, 'is_default' => $setting == 'default', ]); } return $this; } protected function copy(): self { if (! $this->request->filled('copy_widgets')) { return $this; } $sourceId = $this->request->post('source'); /** @var ?CampaignDashboard $source */ $source = CampaignDashboard::find($sourceId); if (empty($source)) { return $this; } /** @var CampaignDashboardWidget $widget */ foreach ($source->widgets()->with('dashboardWidgetTags')->get() as $widget) { $widget->copyTo($this->dashboard); } return $this; } /** * @throws \Exception */ public function update(): CampaignDashboard { $this->dashboard->update([ 'name' => $this->request->post('name'), ]); // Existing roles $roles = []; foreach ($this->dashboard->roles as $role) { $roles[$role->id] = $role; } // Loop through the permissions $rolesForm = (array) $this->request->post('roles'); foreach ($rolesForm as $roleId => $setting) { if (empty($setting)) { continue; } // Validate the role /** @var ?CampaignRole $role */ $role = $this->campaign->roles()->where('id', $roleId)->first(); if (empty($role)) { continue; } if (isset($roles[$roleId])) { $role = $roles[$roleId]; $role->update([ 'is_default' => $setting == 'default', ]); unset($roles[$roleId]); } else { // New $dashboardRole = CampaignDashboardRole::create([ 'campaign_dashboard_id' => $this->dashboard->id, 'campaign_role_id' => $role->id, 'is_visible' => true, 'is_default' => $setting == 'default', ]); } } // Delete any leftover roles that weren't updates foreach ($roles as $role) { $role->delete(); } return $this->dashboard; } /** * Get the default dashboards for the various roles */ protected function availableDashboards(): array { return CampaignCache::campaign($this->campaign)->dashboards(); } /** * Get the default dashboard of a user */ protected function defaultDashboard(array $available) { // Unlogged or not a member if (! isset($this->user) || ! $this->user->can('member', $this->campaign)) { foreach ($available['public'] as $role) { if ($role->is_default) { return $role->dashboard; } } return null; } // Admin? if ($this->user->isAdmin()) { foreach ($available['admin'] as $role) { if ($role->is_default) { return $role->dashboard; } } return null; } // Member of the campaign, check dashboards for roles of them $roles = UserCache::roles(); foreach ($roles as $role) { $key = 'role_' . $role['id']; if (! isset($available[$key])) { continue; } foreach ($available[$key] as $r) { if ($r->is_default) { return $r->dashboard; } } } return null; } /** * Validate that a requested dashboard is available to the user * * @return null|CampaignDashboard */ protected function validateDashboard(array $available, int $dashboard) { $filtered = false; if (! isset($this->user) || ! $this->user->can('member', $this->campaign)) { $filtered = $available['public']; } elseif ($this->user) { $filtered = $available['admin']; } if ($filtered !== false) { foreach ($filtered as $role) { if ($role->campaign_dashboard_id == $dashboard) { return $role->dashboard; } } return null; } $roles = UserCache::roles(); foreach ($roles as $role) { $key = 'role_' . $role['id']; if (empty($available[$key])) { continue; } foreach ($available[$key] as $r) { if ($r->campaign_dashboard_id == $dashboard) { return $r->dashboard; } } } return null; } } ================================================ FILE: app/Services/Dashboards/GettingStartedService.php ================================================ tasks = []; $this->start() ->rename() ->character() ->location() ->invite() ->widgets(); return $this->tasks; } protected function start(): self { $this->track( 'campaign', true ); return $this; } protected function rename(): self { $completed = true; $originalName = Arr::get($this->campaign->settings, 'default-name'); if (! empty($originalName) && $originalName === $this->campaign->name) { $completed = false; } $this->track( 'rename', $completed, route('campaigns.edit', [$this->campaign, 'from' => 'onboarding']) ); return $this; } protected function character(): self { $completed = $this->campaign ->entities() ->where('type_id', config('entities.ids.character')) ->where('source', 'user') ->count() > 0; $this->track( 'character', $completed, route('characters.create', [$this->campaign, 'from' => 'onboarding']) ); return $this; } protected function location(): self { $completed = $this->campaign ->entities() ->where('type_id', config('entities.ids.location')) ->where('source', 'user') ->count() > 0; $this->track( 'location', $completed, route('locations.create', [$this->campaign, 'from' => 'onboarding']) ); return $this; } protected function invite(): self { $completed = $this->campaign->invites()->count() > 0; $this->track( 'invite', $completed, route('campaign_users.index', [$this->campaign, 'from' => 'onboarding']) ); return $this; } protected function widgets(): self { $completed = $this->campaign ->widgets() ->where('created_at', '>', $this->campaign->created_at->addSeconds(10)) ->count() > 0; $this->track( 'widgets', $completed, route('dashboard.setup', [$this->campaign, 'from' => 'onboarding']) ); return $this; } protected function track(string $key, bool $completed, ?string $url = null) { $this->tasks[] = [ 'name' => __('dashboards/widgets/onboarding.tasks.' . $key . '.name'), 'helper' => __('dashboards/widgets/onboarding.tasks.' . $key . '.helper'), 'completed' => $completed, 'url' => $url ?? null, ]; } } ================================================ FILE: app/Services/DiceRollerService.php ================================================ parameters); $attributes = []; if ($diceRoll->character) { foreach ($diceRoll->character->entity->attributes as $attribute) { $attributes[mb_strtolower('{character.' . $attribute->name . '}')] = $attribute->value; } } preg_match_all("/\{(.*?)\}/", $query, $matches); foreach ($matches[0] as $match) { $match = mb_strtolower($match); if (isset($attributes[$match])) { $query = str_replace($match, $attributes[$match], $query); } } $query = str_replace('+-', '-', $query); $calc = new Calc($query); return $calc(); } } ================================================ FILE: app/Services/Discord/NotificationService.php ================================================ title = $title; return $this; } public function content(string $content): self { $this->content = $content; return $this; } public function description(string $description): self { $this->description = strip_tags($description); return $this; } public function url(string $url): self { $this->url = $url; return $this; } public function webhook(string $webhook): self { $this->webhook = $webhook; return $this; } public function json(): ?array { return $this->json; } public function send(): self { $embeds = $this->embeds(); $response = Http::post($this->webhook . '?wait=true', [ 'content' => $this->content, 'embeds' => [ $embeds, ], 'wait' => true, ]); $this->json = $response->json(); return $this; } protected function embeds(): array { $embeds = [ 'title' => $this->title, 'color' => config('discord.color'), ]; if (isset($this->url)) { $embeds['url'] = $this->url; } if (isset($this->description)) { $embeds['description'] = $this->description; } if (isset($this->user)) { $embeds['author'] = [ 'name' => $this->user->name, 'url' => route('users.profile', $this->user->id), 'icon_url' => $this->user->hasAvatar() ? $this->user->getAvatarUrl() : null, ]; } return $embeds; } } ================================================ FILE: app/Services/DiscordService.php ================================================ &scope=bot&permissions=268443657 */ class DiscordService { protected User $user; /** @var UserApp|null */ protected $app; protected string $url = 'https://discord.com/api/v10/'; protected $me; protected array $logs = []; protected array $ids = []; public function ids(): array { return $this->ids; } public function user(User $user): self { $this->user = $user; $this->app = $user->apps()->app('discord')->first(); return $this; } public function app(UserApp $app): self { $this->app = $app; $this->user = $app->user; return $this; } public function validate(string $code): self { $body = [ 'client_id' => config('discord.client_id'), 'client_secret' => config('discord.client_secret'), 'grant_type' => 'authorization_code', 'code' => $code, 'redirect_uri' => url('/settings/discord-callback'), 'scope' => 'identify guilds guilds.join', ]; $headers = [ 'Content-Type' => 'application/x-www-form-urlencoded', ]; $content = $this->post('oauth2/token', $body, $headers); $this->saveUserApp($content); return $this; } /** * Add the user to the server. * We don't need to worry about re-adding a user twice, Discord's api will provide * a success message back with the info. */ public function addServer(): self { $me = $this->me(); $headers = [ 'Authorization' => 'Bot ' . config('discord.bot_token'), 'Content-Type' => 'application/json', ]; $body = [ 'access_token' => $this->app->access_token, ]; $this->call('put', 'guilds/' . config('discord.channel_id') . '/members/' . $me->id, $body, $headers); return $this; } public function me() { // Cache the response during a single process if (isset($this->me)) { return $this->me; } $this->refresh(); $client = new Client; $url = $this->url . 'users/@me'; $headers = [ 'Authorization' => 'Bearer ' . $this->app->access_token, ]; $response = $client->get($url, ['headers' => $headers]); $this->me = json_decode($response->getBody()); return $this->me; } /** * Save the user app * * @param object $data */ protected function saveUserApp($data): self { if (! $this->app) { $this->app = new UserApp([ 'user_id' => $this->user->id, 'app' => 'discord', ]); } $this->app->access_token = $data->access_token; $this->app->refresh_token = $data->refresh_token; $this->app->expires_at = Carbon::now()->addSeconds($data->expires_in); $this->app->save(); // Get me for data $me = $this->me(); $this->app->settings = ['username' => $me->username]; $this->app->save(); return $this; } /** * Refresh the user's access token */ public function refresh(): self { // Don't refresh a valid token if (! $this->app->expires_at->isPast()) { return $this; } $body = [ 'client_id' => config('discord.client_id'), 'client_secret' => config('discord.client_secret'), 'grant_type' => 'refresh_token', 'refresh_token' => $this->app->refresh_token, 'redirect_uri' => url('/settings/discord-callback'), 'scope' => 'identify guilds guilds.join', ]; $content = $this->post('oauth2/token', $body); $this->saveUserApp($content); $log = 'Renewed user #' . $this->user->id . ' Discord auth token.'; $this->logs[] = $log; // Clear the cached Discord user unset($this->me); return $this; } /** * Add the user to the discord roles */ public function addRoles(): self { // Don't add roles if the user isn't connected if (empty($this->app)) { $this->logs[] = 'User isn\'t synced with Discord'; return $this; } // Only add roles if the user is a subscriber if (! $this->user->subscribed('kanka')) { $this->logs[] = 'User isn\'t subbed to Kanka'; return $this; } $me = $this->me(); $headers = [ 'Authorization' => 'Bot ' . config('discord.bot_token'), 'Content-Type' => 'application/json', ]; $body = [ 'access_token' => $this->app->access_token, ]; $roles = [ config('discord.roles.' . ($this->user->isElemental() ? 'elemental' : ($this->user->isWyvern() ? 'wyvern' : 'owlbear'))), ]; foreach ($roles as $id) { $url = 'guilds/' . config('discord.channel_id') . '/members/' . $me->id . '/roles/' . $id; $this->logs[] = $this->call('put', $url, $body, $headers); } return $this; } /** * Remove all bonus discord roles for the user */ public function removeRoles(): self { // Don't remove if the user is already disconnected if (empty($this->app)) { return $this; } $me = $this->me(); $headers = [ 'Authorization' => 'Bot ' . config('discord.bot_token'), 'Content-Type' => 'application/json', ]; $body = [ 'access_token' => $this->app->access_token, ]; try { foreach (config('discord.roles') as $id) { $url = 'guilds/' . config('discord.channel_id') . '/members/' . $me->id . '/roles/' . $id; $this->call('delete', $url, $body, $headers); } } catch (Exception $e) { Log::warning('Couldn\'t delete role for user'); } return $this; } /** * Remove a user's discord integration * * @throws Exception */ public function remove(): self { // Don't remove if the user is already disconnected if (empty($this->app)) { return $this; } // Remove any roles the user might have had try { $this->removeRoles(); } catch (Exception $e) { } // Delete the discord app $this->app->delete(); return $this; } /** * Make a post request on the discord api */ protected function post(string $api, array $body = [], ?array $headers = null) { $client = new Client; if ($headers === null) { $headers = [ 'Content-Type' => 'application/x-www-form-urlencoded', ]; } $url = $this->url . mb_ltrim($api, '/'); $response = $client->post($url, ['form_params' => $body, 'headers' => $headers]); return json_decode($response->getBody()); } /** * @param string $action post, get, put, delete */ protected function call(string $action, string $api, array $body = [], ?array $headers = null) { $client = new Client; if ($headers === null) { $headers = [ 'Content-Type' => 'application/x-www-form-urlencoded', ]; } $url = $this->url . mb_ltrim($api, '/'); if ($action === 'put') { $response = $client->{$action}($url, ['json' => $body, 'headers' => $headers]); } else { $response = $client->{$action}($url, ['form_params' => $body, 'headers' => $headers]); } return json_decode($response->getBody()); } public function logs(): array { return $this->logs; } } ================================================ FILE: app/Services/DomainService.php ================================================ request->host() === $this->app(); } /** * Check if the request is for the api */ public function isApi(): bool { return $this->request->is('api/*') || $this->request->host() === $this->api(); } /** * Check if the request is for the frontend */ public function isFront(): bool { return $this->request->host() === $this->front(); } public function app(): string { return config('domains.app'); } public function front(): string { return config('domains.front'); } public function api(): string { return config('domains.api'); } public function importer(): string { return config('domains.importer'); } public function toFront(string $page): string { return '//' . $this->front() . '/' . $page; } } ================================================ FILE: app/Services/Entity/AliasService.php ================================================ request->input('aliases', '[]'), true) ?? []; $this->saveAliases($aliases); } /** * @param array $aliases */ private function saveAliases(array $aliases): void { $submittedIds = collect($aliases) ->filter(fn (array $a): bool => (int) $a['id'] > 0) ->pluck('id') ->map(fn ($id): int => (int) $id) ->all(); // Delete visible aliases that the user removed from the list. // VisibilityIDScope ensures admin-only aliases are excluded here, // so they are never deleted by non-admin users. $this->entity->aliases() ->whereNotIn('id', $submittedIds) ->delete(); foreach ($aliases as $data) { $id = (int) $data['id']; $name = $data['name']; $visibility = $this->visibilityFromString($data['visibility'] ?? 'all'); if ($id <= 0) { $this->entity->assets()->create([ 'type_id' => EntityAssetType::alias, 'name' => $name, 'visibility_id' => $visibility, ]); } else { // VisibilityIDScope prevents non-admin users from updating // aliases they cannot see. $this->entity->aliases() ->where('id', $id) ->update([ 'name' => $name, 'visibility_id' => $visibility, ]); } } } private function visibilityFromString(string $visibility): Visibility { return match ($visibility) { 'admin' => Visibility::Admin, 'admin-self' => Visibility::AdminSelf, 'self' => Visibility::Self, 'members' => Visibility::Member, default => Visibility::All, }; } } ================================================ FILE: app/Services/Entity/ArchiveService.php ================================================ entity->archived_at)) { $this->entity->archived_at = null; } else { $this->entity->archived_at = Carbon::now(); } $this->entity->save(); } } ================================================ FILE: app/Services/Entity/ColumnDefinitionService.php ================================================ > */ protected array $statusCache = []; public function columns(EntityType $entityType, Campaign $campaign): array { $method = Str::camel($entityType->code); if (method_exists($this, $method)) { $columns = $this->{$method}(); } else { $columns = $this->defaultColumns(); } // Inject dynamic status column if the entity type has statuses $statusColumn = $this->statusColumn($entityType); if ($statusColumn) { $tagsIndex = array_search('tags', array_column($columns, 'key')); if ($tagsIndex !== false) { array_splice($columns, $tagsIndex, 0, [$statusColumn]); } else { $columns[] = $statusColumn; } } return $this->filterByModules($columns, $campaign); } /** * @return string[] Sort keys for all sortable columns */ public function sortableFields(EntityType $entityType, Campaign $campaign): array { $columns = $this->columns($entityType, $campaign); return array_values(array_filter(array_map( fn (array $col) => $col['sortable'] ? ($col['sortKey'] ?? $col['key']) : null, $columns ))); } public function defaultVisibleColumns(EntityType $entityType, Campaign $campaign): array { $columns = $this->columns($entityType, $campaign); return array_values(array_map( fn (array $col) => $col['key'], array_filter($columns, fn (array $col) => ! ($col['adminOnly'] ?? false)) )); } public function relationMap(EntityType $entityType, Campaign $campaign): array { $map = ['entityType', 'image', 'tags', 'parent', 'status']; $typeRelations = $this->typeRelations(); foreach ($this->columns($entityType, $campaign) as $col) { if (isset($typeRelations[$col['key']])) { $map = array_merge($map, $typeRelations[$col['key']]); } } return array_unique($map); } /** * @return string[] Child model relation names that need withCount (e.g. ['members', 'elements']) */ public function childCountRelations(EntityType $entityType, Campaign $campaign): array { $countColumns = [ 'members_count' => 'members', 'elements_count' => 'elements', 'eras_count' => 'eras', 'entities_count' => 'entities', ]; $needed = []; foreach ($this->columns($entityType, $campaign) as $col) { if (isset($countColumns[$col['key']])) { $needed[] = $countColumns[$col['key']]; } } return $needed; } /** * @return string[] Entity relation names that need withCount (e.g. ['attributes']) */ public function entityCountRelations(EntityType $entityType, Campaign $campaign): array { $countColumns = [ 'attributes_count' => 'attributes', ]; $needed = []; foreach ($this->columns($entityType, $campaign) as $col) { if (isset($countColumns[$col['key']])) { $needed[] = $countColumns[$col['key']]; } } return $needed; } protected function typeRelations(): array { return [ 'families' => ['character.characterFamilies.family.entity'], 'races' => ['character.characterRaces.race.entity'], 'locations' => ['locations.entity'], 'location' => ['location.entity'], 'calendar_date' => ['calendarDate.calendar.entity'], 'author' => ['journal.author'], 'organisation' => ['organisation.entity'], 'character' => ['character.entity'], 'instigator' => ['quest.instigator'], 'creators' => ['item.itemCreators.creator'], 'entity_type_name' => ['attributeTemplate.entityType'], ]; } protected function statusColumn(EntityType $entityType): ?array { if (! isset($this->statusCache[$entityType->id])) { $this->statusCache[$entityType->id] = CategoryStatus::where('category_id', $entityType->id) ->orderBy('sort_order') ->get() ->all(); } $statuses = $this->statusCache[$entityType->id]; if (empty($statuses)) { return null; } $icons = []; foreach ($statuses as $status) { if (empty($status->icon)) { continue; } $icons[$status->id] = [ 'icon' => $status->icon(), 'tooltip' => $status->setRelation('entityType', $entityType)->name(), ]; } if (empty($icons)) { return null; } return [ 'key' => 'status', 'type' => 'icon', 'label' => __('entities.status'), 'sortable' => true, 'sortKey' => 'status_id', 'icons' => $icons, ]; } protected function filterByModules(array $columns, Campaign $campaign): array { return array_values(array_filter($columns, function (array $col) use ($campaign) { if (! empty($col['moduleGate'])) { return $campaign->enabled($col['moduleGate']); } return true; })); } // --- Entity type column definitions --- protected function defaultColumns(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function character(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'title', 'type' => 'text', 'label' => __('characters.fields.title'), 'sortable' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'families', 'type' => 'entities', 'label' => __('entities.families'), 'sortable' => false, 'moduleGate' => 'families'], ['key' => 'locations', 'type' => 'entities', 'label' => __('entities.locations'), 'sortable' => false, 'moduleGate' => 'locations'], ['key' => 'races', 'type' => 'entities', 'label' => __('entities.races'), 'sortable' => false, 'moduleGate' => 'races'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'sex', 'type' => 'text', 'label' => __('characters.fields.sex'), 'sortable' => true], ['key' => 'pronouns', 'type' => 'text', 'label' => __('characters.fields.pronouns'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function location(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'title', 'type' => 'text', 'label' => __('locations.fields.title'), 'sortable' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function organisation(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'locations', 'type' => 'entities', 'label' => __('entities.locations'), 'sortable' => false, 'moduleGate' => 'locations'], ['key' => 'members_count', 'type' => 'count', 'label' => __('organisations.fields.members'), 'sortable' => false, 'moduleGate' => 'characters'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function family(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'location', 'type' => 'entity', 'label' => __('entities.location'), 'sortable' => true, 'sortKey' => 'location.name', 'moduleGate' => 'locations'], ['key' => 'members_count', 'type' => 'count', 'label' => __('organisations.fields.members'), 'sortable' => false], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function journal(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'location', 'type' => 'entity', 'label' => __('entities.location'), 'sortable' => true, 'sortKey' => 'location.name', 'moduleGate' => 'locations'], ['key' => 'date', 'type' => 'text', 'label' => __('journals.fields.date'), 'sortable' => true], ['key' => 'calendar_date', 'type' => 'calendar_date', 'label' => __('crud.fields.calendar_date'), 'sortable' => true], ['key' => 'author', 'type' => 'entity', 'label' => __('journals.fields.author'), 'sortable' => true, 'sortKey' => 'author.name'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function quest(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'instigator', 'type' => 'entity', 'label' => __('quests.fields.instigator'), 'sortable' => false], ['key' => 'locations', 'type' => 'entities', 'label' => __('entities.locations'), 'sortable' => false, 'moduleGate' => 'locations'], ['key' => 'elements_count', 'type' => 'count', 'label' => __('quests.show.tabs.elements'), 'sortable' => false], ['key' => 'date', 'type' => 'text', 'label' => __('journals.fields.date'), 'sortable' => true], ['key' => 'calendar_date', 'type' => 'calendar_date', 'label' => __('crud.fields.calendar_date'), 'sortable' => true], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function calendar(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function map(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'explore', 'type' => 'explore', 'label' => __('maps.actions.explore'), 'sortable' => false], ['key' => 'location', 'type' => 'entity', 'label' => __('entities.location'), 'sortable' => true, 'sortKey' => 'location.name', 'moduleGate' => 'locations'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function whiteboard(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'draw', 'type' => 'draw', 'label' => __('whiteboards.actions.draw'), 'sortable' => false], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function timeline(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'eras_count', 'type' => 'count', 'label' => __('timelines.fields.eras'), 'sortable' => false], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function race(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function creature(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'locations', 'type' => 'entities', 'label' => __('entities.locations'), 'sortable' => false, 'moduleGate' => 'locations'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function item(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'price', 'type' => 'text', 'label' => __('items.fields.price'), 'sortable' => true], ['key' => 'size', 'type' => 'text', 'label' => __('items.fields.size'), 'sortable' => true], ['key' => 'weight', 'type' => 'text', 'label' => __('items.fields.weight'), 'sortable' => true], ['key' => 'location', 'type' => 'entity', 'label' => __('entities.location'), 'sortable' => true, 'sortKey' => 'location.name', 'moduleGate' => 'locations'], ['key' => 'creators', 'type' => 'entities', 'label' => __('items.fields.creators'), 'sortable' => false], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function event(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'date', 'type' => 'text', 'label' => __('events.fields.date'), 'sortable' => true], ['key' => 'locations', 'type' => 'entities', 'label' => __('entities.locations'), 'sortable' => false, 'moduleGate' => 'locations'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function note(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function ability(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'entities_count', 'type' => 'count', 'label' => __('entities.entries'), 'sortable' => false], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function attributeTemplate(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'entity_type_name', 'type' => 'text', 'label' => __('attribute_templates.fields.auto_apply'), 'sortable' => false], ['key' => 'is_enabled', 'type' => 'icon', 'label' => __('attribute_templates.fields.is_enabled'), 'sortable' => true, 'icon' => 'fa-regular fa-wand-magic', 'tooltip' => __('attribute_templates.fields.is_enabled')], ['key' => 'attributes_count', 'type' => 'count', 'label' => __('entities.properties'), 'sortable' => false], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function diceRoll(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'character', 'type' => 'entity', 'label' => __('entities.character'), 'sortable' => true, 'sortKey' => 'character.name', 'moduleGate' => 'characters'], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } protected function tag(): array { return [ ['key' => 'avatar', 'type' => 'avatar', 'sortable' => false, 'alwaysVisible' => true], ['key' => 'name', 'type' => 'name', 'label' => __('crud.fields.name'), 'sortable' => true, 'alwaysVisible' => true], ['key' => 'type', 'type' => 'text', 'label' => __('crud.fields.type'), 'sortable' => true], ['key' => 'parent', 'type' => 'entity', 'label' => __('crud.fields.parent'), 'sortable' => true, 'sortKey' => 'parent.name'], ['key' => 'colour', 'type' => 'text', 'label' => __('crud.fields.colour'), 'sortable' => true], ['key' => 'entities_count', 'type' => 'count', 'label' => __('tags.fields.children'), 'sortable' => false], ['key' => 'is_auto_applied', 'type' => 'icon', 'label' => __('attribute_templates.fields.auto_apply'), 'sortable' => true, 'icon' => 'fa-regular fa-wand-magic', 'tooltip' => __('tags.fields.is_auto_applied')], ['key' => 'is_hidden', 'type' => 'icon', 'label' => __('campaigns.privacy.hidden'), 'sortable' => true, 'icon' => 'fa-regular fa-eye-slash', 'tooltip' => __('tags.fields.is_hidden')], ['key' => 'tags', 'type' => 'tags', 'label' => __('entities.tags'), 'sortable' => true], ['key' => 'is_private', 'type' => 'private', 'label' => __('crud.fields.is_private'), 'sortable' => true, 'adminOnly' => true], ]; } } ================================================ FILE: app/Services/Entity/Connections/MapService.php ================================================ option = $option; return $this; } protected function family(bool $family = true): self { $this->family = $family; return $this; } protected function organisation(bool $organisation = true): self { $this->organisation = $organisation; return $this; } protected function withoutRelations(bool $without = true): self { $this->withRelations = ! $without; return $this; } protected function withEntity(bool $with = true): self { $this->withEntity = $with; return $this; } /** * Check if there is a special init loop for the entity type, or use a generic fallback one instead. */ public function map(): array { $entityHook = 'init' . ucfirst($this->entity->entityType->code); if (method_exists($this, $entityHook)) { $this->$entityHook(); } else { // Other: just relations $this->addEntity($this->entity) ->withEntity(); $this->loadRelations(); if ($this->withRelated()) { if ($this->entity->entityType->isStandard()) { $this->addParent() ->addLocation() ->addQuests() ->addAuthorJournals() ->addLocations(); } else { $this->addCustom(); } $this->addMapMarkers(); } } $this->addMentions(); $this->cleanup(); return ['relations' => $this->relations, 'entities' => $this->entities]; } /** * Remove any relations that don't match up */ protected function cleanup() { $relations = []; foreach ($this->relations as $relation) { if ( isset($this->entities[$relation['source']], $this->entities[$relation['target']]) ) { $relations[] = $relation; } } $this->relations = $relations; } /** * Add an entity to the map, only loading its data if it hasn't been already into memory */ protected function addEntity(Entity $entity, ?string $image = null): self { // dump('add entity ' . $entity->name); if (Arr::has($this->entities, (string) $entity->id)) { return $this; } // Make sure the child is accessible in case there is a permission mis-match if ($entity->isMissingChild()) { return $this; } $img = $image ?? Avatar::entity($entity)->size(192)->fallback()->thumbnail(); if (empty($img)) { // Fallback? $img = ''; } $params = [$this->campaign, $entity->id, 'mode' => 'map']; if ($this->option) { $params['option'] = $this->option; } $this->entities[$entity->id] = [ 'id' => $entity->id, 'name' => $entity->name . "\n(" . $entity->entityType->name() . ')', 'image' => $img, 'link' => route('entities.relations.index', $params), // 'tooltip' => route('entities.tooltip', $entity->id) ]; return $this; } /** * Add a relation model to the map */ protected function addRelations(Entity $entity): self { // dump('add relations for ' . $entity->name); if (Arr::has($this->entityIds, (string) $entity->id)) { return $this; } $this->entityIds[$entity->id] = true; // Get the relations directly from this entity /** @var Relation[] $relations */ $relations = $entity->relationships() ->select('relations.*') ->with(['target', 'target.entityType', 'mirror', 'target.image']) ->has('target') ->leftJoin('entities as t', 't.id', '=', 'relations.target_id') ->get(); foreach ($relations as $relation) { if ($relation->target === null) { continue; } // Don't add mirrored relations if ($relation->isMirrored()) { if (Arr::has($this->mirrors, $relation->mirror_id . '-' . $relation->id)) { continue; } } // Relation already loaded if (in_array($relation->id, $this->relationIds)) { continue; } if ($this->withEntity) { $this->addEntity($relation->target); } $this->relations[] = [ 'source' => $relation->owner_id, 'target' => $relation->target_id, 'text' => $relation->relation, 'colour' => $relation->colour, 'attitude' => $relation->attitude, 'type' => 'entity-relation', 'is_mirrored' => $relation->isMirrored(), 'shape' => $relation->isMirrored() && $relation->mirror && $relation->relation == $relation->mirror->relation ? 'none' : 'triangle', 'edit_url' => route('entities.relations.edit', [ 'campaign' => $this->campaign, 'entity' => $relation->owner_id, 'relation' => $relation, 'from' => $this->entity->id, 'mode' => 'map', 'option' => $this->option, ]), ]; if ($relation->isMirrored() && $relation->mirror && $relation->relation && $relation->relation == $relation->mirror->relation) { $this->mirrors[$relation->id . '-' . $relation->mirror_id] = true; } $this->relationIds[] = $relation->id; } return $this; } /** * Add an individual family. This only works for characters */ protected function addFamily(): self { if (empty($this->entity->child->family)) { return $this; } $family = $this->entity->child->family; $this->addFamilyRelations($family); // Add relation to the family return $this; } /** * Add the family or a character and the family's members */ protected function addFamilyRelations(Family $family) { /** @var Family $family */ $this->family()->addEntity($family->entity)->addRelations($family->entity); $this->addFamilyMembers($family); } /** * Add the organisations of a character */ protected function addOrganisation(): self { /** @var Character $character */ $character = $this->entity->child; $organisations = $character->organisationMemberships() ->has('organisation') ->with(['organisation', 'organisation.entity', 'organisation.entity.image', 'organisation.entity.entityType']) ->get(); foreach ($organisations as $org) { if ($org->organisation !== null && $org->organisation->entity !== null) { $this->addOrganisationRelations($org->organisation); } } return $this; } /** * Add the relations between character organisation members */ protected function addOrganisationRelations(?Organisation $organisation = null) { if (empty($organisation) || $organisation->entity === null) { return; } $this->organisation()->addEntity($organisation->entity); /** @var OrganisationMember $member */ foreach ($organisation->members()->with(['character.entity', 'character.entity.image', 'character.entity.entityType'])->has('character.entity')->get() as $member) { if (empty($member->character->entity)) { return; } if (isset($this->orgMembers[$member->id])) { continue; } $this->orgMembers[$member->id] = true; $this->addEntity($member->character->entity, Avatar::entity($member->character->entity)->fallback()->size(192)->thumbnail()); // Add relation $this->relations[] = [ 'source' => $organisation->entity->id, 'target' => $member->character->entity->id, 'text' => $member->role, 'colour' => '#ccc', 'attitude' => null, 'type' => 'org-member', 'shape' => 'none', ]; // Show relations of org members if the target is shown here $this->addRelations($member->character->entity); } } /** * Prepare a family */ protected function initFamily(): self { $this->addEntity($this->entity) ->withEntity() ->loadRelations(); if ($this->withRelated()) { /** @var Family $family */ $family = $this->entity->child; $this->addFamilyMembers($family); $this ->addParent() ->addLocation() ->addQuests() ->addMapMarkers() ->addAuthorJournals(); } return $this; } /** * Generate a map for a character */ protected function initCharacter(): self { $this->addEntity($this->entity) ->withEntity() ->loadRelations(); if ($this->withRelated()) { $this->addFamily() ->addOrganisation() ->addItems() ->addAuthorJournals() ->addEntityLocations() ->addDiceRolls() ->addConversations() ->addMapMarkers() ->addQuests(); } return $this; } /** * Generate a map for a location */ protected function initLocation(): self { $this->addEntity($this->entity) ->withEntity() ->loadRelations(); if ($this->withRelated()) { $this->relatedRelations() ->addItems() ->addFamilies() ->addJournals() ->addOrganisations() ->addParent() ->addQuests() ->addMapMarkers() ->addMaps() ->addAuthorJournals() ->addRaces() ->addLocationCreatures() ->addEntities(); } return $this; } /** * Generate a map for an organisation */ protected function initOrganisation(): self { $this->addEntity($this->entity) ->withEntity() ->loadRelations(); if ($this->withRelated()) { $this->addOrganisationMembers($this->entity); $this ->addParent() ->addLocations() ->addQuests() ->addMapMarkers() ->addAuthorJournals(); } return $this; } /** * Generate a map for a map model */ protected function initMap(): self { $this->addEntity($this->entity) ->withEntity(); if ($this->withRelations()) { $this->addRelations($this->entity); } if ($this->withRelated()) { $this->addParent() ->addLocation() ->addQuests() ->addMapMarkers() ->addAuthorJournals(); } return $this; } /** * Add a family member. Note that characters can be in multiple families. */ protected function addFamilyMembers(Family $family): self { /** @var Character $member */ foreach ($family->members()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $member) { $this ->addEntity($member->entity, Avatar::entity($member->entity)->fallback()->size(192)->thumbnail()) ->addRelations($member->entity); // Add relation $this->relations[] = [ 'source' => $family->entity->id, 'target' => $member->entity->id, 'text' => __('entities/relations.types.family_member'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'family-member', 'shape' => 'none', ]; } return $this; } /** * Add related families */ protected function addFamilies(): self { /** @var Location $family */ $family = $this->entity->child; foreach ($family->families()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $subfamily) { $this->addEntity($subfamily->entity); $this->addRelations($subfamily->entity); $this->relations[] = [ 'source' => $subfamily->entity->id, 'target' => $this->entity->id, 'text' => Module::singular(config('entities.ids.family'), __('entities.family')), 'colour' => '#ccc', 'attitude' => null, 'type' => 'sub-family', 'shape' => 'triangle', ]; } return $this; } protected function addOrganisationMembers(Entity $entity): self { /** @var Organisation $organisation */ $organisation = $entity->child; /** @var OrganisationMember[] $members */ $members = $organisation->members()->with(['character', 'character.entity', 'character.entity.image', 'character.entity.entityType'])->has('character')->has('character.entity')->get(); foreach ($members as $member) { $this ->addEntity($member->character->entity, Avatar::entity($member->character->entity)->fallback()->size(192)->thumbnail()) ->addRelations($member->character->entity); // Add relation $this->relations[] = [ 'source' => $entity->id, 'target' => $member->character->entity->id, 'text' => __('entities/relations.types.organisation_member'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'family-member', 'shape' => 'none', ]; } return $this; } protected function addOrganisations(): self { /** @var Location $child */ $child = $this->entity->child; foreach ($child->organisations()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $sub) { $this->addEntity($sub->entity); $this->addRelations($sub->entity); $this->relations[] = [ 'source' => $sub->entity->id, 'target' => $this->entity->id, 'text' => __('entities/relations.types.organisation_member'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'sub-org', 'shape' => 'triangle', ]; } return $this; } protected function addItems(): self { /** @var Character|Location $parent */ $parent = $this->entity->child; /** @var Item $item */ foreach ($parent->items()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $item) { $this->addEntity($item->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $item->entity->id, 'text' => Module::singular(config('entities.ids.item'), __('entities.item')), 'colour' => '#ccc', 'attitude' => null, 'type' => 'entity-item', 'shape' => 'none', ]; } return $this; } protected function addJournals(): self { /** @var Location $parent */ $parent = $this->entity->child; /** @var Journal $journal */ foreach ($parent->journals()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $journal) { $this->addEntity($journal->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $journal->entity->id, 'text' => Module::singular(config('entities.ids.journal'), __('entities.journal')), 'colour' => '#ccc', 'attitude' => null, 'type' => 'journal-location', 'shape' => 'none', ]; } return $this; } protected function addAuthorJournals(): self { $elements = $this->entity->authoredJournals()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get(); foreach ($elements as $journal) { $this->addEntity($journal->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $journal->entity->id, 'text' => __('journals.fields.author'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'journal-author', 'shape' => 'none', ]; } return $this; } protected function addLocation(): self { if (! array_key_exists('location_id', $this->entity->child->getAttributes())) { return $this; } if (empty($this->entity->child->location_id) || empty($this->entity->child->location) || empty($this->entity->child->location->entity)) { return $this; } /** @var Map $child */ $child = $this->entity->child; $this->addEntity($child->location->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $child->location->entity->id, 'text' => Module::singular(config('entities.ids.location'), __('entities.location')), 'colour' => '#ccc', 'attitude' => null, 'type' => 'entity-location', 'shape' => 'none', ]; return $this; } /** * Add the entity's parent if it has one */ protected function addParent(): self { if (! $this->entity->parent) { return $this; } $parent = $this->entity->parent; $this->addEntity($parent); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $parent->id, 'text' => __('crud.fields.parent'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'entity-parent', 'shape' => 'triangle', ]; $this->addChildren(); return $this; } /** * Assuming the entity has a parent field, it probably has children too */ protected function addChildren(): self { if (! method_exists($this->entity, 'children')) { return $this; } /** @var Entity $related */ foreach ($this->entity->children()->with(['image', 'entityType'])->get() as $related) { $this->addEntity($related); $this->relations[] = [ 'target' => $this->entity->id, 'source' => $related->id, 'text' => __('crud.fields.child'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'entity-child', 'shape' => 'triangle', ]; } return $this; } protected function addDiceRolls(): self { /** @var Character $parent */ $parent = $this->entity->child; /** @var DiceRoll $related */ foreach ($parent->diceRolls()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $related) { $this->addEntity($related->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $related->entity->id, 'text' => __('entities.dice_roll'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'character-diceroll', 'shape' => 'none', ]; } return $this; } protected function addConversations(): self { /** @var Character $parent */ $parent = $this->entity->child; /** @var Conversation $related */ foreach ($parent->conversations()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $related) { $this->addEntity($related->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $related->entity->id, 'text' => __('entities.conversation'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'character-diceroll', 'shape' => 'none', ]; } return $this; } protected function addMapMarkers(): self { /** @var MapMarker $related */ foreach ($this->entity->mapMarkers()->with(['map', 'map.entity', 'map.entity.image'])->has('map')->has('map.entity')->get() as $related) { $this->addEntity($related->map->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $related->map->entity->id, 'text' => __('maps/markers.tabs.marker'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'entity-map-marker', 'shape' => 'none', ]; } return $this; } protected function addMaps(): self { /** @var Location $parent */ $parent = $this->entity->child; /** @var Map $related */ foreach ($parent->maps()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $related) { $this->addEntity($related->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $related->entity->id, 'text' => Module::singular(config('entities.ids.map'), __('entities.maps')), 'colour' => '#ccc', 'attitude' => null, 'type' => 'location-map', 'shape' => 'none', ]; } return $this; } protected function addQuests(): self { /** @var QuestElement $related */ foreach ($this->entity->quests()->with(['quest', 'quest.entity', 'quest.entity.image', 'quest.entity.entityType'])->has('quest')->has('quest.entity')->get() as $related) { $this->addEntity($related->quest->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $related->quest->entity->id, 'text' => __('entities/relations.connections.quest_element'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'entity-map-point', 'shape' => 'none', ]; } return $this; } /** * Add a character's races */ protected function addRaces(): self { /** @var Character $race */ $race = $this->entity->child; foreach ($race->races()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $subrace) { $this->addEntity($subrace->entity); $this->addRelations($subrace->entity); $this->relations[] = [ 'source' => $subrace->entity->id, 'target' => $this->entity->id, 'text' => Module::singular(config('entities.ids.race'), __('entities.race')), 'colour' => '#ccc', 'attitude' => null, 'type' => 'sub-race', 'shape' => 'triangle', ]; } return $this; } /** * Creature locations */ protected function addLocationCreatures(): self { /** @var Location $location */ $location = $this->entity->child; foreach ($location->creatures()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $loc) { $this->addEntity($loc->entity); $this->addRelations($loc->entity); $this->relations[] = [ 'source' => $loc->entity->id, 'target' => $this->entity->id, 'text' => Module::singular(config('entities.ids.location'), __('entities.location')), 'colour' => '#ccc', 'attitude' => null, 'type' => 'location-creature', 'shape' => 'triangle', ]; } return $this; } protected function addEntityLocations(): self { $locations = $this->entity->locations() ->with(['entity.image']) ->get(); foreach ($locations as $location) { $entity = $location->entity; $this->addEntity($entity); $this->addRelations($entity); $this->relations[] = [ 'source' => $entity->id, 'target' => $this->entity->id, 'text' => $this->entity->entityType->name(), 'colour' => '#ccc', 'attitude' => null, 'type' => 'location-entity', 'shape' => 'triangle', ]; } return $this; } /** * Location's custom entities */ protected function addEntities(): self { // @phpstan-ignore-next-line $entities = $this->entity->child ->entities() ->with(['image']) ->get(); foreach ($entities as $entity) { $this->addEntity($entity); $this->addRelations($entity); $this->relations[] = [ 'source' => $entity->id, 'target' => $this->entity->id, 'text' => $this->entity->entityType->name(), 'colour' => '#ccc', 'attitude' => null, 'type' => 'location-entity', 'shape' => 'triangle', ]; } return $this; } protected function addLocations(): self { /** @var Location $child */ $child = $this->entity->child; if (! method_exists($child, 'locations')) { return $this; } foreach ($child->locations()->with(['entity', 'entity.image', 'entity.entityType'])->has('entity')->get() as $subrace) { $this->addEntity($subrace->entity); $this->addRelations($subrace->entity); $this->relations[] = [ 'source' => $subrace->entity->id, 'target' => $this->entity->id, 'text' => Module::singular(config('entities.ids.race'), __('entities.race')), 'colour' => '#ccc', 'attitude' => null, 'type' => 'sub-race', 'shape' => 'triangle', ]; } return $this; } /** * Load relations between linked entities */ protected function relatedRelations(): self { // Go through the entities loaded and re-check their relations $relatedEntityIds = []; /** @var Relation $relation */ foreach ($this->relations as $relation) { $relatedEntityIds[] = $relation['target']; } $entities = Entity::whereIn('id', $relatedEntityIds)->get(); foreach ($entities as $entity) { $this->addRelations($entity); } return $this; } /** * Add an entity's mentions to the map */ protected function addMentions(): self { if (! $this->withMentions()) { return $this; } /** @var EntityMention[] $mentions */ $mentions = $this->entity->targetMentions()->with(['entity', 'entity.image', 'entity.entityType']) ->has('entity') ->whereNotNull('entity_id') ->get(); foreach ($mentions as $mention) { // Skip mentions to self if ($mention->entity_id == $this->entity->id) { continue; } $this->addEntity($mention->entity); $this->relations[] = [ 'source' => $this->entity->id, 'target' => $mention->entity->id, 'text' => __('entities/relations.connections.mention'), 'colour' => '#ccc', 'attitude' => null, 'type' => 'entity-mention', 'shape' => 'none', ]; } return $this; } protected function withRelations(): bool { return ! $this->onlyRelations(); } /** * Check if the rendering mode includes related models */ protected function withRelated(): bool { return in_array($this->option, ['related', 'mentions']); } /** * Check if the rendering mode includes mentions */ protected function withMentions(): bool { return $this->option === 'mentions'; } /** * Check if the rendering mode only focuses on direct relations */ protected function onlyRelations(): bool { return $this->option === 'only_relations'; } /** * Load the entity's relations, along with optionally the relation's relations */ protected function loadRelations(): self { $this ->addRelations($this->entity) ->withEntity(false); if ($this->withRelations()) { $this->relatedRelations(); } elseif ($this->onlyRelations()) { // Just requested the entity's relations and target entities, nothing else } return $this; } protected function addCustom(): self { // Add the entitiy's locations $locations = $this->entity ->locations() ->with(['entity', 'entity.image', 'entity.entityType'])->has('entity') ->get(); /** @var Location $sub */ foreach ($locations as $sub) { $this->addEntity($sub->entity); $this->addRelations($sub->entity); $this->relations[] = [ 'source' => $sub->entity->id, 'target' => $this->entity->id, 'text' => $sub->entity->entityType->name(), 'colour' => '#ccc', 'attitude' => null, 'type' => 'child', 'shape' => 'triangle', ]; } return $this; } } ================================================ FILE: app/Services/Entity/Connections/RelatedService.php ================================================ order = $order; return $this; } public function connectionsText(int $entityId): string { return implode(', ', $this->reasons[$entityId]); } public function connections() { // Prepare ids for pagination $this->prepareIds(); return Entity::whereIn('id', $this->ids) ->with(['image', 'entityType', 'map']) ->orderBy($this->order) ->paginate(); } protected function prepareIds() { // Do stuff $entityHook = 'init' . ucfirst($this->entity->entityType->code); if (method_exists($this, $entityHook)) { $this->$entityHook(); } else { $this->loadMapMarkers() ->loadLocation() ->loadTimelines() ->loadQuests() ->loadAuthoredJournals() ->loadChildren() ->loadParent(); } } /** * Load anything related to a character */ protected function initCharacter() { $this->loadMapMarkers() ->loadQuests() ->loadItems() ->loadTimelines() ->loadDicerolls() ->loadAuthoredJournals(); } /** * Load anything related to a location */ protected function initLocation() { $this->loadMapMarkers() ->loadQuests() ->loadItems() ->loadOrganisations() ->loadMaps() ->loadJournals() ->loadFamilies() ->loadTimelines() ->loadAuthoredJournals() ->loadRaces() ->loadParent() ->loadChildren(); } protected function initMap() { $this->loadMapMarkers() ->loadChildren() ->loadParent() ->loadLocation() ->loadTimelines() ->loadAuthoredJournals() ->loadQuests(); } protected function initRace() { $this ->loadChildren() ->loadParent() ->loadLocations(); } protected function initOrganisation() { $this ->loadMapMarkers() ->loadLocations() ->loadTimelines() ->loadQuests() ->loadAuthoredJournals() ->loadChildren() ->loadParent(); } protected function loadQuests(): self { $elements = $this->entity->quests()->with(['quest', 'quest.entity'])->has('quest')->get(); foreach ($elements as $sub) { $entity = $sub->quest->entity; if (empty($entity)) { continue; } $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities/relations.connections.quest_element'); } return $this; } protected function loadTimelines(): self { $elements = $this->entity->timelines()->with(['timeline', 'timeline.entity'])->has('timeline')->get(); foreach ($elements as $sub) { $entity = $sub->timeline->entity; if (empty($entity)) { continue; } $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities/relations.connections.timeline_element'); } return $this; } protected function loadMapMarkers(): self { $elements = $this->entity->mapMarkers()->with(['map', 'map.entity'])->has('map')->get(); foreach ($elements as $sub) { $entity = $sub->map->entity; if (empty($entity)) { continue; } $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities/relations.connections.map_point'); } return $this; } protected function loadMaps(): self { /** @var Location $location */ $location = $this->entity->child; $elements = $location->maps()->with(['entity'])->has('entity')->get(); /** @var Entity $entity */ foreach ($elements as $entity) { $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.map'); } return $this; } protected function loadParent(): self { if (! $this->entity->parent) { return $this; } $this->ids[] = $this->entity->parent->id; $this->reasons[$this->entity->parent->id][] = __('crud.fields.parent'); return $this; } protected function loadDicerolls(): self { /** @var Character $parent */ $parent = $this->entity->child; $elements = $parent->diceRolls()->with(['entity'])->has('entity')->get(); foreach ($elements as $sub) { $entity = $sub->entity; $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.dice_roll'); } return $this; } protected function loadConversations(): self { /** @var Character $parent */ $parent = $this->entity->child; $elements = $parent->conversations()->with(['entity'])->has('entity')->get(); foreach ($elements as $sub) { /** @var Conversation $sub */ $entity = $sub->entity; $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.conversation'); } return $this; } protected function loadItems(): self { $elements = $this->entity->children()->get(); /** @var Entity $entity */ foreach ($elements as $entity) { $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.item'); } return $this; } protected function loadJournals(): self { $elements = $this->entity->children()->get(); /** @var Entity $entity */ foreach ($elements as $entity) { $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.journal'); } return $this; } protected function loadAuthoredJournals(): self { $elements = $this->entity->authoredJournals()->with(['entity'])->has('entity')->get(); foreach ($elements as $sub) { $entity = $sub->entity; $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('journals.fields.author'); } return $this; } protected function loadFamilies(): self { /** @var Location $parent */ $parent = $this->entity->child; $elements = $parent->families()->with(['entity'])->has('entity')->get(); foreach ($elements as $sub) { $entity = $sub->entity; $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.family'); } return $this; } protected function loadOrganisations(): self { $elements = $this->entity->children()->get(); /** @var Entity $entity */ foreach ($elements as $entity) { $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.organisation'); } return $this; } protected function loadRaces(): self { $elements = $this->entity->children()->get(); /** @var Entity $entity */ foreach ($elements as $entity) { $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.race'); } return $this; } protected function loadChildren(): self { foreach ($this->entity->children as $sub) { $this->ids[] = $sub->id; $this->reasons[$sub->id][] = __('crud.fields.child'); } return $this; } protected function loadLocations(): self { $elements = $this->entity->locations; /** @var Location $entity */ foreach ($elements as $entity) { $this->ids[] = $entity->entity->id; $this->reasons[$entity->entity->id][] = __('entities.location'); } return $this; } /** * Load the entity's location if it has one */ protected function loadLocation(): self { if ($this->entity->entityType->isCustom()) { return $this; } if ( ! isset($this->entity->child->location) || empty($this->entity->child->location) || empty($this->entity->child->location->entity) ) { return $this; } $entity = $this->entity->child->location->entity; $this->ids[] = $entity->id; $this->reasons[$entity->id][] = __('entities.location'); return $this; } } ================================================ FILE: app/Services/Entity/CopyService.php ================================================ source = $entity; return $this; } public function force(): self { $this->force = true; return $this; } public function fromId(): self { if (! $this->request->filled('copy_source_id')) { return $this; } $entity = Entity::find((int) $this->request->get('copy_source_id')); if (empty($entity)) { return $this; } $this->source = $entity; return $this; } public function copy(): void { // Invalid source, or of a different type? if (! isset($this->source) || $this->source->type_id !== $this->entity->type_id) { return; } $this->posts() ->links() ->abilities() ->inventory() ->permissions() ->reminders() ->map() ->quest() ->timeline(); } public function posts(): self { if (! $this->force && ! $this->check('copy_posts')) { return $this; } foreach ($this->source->posts()->with(['permissions', 'postTags'])->get() as $post) { $post->copyTo($this->entity, $this->isSameCampaign()); } return $this; } public function attributes(): self { if (! $this->force && ! $this->check('copy_attributes')) { return $this; } foreach ($this->source->attributes as $attribute) { $newAttribute = $attribute->replicate(['entity_id', 'created_by', 'updated_by']); $newAttribute->entity_id = $this->entity->id; $newAttribute->save(); } return $this; } protected function links(): self { if (! $this->force && ! $this->check('copy_links')) { return $this; } foreach ($this->source->assets()->link()->get() as $link) { $link->copyTo($this->entity); } return $this; } protected function abilities(): self { if (! $this->force && ! $this->check('copy_abilities')) { return $this; } foreach ($this->source->abilities as $ability) { $ability->copyTo($this->entity); } return $this; } protected function permissions(): self { if (! $this->force && ! $this->check('copy_permissions')) { return $this; } foreach ($this->source->permissions as $perm) { $perm->copyTo($this->entity); } return $this; } public function inventory(): self { if (! $this->force && ! $this->check('copy_inventory')) { return $this; } foreach ($this->source->inventories as $inventory) { $inventory->copyTo($this->entity, $this->isSameCampaign()); } return $this; } protected function reminders(): self { if (! $this->force && ! $this->check('copy_reminders')) { return $this; } foreach ($this->source->reminders as $reminder) { if ($reminder->isCalendarDate()) { continue; } $reminder->copyTo($this->entity); } return $this; } public function character(): self { if (! $this->source->isCharacter()) { return $this; } /** @var CharacterTrait $trait */ // @phpstan-ignore-next-line foreach ($this->source->child->characterTraits as $trait) { $trait->copyTo($this->entity->entity_id); } // Families, races $relations = ['characterFamilies', 'characterRaces', 'organisationMemberships']; foreach ($relations as $relation) { /** @var CharacterRace $item */ // @phpstan-ignore-next-line foreach ($this->source->child->{$relation} as $item) { $new = $item->replicate(['character_id']); $new->character_id = $this->entity->entity_id; $new->save(); } } return $this; } public function map(): self { if (! $this->source->isMap() || ! $this->check('copy_elements')) { return $this; } $groups = []; // @phpstan-ignore-next-line foreach ($this->source->child->layers as $sub) { // Old layer not linked to the gallery? Skip it if (! empty($sub->image_path)) { continue; } $newSub = $sub->replicate(['map_id']); $newSub->map_id = $this->entity->entity_id; $newSub->save(); } // @phpstan-ignore-next-line foreach ($this->source->child->groups as $sub) { $newSub = $sub->replicate(['map_id']); $newSub->map_id = $this->entity->entity_id; $newSub->save(); $groups[$sub->id] = $newSub->id; } // @phpstan-ignore-next-line foreach ($this->source->child->markers as $sub) { /** @var MapMarker $newSub */ $newSub = $sub->replicate(['map_id']); $newSub->map_id = $this->entity->entity_id; $newSub->group_id = ! empty($newSub->group_id) && isset($groups[$newSub->group_id]) ? $groups[$newSub->group_id] : null; // If moving to another campaign, switch the markers pointing to an entity if (! empty($newSub->entity_id) && ! $this->isSameCampaign()) { $newSub->entity_id = null; if ($newSub->icon == 4) { $newSub->icon = 1; } if (empty($newSub->name)) { // Because the permission engine is already set on the new campaign, searching the marker's entity // will always fail. So we need to go get it directly $raw = DB::table('entities') ->select('name') ->where('id', $sub->entity_id) ->first(); $newSub->name = $raw ? $raw->name : 'Copy of #' . $sub->id; } } $newSub->save(); } return $this; } protected function quest(): self { if (! $this->source->isQuest() || ! $this->check('copy_elements')) { return $this; } // @phpstan-ignore-next-line foreach ($this->source->child->elements as $sub) { $newSub = $sub->replicate(); $newSub->quest_id = $this->entity->entity_id; $newSub->save(); } return $this; } public function timeline(): self { if (! $this->source->isTimeline() || (! $this->force && ! $this->check('copy_eras'))) { return $this; } $copyElements = $this->force || $this->check('copy_elements'); // @phpstan-ignore-next-line foreach ($this->source->child->eras()->with('elements')->get() as $era) { $newEra = $era->replicate(); $newEra->timeline_id = $this->entity->entity_id; $newEra->save(); if (! $copyElements) { continue; } foreach ($era->elements as $element) { /** @var TimelineElement $newElement */ $newElement = $element->replicate(); $newElement->timeline_id = $this->entity->entity_id; $newElement->era_id = $newEra->id; if (! $this->isSameCampaign()) { $newElement->entity_id = null; if (empty($newElement->name)) { continue; } } $newElement->save(); } } return $this; } protected function check(string $field): bool { return isset($this->request) && $this->request->boolean($field); } protected function isSameCampaign(): bool { return $this->source->campaign_id === $this->entity->campaign_id; } } ================================================ FILE: app/Services/Entity/EntitySaveService.php ================================================ name = $data['name']; } if (array_key_exists('is_private', $data)) { $entity->is_private = $data['is_private']; } if (array_key_exists('type', $data)) { $entity->type = $data['type']; } if (array_key_exists('entry', $data)) { $entity->entry = $data['entry']; } if (array_key_exists('parent_id', $data)) { $entity->parent_id = $data['parent_id']; } if (array_key_exists('status_id', $data)) { $entity->status_id = $data['status_id'] ?: null; } $this->applyGalleryFields($entity, $data); $entity->save(); // Post-save: image file cleanup (safe after save — doesn't affect entity record) if (($data['remove-image'] ?? null) == '1') { Images::model($entity)->field('image')->cleanup(); } if (($data['remove-header_image'] ?? null) == '1') { Images::model($entity)->field('header_image')->cleanup(); } $this->saveTags($entity, $data); $this->savePermissions($entity, $data); return $entity; } protected function applyGalleryFields(Entity $entity, array $data): void { // image_uuid is available to all (not premium-gated) if (array_key_exists('entity_image_uuid', $data)) { $entity->image_uuid = $data['entity_image_uuid']; } elseif (Domain::isApp()) { // On the hosted app, a missing key means the user cleared it $entity->image_uuid = null; } // tooltip and header_uuid are boosted-campaign features if (! $entity->campaign->boosted()) { return; } if (array_key_exists('tooltip', $data)) { $entity->tooltip = Purify::clean($data['tooltip']); } if (array_key_exists('entity_header_uuid', $data)) { $entity->header_uuid = $data['entity_header_uuid']; } elseif (Domain::isApp()) { $entity->header_uuid = null; } } protected function saveTags(Entity $entity, array $data): void { if (! array_key_exists('tags', $data) && ! array_key_exists('save-tags', $data)) { return; } $ids = $data['tags'] ?? []; if (! is_array($ids)) { // The API can send malformed values $ids = []; } $this->tagService ->user(auth()->user()) ->entity($entity) ->withNew() ->sync($ids); // Touch entity to update updated_at if tags changed (but quietly if just created) if ($this->tagService->isDirty()) { if (EntityLogger::entity($entity)->created()) { $entity->touchQuietly(); } else { $entity->touch(); } } } protected function savePermissions(Entity $entity, array $data): void { if (! auth()->user()->can('permissions', $entity)) { return; } if (array_key_exists('copy_permissions', $data) && ! empty($data['copy_permissions'])) { return; } if (($data['quick-creator'] ?? null) === '1') { return; } $permData = array_intersect_key($data, array_flip(['role', 'user', 'is_attributes_private', 'permissions_too_many'])); // If the user has been granted permissions on this entity, ensure they keep read/write if (Permissions::granted() && ! empty($permData['user'])) { $userId = auth()->user()->id; if (! in_array(Permission::Update->value, $permData['user'][$userId] ?? [])) { $permData['user'][$userId][Permission::Update->value] = 'allow'; } if (! in_array(Permission::View->value, $permData['user'][$userId] ?? [])) { $permData['user'][$userId][Permission::View->value] = 'allow'; } } $this->permissionService ->user(auth()->user()) ->entity($entity) ->save($permData); } } ================================================ FILE: app/Services/Entity/ExportService.php ================================================ entity->entityType->code); $className = 'App\Http\Resources\\' . $child . 'Resource'; if (class_exists($className)) { $resource = new $className($this->entity->child); return $resource->withRelated(); } elseif ($this->entity->entityType->isCustom()) { return new EntityResource($this->entity); } else { return ['error' => 'unknown resource ' . $className]; } } } ================================================ FILE: app/Services/Entity/InventoryService.php ================================================ isNotFilled('tags')) { $items = Item::with('entity') ->limit($request->get('item_amount', 1)) ->get(); } elseif ($request->has('tags') && $request->has('match_all') && $request['match_all'] == true) { // Match all tags $items = Item::whereHas('entity', function ($query) use ($request) { $query ->whereHas('entityTags', function ($tagQuery) use ($request) { $tagQuery->whereIn('tag_id', $request->get('tags')); }, '=', count($request->get('tags'))); // requires all tags }) ->limit($request->get('item_amount', 1)) ->get(); } else { // Match one tag at least $items = Item::whereHas('entity', function ($query) use ($request) { $query->whereHas('entityTags', function ($tagQuery) use ($request) { $tagQuery->whereIn('tag_id', $request->get('tags')); }); }) ->limit($request->get('item_amount', 1)) ->get(); } if ($request->has('replace') && $request['replace'] == true) { // Replace current inventory Inventory::where('entity_id', $this->entity->id)->delete(); } $count = 0; foreach ($items as $item) { $inventory = new Inventory; $inventory = $inventory->create(['item_id' => $item->id, 'entity_id' => $this->entity->id]); $count++; } return $count; } } ================================================ FILE: app/Services/Entity/LoggerService.php ================================================ model = $model; $this->modelDirty(); return $this; } public function finish(): void { // If the model isn't dirty, or was created right now, there is no need for logging if (! $this->dirty || $this->created) { return; } $this->update(); $this->entity->touchQuietly(); $this->model->touchQuietly(); } public function created(): bool { return in_array($this->entity->id, $this->created); } public function create(): void { if ($this->logged()) { return; } $this->log(EntityLog::ACTION_CREATE); $this->log->save(); $this->created[] = $this->entity->id; } public function dirty(string $key, mixed $values): self { $this->changes[$key] = $values; $this->dirty = true; return $this; } public function isDirty(): bool { return $this->dirty; } public function update(): void { if ($this->logged()) { $this->tail(EntityLog::ACTION_UPDATE); } else { $this->log(EntityLog::ACTION_UPDATE); } $this->buildDirty(); if (empty($this->changes)) { return; } if (! empty($this->log->changes)) { $changes = $this->log->changes + $this->changes; $this->log->changes = $changes; } else { $this->log->changes = $this->changes; } // Only save for superboosted and premium campaigns if (isset($this->campaign) && ! $this->campaign->superboosted()) { $this->log->changes = null; } $this->log->save(); $this->dirty = false; $this->changes = []; } public function delete(): void { $this->log(EntityLog::ACTION_DELETE); $this->log->save(); } public function restore(): void { $this->log(EntityLog::ACTION_RESTORE); $this->log->save(); } public function updatedFamilyTree(): void { $this->log(EntityLog::ACTION_UPDATE_FAMILY_TREE); $this->log->save(); } protected function log(int $action) { $this->log = new EntityLog; $this->log->parent_id = $this->entity->id; $this->log->parent_type = Entity::class; $this->log->created_by = isset($this->user) ? $this->user->id : null; $this->log->action = $action; $this->log->impersonated_by = Identity::getImpersonatorId(); } protected function tail(int $action) { /** @var EntityLog $log */ $log = $this->entity->logs()->where('action', $action)->latest()->first(); if ($log) { $this->log = $log; } } protected function logged(): bool { if (in_array($this->entity->id, $this->logged)) { return true; } $this->logged[] = $this->entity->id; return false; } protected function buildDirty(): self { $this->entityDirty(); return $this; } protected function modelDirty(): void { if (! isset($this->model)) { return; } $ignoredAttributes = [ 'slug', 'campaign_id', 'updated_at', 'deleted_at', ]; foreach ($this->model->getDirty() as $attribute => $value) { // If the model has this attribute as ignored for logs, skip it if (in_array($attribute, $ignoredAttributes)) { continue; } // We log the old value $value = $this->model->getOriginal($attribute); if (Str::endsWith($attribute, '_id')) { // Foreign? Try and get the related model $value = $this->getForeignOriginal($attribute, $value); } // If it's not an array, easy work if (! is_array($value)) { $this->changes[$attribute] = $value; continue; } // An array (config[, moons[) we need to store it differently foreach ($value as $k => $v) { $this->changes[$k] = $v; } } } protected function entityDirty(): void { if (! isset($this->entity)) { return; } $dirty = $this->entity->getDirty(); $ignoredAttributes = ['created_at', 'updated_at', 'updated_by', 'deleted_by', 'deleted_at', 'type_id', 'words']; foreach ($dirty as $attribute => $value) { // If the model has this attribute as ignored for logs, skip it if (in_array($attribute, $ignoredAttributes)) { continue; } // We log the old value $value = $this->entity->getOriginal($attribute); if (Str::endsWith($attribute, '_id')) { // Foreign? Try and get the related model $value = $this->getForeignOriginal($attribute, $value); } // If it's not an array, easy work if (! is_array($value)) { $this->changes[$attribute] = $value; continue; } // An array (config[, moons[) we need to store it differently foreach ($value as $k => $v) { $this->changes[$k] = $v; } } } protected function getForeignOriginal(string $attribute, mixed $original): string { if (empty($original)) { return ''; } try { if ($attribute == 'location_id') { $originalLocation = Location::where('id', $original)->first(); if (! empty($originalLocation)) { return (string) $originalLocation->name; } return ''; } elseif ($attribute == 'center_marker_id') { // Maps can have a "centered marker" which gets tricky $originalMarker = MapMarker::where('id', $original)->first(); if (! empty($originalMarker)) { return (string) $originalMarker->name; } return ''; } elseif (in_array($attribute, ['author_id', 'instigator_id', 'creator_id'])) { // Journals have an author, which can be any entity type. In the future, quests might have this too $originalAuthor = Entity::where('id', $original)->first(); if (! empty($originalAuthor)) { return (string) $originalAuthor->name; } return ''; } elseif ($attribute === 'parent_id') { $originalParent = Entity::where('id', $original)->first(); if (! empty($originalParent)) { return (string) $originalParent->name; } return ''; } // Silence if ($attribute == 'target_id' && $this->model instanceof Conversation) { return __('conversations.targets.' . ( $original == ConversationTarget::users ? 'members' : 'characters' )); } // Enum fields that aren't foreign keys if ($attribute === 'status_id') { return $original instanceof \BackedEnum ? (string) $original->value : (string) $original; } // Let's try based off of the attribute name $relationName = Str::before($attribute, '_id'); $relationName = Str::camel($relationName); $relationClass = 'App\Models\\' . ucfirst($relationName); /** @var MiscModel $relationModel */ $relationModel = new $relationClass; /** @var MiscModel $result */ $result = $relationModel->where('id', $original)->firstOrFail(); return $result->name; } catch (Exception $e) { Log::error('Issue with Logger', ['e' => $e->getMessage()]); return ''; } } } ================================================ FILE: app/Services/Entity/MarkdownExportService.php ================================================ isSingle = $isSingle; return $this; } /** * Main function for the Entity to Markdown conversion. * * @return string|mixed */ public function markdown() { $converter = new HtmlConverter; $converter->getConfig()->setOption('strip_tags', true); $converter->getEnvironment()->addConverter(new TableConverter); $converter->getEnvironment()->addConverter(new LinkConverter); $entityData = $this->entityData(); if (! $this->isSingle) { $this->addToIndex(); } return Blade::render('entities.markdown.base', ['entity' => $this->entity, 'entityData' => $entityData, 'converter' => $converter, 'campaign' => $this->campaign]); } public function addToIndex() { $moduleName = $this->entity->entityType->plural() . '_' . $this->entity->entityType->id; if (! isset($this->index[$moduleName])) { $this->index[$moduleName] = []; } $this->index[$moduleName][$this->entity->id] = '* [' . $this->entity->name . '](' . str_replace(' ', '-', $this->module) . '/' . str_replace(' ', '-', Str::slug($this->entity->name)) . '_' . $this->entity->id . ') '; } public function exportIndex() { return Blade::render('entities.markdown.index', ['index' => $this->index]); } public function module(string $module) { $this->module = $module; return $this; } /** * Main function for the Campaign to Markdown conversion. * * @return string|mixed */ public function campaignMarkdown() { $converter = new HtmlConverter; $converter->getConfig()->setOption('strip_tags', true); $converter->getEnvironment()->addConverter(new TableConverter); return Blade::render('campaigns.markdown', ['converter' => $converter, 'campaign' => $this->campaign]); } public function entityData() { // Move to service $entityData = []; $entityData['tags'] = []; $entityData['attributes'] = ''; $entityData['relations'] = ''; $entityData['pinnedAliases'] = []; $entityData['entry'] = $this->markdownEntry(); $entityData['posts'] = []; if ($this->isSingle) { foreach ($this->entity->tags as $tag) { $entityData['tags'][] = '[' . html_entity_decode($tag->name, ENT_QUOTES, 'UTF-8') . '](' . $tag->entity->url() . ')'; } } else { foreach ($this->entity->tags as $tag) { $entityData['tags'][] = '[' . html_entity_decode($tag->name, ENT_QUOTES, 'UTF-8') . '](tags/' . Str::slug($tag->name) . '_' . $tag->entity->id . ')'; } } foreach ($this->entity->pinnedAliases as $asset) { $entityData['pinnedAliases'][] = $asset->name; } foreach ($this->entity->posts as $post) { if (! $post->layout_id) { $entityData['posts'][$post->id] = $this->markdownPost($post); } } foreach ($this->entity->attributes as $attribute) { $entityData['attributes'] .= '* **' . $attribute->name . '**: ' . html_entity_decode($attribute->value, ENT_QUOTES, 'UTF-8') . ' '; } if ($this->isSingle) { foreach ($this->entity->allRelationships as $relation) { $entityData['relations'] .= '* [' . html_entity_decode($relation->target->name, ENT_QUOTES, 'UTF-8') . '](' . $relation->target->url() . ') '; } } else { foreach ($this->entity->allRelationships as $relation) { if ($relation->target->entityType->isCustom()) { $moduleName = $relation->target->entityType->code . '_' . $relation->target->entityType->id; $entityData['relations'] .= '* [' . $relation->target->name . '](' . Str::slug($moduleName) . '/' . Str::slug($relation->target->name) . '_' . $relation->target_id . ') '; } else { $entityData['relations'] .= '* [' . $relation->target->name . '](' . str_replace(' ', '-', $relation->target->entityType->pluralCode()) . '/' . Str::slug($relation->target->name) . '_' . $relation->target_id . ') '; } } } return $entityData; } /** * Get the entry where mentions are made to look nice for the text editor */ public function markdownEntry(): string { if (isset($this->user)) { $this->markdownMentionsService->user($this->user); } return $this->markdownMentionsService->single($this->isSingle)->parseForMarkdown($this->entity); } /** * Get the entry where mentions are made to look nice for the text editor */ public function markdownPost(Post $post): string { if (isset($this->user)) { $this->markdownMentionsService->user($this->user); } return $this->markdownMentionsService->single($this->isSingle)->parseForMarkdown($post); } } ================================================ FILE: app/Services/Entity/MoveService.php ================================================ storageService = $storageService; $this->copyService = $copyService; $this->mentionsService = $mentionsService; } public function to(Campaign|int $campaign): self { if ($campaign instanceof Campaign) { $this->to = $campaign; } else { $this->to = Campaign::findOrFail($campaign); } return $this; } public function copy(bool $copy): self { $this->copy = $copy; return $this; } public function process(): bool { if ($this->copy) { return $this->copyEntity(); } else { return $this->moveEntity(); } } public function count(): int { return $this->count; } public function target(): Campaign { return $this->to; } public function validate(): self { // First we make sure we have access to the new campaign. /** @var ?Campaign $campaign */ $campaign = $this->user->campaigns()->where('campaign_id', $this->to->id)->first(); if (empty($campaign)) { throw new TranslatableException('entities/move.errors.unknown_campaign'); } // Check that the new campaign is different from the current one. if ($campaign->id == $this->entity->campaign_id) { throw new TranslatableException('entities/move.errors.same_campaign'); } // Can the user create an entity of that type on the new campaign? // UserCache::campaign($campaign); if (! $this->user->can('create', [$this->entity->entityType, $campaign])) { throw (new TranslatableException('entities/move.errors.permission'))->setOptions([ 'type' => $this->entity->entityType->name(), 'target' => '' . $campaign->name . '', ]); } // UserCache::campaign($this->entity->campaign); // Trying to move (not copy) but can't update the original entity if (! $this->copy && ! $this->user->can('update', $this->entity)) { throw new TranslatableException('entities/move.errors.permission_update'); } return $this; } protected function copyEntity(): bool { DB::beginTransaction(); try { if ($this->entity->hasChild()) { $newModel = $this->entity->child->replicate(['campaign_id']); // Remove any foreign keys that wouldn't make any sense in the new campaign $keepFields = ['campaign_id', 'status_id', 'visibility_id']; foreach ($newModel->getAttributes() as $attribute => $value) { if (str_contains($attribute, '_id') && ! in_array($attribute, $keepFields)) { $newModel->$attribute = null; } } } else { $newModel = new Note; $newModel->name = $this->entity->name; $newModel->is_private = $this->entity->is_private; } $newModel->campaign_id = $this->to->id; $image = $this->entity->image; // Load the image before switching campaigns $newEntry = $this->mentionsService->campaign($this->campaign)->mapCopiedEntry($this->entity->entry); CampaignLocalization::forceCampaign($this->to); // The model is ready to be saved. $newModel->saveQuietly(); $newModel->createEntity(); $newModel->entity->entry = $newEntry; // Copy the gallery image over if (! empty($image)) { // If there is enough space in the target campaign gallery $available = $this->storageService->campaign($this->campaign)->available(); if ($available > $image->size) { $newImage = $image->replicate(['id', 'campaign_id']); $newImage->campaign_id = $this->to->id; $newImage->save(); Storage::copy($image->path, $newImage->path); $newModel->entity->image_uuid = $newImage->id; } } $newModel->entity->saveQuietly(); $this->copyService ->entity($newModel->entity) ->source($this->entity) ->force() ->posts() ->inventory() ->attributes() ->character() ->timeline() ->map(); DB::commit(); $success = true; } catch (Exception $e) { DB::rollBack(); throw $e; } CampaignLocalization::forceCampaign($this->campaign); return $success; } protected function moveEntity(): bool { $success = false; DB::beginTransaction(); try { // Made it so far, we can move the entity's campaign_id. We first need to remove all the // relations and, since they won't make sense on the new campaign. $this->entity->relationships()->delete(); $this->entity->targetRelationships()->delete(); $this->entity->reminders()->delete(); $this->entity->imageMentions()->delete(); // Get the child of the entity (the actual Location, Character etc) and remove the permissions, since they // won't make sense on the new campaign either. /* @var MiscModel $child */ if ($this->entity->hasChild()) { $child = $this->entity->child; } $this->entity->permissions()->delete(); // Detach is a custom function on a child to remove itself from where it is parent to other entities. if ($this->entity->hasChild()) { $this->cleanupChild($child); } // Update Entity first, as there are no hooks on the Entity model. CampaignLocalization::forceCampaign($this->to); $this->entity->campaign_id = $this->to->id; $this->entity->parent_id = null; if (! empty($this->entity->image_path)) { $oldImagePath = $this->entity->image_path; $this->entity->image_path = Str::replace( 'w/' . $this->campaign->id . '/', 'w/' . $this->to->id . '/', $oldImagePath ); Storage::move($oldImagePath, $this->entity->image_path); } $this->entity->saveQuietly(); // Update child second. We do this otherwise we'll have an old entity and a new one if ($this->entity->hasChild()) { $child->campaign_id = $this->to->id; $child->saveQuietly(); } DB::commit(); $success = true; } catch (Exception $e) { DB::rollBack(); if (config('app.debug')) { throw $e; } } CampaignLocalization::forceCampaign($this->campaign); return $success; } protected function cleanupChild(MiscModel $model) { // Loop on children attributes and detach. $attributes = $model->getAttributes(); foreach ($attributes as $attribute => $value) { if (Str::endsWith($attribute, '_id') && $attribute != 'campaign_id') { $model->$attribute = null; } } // Detach children via entity parent_id if (isset($model->entity)) { foreach ($model->entity->children as $childEntity) { $childEntity->parent_id = null; $childEntity->saveQuietly(); } $model->entity->parent_id = null; $model->entity->saveQuietly(); } if (method_exists($model, 'detach')) { $model->detach(); } $model->saveQuietly(); } } ================================================ FILE: app/Services/Entity/NewService.php ================================================ available)) { return $this->available; } $this->available = new Collection; if (! isset($this->user)) { return $this->available; } $excludedTypes = [ config('entities.ids.bookmark'), config('entities.ids.attribute_template'), ]; foreach ($this->campaign->getEntityTypes() as $entityType) { // Skip disabled modules if ($entityType->isCustom() && ! $entityType->isEnabled()) { continue; } if ( $entityType->isStandard() && ! $this->campaign->enabled($entityType) ) { continue; } if (in_array($entityType->id, $excludedTypes)) { continue; } // Check permission if (! $this->user->can('create', [$entityType, $this->campaign])) { continue; } $this->available->add($entityType); } return $this->available; } public function create(string $name): Entity { $name = Str::replace(['<', '>'], ['<', '>'], $name); if ($this->entityType->isCustom()) { $this->entity = new Entity; $this->entity->campaign_id = $this->campaign->id; $this->entity->type_id = $this->entityType->id; $this->entity->name = $this->purify(mb_trim(strip_tags($name))); $this->entity->is_private = $this->private(); $this->entity->save(); } else { // Todo: we need a better way to handle this in the future /** @var MiscModel $misc */ $misc = $this->entityType->getClass(); $misc->name = $this->purify(mb_trim(strip_tags($name))); $misc->is_private = $this->private(); $misc->campaign_id = $this->campaign->id; $misc->save(); $this->entity = $misc->entity; } if ($this->entity->isTag()) { return $this->entity; } // Apply auto tags to the entity $allTags = $this->autoTags(); $this->entity->tags()->attach($allTags); return $this->entity; } protected function private(): bool { return (bool) ($this->user->isAdmin() && $this->campaign->entity_visibility); } public function autoTags(): array { if (isset($this->tags)) { return $this->tags; } $this->tags = []; $tags = Tag::autoApplied()->has('entity')->get(); foreach ($tags as $tag) { $this->tags[] = $tag->id; } return $this->tags; } } ================================================ FILE: app/Services/Entity/PopularService.php ================================================ popularEntityIds())->get() as $entityType) { if (! $this->campaign->enabled($entityType)) { continue; } elseif (! $this->user->can('create', [$entityType, $this->campaign])) { continue; } $types->add($entityType); } return $types; } protected function popularEntityIds(): array { return [ config('entities.ids.character'), config('entities.ids.location'), config('entities.ids.race'), config('entities.ids.item'), config('entities.ids.organisation'), ]; } } ================================================ FILE: app/Services/Entity/PostLoggerService.php ================================================ changes[$property] = $values; return $this; } public function dirty(): array { $dirty = $this->post->getDirty(); $ignoredAttributes = ['created_at', 'updated_at', 'updated_by', 'deleted_by', 'deleted_at', 'words']; foreach ($dirty as $attribute => $value) { // If the model has this attribute as ignored for logs, skip it if (in_array($attribute, $ignoredAttributes)) { continue; } // We log the old value $value = $this->post->getOriginal($attribute); if (Str::endsWith($attribute, '_id')) { // Foreign? Try and get the related model $value = $this->getForeignOriginal($attribute, $value); } // If it's not an array, easy work if (! is_array($value)) { $this->changes[$attribute] = $value; continue; } // An array (config[, moons[) we need to store it differently foreach ($value as $k => $v) { $this->changes[$k] = $v; } } return $this->changes; } protected function getForeignOriginal(string $attribute, mixed $original): string { if (empty($original)) { return ''; } try { if ($attribute == 'location_id') { $originalLocation = Location::where('id', $original)->first(); if (! empty($originalLocation)) { return (string) $originalLocation->name; } return ''; } // Let's try based off of the attribute name $relationName = Str::before($attribute, '_id'); $relationName = Str::camel($relationName); $relationClass = 'App\Models\\' . ucfirst($relationName); /** @var MiscModel $relationModel */ $relationModel = new $relationClass; /** @var MiscModel $result */ $result = $relationModel->where('id', $original)->firstOrFail(); if ($result->name) { return $result->name; } else { // @phpstan-ignore-next-line return $result->code; } } catch (Exception $e) { Log::error('Issue with Logger', ['e' => $e->getMessage()]); return ''; } } } ================================================ FILE: app/Services/Entity/PostService.php ================================================ mappingService = $mappingService; } /** * Move or copy a post to another entity */ public function handle(MovePostRequest $request): Post { $this->entityId = (int) $request->get('entity'); if ($request->has('copy')) { return $this->copy(); } return $this->move(); } /** * Copy the post with its permissions to another entity */ protected function copy(): Post { $entity = Entity::findOrFail($this->entityId); $newPost = $this->post->copyTo($entity, true); // Update the "mentioned in" mapping for the entity EntityMappingJob::dispatch($newPost); $this->log($newPost, EntityLog::ACTION_CREATE_POST); return $newPost; } /** * Move the post to another entity */ protected function move(): Post { foreach ($this->post->imageMentions as $imageMention) { $imageMention->entity_id = $this->entityId; $imageMention->save(); } $this->post->entity_id = $this->entityId; $this->post->save(); $this->log($this->post, EntityLog::ACTION_UPDATE_POST); return $this->post; } private function log(Post $post, int $action) { $log = new EntityLog; $log->parent_id = $post->id; $log->parent_type = Post::class; $log->created_by = $this->user->id; $log->impersonated_by = Identity::getImpersonatorId(); $log->action = $action; $log->save(); } } ================================================ FILE: app/Services/Entity/PreviewService.php ================================================ data = [ 'id' => $this->entity->id, 'name' => $this->entity->name, // @phpstan-ignore-next-line 'title' => $this->entity->isCharacter() ? $this->entity->child?->title : null, 'link' => $this->entity->url(), 'image' => $this->image(), ]; $this->data['status'] = $this->status(); $this->data['tags'] = $this->tags(); $this->data['location'] = $this->location(); $this->data['locations'] = $this->locations(); $this->data['attributes'] = $this->attributes(); $this->data['profile'] = $this->profile(); $this->data['connections'] = $this->connections(); $this->data['access'] = []; // $this->access(); $this->data['texts'] = [ 'profile' => __('crud.tabs.profile'), 'connections' => __('search.preview.links'), 'no-connections' => __('search.preview.no-connections'), ]; return $this->data; } /** * Load specific stuff from the child for the profile */ protected function profile(): array { if ($this->entity->entityType->isCustom()) { if (! empty($this->entity->type)) { $this->addProfile('crud.fields.type', 'type', $this->entity->type); } } else { /** @var MiscModel|Character $child */ $child = $this->entity->child; if (! empty($this->entity->type)) { $this->addProfile('crud.fields.type', 'type', $this->entity->type); } } // Entity-specific content? if ($this->entity->isCharacter() && ! $this->entity->isMissingChild()) { /** @var Character $child */ // @phpstan-ignore-next-line $this->characterProfile($child); } return $this->profile; } protected function image(): mixed { return Avatar::entity($this->entity)->size(276)->thumbnail(); } protected function attributes(): array { $attributes = []; /** @var Attribute $attr */ foreach ($this->entity->starredAttributes() as $attr) { if ($attr->isCheckbox()) { $val = __('general.no'); if ($attr->value) { $val = ''; } $attributes[] = [ 'id' => $attr->id, 'name' => $attr->name(), 'value' => $val, ]; continue; } $attributes[] = [ 'id' => $attr->id, 'name' => $attr->name(), 'value' => $attr->mappedValue(), ]; } return $attributes; } protected function tags(): array { $tags = []; foreach ($this->entity->tags()->with('entity')->get() as $tag) { $tags[] = [ 'id' => $tag->id, 'name' => $tag->name, 'icon' => $tag->icon, 'colour' => $tag->colour, 'style' => $tag->colourStyle(), 'link' => $tag->getLink(), 'slug' => $tag->slug, ]; } return $tags; } protected function location(): mixed { if ($this->entity->entityType->isCustom()) { return null; } /** @var ?Location $loc */ $loc = null; if (method_exists($this->entity->child, 'location') && ! empty($this->entity->child->location)) { $loc = $this->entity->child->location; } if (method_exists($this->entity->child, 'parent_location') && ! empty($this->entity->child->parent_location)) { $loc = $this->entity->child->parent_location; } if (empty($loc)) { return null; } return [ 'name' => $loc->name, 'link' => $loc->getLink(), ]; } protected function locations(): ?array { if ($this->entity->locations->isEmpty()) { return null; } $locations = []; foreach ($this->entity->locations as $location) { $locations[] = [ 'name' => $location->name, 'link' => $location->getLink(), ]; } return $locations; } protected function connections(): array { $relations = []; foreach ($this->entity->pinnedRelations as $relation) { if (! $relation->target) { continue; } $rel = [ 'id' => $relation->target->id, 'name' => $relation->target->name, 'type' => $relation->relation, 'image' => Avatar::entity($relation->target)->size(64)->thumbnail(), 'link' => $relation->target->url(), ]; $relations[] = $rel; } return $relations; } protected function characterProfile(Character $child): void { if ($child->characterFamilies->isNotEmpty()) { $races = $child->characterFamilies->pluck('family.name')->toArray(); $key = Module::plural(config('entities.ids.family'), 'entities.families'); $this->addProfile($key, 'families', implode(', ', $races)); } if ($child->characterRaces->isNotEmpty()) { $races = $child->characterRaces->pluck('race.name')->toArray(); $key = Module::plural(config('entities.ids.race'), 'entities.races'); $this->addProfile($key, 'races', implode(', ', $races)); } if ($child->age) { $this->addProfile('characters.fields.age', 'age', $child->age); } if ($child->sex) { $this->addProfile('characters.fields.sex', 'gender', $child->sex); } if ($child->pronouns) { $this->addProfile('characters.fields.pronouns', 'pronouns', $child->pronouns); } } protected function status(): ?array { $status = $this->entity->status; if (! $status?->icon) { return null; } $status->setRelation('entityType', $this->entity->entityType); return [ 'icon' => $status->icon(), 'tooltip' => $status->name(), ]; } protected function addProfile(string $key, string $slug, mixed $value = null): void { $this->profile[] = [ 'field' => __($key), 'slug' => $slug, 'value' => $value, ]; } } ================================================ FILE: app/Services/Entity/PrivacyService.php ================================================ entity = $entity; return $this; } public function visibilities(): array { $this->data = ['roles' => [], 'users' => []]; /** @var CampaignRole[] $roles */ $roles = $this->entity->campaign->roles()->with('permissions')->get(); foreach ($roles as $role) { if ($role->isAdmin()) { continue; } // General role permission $perm = $role->permissions ->where('action', Permission::View->value) ->whereNull('entity_id') ->where('entity_type_id', $this->entity->type_id); if ($perm->count() > 0) { // Add unless it's on the entity denied $subPerm = $role->permissions ->where('action', Permission::View->value) ->where('entity_id', $this->entity->id) ->where('access', 0); if ($subPerm->count() === 0) { $this->data['roles'][] = $role; continue; } } // Specific entity $perm = $role->permissions ->where('action', Permission::View->value) ->where('entity_id', $this->entity->id) ->where('access', 1); if ($perm->count() > 0) { $this->data['roles'][] = $role; } } $this->members(); return $this->data; } public function toggle(): self { return $this; } protected function members(): self { /** @var User[] $users[] */ $users = $this->entity->campaign->users()->with('permissions')->get(); foreach ($users as $user) { // Specific entity $perm = $user->permissions ->where('campaign_id', $this->entity->campaign_id) ->where('action', Permission::View->value) ->where('entity_id', $this->entity->id) ->where('access', 1); if ($perm->count() > 0) { $this->data['users'][] = $user; } } return $this; } } ================================================ FILE: app/Services/Entity/PurgeService.php ================================================ count; } /** * @throws Exception */ public function trash(Entity $entity): void { EntityCache::campaign($entity->campaign); if ($entity->hasChild()) { /** @var MiscModel $child */ // @phpstan-ignore-next-line $child = $entity->child()->onlyTrashed()->first(); $this->trashChild($entity, $child); } $this->entityIds[] = $entity->id; $entity->forceDelete(); if (isset($child)) { Images::model($child)->field('image')->cleanup(); } $this->count++; } /** * @throws Exception */ protected function trashChild(Entity $entity, ?MiscModel $child = null) { if (empty($child)) { return false; } // Set the campaign scope to avoid hitting entities of other campaigns (this can happen with // nested modules) // This probably is no longer the case since. CampaignLocalization::setConsoleCampaign($entity->campaign_id); // Detach children of this entity via entities.parent_id foreach ($entity->children as $childEntity) { if ($childEntity->campaign_id != $entity->campaign_id) { throw new Exception( 'Found a child entity that doesn\'t share the campaign id. ' . $childEntity->id . ' and ' . $entity->id ); } $childEntity->parent_id = null; $childEntity->timestamps = false; $childEntity->saveQuietly(); dump('#' . $entity->id . ' child entity #' . $childEntity->id . ' untreed'); } // Clean up the parent to avoid cascading deletes $entity->parent_id = null; $entity->timestamps = false; $entity->saveQuietly(); $this->childIds[$entity->entityType->code][] = $child->id; // Cleanup any images attached to the child. Images::model($child)->field('image')->cleanup(); if ($child instanceof Location && ! empty($child->map)) { Images::model($child)->field('map')->cleanup(); } CharacterCache::campaign($entity->campaign); $child->forceDelete(); // Unset the campaign id limitation again CampaignLocalization::setConsoleCampaign(0); return true; } } ================================================ FILE: app/Services/Entity/RecoveryService.php ================================================ entity($id); if ($url) { $entities[$id] = $url; } } return $entities; } /** * Restore an entity and it's child */ protected function entity(int $id): ?string { /** @var ?Entity $entity */ $entity = Entity::onlyTrashed()->find($id); if (! $entity) { return null; } $entity->restore(); if ($entity->entityType->isCustom()) { return $entity->url(); } // Sometimes the child is soft-deleted, sometimes not. // Honestly we shouldn't have soft-deleted children and just rely on the entity to reduce complexity. // @phpstan-ignore-next-line $child = $entity->child()->onlyTrashed()->first(); if (! $child) { return $entity->url(); } // Refresh the child first to not re-trigger the entity creation on save $child->refresh(); $child->restoreQuietly(); return $entity->url(); } } ================================================ FILE: app/Services/Entity/RecoverySetupService.php ================================================ storage = $storage; } public function term(?string $term): self { $this->term = $term; return $this; } public function filters(array $filters): self { $this->filters = $filters; return $this; } public function setup(): array { return [ 'acl' => [ 'premium' => $this->campaign->boosted(), ], 'elements' => $this->elements(), 'i18n' => $this->i18n(), 'api' => [ 'search' => route('gallery.search', [$this->campaign]), 'recovery' => route('recovery.save', [$this->campaign]), ], 'upgrade' => $this->upgradeLink(), ]; } public function search(): array { return [ 'elements' => $this->elements(), ]; } protected function elements(): array { // Pre-load all entity types of the campaign for custom module names $moduleNames = []; foreach (EntityType::inCampaign($this->campaign)->get() as $entityType) { $moduleNames[$entityType->id] = $entityType->name(); } // Query yields array of objects $elements = DB::select( 'select e.id, e.name, e.deleted_at, e.deleted_by, e.type_id, t.code as type from entities as e left join entity_types as t on t.id = e.type_id where e.deleted_at is not null and e.campaign_id = ' . $this->campaign->id . ' union all select p.id, p.name, p.deleted_at, p.deleted_by, 0 as type_id, "post" as type from posts as p left join entities as e on e.id = p.entity_id where p.deleted_at is not null and e.deleted_at is null and e.campaign_id = ' . $this->campaign->id . ' order by deleted_at DESC' ); // We fill the rest of the data needed into the objects $data = new Collection; $users = $this->campaign->users()->pluck('users.name', 'users.id')->toArray(); foreach ($elements as $key => $element) { // Cast the object to an array for simplicity $row = [ 'id' => $element->id, 'name' => $element->name, 'deleted_at' => $element->deleted_at, 'type' => $element->type, 'type_id' => $element->type_id, ]; $row['deleted_name'] = $users[$element->deleted_by] ?? 'Unknown'; $row['date'] = Carbon::createFromTimeStamp(strtotime($element->deleted_at))->diffForHumans(); $row['position'] = $key; if ($element->type === 'post') { $row['type_name'] = __('entities.article'); } else { $row['type'] = 'entity'; $row['type_name'] = Arr::get($moduleNames, (int) $element->type_id, __('crud.history.unknown')); } $data->add($row); } return $data->toArray(); } protected function i18n(): array { $translations = [ 'model_0' => __('entities.post'), 'order_by_newest' => __('campaigns/recovery.order.newest'), 'order_by_oldest' => __('campaigns/recovery.order.oldest'), 'order_by_type' => __('campaigns/recovery.order.type'), 'select_all' => __('general.select_all'), 'deselect_all' => __('general.deselect_all'), 'recover' => __('campaigns/recovery.actions.recover'), 'restore_selected' => __('campaigns/recovery.actions.recover_selected'), 'newest' => __('campaigns/recovery.order.newest_first'), 'oldest' => __('campaigns/recovery.order.oldest_first'), 'type' => __('campaigns/recovery.order.type_order'), 'premium_title' => __('callouts.premium.title'), 'premium' => __('campaigns/recovery.premium'), 'upgrade' => __('cookieconsent.link'), 'confirm' => __('crud.actions.confirm'), 'deleted_at' => __('campaigns/recovery.fields.deleted_at', ['date' => 'placeholder', 'user' => 'placeholder']), 'recovery_success' => __('campaigns/recovery.name_link', ['name' => 'placeholder']), ]; // Modules $modules = config('entities.ids'); foreach ($modules as $name => $id) { $moduleName = __('entities.' . $name); if ($this->campaign->superboosted() && $this->campaign->hasModuleName($id)) { $moduleName = $this->campaign->moduleName($id); } $translations['model_' . $id] = $moduleName; } return $translations; } protected function upgradeLink(): ?string { if ($this->campaign->boosted()) { return null; } return route('settings.premium'); } } ================================================ FILE: app/Services/Entity/RelationService.php ================================================ count = 0; $data = $request->only([ 'owner_id', 'attitude', 'relation', 'colour', 'is_pinned', 'two_way', 'visibility_id', ]); $data['campaign_id'] = $this->campaign->id; if ($request->has('targets')) { $this->entities = is_array($request->get('targets')) ? $request->get('targets') : [$request->get('targets')]; } else { $this->entities = [$request->get('target_id')]; } $new = null; foreach ($this->entities as $entity_id) { $data['target_id'] = $entity_id; $relation = new Relation; $relation = $relation->create($data); $this->count++; if (! isset($new)) { $new = $relation; } if ($request->has('two_way')) { $relation->createMirror(); $this->count++; } } $this->new = $new; return $this; } public function getEntities(): array { return $this->entities; } public function getNew(): Relation { return $this->new; } public function getCount(): int { return $this->count; } } ================================================ FILE: app/Services/Entity/Relations/CharacterRelationsService.php ================================================ saveLocations($model, $data); $this->saveTraits($model, 'personality', $data) ->saveTraits($model, 'appearance', $data) ->saveOrganisations($model, $data) ->saveRaces($model, $data) ->saveFamilies($model, $data); EntityLogger::model($model)->entity($model->entity)->finish(); } /** Save character traits for the given section (personality or appearance) */ protected function saveTraits( Character $character, string $trait, array $data, ): self { if ( $trait === 'personality' && ! auth()->user()->can('personality', $character) ) { return $this; } if ($this->isPatch && ! array_key_exists($trait . '_name', $data)) { return $this; } $existing = []; foreach ($character->characterTraits()->{$trait}()->get() as $pers) { $existing[$pers->id] = $pers; } $traitOrder = 0; $traitNames = (array) ($data[$trait . '_name'] ?? []); $traitEntry = (array) ($data[$trait . '_entry'] ?? []); foreach ($traitNames as $id => $name) { if (empty($name)) { continue; } if (! empty($existing[$id])) { $model = $existing[$id]; unset($existing[$id]); } else { $model = new CharacterTrait; $model->character_id = $character->id; $model->section_id = $trait === 'personality' ? CharacterTrait::SECTION_PERSONALITY : CharacterTrait::SECTION_APPEARANCE; EntityLogger::dirty('traits', null); } $model->name = $name; $model->entry = $traitEntry[$id] ?? ''; // Defensive: API callers may omit entry keys for a given trait $model->default_order = $traitOrder; $model->save(); $traitOrder++; } foreach ($existing as $id => $model) { $model->delete(); EntityLogger::dirty('traits', null); } return $this; } /** Save organisation memberships for the given character */ protected function saveOrganisations( Character $character, array $data, ): self { if (! array_key_exists('character_save_organisations', $data)) { return $this; } $existing = []; foreach ( $character->organisationMemberships()->has('organisation')->get() as $org ) { $existing[$org->id] = $org; } $organisations = (array) ($data['organisations'] ?? []); $roles = new Collection($data['organisation_roles'] ?? []); $statuses = new Collection($data['organisation_statuses'] ?? []); $pins = new Collection($data['organisation_pins'] ?? []); $privates = new Collection($data['organisation_privates'] ?? []); $newRoles = new Collection; foreach ($roles as $id => $role) { if (empty($id)) { $newRoles->push($role); } } $newStatuses = new Collection; foreach ($statuses as $id => $status) { if (empty($id)) { $newStatuses->push($status); } } $newPins = new Collection; foreach ($pins as $id => $pin) { if (empty($id)) { $newPins->push($pin); } } $newPrivates = new Collection; foreach ($privates as $id => $private) { if (empty($id)) { $newPrivates->push($private); } } foreach ($organisations as $key => $id) { if (empty($id)) { continue; } if (! empty($existing[$key])) { $model = $existing[$key]; unset($existing[$key]); } else { $model = new OrganisationMember; $model->character_id = $character->id; EntityLogger::dirty('organisations', null); } $model->organisation_id = (int) $id; $model->role = $roles->has($key) ? $roles->get($key, '') : $newRoles->shift(); $model->pin_id = OrganisationMemberPin::tryFrom( (int) ($pins->has($key) ? $pins->get($key, '') : $newPins->shift()), ); $model->status_id = OrganisationMemberStatus::from( (int) ($statuses->has($key) ? $statuses->get($key, '') : $newStatuses->shift()), ); if (array_key_exists('organisation_privates', $data)) { $model->is_private = $privates->has($key) ? $privates->get($key, false) : $newPrivates->shift(); } else { $model->is_private = false; } $model->save(); } foreach ($existing as $id => $model) { $model->delete(); EntityLogger::dirty('organisations', null); } return $this; } /** Save race associations for the given character */ protected function saveRaces(Character $character, array $data): self { if ( ! array_key_exists('save_races', $data) && ! array_key_exists('races', $data) ) { return $this; } $races = $this->resolveNewModels( $data['races'] ?? [], Race::class, config('entities.ids.race'), ); $this->saveMany( $character, 'races', $races, Race::class, 'characterRaces', 'race_id', ); return $this; } /** Save family associations for the given character */ protected function saveFamilies(Character $character, array $data): self { if ( ! array_key_exists('save_families', $data) && ! array_key_exists('families', $data) ) { return $this; } $families = $this->resolveNewModels( $data['families'] ?? [], Family::class, config('entities.ids.family'), ); $this->saveMany($character, 'families', $families, Family::class); return $this; } } ================================================ FILE: app/Services/Entity/Relations/Concerns/SavesLocations.php ================================================ save($model, $data); } } ================================================ FILE: app/Services/Entity/Relations/Concerns/SupportsPatchMode.php ================================================ isPatch = true; return $this; } } ================================================ FILE: app/Services/Entity/Relations/CreatureRelationsService.php ================================================ saveLocations($model, $data); EntityLogger::model($model)->entity($model->entity)->finish(); } } ================================================ FILE: app/Services/Entity/Relations/EntityRelationsServiceFactory.php ================================================ entityType->code) { 'character' => $this->characterRelationsService, 'creature' => $this->creatureRelationsService, 'event' => $this->eventRelationsService, 'family' => $this->familyRelationsService, 'item' => $this->itemRelationsService, 'organisation' => $this->organisationRelationsService, 'location' => $this->locationRelationsService, 'race' => $this->raceRelationsService, default => null, }; } } ================================================ FILE: app/Services/Entity/Relations/EventRelationsService.php ================================================ saveLocations($model, $data); EntityLogger::model($model)->entity($model->entity)->finish(); } } ================================================ FILE: app/Services/Entity/Relations/FamilyRelationsService.php ================================================ saveLocations($model, $data); $this->saveMembers($model, $data); EntityLogger::model($model)->entity($model->entity)->finish(); } /** Save family members from submitted data */ protected function saveMembers(Family $family, array $data): void { if (! array_key_exists('sync_family_members', $data)) { return; } $ids = (array) ($data['members'] ?? []); $existing = []; foreach ($family->members as $member) { $existing['m_' . $member->id] = $member; } foreach ($ids as $id) { if (! empty($existing[$id])) { unset($existing[$id]); } else { $character = Character::find($id); if (! empty($character)) { $character->families()->attach($family->id); EntityLogger::dirty('members', null); } } } foreach ($existing as $k) { $k->families()->detach($family->id); EntityLogger::dirty('members', null); } } } ================================================ FILE: app/Services/Entity/Relations/ItemRelationsService.php ================================================ saveLocations($model, $data); if (! array_key_exists('save_creators', $data) && ! array_key_exists('creators', $data)) { return; } $creators = $data['creators'] ?? []; $this->saveMany($model, 'creators', $creators, Entity::class, 'itemCreators', 'creator_id'); // Note: EntityLogger::finish() is intentionally not called here. } } ================================================ FILE: app/Services/Entity/Relations/LocationRelationsService.php ================================================ syncLocations($model->entity, (array) ($data['locations'] ?? []), true); } /** * Add-only location attach — used by BulkService which passes an explicit ID array * and never wants to detach existing locations. */ public function attach(Entity $entity, array $locationIds): void { $this->syncLocations($entity, $locationIds, false); } /** Sync entity locations, optionally detaching removed ones */ protected function syncLocations(Entity $entity, array $locations, bool $detach): void { $existing = $unique = $recreate = []; foreach ($entity->locations as $location) { if (! empty($existing[$location->id])) { $recreate[$location->id] = $location->id; $entity->locations()->detach($location->id); continue; } $existing[$location->id] = $location->id; $unique[$location->id] = $location->id; } if (! empty($recreate)) { $entity->locations()->attach($recreate, ['created_by' => auth()->user()?->id]); } $newLocations = []; $newNames = []; foreach ($locations as $id) { if (! empty($existing[$id])) { unset($existing[$id]); continue; } if (! empty($unique[$id])) { continue; } if (! is_numeric($id)) { $newNames[] = $id; continue; } $location = Location::find($id); if (empty($location)) { continue; } $newLocations[] = $location->id; EntityLogger::dirty('locations', null); } foreach ($this->resolveNewModels($newNames, Location::class, config('entities.ids.location')) as $newId) { $newLocations[] = $newId; EntityLogger::dirty('locations', null); } $entity->locations()->attach($newLocations, ['created_by' => auth()->user()?->id]); if ($detach && ! empty($existing)) { $entity->locations()->detach($existing); EntityLogger::dirty('locations', null); } } } ================================================ FILE: app/Services/Entity/Relations/OrganisationRelationsService.php ================================================ saveLocations($model, $data); $this->saveMembers($model, $data); EntityLogger::model($model)->entity($model->entity)->finish(); } /** Save organisation members from submitted data */ protected function saveMembers(Organisation $organisation, array $data): void { if (! array_key_exists('sync_org_members', $data)) { return; } $ids = (array) ($data['members'] ?? []); $existing = []; foreach ($organisation->members()->has('character')->get() as $member) { $existing['m_' . $member->id] = $member; } foreach ($ids as $id) { if (! empty($existing[$id])) { unset($existing[$id]); } else { $character = Character::find($id); if (! empty($character)) { OrganisationMember::create([ 'organisation_id' => $organisation->id, 'character_id' => $character->id, ]); EntityLogger::dirty('members', null); } } } foreach ($existing as $k) { $k->delete(); EntityLogger::dirty('members', null); } } } ================================================ FILE: app/Services/Entity/Relations/RaceRelationsService.php ================================================ saveLocations($model, $data); EntityLogger::model($model)->entity($model->entity)->finish(); } } ================================================ FILE: app/Services/Entity/Relations/RelationsServiceInterface.php ================================================ request->has('calendar_skip')) { return; } // Previously, this lookup was only triggered when the calendar_id or date was dirty. However, this excludes just // changing the colour or periodicity. To support the API not overriding the values, we still check to make // sure that the calendar_id property is set. if (! $this->request->has('calendar_id')) { return; } $calendarID = $this->request->post('calendar_id'); // We already had this event linked $reminder = $model->calendarReminder(); if ($reminder !== null) { // We no longer have a calendar attached to this model if ($calendarID === null) { $reminder->delete(); return; } } else { $reminder = new Reminder; if ($model instanceof Post) { $reminder->remindable_type = Post::class; $reminder->remindable_id = $model->id; } else { $reminder->remindable_type = Entity::class; $reminder->remindable_id = $model->id; } } // Validate the calendar /** @var ?Calendar $calendar */ $calendar = Calendar::find($calendarID); if ($calendar === null || $calendar->missingDetails()) { return; } $length = $this->request->post('calendar_length', '1'); $length = max(1, $length); $reminder->calendar_id = $this->request->get('calendar_id'); $reminder->year = (int) $this->request->post('calendar_year', '1'); $reminder->month = (int) $this->request->post('calendar_month', '1'); $reminder->day = (int) $this->request->post('calendar_day', '1'); $reminder->length = $length; $reminder->is_recurring = (bool) $this->request->post('calendar_is_recurring'); $reminder->recurring_periodicity = $this->request->post('calendar_recurring_periodicity'); $reminder->colour = $this->request->post('calendar_colour', '#cccccc'); $reminder->type_id = EntityEventTypes::calendarDate; try { $reminder->save(); $model->setRelation('calendarDate', $reminder); } catch (Exception $e) { // Something went wrong, silence the issue throw $e; } } } ================================================ FILE: app/Services/Entity/ShareService.php ================================================ campaign->roles()->public()->first(); if ($this->entity->is_private) { $this->entity->update(['is_private' => false]); } CampaignPermission::updateOrCreate( [ 'campaign_id' => $this->campaign->id, 'campaign_role_id' => $publicRole->id, 'entity_id' => $this->entity->id, 'action' => CampaignPermission::ACTION_READ, ], [ 'access' => true, ] ); $this->entity->refresh(); return $this; } /** * Share all entities of the same type with the public role using global permissions. */ public function shareGlobal(): self { $publicRole = $this->campaign->roles()->public()->first(); CampaignPermission::updateOrCreate( [ 'campaign_id' => $this->campaign->id, 'campaign_role_id' => $publicRole->id, 'entity_type_id' => $this->entity->type_id, 'entity_id' => null, 'action' => CampaignPermission::ACTION_READ, ], [ 'access' => true, ] ); $this->entity->refresh(); return $this; } } ================================================ FILE: app/Services/Entity/StoryService.php ================================================ get('posts'); if (empty($posts)) { return false; } // If the story element isn't in first place, we need to have negative starting positions. $position = 0; $storyPosition = array_search('story', array_values($posts)); $position -= $storyPosition; foreach ($posts as $id => $data) { // We only want to process posts if (! is_array($data) || $data == 'story' || $id === 'story') { continue; } $id = $data['id']; /** @var ?Post $story */ $story = $this->entity->posts->where('id', $id)->first(); if (empty($story)) { continue; } // Collapses status if (isset($data['collapsed'])) { $settings = $story->settings; if ($data['collapsed']) { $settings['collapsed'] = true; } else { unset($settings['collapsed']); } $story->settings = $settings; } if (isset($data['visibility_id'])) { $story->visibility_id = $data['visibility_id']; } // We want the first post after the story to always have the "1" position if ($position === 0) { $position = 1; } $story->position = $position; $story->timestamps = false; $story->saveQuietly(); $position++; } $this->log(); return true; } /** * Log the changes in the entity_logs table */ private function log() { $log = new EntityLog; $log->parent_id = $this->entity->id; $log->parent_type = Entity::class; $log->created_by = $this->user->id; $log->impersonated_by = Identity::getImpersonatorId(); $log->action = EntityLog::ACTION_REORDER_POST; $log->save(); $this->entity->touchSilently(); } } ================================================ FILE: app/Services/Entity/TagService.php ================================================ withNew = true; return $this; } public function model(Model $model): self { $this->model = $model; return $this; } public function add(array $ids): self { $this->withDetach = false; return $this->sync($ids); } public function isAllowed(): bool { if (! empty($this->canCreate)) { return $this->canCreate; } $campaign = $this->campaign ?? $this->entity->campaign; return $this->canCreate = $this->user->can('create', [ $campaign->getEntityTypes()->firstWhere('id', config('entities.ids.tag')), $campaign, ]); } public function create(mixed $name): Tag { $tag = new Tag([ 'name' => Purify::clean($name), ]); $tag->campaign_id = isset($this->campaign) ? $this->campaign->id : $this->entity->campaign_id; $tag->slug = Str::slug($tag->name, ''); $tag->is_private = false; $tag->saveQuietly(); $tag->createEntity(); return $tag; } public function sync(array $ids): self { // Only use tags the user can actually view. This way admins can // have tags on entities that the user doesn't know about. $existing = []; $this->dirty = false; /** @var MiscModel|Entity $model */ $model = $this->entity ?? $this->model; /** @var Tag[]|Collection $tags */ $tags = $model->tags()->with('entity')->has('entity')->get(); foreach ($tags as $tag) { $existing[$tag->id] = $tag->name; } $new = []; foreach ($ids as $id) { if (! empty($existing[$id])) { unset($existing[$id]); } else { $tag = $this->fetch($id); if (empty($tag)) { continue; } $new[] = $tag->id; $this->dirty = true; EntityLogger::dirty('tags', null); } } $model->tags()->attach($new); // Detach previously existing tags that were not requested if (empty($existing) || ! $this->withDetach) { return $this; } $this->dirty = true; EntityLogger::dirty('tags', null); $model->tags()->detach(array_keys($existing)); return $this; } /** * If the tags of the entity were changed, it gets flagged */ public function isDirty(): bool { return $this->dirty; } protected function fetch(mixed $id): ?Tag { /** @var ?Tag $tag */ $tag = Tag::select(['id', 'name'])->find($id); // Create the tag if the user has permission to do so if (empty($tag) && $this->withNew && $this->isAllowed()) { $tag = $this->create($id); } return $tag; } } ================================================ FILE: app/Services/Entity/TemplateService.php ================================================ post)) { $this->post->is_template = ! $this->post->isTemplate(); $this->post->save(); } else { $this->entity->is_template = ! $this->entity->isTemplate(); $this->entity->save(); } } } ================================================ FILE: app/Services/Entity/TooltipService.php ================================================ campaign->boosted()) { $limit = 1000; // If the campaign is boosted, entities can have a custom tooltip. This allows them to use some // html syntax, and thus a lot more control on what is displayed. $boostedTooltip = strip_tags($this->entity->tooltip); if (! empty(mb_trim($boostedTooltip))) { $text = Mentions::mapEntity($this->entity); $text = strip_tags($text, $this->allowedTooltipTags()); if (! empty($text)) { return '
    ' . nl2br($text) . '
    '; } } } if (! method_exists($this->entity, 'parsedEntry')) { return ''; } $text = $this->entity->parsedEntry(); $text = strip_tags($text, $this->allowedTooltipTags()); $text = $this->limitTooltipTextLength($text, $limit); return $text; } protected function limitTooltipTextLength(string $text, int $limit): string { // Extract links to exclude them from the character count $links = []; preg_match_all('/]*>(.*?)<\/a>/is', $text, $matches, PREG_SET_ORDER); foreach ($matches as $index => $match) { // Temporarily replace link with placeholder $placeholder = "___LINK_{$index}___"; $text = Str::replace($match[0], $placeholder, $text); $links[$placeholder] = $match[0]; } // Limit text length excluding link placeholders $text = Str::limit($text, $limit); // Restore links from placeholders foreach ($links as $placeholder => $link) { $text = Str::replace($placeholder, $link, $text); } return $text; } /** * Allowed tags in tooltips, allowing Salvatos to do some interesting things with css */ protected function allowedTooltipTags(): array { $html = []; foreach (config('purify.configs.tooltips.allowed') as $tag) { $html[] = "<{$tag}>"; } $html[] = '
    '; return $html; } } ================================================ FILE: app/Services/Entity/TransformService.php ================================================ child = $child; $this->entity = $child->entity; return $this; } public function confirm(): array { $confirm = []; if ($this->entity->isTimeline()) { $eras = $this->entity->timeline->eras()->count(); if ($eras > 0) { $confirm['timelines.fields.eras'] = $eras; } $elements = $this->entity->timeline->elements()->count(); if ($elements > 0) { $confirm['quests.show.tabs.elements'] = $elements; } } elseif ($this->entity->isMap()) { $layers = $this->entity->map->layers()->count(); if ($layers > 0) { $confirm['maps.fields.layers'] = $layers; } $groups = $this->entity->map->groups()->count(); if ($groups > 0) { $confirm['maps.fields.groups'] = $groups; } $markers = $this->entity->map->markers()->count(); if ($markers > 0) { $confirm['maps.fields.markers'] = $markers; } } elseif ($this->entity->isQuest()) { $elements = $this->entity->quest->elements()->count(); if ($elements > 0) { $confirm['quests.show.tabs.elements'] = $elements; } } return $confirm; } public function transform(): Entity { // Custom to custom, just update the type_id if ($this->entity->entityType->isCustom() && $this->entityType->isCustom()) { $this->orphanChildren(); $this->entity->type_id = $this->entityType->id; $this->entity->parent_id = null; $this->entity->status_id = $this->defaultStatusId(); $this->entity->save(); return $this->entity; } // Custom to child if ($this->entity->entityType->isCustom() && $this->entityType->isStandard()) { return $this->specialToMisc(); } elseif ($this->entity->entityType->isStandard() && $this->entityType->isCustom()) { return $this->miscToSpecial(); } if (empty($this->child) && $this->entity->hasChild()) { $this->child = $this->entity->child; } $this->new = $this->entityType->getMiscClass(); $this ->attributes() ->location() ->removePosts(); // Finally, we can save. Should be all good. $this->new->campaign_id = $this->child->campaign_id; $this->new->saveQuietly(); $this ->members() ->participants() ->locations(); $this->finish(); return $this->entity; } protected function location(): self { // Copy location_id if the new model supports it if (in_array('location_id', $this->fillable) && empty($this->new->location_id) && ! empty($this->child->location_id)) { // @phpstan-ignore-next-line $this->new->location_id = $this->child->location_id; } $raceID = config('entities.ids.race'); $creatureID = config('entities.ids.creature'); $organisationID = config('entities.ids.organisation'); $characterID = config('entities.ids.character'); $entityLocations = [$raceID, $creatureID, $organisationID, $characterID]; // If moving from a multi-location to single location if (in_array($this->child->entityTypeId(), $entityLocations) && ! in_array($this->new->entityTypeId(), $entityLocations) && $this->entity->locations->isNotEmpty()) { if (in_array('location_id', $this->fillable)) { // @phpstan-ignore-next-line $this->new->location_id = $this->entity->locations->first()->id; } elseif (in_array('location_id', $this->fillable)) { // @phpstan-ignore-next-line $this->new->setParentId($this->child->locations()->first()->id); } } return $this; } /** * For entities with multiple locations, they can sometimes be moved around */ protected function locations(): self { $raceID = config('entities.ids.race'); $creatureID = config('entities.ids.creature'); $organisationID = config('entities.ids.organisation'); $characterID = config('entities.ids.character'); $entityLocations = [$raceID, $creatureID, $organisationID, $characterID]; // If the entity is switched from one location to entity locations if (! in_array($this->child->entityTypeId(), $entityLocations) && in_array($this->new->entityTypeId(), $entityLocations)) { // If the if (in_array('location_id', $this->child->getFillable()) && ! empty($this->child->location_id)) { $this->entity->locations()->sync([$this->child->location_id]); } return $this; } if ( ! in_array($this->child->entityTypeId(), $entityLocations) || ! in_array($this->new->entityTypeId(), $entityLocations) ) { if (property_exists($this->child, 'locations')) { // @phpstan-ignore-next-line $this->child->locations()->sync([]); } return $this; } if (property_exists($this->child, 'locations')) { foreach ($this->child->locations as $loc) { $this->new->locations()->attach($loc->id); } } return $this; } protected function new(string $class): MiscModel { $class = '\App\Models\\' . Str::studly($class); try { return app()->make($class); } catch (Exception $e) { throw new Exception("Unknown target '{$class}' for transforming entity"); } } protected function attributes(): self { $oldAttributes = $this->child->getAttributes(); unset($oldAttributes['id']); $this->fillable = $this->new->getFillable(); foreach ($oldAttributes as $attribute => $value) { if (in_array($attribute, $this->fillable)) { $this->new->{$attribute} = $value; } } return $this; } protected function removePosts(): self { // Delete non compatible posts. Post::where('entity_id', $this->entity->id) ->leftJoin('post_layouts', 'posts.layout_id', '=', 'post_layouts.id') ->whereNotNull('post_layouts.entity_type_id') ->delete(); return $this; } /** * If switching from an organisation to a family, we need to move the members? */ protected function members(): self { if ( $this->child->entityTypeId() == config('entities.ids.organisation') && $this->new->entityTypeId() == config('entities.ids.family') ) { // @phpstan-ignore-next-line foreach ($this->child->members as $member) { $member->delete(); // @phpstan-ignore-next-line $this->new->members()->attach($member->character_id); } } elseif ( $this->child->entityTypeId() == config('entities.ids.family') && $this->new->entityTypeId() == config('entities.ids.organisation') ) { // @phpstan-ignore-next-line foreach ($this->child->members as $character) { $orgMember = new OrganisationMember; $orgMember->character_id = $character->id; $orgMember->organisation_id = $this->new->id; $orgMember->role = ''; $orgMember->save(); // @phpstan-ignore-next-line $this->child->members()->detach($character->id); } } else { // Remove members when they aren't characters if (isset($this->child->members)) { foreach ($this->child->members as $member) { // We make sure this isn't a character, because a family has members which are // directly characters while orgs have members which are an in between entity. if (! $member instanceof Character) { $member->delete(); } } } } return $this; } /** * Remove the old participants from a convo */ protected function participants(): self { if ($this->child->entityTypeId() != config('entities.ids.character')) { return $this; } // @phpstan-ignore-next-line foreach ($this->child->conversationParticipants as $conPar) { $conPar->delete(); } return $this; } protected function finish(): self { $type = $this->entity->entityType->name(); // Update entity to its new type. We don't use a new entity to keep all mentions, attributes and // other related elements attached. $this->entity->type_id = $this->entityType->id; // Clean up the parent $this->entity->parent_id = null; // Reset status to the target category's default $this->entity->status_id = $this->defaultStatusId(); // If attached to a misc model, save the entity_id if (isset($this->new)) { $this->entity->entity_id = $this->new->id; } $this->entity->saveQuietly(); EntityLogger::entity($this->entity)->dirty('entity_type', $type)->update(); // Delete old, this will take care of pictures and stuff. We detach the // entity to avoid the softDelete affecting it and causing duplicate // entities in the db. ForceDelete the MiscModel for img cleanup. if (isset($this->child)) { $this->child->entity = null; // Force delete the old entity to avoid it creating weird issues in the db by being soft deleted. $this->child->forceDelete(); unset($this->child); } return $this; } protected function specialToMisc(): Entity { $firstLocation = $this->entity->locations()->first(); $this->orphanChildren(); // Create misc without calling its observers, to not create duplicates $this->new = $this->entityType->getMiscClass(); $this->new->name = $this->entity->name; $this->new->is_private = $this->entity->is_private; $this->new->campaign_id = $this->campaign->id; if ($firstLocation && $this->new->isFillable('location_id')) { // @phpstan-ignore-next-line $this->new->location_id = $firstLocation->id; } $this->new->save(); // We need to get rid of the entity's locations, for now. In a future refactor, we can hopefully skip this part if (method_exists($this->new, 'locations')) { $this->new->locations()->sync($this->entity->locations()->get()->pluck('id')); } elseif (method_exists($this->new, 'entityLocations')) { // Characters use the entity.locations table so no mess needed. // todo: races, orgs, families, creatures } else { $this->entity->locations()->sync([]); } $this->finish(); return $this->entity; } protected function miscToSpecial(): Entity { $this->child = $this->entity->child; // Transfer over location_id to entityLocations // @phpstan-ignore-next-line if ($this->child->isFillable('location_id') && $this->child->location_id) { $this->entity->locations()->sync([$this->child->location_id]); } $this->finish(); return $this->entity; } protected function defaultStatusId(): ?int { return CategoryStatus::where('category_id', $this->entityType->id) ->where('is_default', true) ->value('id'); } protected function orphanChildren(): void { /** @var Entity $child */ foreach ($this->entity->children as $child) { $child->parent_id = null; $child->save(); } } } ================================================ FILE: app/Services/EntityFileService.php ================================================ make(StorageService::class); $this->files = []; // Prepare the file for the journey $uploadedFiles = $request->file($field); if (! is_array($uploadedFiles)) { $uploadedFiles = [$uploadedFiles]; } $files = []; foreach ($uploadedFiles as $uploadedFile) { // Already above max capacity? if (! $request->user()->can('addFile', [$this->entity, $this->campaign])) { throw (new TranslatableException('crud.files.errors.max')) ->setOptions(['max' => Limit::campaign($this->campaign)->entityFiles()]); } if ($service->campaign($this->campaign)->available() < $uploadedFile->getSize() / 1024) { $key = 'gallery.download.errors.gallery_full_free'; if ($this->campaign->boosted()) { $key = 'gallery.download.errors.gallery_full_premium'; } throw new TranslatableException($key); } $name = $request->get('name'); if (empty($name)) { $name = $uploadedFile->getClientOriginalName(); $name = Str::limit(Str::beforeLast($name, '.'), 45, ''); } $image = new Image; $image->campaign_id = $this->campaign->id; $image->ext = $uploadedFile->extension(); $image->size = (int) ceil($uploadedFile->getSize() / 1024); // kb $image->name = mb_substr($name, 0, 45); $image->visibility_id = $this->campaign->defaultGalleryVisibility(); $image->save(); $uploadedFile ->storePubliclyAs( $image->folder, $image->file, ['disk' => 's3'] ); $file = new EntityAsset; $file->type_id = EntityAssetType::file; $file->entity_id = $this->entity->id; $file->metadata = [ 'size' => $uploadedFile->getSize(), 'type' => $uploadedFile->getMimeType(), ]; $file->name = $name; $file->visibility_id = $request->get('visibility_id', 1); $file->is_pinned = $request->get('is_pinned', 1); $file->image_uuid = $image->id; $file->save(); Cache::forget('campaign_' . $this->campaign->id . '_gallery'); $this->files[] = $file; } return $this; } public function files(): array { return $this->files; } } ================================================ FILE: app/Services/EntityMappingService.php ================================================ throwExceptions = false; $this->verbose = false; return $this; } public function with(Model|Post|Entity|QuestElement|TimelineElement|Campaign $model): self { $this->model = $model; return $this; } /** * @throws Exception */ public function map(): self { return $this ->images() ->entry(); } protected function createNewMention(int $target): void { $mention = new EntityMention; // Determine what kind of entity this is // Todo: should be the model that gives us this info, not for the service to figure out if ($this->model instanceof Campaign) { $mention->campaign_id = $this->model->id; } elseif ($this->model instanceof Post) { $mention->post_id = $this->post()->id; $mention->entity_id = $this->post()->entity_id; // If we are making a reference to ourselves, no need to save it if ($this->model->entity_id == $target) { return; } } elseif ($this->model instanceof TimelineElement) { $mention->timeline_element_id = $this->model->id; $mention->entity_id = $this->model->timeline->entity->id; // If we are making a reference to ourselves, no need to save it if ($this->model->timeline_id == $target) { return; } } elseif ($this->model instanceof QuestElement) { $mention->quest_element_id = $this->model->id; $mention->entity_id = $this->model->quest->entity->id; // If we are making a reference to ourselves, no need to save it if ($this->model->quest_id == $target) { return; } } else { // @phpstan-ignore-next-line $mention->entity_id = $this->model->id; // If we are making a reference to ourselves, no need to save it if ($this->model->id == $target) { // @phpstan-ignore-line return; } } $mention->target_id = $target; $mention->save(); } /** * Entities and Posts will track gallery images uses in their text */ protected function images(): self { if (! method_exists($this->model, 'imageMentions')) { return $this; } $images = []; if ($this->model instanceof Entity) { $images = $this->extractImages($this->entity()->entry); } elseif ($this->model instanceof Post) { $images = $this->extractImages($this->post()->entry); } $this->existingTargets = []; if ($this->model instanceof Entity) { /** @var ImageMention $map */ foreach ($this->model->imageMentions()->whereNull('post_id')->get() as $map) { $this->existingTargets[$map->image_id] = $map; } } else { foreach ($this->model->imageMentions as $map) { $this->existingTargets[$map->image_id] = $map; } } foreach ($images as $data) { $id = $data; // Determine the real campaign id from the model. if ($this->model instanceof Post) { $campaignId = $this->post()->entity->campaign_id; } else { $campaignId = $this->entity()->campaign_id; } /** @var ?Image $target */ $target = Image::where([ 'id' => $id, 'campaign_id' => $campaignId, ])->first(); if (! $target) { continue; } // Don't map the same image multiple times if (! empty($this->existingTargets[$target->id])) { if ($this->model instanceof Post && $this->existingTargets[$target->id]->post_id == $this->model->id) { unset($this->existingTargets[$target->id]); $this->updatedImages++; continue; } elseif ($this->model instanceof Entity && ! $this->existingTargets[$target->id]->post_id) { unset($this->existingTargets[$target->id]); $this->updatedImages++; continue; } } $this->createNewImageMention($target->id); } // Existing mappings that are no longer needed foreach ($this->existingTargets as $targetId => $map) { $map->delete(); $this->deletedImages++; } return $this; } protected function entry(): self { // Build a list of existing targets, so that we delete the unused ones $this->existingTargets = $alreadyMentioned = []; // @phpstan-ignore-next-line foreach ($this->model->mentions as $map) { $this->existingTargets[$map->target_id] = $map; } // @phpstan-ignore-next-line $entryMentions = $this->extract($this->model->{$this->model->entryFieldName()}); // @phpstan-ignore-next-line $tooltipMentions = $this->extract($this->model->{$this->model->tooltipFieldName()}); $mentions = array_merge($tooltipMentions, $entryMentions); foreach ($mentions as $data) { $type = $data['type']; $id = $data['id']; // Old redirects or mapping to something else (like the map of a location) that doesn't have a tooltip // But a link to a mention without a title will also break here. if ($id == 'redirect') { continue; } $id = (int) $id; if (empty($id)) { continue; } $singularType = $type; // If we're targeting a campaign, no support for auto-updates if ($singularType == 'campaign') { continue; } $target = null; // Validate that it's either targeting a post, or a valid entity type $entityType = $this->getEntityTypeID($singularType); if (! $entityType && $singularType !== 'post') { continue; } /** @var ?Entity $target */ $target = $this->getTarget($id, $entityType); if (! $target) { continue; } // If already mentioned, don't create more mentions if (in_array($target->id, $alreadyMentioned)) { $alreadyMentioned[] = $target->id; continue; } // Do we already have this mention mapped? if (! empty($this->existingTargets[$target->id])) { $alreadyMentioned[] = $target->id; // $this->log("- already have mapping"); unset($this->existingTargets[$target->id]); continue; } $alreadyMentioned[] = $target->id; // If a same target is mentioned multiple times, don't create a new mention each time $this->createNewMention($target->id); } // Existing mappings that are no longer needed foreach ($this->existingTargets as $targetId => $map) { $map->delete(); } return $this; } protected function getTarget(int $id, ?int $entityType): ?Entity { if (! isset($entityType)) { $post = Post::with('entity')->where([ 'id' => $id, ])->first(); if ($post && $post->entity && $post->entity->campaign_id === $this->campaignID()) { return $post->entity; } return null; } return Entity::where([ 'type_id' => $entityType, 'id' => $id, 'campaign_id' => $this->campaignID(), ])->first(); } protected function campaignID(): int { // Todo: should be a method on the object or something, not the service's job to figure out if ($this->model instanceof Campaign) { return $this->model->id; } elseif ($this->model instanceof Post) { return $this->model->entity->campaign_id; } elseif ($this->model instanceof TimelineElement) { return $this->model->timeline->campaign_id; } elseif ($this->model instanceof QuestElement) { return $this->model->quest->campaign_id; } // @phpstan-ignore-next-line return $this->model->campaign_id; } protected function createNewImageMention(string $target): void { $mention = new ImageMention; // Determine what kind of entity this is if ($this->model instanceof Post) { $mention->post_id = $this->model->id; $mention->entity_id = $this->model->entity_id; } elseif ($this->model instanceof Entity) { $mention->entity_id = $this->model->id; } $mention->image_id = $target; $mention->save(); $this->createdImages++; } protected function log(?string $message = null) { if (! $this->verbose) { return; } echo $message; if (app()->runningInConsole()) { echo "\n"; } else { echo '
    '; } } protected function post(): Post { // @phpstan-ignore-next-line return $this->model; } protected function entity(): Entity { // @phpstan-ignore-next-line return $this->model; } protected function getEntityTypeID(string $code): ?int { $this->loadEntityTypes(); return Arr::get($this->entityTypes, $code); } protected function loadEntityTypes(): void { if (isset($this->entityTypes)) { return; } $this->entityTypes = []; foreach (EntityType::inCampaign($this->campaignID())->get() as $entityType) { $this->entityTypes[$entityType->code] = $entityType->id; } } } ================================================ FILE: app/Services/EntityTypeService.php ================================================ exclude = is_array($ids) ? $ids : [$ids]; return $this; } public function skip(mixed $skip): self { $this->skip = is_array($skip) ? $skip : [$skip]; return $this; } public function withDisabled(): self { $this->withDisabled = true; return $this; } public function prepend(array $prepend): self { $this->prepend = $prepend; return $this; } public function available(): Collection { $types = new Collection; $search = EntityType::inCampaign($this->campaign)->exclude($this->exclude); if (! $this->withDisabled) { $search->enabled(); } /** @var EntityType $entityType */ foreach ($search->get() as $entityType) { if (in_array($entityType->pluralCode(), $this->skip)) { continue; } // Skip disabled standard modules if (! $this->withDisabled && $entityType->isStandard() && ! $this->campaign->enabled($entityType)) { continue; } if ($this->creatable && ! $this->user->can('create', [$entityType, $this->campaign])) { continue; } $types->add($entityType); } return $types; } public function creatable(): self { $this->creatable = true; return $this; } public function ordered(): Collection { return $this->available()->sortBy(fn (EntityType $a) => $a->name()); // // $collator = new \Collator(app()->getLocale()); // usort($types, function ($a, $b) use ($collator) { // return $collator->compare($a->name(), $b->name()); // }); // // return $types; } public function toSelect(): array { $options = $this->ordered(); $values = []; foreach ($options as $entityType) { $values[$entityType->id] = $entityType->name(); } if (! isset($this->prepend)) { return $values; } return $this->prepend + $values; } public function save(): EntityType { if (! isset($this->entityType)) { $this->entityType = new EntityType; $this->entityType->campaign_id = $this->campaign->id; $this->entityType->is_special = true; $this->entityType->code = Str::slug($this->request->get('singular')); } $this->entityType->singular = $this->request->get('singular'); $this->entityType->plural = $this->request->get('plural'); $this->entityType->icon = $this->request->get('icon'); $this->entityType->is_enabled = (int) $this->request->get('is_enabled'); $this->entityType->save(); if ($this->entityType->wasRecentlyCreated) { $this->permissions()->bookmark(); } if ($this->request->get('update_name')) { $this->updateBookmark(); } if ($this->request->hasFile('default_entity_image') || $this->request->filled('remove-image')) { $this->defaultImageService->campaign($this->campaign) ->user($this->user) ->entityType($this->entityType) ->destroy(); if ($this->request->hasFile('default_entity_image')) { $this->defaultImageService->save($this->request); } } return $this->entityType; } public function toggle(): void { $this->entityType->is_enabled = ! $this->entityType->is_enabled; $this->entityType->save(); } protected function updateBookmark(): self { $bookmarks = $this->entityType->bookmarks()->whereNull('filters')->get(); foreach ($bookmarks as $bookmark) { $bookmark->name = $this->entityType->plural; $bookmark->update(); } return $this; } protected function bookmark(): self { $bookmark = new Bookmark; $bookmark->campaign_id = $this->campaign->id; $bookmark->entity_type_id = $this->entityType->id; $bookmark->name = $this->entityType->plural; $bookmark->save(); return $this; } protected function permissions(): self { if (! $this->request->has('role')) { return $this; } foreach ($this->request->get('role') as $roleID) { /** @var CampaignRole $campaignRole */ $campaignRole = $this->campaign->roles->where('id', $roleID)->first(); if (! $campaignRole || $campaignRole->isAdmin()) { continue; } $perm = new CampaignPermission; $perm->entity_type_id = $this->entityType->id; $perm->campaign_id = $this->campaign->id; $perm->campaign_role_id = $campaignRole->id; $perm->access = 1; $perm->save(); } return $this; } public function delete() { $this->entityType->bookmarks()->delete(); $this->entityType->entities()->delete(); $this->entityType->attributeTemplates()->delete(); $this->entityType->entities()->delete(); $this->entityType->delete(); } } ================================================ FILE: app/Services/Families/FamilyTreeService.php ================================================ family = $family; return $this; } public function api()// : array { $this->loadSetup(); // return $this->fake(); return $this->tree(); } public function familyTree() { return $this->familyTree; } /** * Return all data required to generate the family tree */ public function tree(): array { return [ 'nodes' => $this->fillNodes(), 'entities' => $this->entities, 'suggestions' => $this->characterSuggestions, 'texts' => $this->texts(), ]; } /** * Get an entity's representation for the rendering engine * * @return array|string[] */ public function entity(Entity $entity): array { if (! $entity->isCharacter()) { return ['error' => 'invalid-character']; } $entity->loadMissing(['status', 'entityType', 'tags', 'image', 'elapsedEvents']); return $this->formatEntity($entity); } protected function loadSetup(): void { $this->loadFamilyTree(); $this->loadFamily(); // Get all the entity ids if (empty($this->familyTree->config)) { return; } $this->prepareEntities(); // foreach () } protected function loadFamily(): void { $familyMembers = $this->family->allMembers()->with(['entity', 'entity.entityType'])->orderBy('name')->take(10)->get(); foreach ($familyMembers as $member) { $this->characterSuggestions[] = ['id' => $member->entity->id, 'name' => $member->name]; } } protected function loadFamilyTree(): void { $familyTree = $this->family->familyTree; if (! $familyTree) { $familyTree = new FamilyTree; $familyTree->family_id = $this->family->id; if (isset($this->user)) { $familyTree->save(); } } $this->familyTree = $familyTree; } /** * Get all the unique entity ids from the family tree */ protected function prepareEntities(): void { $data = $this->familyTree->config; // Go find every unique entity id array_walk_recursive($data, function ($v, $k) { if ($k !== 'entity_id') { return; } if (empty($v) || in_array($v, $this->configEntityIds)) { return; } $this->configEntityIds[] = $v; }); // Empty family tree if (empty($this->configEntityIds)) { return; } $this->entityIds = array_unique(array_values($this->configEntityIds)); // dump($this->entityIds); // Prepare entities $entities = Entity::inTypes([config('entities.ids.character')]) ->with([ 'status', 'entityType', 'tags', 'image', 'elapsedEvents', ]) ->find($this->entityIds); foreach ($entities as $entity) { $this->entities[$entity->id] = $this->formatEntity($entity); } // dump($this->entities); if (! empty($this->entities)) { $this->missingIds = array_diff($this->entityIds, array_keys($this->entities)); $this->cleanupMissingEntities(); $this->visibilityCheck(); } else { $this->entities = []; $this->familyTree->config = []; // $this->generatePlaceholder(); } // dd($this->characterSuggestions); // dump($this->missingIds); } /** * Format an entity for the rendering engine */ protected function formatEntity(Entity $entity): array { $tags = []; foreach ($entity->tags as $tag) { $tags[] = 'kanka-tag-' . $tag->id; $tags[] = 'kanka-tag-' . $tag->slug; } $elapsed = $entity->elapsedEvents; // Prepare birth and death events $birth = null; $death = null; foreach ($elapsed as $event) { if ($event->isBirth() && $birth === null) { $birth = $event->year; } elseif ($event->isDeath() && $death === null) { $death = $event->year; } } $status = null; if ($entity->status !== null) { $entity->status->setRelation('entityType', $entity->entityType); $status = [ 'key' => $entity->status->key, 'icon' => $entity->status->icon(), 'name' => $entity->status->name(), ]; } return [ 'id' => $entity->id, 'name' => $entity->name, 'url' => $entity->url(), 'thumb' => Avatar::entity($entity)->size(40)->fallback()->thumbnail(), 'status' => $status, 'death' => $death, 'birth' => $birth, 'tags' => $tags, ]; } protected function visibilityCheck(): void { $config = []; foreach ($this->config as $key => $node) { $config[$key] = $this->cleanInvisible($node, $key); } $this->config = $config; } protected function cleanInvisible($node, $key): mixed { if (isset($node['relations'])) { foreach ($node['relations'] as $k => $rel) { if (! isset($rel['children'])) { $relations[] = $rel; continue; } $children = []; foreach ($rel['children'] as $ck => $child) { $child = $this->cleanInvisible($child, $ck); if (! $child) { continue; } if ($this->isVisible($child)) { $children[] = $child; } } unset($rel['children']); if (! empty($children)) { $rel['children'] = $children; } if ($this->isVisible($rel)) { $relations[] = $rel; } } unset($node['relations']); if (! empty($relations)) { $node['relations'] = $relations; } } return $node; } protected function isVisible($relation): bool { return (bool) ( ! isset($relation['visibility']) || $relation['visibility'] == Visibility::All->value || ($relation['visibility'] == Visibility::Admin->value && isset($this->user) && $this->user->isAdmin()) || ($relation['visibility'] == Visibility::Member->value && isset($this->user) && $this->user->can('member', $this->campaign)) ); } protected function cleanupMissingEntities(): void { if (empty($this->missingIds)) { $this->config = $this->familyTree->config; return; } $config = []; // Loop everything and remove entities that match foreach ($this->familyTree->config as $key => $node) { $config[$key] = $this->cleanupNode($node, $key); } // Save the config now that its clean? $this->config = $config; } protected function cleanupNode($node, $key): mixed { if (in_array($node['entity_id'], $this->missingIds) && $node['entity_id'] != null) { return null; } if (! isset($node['relations'])) { return null; } $relations = []; foreach ($node['relations'] as $k => $rel) { if (in_array($rel['entity_id'], $this->missingIds) && $rel['entity_id'] != null) { continue; } if (! isset($rel['children'])) { continue; } $children = []; foreach ($rel['children'] as $ck => $child) { $child = $this->cleanupNode($child, $ck); if (! $child) { continue; } $children[] = $child; } unset($rel['children']); if (! empty($children)) { $rel['children'] = $children; } $relations[] = $rel; } unset($node['relations']); if (! empty($relations)) { $node['relations'] = $relations; } /*if (!empty($node['relations'])) { $parent[$key]['relations'] = $node['relations']; } elseif (isset($node['relations'])) { unset($parent[$key]['relations']); }*/ return $node; } protected function emptyNode(): array { return []; } /** * Return an error handled by the frontend */ protected function error(string $code): array { return [ 'error' => true, 'code' => __($code), ]; } /** * Save a new tree config to the database */ public function save(array $data = []): self { // If the campaign is not premium dont save the tree. if (! $this->campaign->premium()) { return $this; } $this->loadFamilyTree(); if (empty($data)) { $this->familyTree->config = []; $this->familyTree->save(); return $this; } // $data = json_decode($data); $data = $this->prepareForSave($data); $this->familyTree->config = $data; $this->familyTree->save(); return $this; } /** * Prepare a new config for the database by adding a uuid everywhere * * @return array */ protected function prepareForSave(array $data)// : array { $assingUuid = function (&$value, $key) { if ($key == 'uuid' && (! is_string($value) || ! Str::isUuid($value))) { $value = (string) Str::uuid(); } // echo "The key $key has the value $value
    "; }; array_walk_recursive($data, $assingUuid); // Loop on the data, adding a uuid on each element that is missing one // dd('ended recursive', $data); return $data; } protected function fillNodes(): array { $nodes = $this->config; if (empty($nodes)) { return []; } // dd($nodes); if (! isset($nodes[0]['relations'])) { return $nodes; } foreach ($nodes[0]['relations'] as $i => $relation) { $nodes[0]['relations'][$i] = $this->informRelation($relation); } return $nodes; } protected function informRelation(array $relation): array { // No children, single relation if (empty($relation['children'])) { $relation['width'] = 1; return $relation; } $count = 0; foreach ($relation['children'] as $i => $child) { $count++; } $relation['width'] = $count; return $relation; } protected function texts(): array { return [ 'actions' => [ 'edit' => __('crud.edit'), 'clear' => __('families/trees.actions.clear'), 'reset' => __('families/trees.actions.reset'), 'save' => __('families/trees.actions.save'), 'first' => __('families/trees.actions.first'), 'founder' => __('families/trees.actions.founder'), ], 'modals' => [ 'clear' => [ 'confirm' => __('families/trees.modals.clear.confirm'), ], 'relation' => [ 'add' => [ 'title' => __('families/trees.modals.relations.add.title'), ], 'edit' => [ 'title' => __('families/trees.modals.relations.edit.title'), ], ], 'entity' => [ 'add' => [ 'title' => __('families/trees.modals.entity.add.title'), ], 'edit' => [ 'title' => __('families/trees.modals.entity.edit.title'), 'helper' => __('families/trees.modals.entity.edit.helper'), ], 'child' => [ 'title' => __('families/trees.modals.entity.child.title'), ], 'founder' => [ 'title' => __('families/trees.modals.entity.founder.title'), ], 'remove' => [ 'title' => __('crud.remove'), 'confirm' => __('families/trees.modals.entity.remove.confirm'), ], ], 'pitch' => [ 'title' => __('concept.premium-feature'), 'content' => __('families/trees.pitch'), 'more' => __('callouts.premium.learn-more'), 'subscription' => __('callouts.actions.subscription'), ], 'reset' => [ 'confirm' => __('families/trees.modals.reset.confirm'), ], 'fields' => [ 'relation' => __('entities/relations.fields.role'), 'character' => __('entities.character'), 'member' => __('families/trees.modals.entity.add.member'), 'css' => __('dashboard.widgets.fields.class'), 'colour' => __('crud.fields.colour'), 'unknown' => __('families/trees.modals.relations.unknown'), 'founder' => __('families/trees.modals.entity.add.founder'), 'visibility' => [ 'title' => __('crud.fields.visibility'), 'all' => __('crud.visibilities.all'), 'admins' => __('crud.visibilities.admin'), 'members' => __('crud.visibilities.members'), ], ], ], 'toasts' => [ 'relations' => [ 'add' => __('families/trees.modals.relations.add.success'), 'edit' => __('families/trees.modals.relations.edit.success'), ], 'entity' => [ 'add' => __('families/trees.modals.entity.add.success'), 'edit' => __('families/trees.modals.entity.edit.success'), 'child' => __('families/trees.modals.entity.child.success'), 'removed' => __('families/trees.modals.entity.remove.success'), ], 'saved' => __('families/trees.success.saved'), 'cleared' => __('families/trees.success.cleared'), 'reseted' => __('families/trees.success.reseted'), ], 'unknown' => __('families/trees.unknown'), ]; } } ================================================ FILE: app/Services/FilterService.php ================================================ request = $request; $this->data = $request->all(); $this->hasRequest = true; return $this; } public function options(array $data): self { $this->data = $data; return $this; } /** * Mark keys as navigation-only so they are not persisted to the filter session. * * @param string[] $keys */ public function ignoring(array $keys): self { $this->ignored = $keys; return $this; } /** * @throws \Exception */ public function model(Model $model): self { if (! method_exists($model, 'getFilterableColumns')) { throw new \Exception( 'Model ' . $model . ' doesn\'t implement the Filterable trait.', ); } $this->model = $model; return $this; } public function build(array $sortableColumns = []) { $this->crud = $this->entityType->id . '-' . $this->campaign->id; $orderFields = array_unique( array_merge(['name', 'type', 'is_private'], $sortableColumns), ); $baseFilters = [ 'name', 'type', 'is_private', 'status_id', 'parent_id', 'template', 'tag_id', 'tags', 'has_image', 'has_posts', 'has_entry', 'has_entity_files', 'has_attributes', 'created_by', 'updated_by', 'attribute_name', 'attribute_value', 'archived', ]; // Merge entity-type-specific filterable columns if ($this->entityType->isStandard()) { $model = $this->entityType->getClass(); if (method_exists($model, 'getFilterableColumns')) { $baseFilters = array_unique( array_merge($baseFilters, $model->getFilterableColumns()), ); } } $this->prepareFilters($baseFilters) ->prepareOrder($orderFields) ->prepareSearch(); } public function make(string $crud) { $this->crud = isset($this->campaign) ? $crud . '-' . $this->campaign->id : $crud; $this->prepareFilters($this->model->getFilterableColumns()) // @phpstan-ignore-line ->prepareOrder($this->model->sortableColumns()) // @phpstan-ignore-line ->prepareSearch(); } /** * Prepare the filters */ protected function prepareFilters(array $availableFilters = []): self { // No point in doing any work if the model has no fields to filter. if (empty($availableFilters)) { return $this; } // Load the filters from the session if we're revisiting a page $sessionKey = 'filterService-filter-' . $this->crud; if ( $this->hasRequest && $this->request->get('_from', false) == 'bookmark' ) { $sessionKey .= '-bookmark'; } $this->filters = $this->sessionLoad($sessionKey); // If the request has _clean, we only want filters that are set in the url if ($this->hasRequest && $this->request->get('_clean', false)) { $this->filters = []; } foreach ( [ 'tags', 'locations', 'organisations', 'races', 'families', 'creators', ] as $key ) { $this->checkFilterData($key, $availableFilters); } // If we have data but no "tags" array, it's empty if ( ! empty($this->data) && in_array('tags', $availableFilters) && ! isset($this->data['tags']) ) { // Not calling from a page or order result, we can junk the filters if (empty($this->data['page']) && empty($this->data['order'])) { unset($this->data['tags']); } } // If we have data but no "locations" array, it's empty if ( ! empty($this->data) && in_array('locations', $availableFilters) && ! isset($this->data['locations']) ) { // Not calling from a page or order result, we can junk the filters if (empty($this->data['page']) && empty($this->data['order'])) { unset($this->data['locations']); } } // If we have data but no "organisations" array, it's empty if ( ! empty($this->data) && in_array('organisations', $availableFilters) && ! isset($this->data['organisations']) ) { // Not calling from a page or order result, we can junk the filters if (empty($this->data['page']) && empty($this->data['order'])) { unset($this->data['organisations']); } } // If we have data but no "races" array, it's empty if ( ! empty($this->data) && in_array('races', $availableFilters) && ! isset($this->data['races']) ) { // Not calling from a page or order result, we can junk the filters if (empty($this->data['page']) && empty($this->data['order'])) { unset($this->data['races']); } } // If we have data but no "families" array, it's empty if ( ! empty($this->data) && in_array('families', $availableFilters) && ! isset($this->data['families']) ) { // Not calling from a page or order result, we can junk the filters if (empty($this->data['page']) && empty($this->data['order'])) { unset($this->data['families']); } } // Convert legacy tag_id to tags[] so it is treated like a normal tag filter if (isset($this->data['tag_id']) && in_array('tags', $availableFilters)) { $this->data['tags'] = array_merge( is_array($this->data['tags'] ?? null) ? $this->data['tags'] : [], [(int) $this->data['tag_id']], ); unset($this->data['tag_id']); } // Don't support the old tags, force using tags[] if (isset($this->data['tags']) && ! is_array($this->data['tags'])) { unset($this->data['tags']); } foreach ($this->data as $key => $value) { if (in_array($key, $this->ignored)) { continue; } if (in_array($key, $availableFilters)) { // Update the value we have in the session. // But skip empty arrays (typically when sending `families[]=` if ($value === [0 => null]) { $value = null; } $this->filters[$key] = $value; continue; } // Of if it's the _option of a filter if (Str::endsWith($key, '_option')) { $stripedKey = Str::before($key, '_option'); if (in_array($stripedKey, $availableFilters)) { // Update the value we have in the session. $this->filters[$key] = $value; continue; } } } // Foreign keys that are not set might have been cleared. If so, remove them from the filters. // However, only do this if not ordering or changing pages, and not doing a clean reset. $isClean = $this->hasRequest && $this->request->get('_clean', false); if ( ! $isClean && ! empty($this->data) && ! array_key_exists('order', $this->data) && ! array_key_exists('page', $this->data) ) { foreach ($availableFilters as $filter) { if (in_array($filter, $this->ignored)) { continue; } if ( ! isset($this->data[$filter]) && (Str::endsWith($filter, '_id') || in_array($filter, ['created_by', 'updated_by'])) ) { $this->filters[$filter] = null; } } } // Reset the filters if requested, before saving it to the session. if (Arr::has($this->data, 'reset-filter')) { $this->filters = []; } // Save the new data into the session $this->sessionSave($sessionKey, $this->filters); return $this; } /** * Prepare the Order By data * * @property array $availableFields */ protected function prepareOrder(array $availableFields = []): self { // Get all the posted data. We need to see if any of it is part of a filter. $field = Arr::get($this->data, 'order'); $direction = Arr::get($this->data, 'desc'); $sessionKey = 'filterService-order-' . $this->crud; $this->order = session()->get($sessionKey, []); if ($this->order === null) { $this->order = []; } // Validate that the session's saved order field is still valid if (! empty($this->order) && ! empty($availableFields)) { $sessionField = array_key_first($this->order); if (! in_array($sessionField, $availableFields)) { $this->order = []; } } if (! empty($field) && is_string($field)) { if ($field === 'clear') { // Explicit reset from the grid's third-click cycle $this->order = []; } else { $this->order = [ $field => empty($direction) ? 'ASC' : 'DESC', ]; if (! in_array($field, $availableFields)) { $this->order = []; } } } // Reset the filters if requested, before saving it to the session. if (Arr::has($this->data, 'reset-filter')) { $this->order = []; } // Save the new data into the session session()->put($sessionKey, $this->order); return $this; } private function checkFilterData(string $key, array $availableFilters): void { // If we have data but no array, it's empty if ( ! empty($this->data) && in_array($key, $availableFilters) && ! isset($this->data[$key]) ) { // Not calling from a page or order result, we can junk the filters if (empty($this->data['page']) && empty($this->data['order'])) { unset($this->data[$key]); } } } private function prepareSearch(): self { $search = Arr::get($this->data, 'search'); $this->search = $search ? strip_tags($search) : ''; return $this; } /** * @param string|array $key * @return array|Translator|mixed|string|null * * @throws \Exception */ public function single(mixed $key, ?string $default = null) { if (is_array($key)) { throw new \Exception('Key for FilterService can\'t be an array'); } if (! empty($this->filters) && isset($this->filters[$key])) { if ($this->isCheckbox($key)) { return $this->filters[$key] == '1' ? __('general.yes') : __('general.no'); } $result = $this->filters[$key]; return is_array($result) ? $default : $result; } return $default; } /** * @param string|array $key * * @throws \Exception */ public function filterValue(mixed $key, ?string $default = null) { if (is_array($key)) { throw new \Exception('Key for FilterService can\'t be an array'); } if (! empty($this->filters) && isset($this->filters[$key])) { return $this->filters[$key]; } return $default; } /** * Get the filters */ public function filters(): array { return $this->filters; } /** * Get the order data */ public function order(): array { return $this->order; } /** * Get the search data */ public function search(): string { return $this->search; } /** * Determine if a filter is a checkbox */ public function isCheckbox(string $field): bool { return Str::startsWith($field, ['is_', 'has_']); } /** * Get the active filters */ public function activeFilters(): array { if (empty($this->filters)) { return []; } $filters = []; foreach ($this->filters as $key => $val) { if ($val !== null) { $filters[$key] = $val; } } return $filters; } public function activeFiltersCount(): int { return count($this->activeFilters()); } /** * Determine if the request has active filters */ public function hasFilters(): bool { return $this->activeFiltersCount() > 0; } /** * Prepare data to append to the crud pagination */ public function pagination(): array { $options = []; if (! empty($this->search)) { $options['search'] = $this->search; } if (! $this->hasRequest) { return $options; } if ($this->request->get('_from', false) == 'bookmark') { $options['_from'] = 'bookmark'; } if ($bookmarkId = $this->request->get('bookmark')) { $options['bookmark'] = (int) $bookmarkId; } if ($this->request->filled('parent_id')) { $options['parent_id'] = (int) $this->request->get('parent_id'); } return $options; } /** * Flag the service to save the filters in session */ public function session(bool $session = true): self { $this->session = $session; return $this; } /** * Load the stored filter data from session */ protected function sessionLoad(string $key): array { if (! $this->session) { return []; } if (! session()->has($key)) { return []; } return session()->get($key); } /** * Save the filter data to the session */ protected function sessionSave(string $key, $data): self { if (! $this->session) { return $this; } session()->put($key, $data); return $this; } /** * Prepare filters to be copied to the clipboard */ public function clipboardFilters(): string { $filters = []; foreach ($this->filters as $key => $val) { if ($val !== null) { if (! is_array($val)) { $filters[] = $key . '=' . $val; continue; } foreach ($val as $arrValue) { // If it's an array in an array, we don't support it. // For example calling the page with tags[bla][bli][blo] if (is_array($arrValue)) { continue; } $filters[] = $key . '[]=' . $arrValue; } } } return (string) implode('&', $filters); } } ================================================ FILE: app/Services/FormCopyService.php ================================================ fromChild = true; return $this; } public function source(Entity|Model|null $source = null): self { $this->source = $source; return $this; } public function field(string $field): self { $this->field = $field; return $this; } /** * @param string|null $default * @return string|null */ public function string(mixed $default = null) { if ($this->valid()) { return $this->getValue(); } return $default; } /** * Get values for a select field */ public function select(bool $checkForParent = false, ?string $parentClass = null): array { // Only copy on MiscModel (entity) models if ($this->valid()) { $value = $this->getValues(); if (! empty($value) && is_object($value)) { return [$value->id => $value->name]; } } $parent = isset($this->request) ? $this->request->get('parent_id', false) : false; if ($checkForParent && $parent !== false) { /** @var Model $class */ $class = new $parentClass; /** @var ?MiscModel $parent */ $parent = $class->find($parent); if ($parent) { return [$parent->id => $parent->name]; } } return []; } /** * Character traits */ public function characterPersonality(): Collection { if ($this->valid()) { $this->fromChild = false; // @phpstan-ignore-next-line if (auth()->user()->can('personality', $this->source->child)) { // @phpstan-ignore-next-line return $this->source->child->characterTraits()->personality()->get(); } } return new Collection; } /** * Character traits */ public function characterAppearance(): Collection { if ($this->valid()) { $this->fromChild = false; // @phpstan-ignore-next-line return $this->source->child->characterTraits()->appearance()->get(); } return new Collection; } /** * Character organisations */ public function characterOrganisation(): Collection { if ($this->valid()) { $this->fromChild = false; // @phpstan-ignore-next-line return $this->source->child->organisationMemberships() ->with('organisation') ->has('organisation') ->get(); } return new Collection; } public function boolean(bool $default = false): bool { // Only copy on MiscModel (entity) models if ($this->valid()) { return $this->getValue() ? true : false; } return $default; } /** * Prefill model for custom blade directives */ public function model(): ?MiscModel { // Only copy on MiscModel (entity) models if ($this->valid()) { // @phpstan-ignore-next-line return $this->source->child; } return null; } /** * Prefill model for custom blade directives */ public function related() { // Only copy on MiscModel (entity) models if ($this->valid()) { return $this->source->{$this->field}; } return null; } /** * @param bool $withNull include "none" option */ public function colours(bool $withNull = true): array { $colours = $withNull ? [ '' => __('colours.none'), ] : []; $colourKeys = config('colours.keys'); foreach ($colourKeys as $colour) { $colours[$colour] = trans('colours.' . $colour); } asort($colours); return $colours; } public function __toString(): string { return (string) $this->getValue(); } private function valid(): bool { return ! empty($this->source); } private function getValue() { if (! $this->valid()) { return null; } if (! $this->source instanceof Entity || ! $this->fromChild) { return $this->source->getAttributeValue($this->field); } $this->fromChild = false; if ($this->source->isMissingChild()) { return null; } return $this->source->child->getAttributeValue($this->field); } private function getValues() { if (! $this->valid()) { return null; } if (! $this->source instanceof Entity || ! $this->fromChild) { return $this->source->{$this->field}; } $this->fromChild = false; if ($this->source->isMissingChild()) { return null; } return $this->source->child->{$this->field}; } } ================================================ FILE: app/Services/Gallery/BrowseService.php ================================================ term = $term; return $this; } public function folder(?string $folder): self { $this->folder = $folder; return $this; } public function images(): array { $results = []; $canBrowse = $this->user->can('galleryBrowse', $this->campaign); if (! empty($this->folder)) { $image = Image::where('is_folder', true)->where('id', $this->folder)->firstOrFail(); $results['images'][] = [ 'name' => __('crud.actions.back'), 'folder' => true, 'icon' => 'fa-regular fa-arrow-left', 'url' => route('gallery.browse', [$this->campaign, 'folder' => $image->folder_id]), ]; } $images = Image::acl($canBrowse) ->search($this->folder, $this->term) ->where('is_default', false) ->orderBy('is_folder', 'desc') ->orderBy('updated_at', 'desc') ->offset(0) ->take(50) ->get(); /** @var Image $image */ foreach ($images as $image) { $results['images'][] = [ 'src' => $image->url(), 'name' => $image->name, 'folder' => $image->isFolder(), 'uuid' => $image->id, 'icon' => 'fa-regular fa-folder', 'url' => $image->isFolder() ? route('gallery.browse', [$this->campaign, 'folder' => $image->id]) : null, 'thumbnail' => $image->getUrl(192, 144), ]; } return $results; } } ================================================ FILE: app/Services/Gallery/CreateService.php ================================================ campaign_id = $this->campaign->id; $folder->folder_id = $request->get('folder_id'); $folder->name = $request->get('name'); $folder->visibility_id = $request->get('visibility_id'); $folder->is_folder = true; $folder->save(); return (new GalleryFile($folder))->campaign($this->campaign); } } ================================================ FILE: app/Services/Gallery/DeleteService.php ================================================ first(); if (! $image) { continue; } $image->delete(); $count++; } return $count; } } ================================================ FILE: app/Services/Gallery/SelectionService.php ================================================ storage = $storage; } public function image(Image $image): self { $this->image = $image; return $this; } public function term(?string $term): self { $this->term = $term; return $this; } public function filters(array $filters): self { $this->filters = $filters; return $this; } public function sort(array $sort): self { $this->sort = $sort; return $this; } public function setup(): array { return [ 'acl' => [ 'manage' => $this->user->can('galleryManage', $this->campaign), 'upload' => $this->user->can('galleryUpload', $this->campaign), 'premium' => $this->campaign->boosted() || $this->storage->campaign($this->campaign)->isUnlimited(), 'elemental' => $this->campaign->isElemental(), 'wyvern' => $this->campaign->isWyvern(), ], 'files' => $this->files(), 'i18n' => $this->i18n(), 'folders' => $this->folders(), 'api' => [ 'search' => route('gallery.search', [$this->campaign]), 'delete' => route('gallery.delete', [$this->campaign]), 'create' => route('gallery.create', [$this->campaign]), 'update' => route('gallery.update', [$this->campaign]), 'upload' => route('gallery.upload.files', [$this->campaign]), ], 'next' => $this->nextPage ?? null, 'visibilities' => $this->visibilities(), 'bulkVisibilities' => $this->visibilities(true), 'url' => route('gallery', $this->campaign), 'space' => $this->space(), 'upgrade' => $this->upgradeLink(), ]; } /** * Open a file */ public function open(): array { return [ 'folder' => $this->image, 'files' => $this->files(), 'breadcrumbs' => $this->breadcrumbs(), 'next' => $this->nextPage ?? null, 'url' => route('gallery', [$this->campaign, 'folder' => $this->image->id]), ]; } public function search(): array { return [ 'files' => $this->files(), 'next' => $this->nextPage ?? null, ]; } protected function files(): array { $this->files = new Collection; $query = $this ->campaign ->images() ->with(['images', 'creator']); if (isset($this->sort) && Arr::has($this->sort, 'sort') && in_array($this->sort['sort'], ['asc', 'desc'])) { $query->sortOrder($this->sort['sort']); } else { $query->defaultOrder(); } if (isset($this->filters) && Arr::has($this->filters, 'unused')) { $query ->distinct() ->select('images.*') ->leftJoin('image_mentions as im', 'im.image_id', 'images.id') ->leftJoin('entities as e', 'e.image_uuid', 'images.id') ->leftJoin('entities as eh', 'eh.header_uuid', 'images.id') ->whereNull('im.id') ->whereNull('e.id') ->whereNull('eh.id') ->where('is_folder', false); } if (isset($this->term)) { $query->named($this->term); } else { $query->imageFolder(isset($this->image) ? $this->image->id : null); } $files = $query->paginate(25); /** @var Image $file */ foreach ($files as $file) { $fileData = (new GalleryFile($file))->campaign($this->campaign); $this->files->add($fileData); } if ($files->hasMorePages()) { $this->nextPage = $files->appends($this->filters ?? null)->nextPageUrl(); } return $this->files->toArray(); } protected function i18n(): array { return [ 'filters' => __('bookmarks.fields.filters'), 'new_folder' => __('campaigns/gallery.uploader.new_folder'), 'select' => __('crud.select'), 'cancel' => __('crud.cancel'), 'remove' => __('crud.remove'), 'create' => __('crud.create'), 'update' => __('crud.update'), 'move' => __('crud.actions.move'), 'home' => __('Home'), 'load_more' => __('Load more'), 'upload_hint' => empty(config('limits.filesize.image.standard')) ? __('crud.hints.image_formats', ['formats' => 'jpg, png, webp, gif, woff2']) : __('crud.files.hints.limitations', ['formats' => 'jpg, png, webp, gif, woff2', 'size' => Limit::readable()->upload()]), // Space 'storage' => __('campaigns/gallery.storage.title'), 'of' => __('campaigns/gallery.storage.of'), 'upgrade' => __('campaigns/gallery.actions.upgrade'), // Files 'details' => __('campaigns/gallery.fields.details'), 'used_in' => __('campaigns/gallery.fields.used_in'), 'unused' => __('campaigns/gallery.fields.unused'), 'name' => __('crud.fields.name'), 'delete' => __('crud.remove'), 'save' => __('crud.save'), 'saved' => __('gallery.file.saved'), 'confirm' => __('crud.actions.confirm'), 'visibility' => __('crud.fields.visibility'), 'size' => __('campaigns/gallery.fields.size'), 'file_type' => __('campaigns/gallery.fields.file_type'), 'uploaded_by' => __('campaigns/gallery.fields.created_by'), 'focus_point' => __('campaigns/gallery.actions.focus_point'), 'link' => __('campaigns/gallery.fields.link'), 'open' => __('crud.actions.open'), 'focus_locked' => __('campaigns/gallery.focus.locked'), 'folder' => __('campaigns/gallery.fields.folder'), 'change' => __('crud.actions.change'), // Filters 'filter_only_unused' => __('gallery.filters.only_unused'), 'visibility.1' => __('crud.visibilities.all'), 'visibility.2' => __('crud.visibilities.admin'), 'visibility.3' => __('crud.visibilities.admin-self'), 'visibility.4' => __('crud.visibilities.self'), 'visibility.5' => __('crud.visibilities.members'), 'sort' => __('gallery.filters.sort'), 'sort_asc' => __('crud.filters.sorting.asc', ['field' => 'Name']), 'sort_desc' => __('crud.filters.sorting.desc', ['field' => 'Name']), 'sort_default' => __('dashboard.widgets.orders.recent'), ]; } protected function folders(): array { $folders = [ '' => '', 0 => __('gallery.update.home'), ]; $query = $this ->campaign ->images() ->select(['id', 'name']) ->folders() ->get(); /** @var Image $folder */ foreach ($query as $folder) { $folders[$folder->id] = $folder->name; } return $folders; } protected function breadcrumbs(): array { $crumbs = []; if (! $this->image->imageFolder) { return $crumbs; } $parent = $this->image->imageFolder; if ($parent->imageFolder) { $crumbs[] = [ 'name' => $parent->imageFolder->name, 'open' => route('gallery.show', [$this->campaign, $parent->imageFolder]), ]; } $crumbs[] = [ 'name' => $parent->name, 'open' => route('gallery.show', [$this->campaign, $parent]), ]; return $crumbs; } protected function visibilities(bool $withNull = false): array { $options = []; if ($withNull) { $options[] = __(''); } $options[Visibility::All->value] = __('crud.visibilities.all'); if ($this->user->isAdmin()) { $options[Visibility::Admin->value] = __('crud.visibilities.admin'); $options[Visibility::Member->value] = __('crud.visibilities.members'); } $options[Visibility::Self->value] = __('crud.visibilities.self'); $options[Visibility::AdminSelf->value] = __('crud.visibilities.admin-self'); return $options; } protected function space(): array { $storage = $this->storage->campaign($this->campaign); return [ 'total' => $storage->isUnlimited() ? 0 : $storage->totalSpace(), 'used' => $storage->usedSpace(), ]; } protected function upgradeLink(): ?string { if ($this->campaign->isElemental() || $this->campaign->isWyvern()) { return null; } elseif ($this->campaign->boosted()) { return route('settings.subscription'); } return route('settings.premium'); } } ================================================ FILE: app/Services/Gallery/StorageService.php ================================================ used)) { return $this->used; } return $this->used = Cache::remember($this->cacheKey(), 24 * 3600, function () { return Image::sum('size'); }); } public function uncachedUsedSpace(): int { return Image::sum('size'); } /** * Available space in KB */ public function available(): int { return $this->totalSpace() - $this->usedSpace(); } public function isUnlimited(): bool { $flags = CampaignCache::campaign($this->campaign)->flags(); if ($flags->has('gallery') || $this->campaign->boosted()) { return false; } return empty(config('limits.gallery.standard')); } /** * Total size in mb */ public function totalSpace(): int { $flags = CampaignCache::campaign($this->campaign)->flags(); if ($flags->has('gallery')) { return $flags->get('gallery'); } if ($this->campaign->boosted()) { if ($this->campaign->isWyvern()) { return config('limits.gallery.wyvern'); } elseif ($this->campaign->isElemental()) { return config('limits.gallery.elemental'); } return config('limits.gallery.premium'); } $standard = config('limits.gallery.standard'); if (empty($standard)) { return PHP_INT_MAX; } return $standard; } protected function cacheKey(): string { return 'campaign_' . $this->campaign->id . '_gallery'; } public function clearCache(): self { Cache::forget($this->cacheKey()); return $this; } } ================================================ FILE: app/Services/Gallery/SummernoteService.php ================================================ storage = $storageService; } public function store(GalleryImageStore $request, string $field = 'file'): array { $images = []; $files = $request->file($field); if (! is_array($files)) { $files = [$files]; } $available = $this->storage->campaign($this->campaign)->available(); foreach ($files as $source) { // Prepare the name as sent by the user. It gets purified in the observer if (empty($source)) { continue; } $name = $source->getClientOriginalName(); $name = Str::beforeLast($name, '.'); $image = new Image; $image->campaign_id = $this->campaign->id; $image->ext = $source->extension(); $image->size = (int) ceil($source->getSize() / 1024); // kb $image->name = mb_substr($name, 0, 45); $image->folder_id = $request->post('folder_id'); $image->visibility_id = $this->campaign->defaultGalleryVisibility(); // Check remaining space again before saving, as the user could be near max and uploading multiple // files at a time to bypass the size restrictions $available -= $image->size; if ($available < 0) { continue; } $image->save(); $source ->storePubliclyAs( $image->folder, $image->file, ['disk' => 's3'] ); $images[] = $image; } $this->storage->clearCache(); return $images; } } ================================================ FILE: app/Services/Gallery/TiptapService.php ================================================ setup(); } /** * Return the initial gallery view: folders and up to 50 images */ protected function setup(): LengthAwarePaginator { $canBrowse = $this->user->can('galleryBrowse', $this->campaign); $images = Image::acl($canBrowse) ->search($this->request->get('folder'), $this->request->get('term')) ->where('is_default', false) ->orderBy('is_folder', 'desc') ->orderBy('updated_at', 'desc') ->paginate(50); return $images; } } ================================================ FILE: app/Services/Gallery/UpdateService.php ================================================ files = []; foreach ($ids as $id) { $image = Image::where('id', $id)->first(); if (! $image) { continue; } $this->files[] = $image; } return $this; } public function update(array $data): int { $folderId = Arr::get($data, 'folder_id'); $home = Arr::get($data, 'folder_home'); $visibilityId = Arr::get($data, 'visibility_id'); if (! empty($folderId)) { // Make sure it's a folder from the current campaign $folder = Image::where('id', $folderId)->where('is_folder', 1)->firstOrFail(); } foreach ($this->files as $file) { // todo: prevent loops? if (isset($folder)) { $file->folder_id = $folder->id; } elseif (! empty($home)) { $file->folder_id = null; } if (! empty($visibilityId)) { $file->visibility_id = $visibilityId; } $file->save(); } return count($this->files); } } ================================================ FILE: app/Services/Gallery/UploadService.php ================================================ storage = $storageService; } public function request(FormRequest $request): self { $this->request = $request; return $this; } public function image(): Image { return $this->image; } public function folder(string $folder): self { if (empty($folder)) { unset($this->folder); return $this; } $this->folder = $folder; return $this; } public function data(array $data): self { $this->data = $data; return $this; } public function file(UploadedFile $file): array { $this->image = new Image; $this->image->campaign_id = $this->campaign->id; $this->image->name = Str::beforeLast($file->getClientOriginalName(), '.'); $this->image->ext = Str::before($file->extension(), '?'); $this->image->size = (int) ceil($file->getSize() / 1024); // kb $this->image->visibility_id = $this->campaign->defaultGalleryVisibility(); if (isset($this->folder)) { $this->image->folder_id = $this->folder; } $this->image->save(); $file->storePubliclyAs($this->image->folder, $this->image->file); $this->storage->campaign($this->campaign)->clearCache(); return $this->format(); } public function files(array $files): array { $data = []; $available = $this->storage->campaign($this->campaign)->available(); foreach ($files as $file) { // If we have enough space $kb = (int) ceil($file->getSize() / 1024); if ($kb > $available) { continue; } $this->file($file); $available -= $kb; $data[] = (new GalleryFile($this->image))->campaign($this->campaign); } return $data; } public function url(string $url): array { $this->image = new Image; $externalFile = basename($url); $tempImage = tempnam(sys_get_temp_dir(), $externalFile); try { copy($url, $tempImage); } catch (\Exception $e) { throw ValidationException::withMessages([__('gallery.download.errors.copy_failed')]); } $cleanImageName = Str::slug( Str::before( Str::before($externalFile, '%3F'), '?' ) ); $cleanImageName = str_replace(['.', '/'], ['', ''], $cleanImageName); // Check if file is too big $copiedFileSize = ceil(filesize($tempImage) / 1024); if ($this->request->has('map')) { Limit::map(); } $max = Limit::upload(); if ($copiedFileSize > $max) { unlink($tempImage); throw ValidationException::withMessages([__('gallery.download.errors.too_big', [ 'size' => Number::format($copiedFileSize / 1024, 2), 'max' => Number::format($max / 1024, 2), ])]); } $available = $this->storage->campaign($this->campaign)->available(); if ($copiedFileSize > $available) { unlink($tempImage); $key = 'gallery.download.errors.gallery_full_free'; if ($this->campaign->boosted()) { $key = 'gallery.download.errors.gallery_full_premium'; } throw ValidationException::withMessages([__($key)]); } $file = new UploadedFile($tempImage, basename($url)); // Invalid file type? $ext = mb_strtolower($file->guessExtension()); if (! in_array($ext, ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'])) { unlink($tempImage); $key = 'gallery.download.errors.invalid_format'; throw ValidationException::withMessages([__($key)]); } $this->image->name = $cleanImageName; $this->image->campaign_id = $this->campaign->id; $this->image->ext = $file->guessExtension(); $this->image->size = (int) ceil($copiedFileSize); // kb $this->image->visibility_id = $this->campaign->defaultGalleryVisibility(); if (isset($this->folder)) { $this->image->folder_id = $this->folder; } $this->image->save(); if ($this->image->isSvg()) { // GD can't handle svgs, so we need to move them directly Storage::put($this->image->path, $this->sanitizedSvg($file), 'public'); } else { $manager = new ImageManager( new Driver ); $image = $manager->read($file); Storage::put($this->image->path, (string) $image->toJpeg(), 'public'); } unlink($tempImage); $this->storage->clearCache(); return $this->format(); } protected function sanitizedSvg(string $path): string { $sanitizer = new Sanitizer; // Custom allowed attributes for AFMG $allowedAttributes = new SvgAllowedAttributes; $sanitizer->setAllowedAttrs($allowedAttributes); $dirtySVG = file_get_contents($path); $cleanSVG = $sanitizer->sanitize($dirtySVG); file_put_contents($path, $cleanSVG); return $cleanSVG; } protected function format(): array { return [ 'id' => $this->image->id, 'name' => $this->image->name, 'uuid' => $this->image->id, 'path' => $this->image->path, 'thumbnail' => $this->image->getUrl(192, 144), 'src' => $this->image->getUrl(), ]; } } ================================================ FILE: app/Services/GenreService.php ================================================ __('campaigns.fields.genre')]; } foreach (Genre::get() as $genre) { $genres[$genre->id] = trans('genres.' . $genre->slug); } return $genres; } } ================================================ FILE: app/Services/IdentityManager.php ================================================ app = $app; } public function switch(CampaignUser $campaignUser): bool { try { Switched::dispatch($campaignUser->campaign, auth()->user(), $campaignUser); // Save the current user in the session to know we have limitation on the current user. session()->put($this->getSessionKey(), $this->app['auth']->user()->id); session()->put($this->getSessionCampaignKey(), $this->campaign->id); // Log this action session()->put('kanka.userLog', UserAction::userSwitchLogin); $this->app['auth']->loginUsingId($campaignUser->user->id); } catch (Exception $e) { return false; } // Dispatch a log for the user? return true; } public function back(): bool { // Not actually impersonating anyone? Sure. if (! $this->isImpersonating()) { return false; } try { // $impersonated = $this->app['auth']->user(); $impersonator = $this->findUserById($this->getImpersonatorId()); session()->put('kanka.userLog', UserAction::userRevert); $this->app['auth']->loginUsingId($impersonator->id); $this->clear(); } catch (Exception $e) { return false; } // Dispatch a log for the user? return true; } /** * Determine if we are someone else that we usually are. */ public function isImpersonating(): bool { return session()->has($this->getSessionKey()); } protected function findUserById(int $id): User { return User::findOrFail($id); } /** * The Key used to determine where our original user is stored */ public function getSessionKey(): string { return 'kanka.originalUserID'; } /** * The Key used to determine where our original campaign is stored */ public function getSessionCampaignKey(): string { return 'kanka.originalCampaignID'; } public function getImpersonatorId() { return session($this->getSessionKey(), null); } public function getCampaignId() { return session($this->getSessionCampaignKey(), null); } /** * Forget the saved user identity. */ protected function clear(): bool { session()->forget($this->getSessionKey()); session()->forget($this->getSessionCampaignKey()); return true; } } ================================================ FILE: app/Services/Images/AvatarService.php ================================================ child = $miscModel; return $this; } public function field(string $field): self { $this->field = $field; return $this; } public function cached(): self { $this->withCache = true; return $this; } public function size(int $width, int $height = 0): self { $this->width = $width; $this->height = ! empty($height) ? $height : $width; return $this; } public function fallback(): self { $this->fallback = true; return $this; } public function reset(): self { $this->field = 'image'; $this->fallback = false; $this->withCache = false; unset($this->entity, $this->child); return $this; } public function thumbnail(): string { if (! $this->hasImage()) { return $this->fallbackThumbnail(); } if ($this->onEntity()) { return $this->entity->image->getUrl($this->width, $this->height); } return $this->childThumbnail(); } /** * Get the full url of the original images */ public function original(): string { if (! $this->hasImage()) { return ''; } if ($this->onEntity()) { return $this->entity->image->url(); } $path = $this->childThumbnailPath(); $cdn = config('cdn.ugc'); if ($cdn) { return $cdn . '/' . $path; } return Storage::url($path); } protected function onEntity(): bool { return ! empty($this->entity->image); } protected function childThumbnail(): string { $url = $this->childThumbnailPath(); if (empty($url)) { return $this->fallbackThumbnail(); } $img = Img::resetCrop()->crop($this->width, $this->height); if (! empty($this->width)) { if (! empty($this->entity->focus_x) && ! empty($this->entity->focus_y)) { $img = $img->focus($this->entity->focus_x, $this->entity->focus_y); } } return $this->return( $img->url($url) ); } public function hasImage(): bool { return $this->entity->image || ! empty($this->childThumbnailPath()); } protected function fallbackThumbnail(): string { if (! $this->fallback) { return $this->return(''); } $cloudfront = config('filesystems.disks.cloudfront.url'); if ($this->campaign->boosted() && Arr::has(CampaignCache::campaign($this->campaign)->defaultImages(), $this->entity->entityType->code)) { $url = Img::crop($this->width, $this->height) ->url(CampaignCache::defaultImages()[$this->entity->entityType->code]); return $this->return($url); } elseif ($this->entity->entityType->isStandard() && ($this->campaign->premium() || (isset($this->user) && $this->user->isGoblin()))) { return $this->return($cloudfront . '/images/defaults/subscribers/' . $this->entity->entityType->pluralCode() . '.jpeg'); } // Default fallback return $this->return($cloudfront . '/images/defaults/thumbnail.jpg'); } protected function return(string $url): string { $this->reset(); return $url; } protected function getChild(): MiscModel { if (isset($this->child)) { return $this->child; } return $this->child = $this->entity->child; } protected function childThumbnailPath(): ?string { return $this->entity->image_path; } public function forget(): void { Cache::forget($this->cacheKey()); } protected function cacheKey(): string { return 'entity_' . $this->entity->id . '_avatar_v3'; } } ================================================ FILE: app/Services/ImagesService.php ================================================ model = $model; return $this; } public function field(string $field): self { $this->field = $field; return $this; } public function folder(string $folder): self { $this->folder = $folder; return $this; } public function handle() { // Remove the old image if ($this->request->post('remove-' . $this->field) == '1') { $this->cleanup(); return; } // No new image if (! $this->request->has($this->field) && ! $this->request->filled($this->field . '_url')) { return; } try { $cleanSVG = null; $url = $this->request->filled($this->field . '_url'); // Download the file locally to check it out if ($url) { $externalUrl = $this->request->input($this->field . '_url'); $externalFile = basename($externalUrl); $tempImage = tempnam(sys_get_temp_dir(), $externalFile); copy($externalUrl, $tempImage); $file = $tempImage; // Clean up the file name because weird letters can confuse thumbor // $cleanImageName = Str::replace('&', '', $externalFile); $cleanImageName = Str::slug( Str::before( Str::before($externalFile, '%3F'), '?' ) ); $cleanImageName = str_replace(['.', '/'], ['', ''], $cleanImageName); $path = "{$this->folder}/" . uniqid() . '_' . Str::limit($cleanImageName, 20, ''); // Check if file is too big $copiedFileSize = ceil(filesize($tempImage) / 1000); if ($copiedFileSize > Limit::upload()) { unlink($tempImage); throw new Exception('image_url target too big'); } $file = new UploadedFile($tempImage, basename($externalUrl)); // Add back the extension if it's missing after trimming long names $imageUrlExt = '.' . str_replace('image/', '', $file->getMimeType()); if (! Str::endsWith($path, $imageUrlExt)) { $path = $path . mb_strtolower($imageUrlExt); } } else { $file = $this->request->file($this->field); $path = $file->hashName($this->folder); } // Sanitize SVGs to avoid any XSS attacks if ($file->getMimeType() == 'image/svg+xml') { $sanitizer = new Sanitizer; // Custom allowed attributes for AFMG $allowedAttributes = new SvgAllowedAttributes; $sanitizer->setAllowedAttrs($allowedAttributes); $dirtySVG = file_get_contents($file); $cleanSVG = $sanitizer->sanitize($dirtySVG); file_put_contents($file, $cleanSVG); } if (! empty($path)) { // Remove old $this->cleanup(); // Save the new image if ($url) { if ($file->getMimeType() == 'image/svg+xml') { // GD can't handle svgs, so we need to move them directly Storage::put($path, $cleanSVG, 'public'); } else { $manager = new ImageManager( new Driver ); $image = $manager->read($file); Storage::put($path, (string) $image->toJpeg(), 'public'); } } else { $path = $this->request->file($this->field)->storePublicly($this->folder); } if ($this->model instanceof Entity) { $this->model->image_path = $path; } else { $this->model->{$this->field} = $path; } } } catch (Exception $e) { // throw $e; // There was an error getting the image. Could be the url, could be the request. session()->flash('warning', trans('crud.image.error', ['size' => Limit::readable()->upload()])); } } /** * Delete old image and thumb */ public function cleanup(): void { if ($this->model instanceof Entity && $this->field === 'image') { $this->field = 'image_path'; } if (empty($this->model->{$this->field})) { return; } try { Storage::delete($this->model->{$this->field}); // Leave removing thumbs for old campaigns $thumb = str_replace('.', '_thumb.', $this->model->{$this->field}); if (Storage::has($thumb)) { Storage::delete($thumb); } // If it's a map, reset its height to be re-calculated if ($this->model instanceof Entity && $this->model->isMap()) { $this->model->map->height = null; $this->model->map->width = null; $this->model->map->saveQuietly(); } } catch (Exception $e) { // silence exception, didn't find the image to delete. } $this->model->{$this->field} = null; } } ================================================ FILE: app/Services/ImgService.php ================================================ enabled = ! empty(config('thumbor.key')); $this->local = config('thumbor.key') === 'local'; } public function console(): self { $this->console = true; return $this; } public function new(): self { $this->new = true; return $this; } public function crop(int $width, ?int $height = null): self { if ($width !== 0) { if ($height === null) { $height = $width; } $this->crop = "{$width}x{$height}/"; } return $this; } public function focus(int $x, int $y): self { $this->focusX = $x; $this->focusY = $y; return $this; } public function resetCrop(): self { $this->crop = ''; return $this; } public function reset(): self { $this->crop = ''; $this->focusX = null; $this->focusY = null; return $this; } public function base(?string $base = 'user'): self { // if (!empty($this->s3)) { // return $this;w // } $this->base = $base; if ($base === 'app') { $this->s3 = config('thumbor.bases.app'); } else { $this->s3 = config('thumbor.bases.user'); } return $this; } public function url(string $img): string { // Self-hosted with no s3/minio instance or SVG files load directly from the storage if (! $this->enabled || Str::contains($img, '?') || Str::endsWith($img, '.svg')) { return Storage::url($img); } // Default base if (! $this->console) { $this->base(); } $img = Str::before($img, '?'); $full = $this->s3 . $img; $filter = 'smart/'; if (! empty($this->focusX)) { // left x top:right x bottom $filter = 'filters:focal(' . ($this->focusX - 10) . 'x' . ($this->focusY - 10) . ':' . ($this->focusX + 10) . 'x' . ($this->focusY + 10) . ')/'; $this->focusX = $this->focusY = null; } $thumborUrl = $this->crop . $filter . $full; $sign = $this->sign($thumborUrl); // If we're on a local instance, it's a lot easier, everything is in minio if ($this->local) { return config('thumbor.url') . 'unsafe/' . $this->crop . $filter . app()->environment() . '/' . urlencode($img); } elseif (Str::contains(config('thumbor.url'), 'th.kanka.io')) { // New server if (! app()->isProduction()) { $img = app()->environment() . '/' . $img; $full = $this->s3 . $img; } $thumborUrl = $this->crop . $filter . $full; $sign = $this->sign($thumborUrl); return config('thumbor.url') . $sign . '/' . $this->crop . $filter . 'src/' . $img; } // Old system return config('thumbor.url') . $this->base . '/' . $sign . '/' . $this->crop . $filter . 'src/' . urlencode($img); } protected function sign(string $url): string { $signature = hash_hmac('sha1', $url, config('thumbor.key'), true); return strtr(base64_encode($signature), '/+', '_-'); } } ================================================ FILE: app/Services/InviteService.php ================================================ campaignFollowService = $campaignFollowService; } /** * @throws RequireLoginException * @throws Exception */ public function useToken(?string $token = null): self { if (empty($token)) { throw new Exception(__('campaigns.invites.error.invalid_token')); } $this->invite = CampaignInvite::where('token', $token)->first(); if (empty($this->invite)) { throw new Exception(__('campaigns.invites.error.invalid_token')); } // Inactive (removed campaigns won't have their token still in the db) if (! $this->invite->is_active) { throw new Exception(__('campaigns.invites.error.inactive_token')); } if (! $this->invite->campaign->canHaveMoreMembers()) { throw new Exception(__('campaigns/limits.members')); } if (! isset($this->user)) { Session::put('invite_token', $this->invite->token); throw new RequireLoginException(__('campaigns.invites.error.join', ['campaign' => '' . $this->invite->campaign->name . ''])); } $this->join(); return $this; } public function attribute(): self { $this->user->referred_by = $this->invite->created_by; $this->user->save(); ReferralEvent::create([ 'created_by' => $this->user->id, 'referred_by' => $this->invite->created_by, 'type' => ReferralEventType::invite, ]); return $this; } public function invite(CampaignInvite $invite): self { $this->invite = $invite; return $this; } public function campaign(): Campaign { return $this->invite->campaign; } public function join(): self { Session::forget('invite_token'); // Already a member? $role = CampaignUser::campaignUser($this->invite->campaign->id, $this->user->id) ->first(); if (empty($role)) { $role = new CampaignUser([ 'user_id' => $this->user->id, 'campaign_id' => $this->invite->campaign->id, ]); $role->save(); } else { // User is already part of the campaign, don't go further otherwise one user can spam the join link and // use up all the available tokens (validity field). UserCache::clear(); return $this; } // Add the user to a role if it's provided by the invite link if ($this->invite->role) { $memberRole = CampaignRoleUser::create([ 'campaign_role_id' => $this->invite->role->id, 'user_id' => $role->user_id, ]); } // Invitation links can have a set number of usage (validity) $this->invalidate(); // If the user was following the campaign, remove it if ($this->invite->campaign->isFollowing()) { $this->campaignFollowService ->campaign($this->invite->campaign) ->user($this->user) ->remove(); } UserJoined::dispatch($this->invite->campaign, $this->user, $this->invite); return $this; } protected function invalidate(): void { if (empty($this->invite->validity)) { return; } $this->invite->validity--; if ($this->invite->validity <= 0) { $this->invite->is_active = false; } $this->invite->save(); } } ================================================ FILE: app/Services/LanguageService.php ================================================ '']; } foreach (LaravelLocalization::getSupportedLocales() as $langKey => $langData) { $languages[$langKey] = trans('languages.codes.' . $langKey); } return $languages; } } ================================================ FILE: app/Services/Layout/NavigationService.php ================================================ $this->profile(), 'campaigns' => $this->campaigns(), 'notifications' => $this->notificationsData(), 'marketplace' => $this->marketplace(), 'releases' => $this->releasesData(), 'has_unread' => $this->user->hasUnread(), 'fontawesome_pro' => ! empty(config('fontawesome.kit')), ]; } protected function profile(): array { $data = [ 'name' => $this->user->name, 'email' => $this->user->email, 'created' => __('users/profile.fields.member_since', ['date' => $this->user->created_at->format('M d, Y')]), 'is_impersonating' => false, 'your_profile' => __('header.user.your-profile'), ]; if (Identity::isImpersonating()) { $data['is_impersonating'] = true; // Get the campaign linked to the impersonation $campaign = Campaign::findOrFail(Identity::getCampaignId()); // \App\Facades\CampaignLocalization::setCampaign(Identity::getCampaignId()); $returnUrl = route('identity.back', $campaign); $campaignUrl = 'campaign/' . $campaign->id; $returnUrl = str_replace('campaign/navigation', $campaignUrl, $returnUrl); $data['return'] = [ 'url' => $returnUrl, 'name' => __('campaigns.members.actions.switch-back'), ]; return $data; } if (! empty($this->user->pledge) && $this->user->subscribed('kanka')) { $data['subscription'] = [ 'tier' => $this->user->pledge, 'created' => __('users/profile.fields.subscriber_since', ['date' => $this->user->subscription('kanka')->created_at->format('M d, Y')]), 'image' => 'https://d3a4xjr8r2ldhu.cloudfront.net/app/tiers/' . mb_strtolower($this->user->pledge) . '-128.png', 'boosters' => __('settings/boosters.available', [ 'amount' => $this->user->availableBoosts(), 'total' => $this->user->maxBoosts(), ]), ]; } else { $data['subscription'] = [ 'tier' => Pledge::KOBOLD, 'image' => 'https://d3a4xjr8r2ldhu.cloudfront.net/app/tiers/kobold-128.png', // 'call_to_action' => __('Subscriptions start at USD 5.00 per month') 'call_to_action' => __('settings/boosters.available', [ 'amount' => $this->user->availableBoosts(), 'total' => $this->user->maxBoosts(), ]), 'call_to_action_2' => __('header.user.upgrade'), ]; } $data['subscription']['title'] = __('settings.menu.subscription'); if (! config('services.stripe.enabled')) { unset($data['subscription']); } $data['urls'] = [ 'settings' => ['url' => route('settings.profile'), 'name' => __('header.user.settings')], 'profile' => ['url' => route('users.profile', $this->user->id), 'name' => __('header.user.your-profile')], 'help' => ['url' => 'https://docs.kanka.io/en/latest', 'name' => __('crud.actions.help')], 'logout' => ['url' => route('logout'), 'name' => __('header.user.sign-out')], 'subscription' => route('settings.subscription'), ]; return $data; } protected function campaigns(): array { $data = [ 'member' => [], 'following' => [], 'texts' => [ 'campaigns' => __('sidebar.campaign_switcher.created_campaigns'), ], ]; if (Identity::isImpersonating()) { return $data; } $member = 0; foreach (UserCache::campaigns() as $campaign) { $data['member'][] = [ 'name' => $campaign['name'], 'is_boosted' => $campaign['boosted'], 'image' => $campaign['image'] ? Img::crop(100, 96)->url($campaign['image']) : null, 'url' => $campaign['route'], ]; $member++; } foreach (UserCache::follows() as $campaign) { $data['following'][] = [ 'name' => $campaign['name'], 'is_boosted' => $campaign['boosted'], 'image' => $campaign['image'] ? Img::crop(100, 96)->url($campaign['image']) : null, 'url' => $campaign['route'], ]; } $data['urls'] = [ 'new' => route('start'), 'follow' => Domain::toFront('campaigns'), 'reorder' => route('settings.appearance', ['highlight' => 'campaign-switcher']), ]; $data['texts'] = [ 'new' => __('sidebar.campaign_switcher.new_campaign'), 'campaigns' => __('sidebar.campaign_switcher.created_campaigns'), 'followed' => __('sidebar.campaign_switcher.followed_campaigns'), 'featured' => __('front.campaigns.featured.title'), 'reorder' => __('sidebar.campaign_switcher.reorder'), 'count' => __('sidebar.campaign_switcher.count', [ 'member' => $member, ]), 'follow' => __('sidebar.campaign_switcher.follow_more'), ]; return $data; } protected function notificationsData(): array { if (Identity::isImpersonating()) { return []; } $data = [ 'title' => __('settings.menu.notifications'), 'all' => [ 'url' => route('notifications'), 'text' => __('header.notifications.read_all'), ], 'none' => __('header.notifications.no-unread'), ]; $data['messages'] = $this->notifications(); return $data; } protected function notifications(): array { $notifications = []; /** @var Header $not */ foreach ($this->user->notifications()->unread()->take(5)->get() as $not) { $url = ''; // @phpstan-ignore-next-line $data = $not->data; if (Arr::has($data['params'], 'link')) { $url = $data['params']['link']; if (! Str::startsWith($url, 'http')) { $url = url(app()->getLocale() . '/' . $url); } } elseif (Arr::has($data['params'], 'route')) { $url = route($data['params']['route']); } $notifications[] = [ 'id' => $not->id, 'icon' => $data['icon'], 'text' => __('notifications.' . $data['key'], $data['params']), 'url' => $url, 'colour' => $data['colour'], 'dismiss' => route('notifications.read', $not->id), 'dismiss_text' => __('header.notifications.dismiss'), 'is_read' => $not->read(), // @phpstan-ignore-line ]; } return $notifications; } protected function marketplace(): array { $data = [ 'title' => __('footer.plugins'), ]; return $data; } protected function releasesData(): array { if (Identity::isImpersonating()) { return []; } $data = [ 'title' => __('header.news.title'), 'news' => [], ]; $data['releases'] = $this->releases(); return $data; } protected function releases(): array { $releases = ReleaseCache::latest(); $unreadReleases = []; /** @var AppRelease $release */ foreach ($releases as $release) { if ($release->alreadyRead()) { continue; } $unreadReleases[] = [ 'id' => $release->id, 'url' => $release->link, 'title' => $release->name, 'text' => $release->excerpt, 'dismiss' => route('settings.release', $release->id), 'dismiss_text' => __('header.notifications.dismiss'), ]; } return $unreadReleases; } public function pull(): array { if (Identity::isImpersonating()) { return [ 'has_alerts' => false, /*'releases' => [], 'notifications' => [],*/ ]; } return [ 'has_alerts' => $this->user->hasUnread(), /*'releases' => $this->releases(), 'notifications' => $this->notifications(),*/ ]; } } ================================================ FILE: app/Services/LengthValidatorService.php ================================================ daysInYear(); $counter = 0; $monthLength = 0; foreach ($calendar->monthDataProperties() as $monthData) { $counter = $counter + 1; if ($counter >= $month) { $monthLength = $monthLength + $monthData['data-length']; } } $totalLength = $monthLength - $day + $daysInYear; if ($length >= $totalLength) { return [ 'overflow' => true, 'message' => __('calendars.warnings.event_length', ['documentation' => ' ' . __('footer.documentation') . '', ])]; } return [ 'overflow' => false, 'message' => __('calendars.warnings.event_length', ['documentation' => ' ' . __('footer.documentation') . '', ])]; } } ================================================ FILE: app/Services/LimitService.php ================================================ map = true; return $this; } public function readable(): self { $this->readable = true; return $this; } public function upload(): int|string { // Default for Owlbears and legacy Goblins/Kobolds, or members of a campaign $min = config('limits.filesize.image.owlbear'); if (! $this->user->isSubscriber() && (! isset($this->campaign) || ! $this->campaign->boosted())) { $min = config('limits.filesize.image.standard'); if ($this->map) { $min = config('limits.filesize.map'); } } elseif ($this->user->isElemental() || (isset($this->campaign) && $this->campaign->isElemental())) { $min = config('limits.filesize.image.elemental') * 1024; } elseif ($this->user->isWyvern() || (isset($this->campaign) && $this->campaign->isWyvern())) { $min = config('limits.filesize.image.wyvern'); } $this->map = false; if (empty($min)) { $this->readable = false; return PHP_INT_MAX; } $size = ($min * 1024); return $this->finalize($size); } protected function finalize(int $size): string|int { if (isset($this->campaign)) { $flags = UserCache::user($this->user)->campaign($this->campaign)->flags(); } if (isset($flags[UserFlags::uploadSize->value]) && $flags[UserFlags::uploadSize->value]['amount'] > ceil($size / 1024)) { $size = $flags[UserFlags::uploadSize->value]['amount'] * 1024; } if (! $this->readable) { return $size; } $this->readable = false; return ceil($size / 1024) . 'MiB'; } public function entityFiles(): int { if ($this->campaign->boosted()) { return config('limits.campaigns.files.premium'); } return config('limits.campaigns.files.standard'); } } ================================================ FILE: app/Services/Logs/ApiLogService.php ================================================ duration = $duration; return $this; } public function response(JsonResponse $response): self { $this->response = $response; return $this; } public function exception(Throwable $exception): self { $this->exception = $exception; return $this; } public function log() { if (! config('logging.enabled')) { return; } // Front-facing APIs? Don't log if (! isset($this->user)) { return; } if (isset($this->exception)) { $code = 500; } else { $code = isset($this->response) ? $this->response->getStatusCode() : 200; } ApiLog::create([ 'campaign_id' => isset($this->campaign) ? $this->campaign->id : null, 'user_id' => $this->user->id, 'uri' => $this->request->path(), 'params' => $this->request->all(), 'duration' => $this->duration, 'response' => $code, ]); } } ================================================ FILE: app/Services/Maps/ChunkingService.php ================================================ map = $map; return $this; } public function chunk(): bool { if (empty($this->map->image)) { throw new Exception('Map #' . $this->map->id . ' has no image.'); } // Set the map chunking process $this->map->chunking_status = Map::CHUNKING_RUNNING; $this->map->saveQuietly(); // Get original image and load it into memory $this->log('File ' . $this->map->image); $this->openOriginal(); $this->log('Generating levels ' . $this->minZoom . ' to ' . $this->maxZoom); // Create the folder for storing the chunks $folder = 'maps/' . $this->map->id . '/chunks'; Storage::deleteDirectory($folder); Storage::makeDirectory($folder); // create new manager instance with desired driver $this->manager = new ImageManager(Driver::class); for ($level = $this->minZoom; $level <= $this->maxZoom; $level++) { $this->log('creating chunks for level ' . $level); $levelFolder = $folder . '/' . $level; Storage::makeDirectory($levelFolder); // Get the scale and dimension of the image tile we're creating, based on the level $scale = $this->scale($level); [$width, $height] = $this->dimension($scale); $this->createTile($width, $height, $level, $levelFolder); } // Update the map's min/max zoom levels $this->finish(); return true; } protected function scale(int $level): float { $max = $this->maxZoom - 1; return pow(0.5, $max - $level); } /** * @return int[] */ protected function dimension(float $scale): array { $width = (int) ceil($this->width * $scale); $height = (int) ceil($this->height * $scale); // dump("Checking dimensions for scale $scale (" . $this->width . 'x' . $this->height . ") => $width x $height"); return [$width, $height]; } protected function countTiles(int $width, int $height): array { $cols = (int) ceil(floatval($width) / $this->tileSize); $rows = (int) ceil(floatval($height) / $this->tileSize); return [$cols, $rows]; } public function tileBounds(int $col, int $row, $w, $h): array { [$posX, $posY] = $this->tileBoundsPosition($col, $row); $width = $this->tileSize + 2 * $this->tileOverlap; $height = $this->tileSize + 2 * $this->tileOverlap; $newWidth = min($width, $w - $posX); $newHeight = min($height, $h - $posY); // Make sure the new height and width doesn't get bigger than the available image size // dump("$col / $row (max $w x $h)"); // dump("Building a $newWidth x $newHeight image, offset at $posX x $posY"); return ['x' => $posX, 'y' => $posY, 'height' => $newHeight, 'width' => $newWidth]; } protected function tileBoundsPosition(int $column, int $row): array { $offsetX = $column === 0 ? 0 : $this->tileOverlap; $offsetY = $row === 0 ? 0 : $this->tileOverlap; $x = ($column * $this->tileSize) - $offsetX; $y = ($row * $this->tileSize) - $offsetY; return [$x, $y]; } protected function createTile(int $width, int $height, int $level, string $levelFolder): void { $original = $this->generate($width, $height); /*Storage::put( $levelFolder . '/base.png', (string)$this->original->encode($this->tileFormat, 70), 'public' );*/ [$cols, $rows] = $this->countTiles($width, $height); // dump("Create title for level $level"); // dump("cols $cols rows $rows ($width x $height)"); // $total = $cols * $rows; foreach (range(0, $cols - 1) as $col) { // dump("- Col $col"); foreach (range(0, $rows - 1) as $row) { $file = $col . '_' . $row . '.' . $this->tileFormat; // dump('tile ' . $levelFolder . '/' . $file); // dump("width $width height $height"); $bounds = $this->tileBounds($col, $row, $width, $height); // We need to clone the original, because Image::make($this->original) crops // the original for some reason. // $tile = clone $image; /*Storage::put( $levelFolder . '/' . str_replace('.', '_make.', $file), (string)$tile->encode($this->tileFormat, 50), 'public' );*/ $image = clone $original; $image->crop($bounds['width'], $bounds['height'], $bounds['x'], $bounds['y']); /*Storage::put( $levelFolder . '/' . str_replace('.', '_tile.', $file), (string)$tile->encode($this->tileFormat, 50), 'public' );*/ // Create a 256x256 blank transparent canvas on which we'll insert the crop. This is to make sure each // image create is a square tile (and avoid distortion in leafletjs) $png = $this->manager->create($this->tileSize, $this->tileSize); $png->place($image); Storage::put( $levelFolder . '/' . $file, (string) $png->toPng(), 'public' ); // unset($tile); unset($png, $image); } } unset($original); } /** * Define the minimum and maximum zoom level based on the image dimensions */ protected function zoomLevels(int $max): self { $this->maxZoom = min((int) ceil(log($max, 2)), $this->maxZoomThreshold); $this->levelMin = (int) floor(log($max, 2)); return $this; } /** * Finish the process by updating the map */ protected function finish(): self { $this->map->chunking_status = Map::CHUNKING_FINISHED; $this->map->min_zoom = $this->minZoom; $this->map->max_zoom = $this->maxZoom; $this->map->center_x = 0; $this->map->center_y = 0; // Set initial zoom in bounds if ($this->map->initial_zoom > $this->maxZoom || $this->map->initial_zoom < $this->minZoom) { $this->map->initial_zoom = max($this->minZoom, min($this->maxZoom, $this->map->initial_zoom)); } $this->map->saveQuietly(); Log::info('Saved map #' . $this->map->id); if ($this->map->entity->creator) { /** @var User $user */ $user = $this->map->entity->creator; $user->notify(new Header( 'map.chunked', 'fa-regular fa-map', 'success', ['name' => $this->map->name] )); Log::info('Notified user #' . $this->map->entity->created_by); } // Cleanup the locally downloaded file Storage::disk('local')->delete($this->map->image); return $this; } protected function log($log): self { Log::info($log); return $this; } protected function generate(int $width, int $height) { $image = $this->manager->read($this->path); return $image->resize($width, $height); } /** * Open the original image */ protected function openOriginal(): self { $this->path = Storage::disk('local')->path($this->map->image); // Download from s3 to local $s3 = Storage::disk('s3')->get($this->map->image); Storage::disk('local')->put( $this->map->image, $s3 ); $original = $this->manager->read($this->path); $this->width = $original->width(); $this->height = $original->height(); $this->maxBound = max([$this->width, $this->height]); $this->zoomLevels($this->maxBound); unset($s3, $original); return $this; } } ================================================ FILE: app/Services/Maps/MigrateLayerService.php ================================================ layer = $layer; return $this; } public function migrate(): void { if (empty($this->layer->image_path)) { throw new TranslatableException('maps/layers.migrate.empty'); } $path = $this->layer->image_path; $ext = Str::afterLast('.', $path); $this->image = new Image; $this->image->campaign_id = $this->layer->map->campaign_id; $this->image->created_by = $this->layer->created_by; $this->image->name = $this->layer->name; $this->image->ext = $ext; $this->image->size = (int) ceil(Storage::fileSize($path) / 1024); // kb $this->image->visibility_id = $this->layer->visibility_id; $this->image->save(); Storage::move($path, $this->image->path); $this->layer->image_path = null; $this->layer->image_uuid = $this->image->id; $this->layer->save(); } } ================================================ FILE: app/Services/MarkdownMentionsService.php ================================================ isSingle = $isSingle; return $this; } /** * Parse a model's text for markdown export */ public function parseForMarkdown(Model $model, string $field = 'entry'): string { return $this->prepareEntity($model, $field); } protected function prepareEntity(Model $model, string $field): string { // We have to cast to a string for when the entity was created in the API with a NULL entry $this->text = (string) $model->$field; return $this->replaceForMarkdown(); } protected function replaceForMarkdown(): string { // Extract links from the entry to foreign $this ->parseMentions() ->parseAttributes(); return (string) $this->text; } /** * Replace mentions of entities to a supported markdown converter link */ protected function parseMentions(): self { $this->text = preg_replace_callback('`\[([a-z_-]+):(.*?)\]`i', function ($matches) { $data = $this->extractData($matches); if ($data['type'] === 'post') { return $this->parsePost($data); } $hasCustom = Arr::has($data, 'custom'); // If the user always wants advanced mentions, we force the [] syntax upon them if ($hasCustom || (isset($this->user) && $this->user->alwaysAdvancedMentions())) { // Get entity $entity = $this->entity($data['id']); if (! empty($entity) && $entity->entityType->isCustom()) { $moduleName = Str::slug($entity->entityType->plural() . '_' . $entity->entityType->id); } elseif (! empty($entity)) { $moduleName = Str::slug($entity->entityType->pluralCode()); } // If no entity then render as unknown if (empty($entity) || ($entity->hasChild() && $entity->isMissingChild())) { return __('crud.history.unknown'); } if ($this->isSingle) { $url = $entity->url(); } else { $url = str_replace(' ', '-', $moduleName) . '/' . str_replace(' ', '-', Str::slug($entity->name)) . '_' . $entity->id; } // If field, render that. if (Arr::has($data, 'field')) { $name = $entity->name; $field = $data['field']; // Check for field if (isset($entity->$field)) { $name = $entity->$field; } return '' . $name . ''; } // If no alias, render name if (! Arr::has($data, 'alias')) { $name = $entity->name; // Check for custom name if (isset($data['text'])) { $name = $data['text']; } return '' . $name . ''; } // An alias was attached, try loading that too $alias = $this->alias($data['alias']); if (empty($alias) || empty($alias->entity)) { return '' . $entity->name . ''; } // If alias use that. return '' . $alias->name . ''; } $entity = $this->entity($data['id']); // No entity found, the user might not be allowed to see it if (empty($entity) || ($entity->hasChild() && $entity->isMissingChild())) { return __('crud.history.unknown'); } else { if ($this->isSingle) { $url = $entity->url(); } else { // Get module name if ($entity->entityType->isCustom()) { $moduleName = Str::slug($entity->entityType->plural() . '_' . $entity->entityType->id); } else { $moduleName = Str::slug($entity->entityType->pluralCode()); } $url = str_replace(' ', '-', $moduleName) . '/' . str_replace(' ', '-', Str::slug($entity->name)) . '_' . $entity->id; } // Render normally return '' . $entity->name . ''; } }, $this->text); return $this; } /** * Replace mentions of attributes to supported markdown converter link */ protected function parseAttributes(): self { // Extract links from the entry to attribute $this->text = preg_replace_callback('`\{attribute:(.*?)\}`i', function ($matches) { $id = (int) $matches[1]; /** @var ?Attribute $attribute */ $attribute = $this->attribute($id); // No entity found, the user might not be allowed to see it if (empty($attribute) || ! $attribute->entity) { return __('crud.history.unknown'); } if ($this->isSingle) { $url = $attribute->entity->url(); } else { // Get entity type for linking if ($attribute->entity->entityType->isCustom()) { $moduleName = Str::slug($attribute->entity->entityType->plural() . '_' . $attribute->entity->entityType->id); } else { $moduleName = Str::slug($attribute->entity->entityType->pluralCode()); } $url = str_replace(' ', '-', $moduleName) . '/' . str_replace(' ', '-', Str::slug($attribute->entity->name)) . '_' . $attribute->entity->id; } // Render attribute. return '' . $attribute->value . ''; }, $this->text); return $this; } protected function parsePost(array $data): string { $post = $this->post($data['id']); // If no post then render unknown if (empty($post) || $post->entity->isMissingChild()) { return __('crud.history.unknown'); } if ($this->isSingle) { $url = $post->entity->url(); } else { // Get entitytype from the post if ($post->entity->entityType->isCustom()) { $moduleName = Str::slug($post->entity->entityType->plural() . '_' . $post->entity->entityType->id); } else { $moduleName = Str::slug($post->entity->entityType->pluralCode()); } $url = str_replace(' ', '-', $moduleName) . '/' . str_replace(' ', '-', Str::slug($post->entity->name)) . '_' . $post->entity->id; } // If transcluding a post, render its entry if (Arr::has($data, 'text') && $data['text'] == 'transclude') { return '' . $post->entry . ''; } // Render normally return '' . $post->name . ''; } protected function entity(int $id): ?Entity { if (! Arr::has($this->entities, (string) $id) && ! Arr::has($this->privateEntities, (string) $id)) { $this->entities[$id] = Entity::with(['entityType'])->where(['id' => $id])->first(); } return Arr::get($this->entities, $id); } protected function alias(int $id): ?EntityAsset { if (! Arr::has($this->aliases, (string) $id)) { $this->aliases[$id] = EntityAsset::with(['entity', 'entity.tags', 'entity.entityType']) ->alias() ->where(['id' => $id])->first(); } return Arr::get($this->aliases, $id); } protected function attribute(int $id): ?Attribute { if (! Arr::has($this->attributes, (string) $id)) { $this->attributes[$id] = Attribute::with(['entity', 'entity.entityType']) ->where(['id' => $id])->first(); } return Arr::get($this->attributes, $id, null); } protected function post(int $id): ?Post { if (! Arr::has($this->posts, (string) $id)) { $this->posts[$id] = Post::with(['entity', 'entity.entityType']) ->has('entity') ->find($id); } return Arr::get($this->posts, $id); } } ================================================ FILE: app/Services/Mentions/SaveService.php ================================================

    . * Who knows what we'll get, but we'll do our best to be useful. */ public function text(?string $text): self { $this->text = $text; return $this; } /** * If new entities were created from the mentions */ public function hasNewEntities(): bool { return $this->createdNewEntities; } /** * Transform the html from the text editor with its weird mention syntax into mentions that can be parsed later on */ public function save(): string { if (empty($this->text)) { return ''; } return $this ->parseNewEntities() ->prepareDocument() ->parseMentions() ->cleanup() ->html(); } /** * The user can type @NewEntity and create a bunch of new things on the fly. This function * supports that. */ protected function parseNewEntities(): self { $this->text = preg_replace_callback( '`\[new:([a-z_-]+)\|(.*?)\]`i', function ($data) { if (count($data) !== 3) { return $data[0]; } // check type is valid return $this->newEntityMention($data[1], $data[2]); }, $this->text ); return $this; } /** * We have a text of html, transform that into a DomDocument and DomXPath to be able to loop on various html * elements easily. */ protected function prepareDocument(): self { // Parse all links and transform them into advanced mentions [] if needed $this->document = new DOMDocument; libxml_use_internal_errors(true); // Suppress warnings for malformed HTML $this->document->loadHTML(mb_convert_encoding($this->text, 'HTML-ENTITIES', 'UTF-8')); libxml_clear_errors(); $this->xpath = new DOMXPath($this->document); return $this; } /** * Mentions come in different shapes and sizes. Handle them all in a single function call. */ protected function parseMentions(): self { $nodes = $this->xpath->query('//a[ contains(concat(" ", normalize-space(@class), " "), " mention ") or contains(concat(" ", normalize-space(@class), " "), " post-mention ") or contains(concat(" ", normalize-space(@class), " "), " attribute-mention ") ]'); foreach ($nodes as $element) { if ($element instanceof DomElement) { $this->parseMention($element); } } return $this; } /** * We have a mention link, do some magic */ protected function parseMention(DomElement $mentionLink): void { $text = $mentionLink->nodeValue; $name = html_entity_decode($mentionLink->getAttribute('data-name')); // Now you can compare $name with $text to check for edits $mentionName = Str::replace(['&'], ['&'], $text); $advancedMention = $mentionLink->getAttribute('data-mention'); $advancedAttribute = $mentionLink->getAttribute('data-attribute'); // It's not a mention or attribute, keep it as is if (empty($advancedMention) && empty($advancedAttribute)) { $this->replace($name, $mentionLink); return; } // Advanced attribute [attribute:123], use that if (! empty($advancedAttribute)) { $this->replace($advancedAttribute, $mentionLink); return; } // If the name isn't the target name, transform it into an advanced mention $originalName = $mentionLink->getAttribute('data-name'); // Normalize data-mention to just [type:id] in case it contains extra params if (preg_match('/\[?([a-zA-Z_]+:\d+)/', $advancedMention, $mentionParts)) { $advancedMention = '[' . $mentionParts[1] . ']'; } $params = new Collection; // Tiptap sends config in a property to keep things clean if (! empty($mentionLink->getAttribute('data-config'))) { $params = new Collection(explode('|', $mentionLink->getAttribute('data-config'))); } // Check if the mention name was customised by the user $hasCustomName = ! empty($originalName) && $originalName != Str::replace('"', '"', $mentionName); if ($hasCustomName) { if ($params->isNotEmpty()) { // Tiptap: merge custom name with params (page:abc|anchor:#post-1 etc) $params->prepend($mentionName); $mention = Str::replace(']', '|' . $params->implode('|') . ']', $advancedMention); } else { // Other editors: just attach the custom name $mention = Str::replace(']', '|' . $mentionName . ']', $advancedMention); } $this->replace($mention, $mentionLink); return; } // Add params to the mention link if ($params->isNotEmpty()) { $advancedMention = Str::replaceLast(']', '|' . $params->implode('|') . ']', $advancedMention); } $this->replace($advancedMention, $mentionLink); } /** * Get rid of any fancy and special elements leftover from mentions with custom names */ protected function cleanup(): self { // Remove legacy and advanced-mention elements $advancedNodes = $this->xpath->query('//ins[@class="' . $this->advancedMentionNameClass . '" and @data-name] | //span[@class="' . $this->advancedMentionNameClass . '" and @data-name]'); foreach ($advancedNodes as $node) { $node->parentNode->removeChild($node); } return $this; } protected function replace(string $text, DomElement $node): void { $textNode = $this->document->createTextNode($text); $node->parentNode->replaceChild($textNode, $node); } /** * Create a new entity based on a mention */ protected function newEntityMention(string $type, string $name): string { if (empty($type) || empty($name)) { return $name; } $types = $this->newService->campaign($this->campaign)->user($this->user)->available(); /** @var ?EntityType $entityType */ $entityType = $types->where('code', $type)->first(); if (! $entityType) { return $name; } // Do we already have it cached? $key = $type . ':' . mb_strtolower($name); if (isset($this->newEntityMentions[$key])) { return "[{$type}:" . $this->newEntityMentions[$key] . ']'; } // Create the new model $newEntity = $this->newService ->user($this->user) ->entityType($entityType) ->create($name); $this->newEntityMentions[$key] = $newEntity->id; $this->createdNewEntities = true; return '[' . $type . ':' . $newEntity->id . ']'; } /** * When all is said and done, get the body content of DomDocument and save that to the db */ protected function html(): string { $body = $this->document->getElementsByTagName('body')->item(0); if (empty($body)) { return ''; } $newHtml = ''; foreach ($body->childNodes as $child) { $newHtml .= $this->document->saveHTML($child); } return $newHtml; } } ================================================ FILE: app/Services/MentionsService.php ================================================ text = ! empty($model->$field) ? $model->$field : ''; return $this->extractAndReplace(); } /** * Map a string */ public function mapText(?string $text = null): string { $this->text = $text; return $this->extractAndReplace(); } /** * Map a string */ public function mapCopiedEntry(?string $text = null): string { $this->text = $text; return $this->extractAndLink(); } /** * Map the mentions in an entity's tooltip (boosted feature) */ public function mapEntity(Entity $entity, string $field = 'tooltip'): string { $this->text = ! empty($entity->$field) ? $entity->$field : ''; return $this->extractAndReplace(); } /** * Map the mentions in any model * * @return string|string[]|null */ public function mapAny(Model $model, string $field = 'entry') { $this->text = (string) $model->{$field}; return $this->extractAndReplace(); } /** * Map the mentions in an attribute * * @return string|string[]|null */ public function mapAttribute(Attribute $attribute, ?string $text = null) { // If the attribute mentions itself in the value, don't do any parsing, it would cause an endless loop. // The first check is for unchecked checkboxes if (! empty($attribute->value) && Str::contains($attribute->value, $attribute->mentionName())) { return Attributes::parse($attribute); } // Called in this order to avoid a bug that would render an attribute mention inside an attribute wrong. if (! $text) { $this->text = Attributes::parse($attribute); } else { $this->text = $text; } return $this->extractAndReplace(); } public function onlyName(bool $option = true): self { $this->onlyName = $option; return $this; } /** * Parse a model's text for editing (transform mentions into advanced mentions, normal * mentions visually, etc) */ public function parseForEdit(Model $model, string $field = 'entry'): string { return $this->editEntity($model, $field); } protected function editEntity(Model $model, string $field): string { // We have to cast to a string for when the entity was created in the API with a NULL entry $this->text = (string) $model->$field; return $this->replaceForEdit(); } /** * Parse an entity and create the advanced mention helper bubble */ public function advancedMentionHelper(string $name): string { $cleanEntityName = Str::replace(['"', '&'], ['\'', '&'], $name); return ''; } /** * Search mentions in a text and replace them with tooltiped links * * @return string|string[]|null */ protected function extractAndReplace() { // Pre-fetch all the entities $this->prepareEntities(); $this->prepareHiddenEntities(); // Extract links from the entry to foreign $this->replaceEntityMentions(); // And now for extra fun, let's do attributes! $this->mapAttributes(); // Can't forget our custom blocks $this->mapCodes(); // Clean up weird ` chars that break the js $this->text = str_replace('`', '\'', $this->text); $this->fixGalleryUrls(); return $this->text; } public function extractAndLink(): string { CampaignLocalization::forceCampaign($this->campaign); $this->isCopying = true; // Pre-fetch all the entities $this->prepareEntities(); $this->prepareHiddenEntities(); // Extract links from the entry to foreign $this->replaceEntityMentions(); return $this->text; } protected function replaceEntityMentions(): void { $this->text = preg_replace_callback('`\[([a-z_-]+):(.*?)\]`i', function ($matches) { // Icons $fontAwesomes = ['fa ', 'fas ', 'far ', 'fab ', 'ra ', 'fa-solid ', 'fa-regular ', 'fa-brands ']; if ($matches[1] == 'icon' && Str::startsWith($matches[2], $fontAwesomes)) { return ''; } $data = $this->extractData($matches); if (Arr::get($data, 'type') === 'post') { return $this->mentionPost($data); } $entity = $this->entity($data['id']); $tagClasses = []; $cssClasses = ['entity-mention']; // No entity found, the user might not be allowed to see it if (empty($entity)) { $hiddenEntity = $this->hiddenEntity($data['id']); if (empty($hiddenEntity)) { if ($this->onlyName) { return __('crud.history.unknown'); } $replace = Arr::get( $data, 'text', '' . __('crud.history.unknown') . '' ); } else { // An alias was used for this mention, so let's try and find it. ACL is handled directly // on the EntityAlias object. if (! empty($data['alias'])) { $alias = $hiddenEntity->assets()->alias()->where('id', $data['alias'])->first(); if (! empty($alias)) { $data['text'] = $alias->name; } } if ($this->onlyName) { return Arr::get($data, 'text', $hiddenEntity->name); } $replace = '' . Arr::get($data, 'text', $hiddenEntity->name) . ''; } } else { $routeOptions = []; if (! empty($data['params'])) { $routeParams = explode('&', $data['params']); foreach ($routeParams as $routeParam) { // Do we whitelist? or have a max length to avoid shenanigans? if (mb_strlen($routeParam) > 20) { continue; } $paramOptions = explode('=', $routeParam); if (count($paramOptions) != 2) { continue; } $routeOptions[$paramOptions[0]] = $paramOptions[1]; } } $url = $entity->url('show', $routeOptions); if (! empty($data['page'])) { // Let's validate this new url first. Maybe we need to map to entities/id (ex inventory) $entityPages = ['inventory', 'abilities', 'relations', 'attributes', 'assets']; if (in_array($data['page'], $entityPages)) { $page = $data['page']; if ($page === 'relations') { $page = 'relations.index'; } elseif ($page === 'assets') { $page = 'entity_assets.index'; } $url = route('entities.' . $page, [$this->campaign, $entity->id] + $routeOptions); } else { $url = $entity->url($data['page'], [$this->campaign] + $routeOptions); } } // An alias was used for this mention, so let's try and find it. ACL is handled directly // on the EntityAlias object. if (! empty($data['alias'])) { $alias = $entity->assets()->alias()->where('id', $data['alias'])->first(); if (! empty($alias)) { $data['text'] = $alias->name; } } if (! empty($data['anchor'])) { $url .= '#' . $data['anchor']; } $dataUrl = route('entities.tooltip', [$this->campaign->id, $entity->id]); if (! empty($data['tooltip']) && $data['tooltip'] === 'attributes') { $dataUrl = route('entities.tooltip', [$this->campaign, $entity, 'render' => 'attributes']); } // If this request is through the API, we need to inject the language in the url if (Domain::isApi()) { $url = Str::replaceFirst('/campaign/', '/w/', $url); $dataUrl = Str::replaceFirst('/w/', '/w/', $dataUrl); } // Add tags as a class foreach ($entity->tags as $tag) { $tagClasses[] = 'id-' . $tag->id; $tagClasses[] = $tag->slug; } // Referencing a custom field on the entity if (! empty($data['field'])) { $field = $data['field']; // Mapping if ($field == 'gender') { $field = 'sex'; } if ($entity->hasChild() && ! $entity->isMissingChild()) { /** @var Character|Map|Quest $child */ $child = $entity->child; if ($field == 'family' && ! $child->families->isEmpty()) { $data['text'] = $child->characterFamilies->first()->family->name; } if ($field == 'race' && ! $child->characterRaces->isEmpty()) { $data['text'] = $child->characterRaces->first()->race->name; } if ($field == 'calendar_date' && $child->calendar_id) { $data['text'] = $entity->calendarReminder()->readableDate(); } } if ($field === 'entry' && method_exists($entity, 'parsedEntry')) { if ($this->enableEntryField) { $this->lockEntryRendering(); $parsedTargetEntry = $entity->parsedEntry(); $this->unlockEntryRendering(); } else { $parsedTargetEntry = $entity->entry; } $cssClasses[] = 'mention-field-entry block'; $entityName = '' . Arr::get($data, 'text', $entity->name) . ''; return '' . $entityName . '
    ' . $parsedTargetEntry . '
    ' . '
    '; } elseif ($field === 'attributes') { return ''; } elseif ($field == 'map' && isset($child) && $child->explorable()) { $height = 300; $width = 300; if (isset($routeOptions['height']) && is_numeric($routeOptions['height'])) { $height = $routeOptions['height']; } if (isset($routeOptions['width']) && is_numeric($routeOptions['width'])) { $width = $routeOptions['width']; } return ''; } elseif ($entity->hasChild() && ! $entity->isMissingChild() && isset($entity->child->$field)) { $foreign = $entity->child->$field; if ($foreign instanceof Model) { if (isset($foreign->name) && ! empty($foreign->name)) { $data['text'] = $foreign->name; } } elseif (is_string($foreign)) { $data['text'] = $foreign; } if ($field == 'date' && $entity->child instanceof Calendar) { $data['text'] = $entity->child->niceDate(); } } elseif (isset($entity->$field) && is_string($entity->$field)) { $data['text'] = $entity->$field; } $cssClasses[] = 'mention-field-' . Str::slug($field); } if ($this->onlyName) { return Arr::get($data, 'text', $entity->name); } if ($this->isCopying) { return '' . Arr::get($data, 'text', $entity->name) . ''; } $replace = '' . Arr::get($data, 'text', $entity->name) . ''; } return $replace; }, $this->text); } /** * The gallery injects images as a thumbnail, instead of the final URL. * Meaning that when we switched from images.kanka.io to th.kanka.io, * all the gallery images in text were broken. */ protected function fixGalleryUrls(): self { if (empty(config('thumbor.key'))) { return $this; } $this->text = Str::replace( 'https://images.kanka.io/user/', config('thumbor.url'), $this->text ); return $this; } protected function replaceForEdit(): string { // Extract links from the entry to foreign $this ->parseMentionsForEdit() ->parseAttributesForEdit(); return (string) $this->text; } /** * Replace mentions of entities to a visual representation for the text editor */ protected function parseMentionsForEdit(): self { $this->text = preg_replace_callback('`\[([a-z_-]+):(.*?)\]`i', function ($matches) { $data = $this->extractData($matches); if ($data['type'] === 'post') { return $this->parsePostForEdit($matches[0], $data); } $hasCustom = Arr::has($data, 'custom'); // If the user always wants advanced mentions, we force the [] syntax upon them if ($hasCustom || auth()->user()->alwaysAdvancedMentions()) { // Still need to show the target's name in the advanced mention $entity = $this->entity($data['id']); if (empty($entity) || ($entity->hasChild() && $entity->isMissingChild())) { return $matches[0]; } $advancedName = $this->advancedMentionHelper($entity->name); if (! Arr::has($data, 'alias')) { return Str::replaceLast(']', $advancedName . ']', $matches[0]); } // An alias was attached, try loading that too $alias = $this->alias($data['alias']); if (empty($alias) || empty($alias->entity)) { return Str::replaceLast(']', $advancedName . ']', $matches[0]); } $aliasName = $this->advancedMentionHelper($alias->name); $mention = Str::replaceLast(']', $aliasName . ']', $matches[0]); $replaceId = ':' . $data['id'] . '|'; return Str::replaceFirst($replaceId, ':' . $data['id'] . $advancedName . '|', $mention); } // This was matched on an attribute if ($data['type'] == 'icon') { return $matches[0]; } $entity = $this->entity($data['id']); // No entity found, the user might not be allowed to see it if (empty($entity) || ($entity->hasChild() && $entity->isMissingChild())) { $name = __('crud.history.unknown'); $dataName = $name; } else { $name = $entity->name; $dataName = Str::replace('"', '"', $entity->name); } return '' . $name . ''; }, $this->text); return $this; } /** * Replace mentions of attributes to a visual representation for the text editor */ protected function parseAttributesForEdit(): self { // If the user has advanced mentions always on, don't replace attributes if (auth()->user()->alwaysAdvancedMentions()) { return $this; } // Extract links from the entry to attribute $this->text = preg_replace_callback('`\{attribute:(.*?)\}`i', function ($matches) { $id = (int) $matches[1]; /** @var ?Attribute $attribute */ $attribute = $this->attribute($id); // No entity found, the user might not be allowed to see it if (empty($attribute)) { $name = __('crud.history.unknown'); } else { $name = $attribute->name; } if (str_contains($matches[1], '|')) { return $matches[0]; } return '{' . $name . '}'; }, $this->text); return $this; } protected function parsePostForEdit(string $mention, array $data): string { $post = $this->post($data['id']); $hasCustom = Arr::has($data, 'custom'); if ($hasCustom || auth()->user()->alwaysAdvancedMentions()) { if (! $post) { return $mention; } $advancedName = $this->advancedMentionHelper($post->name); return Str::replaceLast(']', $advancedName . ']', $mention); } // No entity found, the user might not be allowed to see it if (empty($post) || $post->entity->isMissingChild()) { $name = __('crud.history.unknown'); $dataName = $name; } else { $name = $post->name; $dataName = Str::replace('"', '"', $post->name); } return '' . $name . ''; } protected function entity(int $id): ?Entity { if (! Arr::has($this->entities, (string) $id) && ! Arr::has($this->privateEntities, (string) $id)) { if ($this->isCopying) { CampaignLocalization::forceCampaign($this->campaign); } $this->entities[$id] = Entity::where(['id' => $id])->first(); } return Arr::get($this->entities, $id); } protected function post(int $id): ?Post { if (! Arr::has($this->posts, (string) $id)) { $this->posts[$id] = Post::with(['entity', 'entity.tags', 'entity.entityType']) ->has('entity') ->find($id); } return Arr::get($this->posts, $id); } protected function hiddenEntity(int $id): ?Entity { if (! $this->campaign->showPrivateEntityMentions()) { return null; } if (! Arr::has($this->entities, (string) $id) && ! Arr::has($this->privateEntities, (string) $id)) { // @phpstan-ignore-next-line $this->privateEntities[$id] = Entity::where(['id' => $id])->withInvisible()->first(); } return Arr::get($this->privateEntities, $id); } public function preloadEntity(Entity $entity): void { if (Arr::has($this->entities, (string) $entity->id)) { return; } $this->entities[$entity->id] = $entity; } protected function alias(int $id): ?EntityAsset { if (! Arr::has($this->aliases, (string) $id)) { $this->aliases[$id] = EntityAsset::alias()->where(['id' => $id])->first(); } return Arr::get($this->aliases, $id); } protected function attribute(int $id): ?Attribute { if (! Arr::has($this->attributes, (string) $id)) { $this->attributes[$id] = Attribute::where(['id' => $id])->first(); } return Arr::get($this->attributes, $id, null); } /** * Pre-fetch all mentioned entities */ protected function prepareEntities(): void { // First, let's prepare all mentions to do a single query on the entities table $this->mentionedEntities = []; preg_replace_callback('`\[([a-z_-]+):(.*?)\]`i', function ($matches) { $segments = explode('|', $matches[2]); $id = (int) $segments[0]; if (! in_array($id, $this->mentionedEntities)) { $this->mentionedEntities[] = $id; } return $matches[0]; }, $this->text); // Remove those already cached in memory $ids = []; // @phpstan-ignore-next-line foreach ($this->mentionedEntities as $id) { if (! Arr::has($this->entities, $id)) { $ids[] = $id; } } // @phpstan-ignore-next-line if (empty($ids)) { return; } // Directly get with the mentioned entity types (provided they are valid) // @phpstan-ignore-next-line $entities = Entity::whereIn('id', $ids)->with(['tags:id,name,slug', 'entityType:id,code,is_special'])->get(); // dump(count($ids)); foreach ($entities as $entity) { $this->entities[$entity->id] = $entity; $findKey = array_search($entity->id, $ids); unset($ids[$findKey]); } $this->hiddenEntities = $ids; } /** * Pre-fetch all private mentioned entities */ protected function prepareHiddenEntities(): void { if (isset($this->campaign) && ! $this->campaign->showPrivateEntityMentions()) { return; } // Remove those already cached in memory $ids = []; foreach ($this->hiddenEntities as $id) { if (! Arr::has($this->privateEntities, $id)) { $ids[] = $id; } } if (empty($ids)) { return; } // Directly get with the mentioned entity types (provided they are valid) // @phpstan-ignore-next-line $entities = Entity::whereIn('id', $ids)->with(['entityType:id,code,is_special'])->withInvisible()->get(); // dump(count($ids)); foreach ($entities as $entity) { $this->privateEntities[$entity->id] = $entity; } } /** * Pre-fetch the attributes of the entity */ protected function prepareAttributes() { // Remove those already cached in memory $ids = []; foreach ($this->mentionedAttributes as $id) { if (! Arr::has($this->attributes, $id)) { $ids[] = $id; } } if (empty($ids)) { return; } $attributes = Attribute::whereIn('id', $ids)->get(); foreach ($attributes as $attribute) { $this->attributes[$attribute->id] = $attribute; } } /** * Validate the entity type that was inserted in the mention block */ protected function validEntityType(string $type): bool { return in_array($type, $this->validEntityTypes()); } /** * List of valid entity types */ protected function validEntityTypes(): array { if (! empty($this->validEntityTypes)) { return $this->validEntityTypes; } $validEntityTypes = array_keys(config('entities.ids')); return $this->validEntityTypes = $validEntityTypes; } /** * Replace all attributes with their values and a toolip */ protected function mapAttributes() { $this->mentionedAttributes = []; preg_replace_callback('`\{attribute:(.*?)\}`i', function ($matches) { $id = (int) $matches[1]; if (! in_array($id, $this->mentionedAttributes)) { $this->mentionedAttributes[] = $id; } return $matches[0]; }, $this->text); // Pre-fetch all the entities $this->prepareAttributes(); // Extract links from the entry to foreign $this->text = preg_replace_callback('`\{attribute:(.*?)\}`i', function ($matches) { $id = (int) $matches[1]; $attribute = $this->attribute($id); $fallback = ''; if (str_contains($matches[1], '|')) { $fallback = Str::after($matches[1], '|'); } // No entity found, the user might not be allowed to see it, if theres a fallback, apply it if (empty($attribute)) { if (! $fallback) { $replace = '' . __('crud.history.unknown') . ''; } else { $replace = '' . $fallback . ''; } } else { $replace = '' . $attribute->mappedValue() . ''; } return $replace; }, $this->text); } /** * Replace any table-of-content blocks with a real HTML table of content, adding unique ids to each heading * so that links can work. * * @return void */ protected function mapCodes() { // Re-use the same markupFixer to keep references of previously generated slugs on this page // @phpstan-ignore-next-line if (! isset($this->markupFixer)) { $this->markupFixer = new MarkupFixer(null, new TocSlugify); } // $markupFixer = new MarkupFixer(null, new TocSlugify()); $tocGenerator = new TocGenerator; $this->text = $this->markupFixer->fix($this->text); if (! Str::contains($this->text, '{table-of-contents}')) { return; } // $this->text = $this->markupFixer->fix($this->text); $toc = $tocGenerator->getHtmlMenu($this->text); $this->text = Str::replaceFirst( '{table-of-contents}', '
    ' . $toc . "
    \n", $this->text ); } /** * Protect from rendering future field:entry mentions to avoid endless loops */ protected function lockEntryRendering(): void { $this->enableEntryField = false; } /** * Re-enable rendering field:entry mentions */ protected function unlockEntryRendering(): void { $this->enableEntryField = true; } protected function mentionPost(array $data): string { $post = $this->post($data['id']); $isTranscluding = Arr::get($data, 'text') === 'transclude'; if (! $post) { if ($this->onlyName || $isTranscluding) { return __('crud.history.unknown'); } return Arr::get( $data, 'text', '' . __('crud.history.unknown') . '' ); } $url = route('entities.show', [$this->campaign, $post->entity, '#post-' . $post->id]); $tooltipUrl = route('entities.tooltip', [$this->campaign, $post->entity]); $cssClasses = ['entity-mention']; $tagClasses = []; foreach ($post->entity->tags as $tag) { $tagClasses[] = 'id-' . $tag->id; $tagClasses[] = $tag->slug; } if ($isTranscluding) { if ($this->enableEntryField) { $this->lockEntryRendering(); $parsedTargetEntry = $post->parsedEntry(); $this->unlockEntryRendering(); } else { $parsedTargetEntry = $post->entry; } $cssClasses[] = 'mention-field-post block'; $entityName = '' . $post->name . ''; return '' . $entityName . '
    ' . $parsedTargetEntry . '
    ' . '
    '; } if ($this->onlyName) { return Arr::get($data, 'text', $post->name); } return '' . Arr::get($data, 'text', $post->name) . ''; } } ================================================ FILE: app/Services/MultiEditingService.php ================================================ model = $model; return $this; } /** * Check for users that are currently editing an entity */ public function users(): array { $data = []; // @phpstan-ignore-next-line $users = $this->model ->editingUsers() ->where('type_id', EntityUser::TYPE_KEEPALIVE) ->where('entity_user.updated_at', '>=', Carbon::now()->subMinutes(10)) ->where('user_id', '!=', $this->user->id) ->withPivot('created_at') ->get(); foreach ($users as $user) { $data[$user->id] = $user; } return $data; } /** * Check if the user is editing the entity */ public function isEditing(): bool { // @phpstan-ignore-next-line return $this->model->editingUsers() ->where('type_id', EntityUser::TYPE_KEEPALIVE) ->where('user_id', $this->user->id) ->count() !== 0; } /** * Set the user as editing an entity */ public function edit(): self { $model = new EntityUser; if ($this->model instanceof Post) { $model->post_id = $this->model->id; } elseif ($this->model instanceof Campaign) { $model->campaign_id = $this->model->id; } elseif ($this->model instanceof TimelineElement) { $model->timeline_element_id = $this->model->id; } elseif ($this->model instanceof QuestElement) { $model->quest_element_id = $this->model->id; } elseif ($this->model instanceof Entity) { $model->entity_id = $this->model->id; } $model->user_id = $this->user->id; $model->type_id = EntityUser::TYPE_KEEPALIVE; $model->save(); return $this; } /** * Remove the user as editing the entity */ public function finish(): self { $id = null; if ($this->model instanceof Post) { $id = 'post_id'; } elseif ($this->model instanceof Campaign) { $id = 'campaign_id'; } elseif ($this->model instanceof TimelineElement) { $id = 'timeline_element_id'; } elseif ($this->model instanceof QuestElement) { $id = 'quest_element_id'; } elseif ($this->model instanceof Entity) { $id = 'entity_id'; } $models = EntityUser::userID($this->user->id) ->keepAlive() // @phpstan-ignore-next-line ->where($id, $this->model->id) ->get(); foreach ($models as $model) { $model->delete(); } return $this; } /** * Touch the editing entry in the db */ public function keepAlive(): self { // @phpstan-ignore-next-line $pulse = $this->model->editingUsers() ->where('type_id', EntityUser::TYPE_KEEPALIVE) ->where('user_id', $this->user->id) ->first(); if ($pulse) { $pulse->touch(); } return $this; } public function confirm(): void { if (! $this->isEditing()) { $this->edit(); } } } ================================================ FILE: app/Services/NewsletterService.php ================================================ mailerlite = new MailerLite(['api_key' => $key]); } public function fields(array $fields): self { $this->fields = $fields; return $this; } public function email(string $email): self { $this->email = $email; return $this; } /** * Check if a user is subscribed */ public function isSubscribed(): bool { try { $email = isset($this->user) ? $this->user->email : $this->email; $this->userID = $this->fetch($email); return true; } catch (Exception $e) { return false; } } /** * Unsubscribe a user */ public function remove() { $this->mailerlite->subscribers->delete($this->userID); return true; } /** * Unsubscribe a user */ public function delete() { if (! $this->isSubscribed()) { return false; } $this->mailerlite->subscribers->delete($this->userID); return true; } public function update(array $options): bool { try { // Build the interests of the user $interests = []; if (Arr::has($options, 'releases')) { $interests[] = config('mailerlite.groups.all'); if (isset($this->user) && $this->user->isSubscriber()) { $interests[] = config('mailerlite.groups.subs'); } if (isset($options['new']) && $options['new']) { $interests[] = config('mailerlite.groups.new'); } } $email = $this->user->email ?? $this->email; $data = [ 'email' => $email, 'fields' => $this->buildFields(), 'groups' => $interests, ]; if (empty($this->userID)) { $this->mailerlite->subscribers->create($data); return true; } else { $this->mailerlite->subscribers->update($this->userID, $data); return true; } } catch (Exception $e) { $this->error = $e; return false; } } public function error(): Exception { return $this->error; } /** * Get the user's id based on their email */ protected function fetch(string $email): int { $response = $this->mailerlite->subscribers->find($email); return (int) Arr::get($response, 'body.data.id'); } /** * Guess the user's country based on their login logs */ protected function guessCountry(): ?string { if (! isset($this->user)) { return null; } /** @var ?UserLog $latest */ $latest = $this->user->logs()->whereNotNull('country')->latest()->first(); return $latest?->country; } protected function buildFields(): array { $fields = [ 'name' => $this->user->name, 'language' => $this->user->locale ?? app()->getLocale(), 'country' => $this->guessCountry(), ]; if (! empty($this->user)) { /** @var ?Subscription $first */ $first = $this->user->subscriptions->where('type', 'kanka')->last(); /** @var ?Subscription $latest */ $latest = $this->user->subscription('kanka'); $fields['become_a_customer'] = $first->created_at->format('Y-m-d') ?? null; $fields['last_purchase'] = $latest->created_at->format('Y-m-d') ?? null; $fields['purchases'] = $this->user->subscriptions->where('type', 'kanka')->count(); $fields['package'] = $this->user->subscribed('kanka') ? $this->user->pledge : null; $fields['last_login'] = $this->user->last_login_at->format('Y-m-d'); // Number of logins over the past month $fields['recent_logins'] = $this->user->logs()->logins()->where('created_at', '>=', now()->subMonth())->count(); // Remove abandoned cart data when they sub if ($this->user->isSubscriber()) { $fields['abandoned_cart'] = null; $fields['abandoned_package'] = null; } } if (isset($this->fields)) { $fields = array_merge($fields, $this->fields); } return $fields; } } ================================================ FILE: app/Services/Onboarding/InitialService.php ================================================ saveName() ->saveType(); } public function skip(string $reason) { CampaignEvent::create([ 'campaign_id' => $this->campaign->id, 'created_by' => $this->user->id, 'event' => 'onboarding_dismissed', 'metadata' => ['method' => $reason], ]); $this->log('skip'); } protected function log(string $type): void { $ui = $this->campaign->settings; $ui['onboarding'] = $type; $this->campaign->update(['settings' => $ui]); $this->user->campaignLog( $this->campaign->id, 'onboarding', $type, ); } protected function saveName(): self { if (! $this->request->has('name')) { return $this; } $this->campaign->update([ 'name' => $this->request->get('name'), ]); if (! $this->campaign->wasChanged('name')) { return $this; } $this->user->campaignLog( $this->campaign->id, 'onboarding', 'rename' ); return $this; } protected function saveType(): self { if (! $this->request->has('type')) { return $this; } $type = $this->request->get('type'); $this->log($type); CampaignEvent::create([ 'campaign_id' => $this->campaign->id, 'created_by' => $this->user->id, 'event' => 'onboarding_completed', 'metadata' => ['choice' => $type], ]); if ($type == 'worldbuilding') { $this->worldbuilding(); } elseif ($type == 'campaign') { $this->ttrpg(); } elseif ($type === 'story') { $this->story(); } CampaignCache::campaign($this->campaign)->clear(); return $this; } protected function worldbuilding(): void { /** @var CampaignSetting $settings */ $settings = $this->campaign->setting; $settings->quests = 0; $settings->dice_rolls = 0; $settings->conversations = 0; $settings->abilities = 0; $settings->items = 0; $settings->save(); $playerRole = $this->playerRole(); $playerRole->update(['name' => __('dashboards/onboarding.roles.contributor')]); $entityTypes = config('entities.ids'); foreach ($entityTypes as $entityType => $entityTypeId) { foreach ([CampaignPermission::ACTION_READ, CampaignPermission::ACTION_ADD] as $action) { CampaignPermission::create([ 'campaign_role_id' => $playerRole->id, 'action' => $action, 'entity_type_id' => $entityTypeId, 'access' => true, ]); } } CampaignPermission::create([ 'campaign_role_id' => $playerRole->id, 'action' => CampaignPermission::ACTION_GALLERY_UPLOAD, 'access' => true, ]); CampaignPermission::create([ 'campaign_role_id' => $playerRole->id, 'action' => CampaignPermission::ACTION_GALLERY_BROWSE, 'access' => true, ]); $family = Family::create([ 'name' => __('starter.name', ['name' => __('dashboards/onboarding.families.varren.title')]), 'campaign_id' => $this->campaign->id, ]); $family->entity->update(['source' => 'onboarding']); } protected function ttrpg(): void { $settings = $this->campaign->setting; $settings->dice_rolls = 0; $settings->timelines = 0; $settings->races = 0; $settings->save(); $tag = Tag::create([ 'name' => __('onboarding/tags.npcs'), 'campaign_id' => $this->campaign->id, ]); $tag->entity->update(['source' => 'onboarding']); // Give players some basic permissions to view/edit characters $playerRole = $this->playerRole(); $permissions = [ config('entities.ids.character') => [ CampaignPermission::ACTION_READ, CampaignPermission::ACTION_ADD, ], config('entities.ids.location') => [ CampaignPermission::ACTION_READ, ], config('entities.ids.family') => [ CampaignPermission::ACTION_READ, ], config('entities.ids.quest') => [ CampaignPermission::ACTION_READ, ], config('entities.ids.journal') => [ CampaignPermission::ACTION_READ, CampaignPermission::ACTION_ADD, ], ]; foreach ($permissions as $entityType => $actions) { foreach ($actions as $action) { CampaignPermission::create([ 'campaign_role_id' => $playerRole->id, 'action' => $action, 'entity_type_id' => $entityType, 'access' => true, ]); } } CampaignPermission::create([ 'campaign_role_id' => $playerRole->id, 'action' => CampaignPermission::ACTION_GALLERY_UPLOAD, 'access' => true, ]); CampaignDashboardWidget::create([ 'campaign_id' => $this->campaign->id, 'config' => [ 'filters' => 'status=0', 'text' => __('dashboards/onboarding.widgets.active-quests'), ], 'position' => 3, 'widget' => Widget::Recent, 'entity_type_id' => config('entities.ids.quest'), ]); $quest = Quest::create([ 'name' => __('starter.name', ['name' => __('dashboards/onboarding.quests.crown.title')]), 'campaign_id' => $this->campaign->id, ]); $quest->entity->update(['source' => 'onboarding']); } protected function story(): void { $settings = $this->campaign->setting; $settings->quests = 0; $settings->dice_rolls = 0; $settings->conversations = 0; $settings->abilities = 0; $settings->calendars = 0; $settings->save(); $playerRole = $this->playerRole(); $playerRole->update(['name' => __('dashboards/onboarding.roles.co-writer')]); } protected function playerRole(): CampaignRole { return $this->campaign->roles()->where('is_admin', 0)->where('is_public', 0)->first(); } } ================================================ FILE: app/Services/PaginationService.php ================================================ options; } /** * Options that require a subscription */ public function subscriberOnlyOptions(): array { return array_values(array_filter($this->options, fn ($v) => $v > $this->nonSubscriberMax)); } /** * Get the max pagination amount possible */ public function max(): int { return isset($this->user) && $this->user->isSubscriber() ? Arr::last($this->options) : $this->nonSubscriberMax; } } ================================================ FILE: app/Services/PatreonService.php ================================================ first(); } /** * Remove a user's legacy link to the patreon service */ public function unlink(): bool { if (! $this->user->isLegacyPatron()) { return false; } if ($this->user->hasRole(Pledge::ROLE)) { $this->user->roles()->detach($this->getRole()->id); } $settings = $this->user->settings; unset($settings['patreon_fullname'], $settings['patreon_name'], $settings['patreon_id'], $settings['patreon_email']); if (empty($settings)) { $settings = null; } $this->user->pledge = null; $this->user->settings = $settings; $this->user->save(); return true; } } ================================================ FILE: app/Services/PayPalService.php ================================================ tier = $tier; return $this; } public function process(): mixed { if ($this->user->isSubscriber() && ! $this->user->hasPayPal()) { return []; } $oldPrice = ''; $currency = 'USD'; if ($this->user->billedInEur()) { $currency = 'EUR'; } $price = $this->tier->yearly; if ($this->user->isSubscriber()) { if ($this->user->isElemental()) { return []; } elseif ($this->user->isOwlbear()) { $tier = Tier::where('code', 'owlbear')->first(); $oldPrice = $tier->yearly; } elseif ($this->user->isWyvern()) { $tier = Tier::where('code', 'wyvern')->first(); $oldPrice = $tier->yearly; } // @phpstan-ignore-next-line $price = round(($price - ($oldPrice)) * ($this->user->subscriptions()->first()->ends_at->diffInDays(Carbon::now(), true) / 365), 2); } $price = max(0, $price); $provider = new PayPal; $provider->setApiCredentials(config('paypal')); $paypalToken = $provider->getAccessToken(); $response = $provider->createOrder([ 'intent' => 'CAPTURE', 'application_context' => [ 'return_url' => route('paypal.transaction-success'), 'cancel_url' => route('paypal.cancel-transaction'), ], 'purchase_units' => [ 0 => [ 'reference_id' => $this->tier->name, 'amount' => [ 'currency_code' => $currency, 'value' => $price, ], ], ], ]); return $response; } public function subscribe(string $pledge): void { if (! $this->user->isSubscriber()) { // Add the subscriber role $this->user->roles()->syncWithoutDetaching([5]); // Add the subscription to the user level $this->user->pledge = $pledge; $this->user->save(); $sub = new Subscription; $sub->user_id = $this->user->id; $sub->type = 'kanka'; $sub->stripe_id = 'paypal_' . uniqid(); $sub->stripe_status = 'canceled'; $sub->stripe_price = 'paypal_' . $this->user->pledge; $sub->quantity = 1; $sub->ends_at = Carbon::now()->addYear(); $sub->save(); } else { // Add the subscription to the user level $this->user->pledge = $pledge; $this->user->save(); $sub = $this->user->subscriptions()->first(); $sub->stripe_price = 'paypal_' . $this->user->pledge; // @phpstan-ignore-line $sub->save(); } $this->user->log(UserAction::subPaypal); } } ================================================ FILE: app/Services/PermissionService.php ================================================ action = $action->value; return $this; } public function save(array $request): void { // First, let's get all the stuff for this entity $permissions = $this->entityPermissions($this->entity); // Next, start looping the data if (! empty($request['role'])) { foreach ($request['role'] as $roleId => $data) { foreach ($data as $perm => $action) { if ($action === 'allow') { if (empty($permissions['role'][$roleId][$perm])) { CampaignPermission::create([ 'campaign_role_id' => $roleId, 'campaign_id' => $this->entity->campaign_id, 'entity_type_id' => $this->entity->type_id, 'entity_id' => $this->entity->id, 'action' => $perm, 'access' => true, ]); } else { $permissions['role'][$roleId][$perm]->update(['access' => true]); unset($permissions['role'][$roleId][$perm]); } } elseif ($action === 'deny') { if (empty($permissions['role'][$roleId][$perm])) { CampaignPermission::create([ 'campaign_role_id' => $roleId, 'campaign_id' => $this->entity->campaign_id, 'entity_type_id' => $this->entity->type_id, 'entity_id' => $this->entity->id, 'action' => $perm, 'access' => false, ]); } else { $permissions['role'][$roleId][$perm]->update(['access' => false]); unset($permissions['role'][$roleId][$perm]); } } else { // Inherit? Remove it if it exists if (! empty($permissions['role'][$roleId][$perm])) { $permissions['role'][$roleId][$perm]->delete(); } } } } } if (! empty($request['user'])) { foreach ($request['user'] as $userId => $data) { foreach ($data as $perm => $action) { if ($action === 'allow') { if (empty($permissions['user'][$userId][$perm])) { CampaignPermission::create([ 'user_id' => $userId, 'campaign_id' => $this->entity->campaign_id, 'entity_type_id' => $this->entity->type_id, 'entity_id' => $this->entity->id, 'action' => $perm, 'access' => true, ]); } else { $permissions['user'][$userId][$perm]->update(['access' => true]); unset($permissions['user'][$userId][$perm]); } } elseif ($action === 'deny') { if (empty($permissions['user'][$userId][$perm])) { CampaignPermission::create([ 'user_id' => $userId, 'campaign_id' => $this->entity->campaign_id, 'entity_type_id' => $this->entity->type_id, 'entity_id' => $this->entity->id, 'action' => $perm, 'access' => false, ]); } else { $permissions['user'][$userId][$perm]->update(['access' => false]); unset($permissions['user'][$userId][$perm]); } } else { // Inherit? Remove it if it exists if (! empty($permissions['user'][$userId][$perm])) { $permissions['user'][$userId][$perm]->delete(); } } } } } // Delete remaining permissions $skipUsers = Arr::has($request, 'permissions_too_many'); foreach ($permissions as $type => $data) { // Skip users if there are too many users in the UI if ($type === 'user' && $skipUsers) { continue; } foreach ($data as $user => $actions) { foreach ($actions as $action => $perm) { $perm->delete(); } } } // Campaign admins can hide all attributes from an entity if ($this->user->isAdmin()) { $privateAttributes = Arr::get($request, 'is_attributes_private', false); $this->entity->is_attributes_private = $privateAttributes ? 1 : 0; } } /** * Get the permissions of an entity */ public function entityPermissions(Entity $entity): array { if (isset($this->cachedPermissions)) { return $this->cachedPermissions; } $permissions = ['user' => [], 'role' => []]; /** @var CampaignPermission $perm */ foreach (CampaignPermission::where('entity_id', $entity->id)->get() as $perm) { $key = (! empty($perm->user_id) ? 'user' : 'role'); $subkey = (! empty($perm->user_id) ? $perm->user_id : $perm->campaign_role_id); $permissions[$key][$subkey][$perm->action] = $perm; } return $this->cachedPermissions = $permissions; } public function clearEntityPermissions(): self { unset($this->cachedPermissions); return $this; } public function inherited(): bool { if (! isset($this->entityType)) { return false; } if (! isset($this->basePermissions)) { $this->basePermissions = [ 'roles' => [], 'users' => [], ]; /** @var CampaignRole $campaignRole */ foreach ($this->campaign->roles()->with(['users', 'permissions' => fn ($q) => $q->whereNull('entity_id')->whereNull('user_id')->where('entity_type_id', $this->entityType->id)])->get() as $campaignRole) { $campaignPermissions = $campaignRole->permissions; $users = $campaignRole->users->pluck('user_id'); /** @var CampaignPermission $campaignPermission */ foreach ($campaignPermissions as $campaignPermission) { $key = $campaignPermission->entity_type_id . '_' . $campaignPermission->action; $this->basePermissions['roles'][$campaignRole->id][$key] = true; foreach ($users as $permissionUser) { $this->basePermissions['users'][$permissionUser][$key] = [ 'role' => $campaignRole->name, 'access' => $campaignPermission->access, ]; } } } } $key = $this->entityType->id . '_' . $this->action; if (isset($this->role)) { return Arr::has($this->basePermissions, "roles.{$this->role->id}.{$key}"); } return Arr::has($this->basePermissions, "users.{$this->user->id}.{$key}"); } public function inheritedRoleName(): string { $key = $this->entityType->id . '_' . $this->action; return $this->basePermissions['users'][$this->user->id][$key]['role']; } public function inheritedRoleAccess(): bool { $key = $this->entityType->id . '_' . $this->action; return $this->basePermissions['users'][$this->user->id][$key]['access']; } public function selected(string $type): string { if (! isset($this->cachedPermissions)) { return 'inherit'; } $user = isset($this->user) ? $this->user->id : $this->role->id; $value = Arr::get($this->cachedPermissions, $type . '.' . $user . '.' . $this->action, null); if ($value === null) { return 'inherit'; } return $value->access ? 'allow' : 'deny'; } public function reset(): self { unset($this->user, $this->role, $this->action); return $this; } } ================================================ FILE: app/Services/Permissions/BulkPermissionService.php ================================================ service = $permissionService; } public function override(bool $override): self { $this->override = $override; return $this; } public function change(array $request): void { // First, let's get all the stuff for this entity $this->permissions = $this->service ->clearEntityPermissions() ->entityPermissions($this->entity); $this ->roles($request) ->users($request) ->cleanup(); } protected function roles(array $request): self { if (empty($request['role'])) { return $this; } foreach ($request['role'] as $roleId => $data) { foreach ($data as $perm => $action) { if ($action == 'allow') { if (empty($this->permissions['role'][$roleId][$perm])) { CampaignPermission::create([ 'campaign_role_id' => $roleId, 'campaign_id' => $this->entity->campaign_id, 'entity_id' => $this->entity->id, 'action' => $perm, 'access' => true, ]); } else { $this->permissions['role'][$roleId][$perm]->update(['access' => true]); unset($this->permissions['role'][$roleId][$perm]); } } elseif ($action == 'remove') { if (! empty($this->permissions['role'][$roleId][$perm])) { $this->permissions['role'][$roleId][$perm]->delete(); unset($this->permissions['role'][$roleId][$perm]); } } elseif ($action === 'deny') { if (empty($this->permissions['role'][$roleId][$perm])) { CampaignPermission::create([ 'campaign_role_id' => $roleId, 'campaign_id' => $this->entity->campaign_id, 'entity_id' => $this->entity->id, 'action' => $perm, 'access' => false, ]); } else { $this->permissions['role'][$roleId][$perm]->update(['access' => false]); unset($this->permissions['role'][$roleId][$perm]); } } elseif ($action == 'inherit') { // Inherit? Remove it if it exists if (! empty($this->permissions['role'][$roleId][$perm])) { $this->permissions['role'][$roleId][$perm]->delete(); } } } } return $this; } protected function users(array $request): self { if (empty($request['user'])) { return $this; } foreach ($request['user'] as $userId => $data) { foreach ($data as $perm => $action) { if ($action == 'allow') { if (empty($this->permissions['user'][$userId][$perm])) { CampaignPermission::create([ // 'key' => $this->entity->entityType->code . '_' . $perm . '_' . $this->entity->child->id, 'user_id' => $userId, 'campaign_id' => $this->entity->campaign_id, 'entity_id' => $this->entity->id, // 'entity_type_id' => $this->entity->type_id, 'action' => $perm, 'access' => true, ]); } else { $this->permissions['user'][$userId][$perm]->update(['access' => true]); unset($this->permissions['user'][$userId][$perm]); } } elseif ($action == 'remove') { if (! empty($this->permissions['user'][$userId][$perm])) { $this->permissions['user'][$userId][$perm]->delete(); unset($this->permissions['user'][$userId][$perm]); } } elseif ($action === 'deny') { if (empty($this->permissions['user'][$userId][$perm])) { CampaignPermission::create([ // 'key' => $this->entity->entityType->code . '_' . $perm . '_' . $this->entity->child->id, 'user_id' => $userId, 'campaign_id' => $this->entity->campaign_id, 'entity_id' => $this->entity->id, 'action' => $perm, 'access' => false, ]); } else { $this->permissions['user'][$userId][$perm]->update(['access' => false]); } } elseif ($action == 'inherit') { // Inherit? Remove it if it exists if (! empty($this->permissions['user'][$userId][$perm])) { $this->permissions['user'][$userId][$perm]->delete(); } } } } return $this; } protected function cleanup(): void { // If the user requested an override, any permissions that was not specifically set will be deleted. if (! $this->override) { return; } foreach ($this->permissions as $type => $data) { foreach ($data as $user => $actions) { foreach ($actions as $action => $perm) { $perm->delete(); } } } } } ================================================ FILE: app/Services/Permissions/EntityPermission.php ================================================ loadAllPermissions(); if ($this->userIsAdmin()) { return true; } // Check general module permissions $module = isset($this->entityType) ? $this->entityType->id : $this->entity->type_id; $key = '' . $module . '_' . $permission->value; $perm = false; if (isset($this->cached[$key]) && $this->cached[$key]) { $perm = $this->cached[$key]; } // dump('module permission'); // dump($key); // dump($this->cached); if (! isset($this->entity)) { // dd('no entity'); return $perm; } // Search for entity $entityKey = '_' . $permission->value . '_' . $this->entity->id; // dump('check entity'); // dd($entityKey); if (isset($this->cached[$entityKey])) { return $this->cached[$entityKey]; } return $perm; } /** * Determine the permission for a user to interact with an entity * * @param MiscModel|Entity|null $entity */ public function hasPermission( int $entityType, int $action, $entity = null, ): bool { $this->loadAllPermissions(); if ($this->userIsAdmin()) { return true; } // Check if we have permission to `action` all the entities of this type first. The user // might be able to view all quests, but have a specific quest set to denied. This is why // we need to check the specific permissions too. if ($entityType === 0) { // Campaign permissions are a bit funky $entityType = 'campaign'; } $key = $entityType . '_' . $action; // if ($action === Permission::Bookmarks->value) { // dump($key = $entityType . '_' . $action); // dump($this->cached); // } $perm = false; if (isset($this->cached[$key]) && $this->cached[$key]) { $perm = $this->cached[$key]; } // Check if we have permission to do this action for exactly this entity if (! empty($entity)) { // dump('i have an entity?'); // dump($entity); // Check if $entity is an entity type. if (isset($entity->type_id)) { $entityKey = '_' . $action . '_' . $entity->entity_id; } else { // dump('misc object'); $entityKey = '_' . $action . '_' . $entity->id; } // dump('entity key ' . $entityKey); if (isset($this->cached[$entityKey])) { $perm = $this->cached[$entityKey]; } } // dump('have access? ' . ($perm ? 'yes' : 'no')); return $perm; } /** * Check the roles of the user. If the user is an admin, always return true */ protected function getRoleIds(): array|bool { // If we haven't built a list of roles yet, build it. if (isset($this->roleIds)) { return $this->roleIds; } $this->roles = false; // If we have a user, get the user's role for this campaign if (isset($this->user)) { $this->roles = UserCache::user($this->user)->campaign($this->campaign)->roles(); } // If we don't have a user, or our user has no specified role yet, use the public role. if ($this->roles === false || $this->roles->count() == 0) { // Use the campaign's public role $this->roles = $this->campaign->roles()->where('is_public', true)->get(); } // Save all the role ids. If one of them is an admin, stop there. $this->roleIds = []; foreach ($this->roles as $role) { if ($role['is_admin']) { $this->roleIds = true; return true; } $this->roleIds[] = $role['id']; } return $this->roleIds; } /** * Determine if a user is part of a role that can do an action on all entities of a campaign */ public function canRole(string $action, string $modelName): bool { $this->loadAllPermissions(); $key = $modelName . '_' . $action; // We check if the role was set for global entity permissions if (isset($this->cached[$key]) && $this->cached[$key]) { return $this->cached[$key]; } return false; } /** * It's way easier to just load all permissions of the user once and "cache" them, rather than try and be * optional on each query. */ protected function loadAllPermissions(): void { // If no campaign was provided, get the one in the url. One is provided when moving entities between campaigns if (! isset($this->campaign)) { // Before we do that, we need to check if we're in a factory for unit tests if (app()->environment('testing')) { $this->userIsAdmin = true; return; } else { abort(404); } } if ($this->loadedAll === true && $this->campaign->id == $this->loadedCampaignId) { return; } $this->resetPermissions(); $this->loadedCampaignId = $this->campaign->id; // Loop through the roles to build a list of ids, and check if one of our roles is an admin unset($this->roleIds); $roleIds = $this->getRoleIds(); if ($roleIds === true) { // If the role ids is simply true, it means the user is an admin $this->userIsAdmin = true; return; } $campaignRoleIDs = []; /** @var CampaignRole $role */ foreach ($this->roles as $role) { $campaignRoleIDs[] = $role['id']; } // dump('roles'); if (! empty($campaignRoleIDs)) { $permissions = RolePermission::rolesPermissions($campaignRoleIDs); /** @var CampaignPermission $permission */ foreach ($permissions as $permission) { // dump($permission->id . ' - ' . $permission->key()); $this->cached[$permission->key()] = $permission->access; if (! empty($permission->entity_id)) { $this->cachedEntityIds[$permission->entity_type_id][$permission->entity_id][$permission->action] = (bool) $permission->access; } } } // If a user is provided, get their permissions too // dump('user'); if (isset($this->user)) { $userPermissions = $this->user->permissions()->where('campaign_id', $this->campaign->id)->get(); /** @var CampaignPermission $permission */ foreach ($userPermissions as $permission) { $this->cached[$permission->key()] = $permission->access; // dump($permission->id . ' - ' . $permission->key()); if (! empty($permission->entity_id)) { $this->cachedEntityIds[$permission->entity_type_id][$permission->entity_id][$permission->action] = (bool) $permission->access; } } unset($userPermissions); } // dump('finished loading entities:'); // dump($this->cachedEntityIds); } /** * Reset all cached permissions. */ public function resetPermissions(): void { // Reset the values keeping score $this->loadedAll = true; $this->cached = []; unset($this->roleIds, $this->userIsAdmin); } protected function userIsAdmin(): bool { return isset($this->userIsAdmin) && $this->userIsAdmin; } } ================================================ FILE: app/Services/Permissions/PermissionService.php ================================================ loadPermissions(); return $this->admin; } public function entityTypeID(int $id): self { $this->entityTypeID = $id; return $this; } /** * Set the desired action */ public function action(Permission $permission): self { $this->action = $permission->value; return $this; } public function publicRoleId(): int { return $this->publicRoleId; } public function reload(): self { $this->entityIds = []; $this->entityTypes = []; $this->entityTypesIds = []; $this->deniedIds = []; unset($this->loadedRoles); $this->admin = false; unset($this->publicRoleId); return $this; } public function entityType(int $entityType): self { $this->entityType = $entityType; return $this; } /** * List of post ids the user has access to */ public function allowedPosts(): array { return $this->loadPostPermissions() ->allowedPostIDs(); } /** * List of post ids the user doesn't have access to */ public function deniedPosts(): array { return $this->loadPostPermissions() ->deniedPostIDs(); } public function allowedEntities(): array { $this->loadPermissions(); return $this->entityIds; } public function allowedEntityTypes(): array { $this->loadPermissions(); return $this->entityTypesIds; } public function createTemporaryTable(): self { if ($this->tempPermissionFilled) { return $this; } if (! $this->tempPermissionCreated) { Schema::create('tmp_permissions', function (Blueprint $table) { $table->unsignedInteger('id'); $table->temporary(); }); $this->tempPermissionCreated = true; $this->tune('Temp table created'); } $batch = []; foreach ($this->entityIds as $id) { $batch[] = $id; if (count($batch) > 900) { DB::statement('INSERT INTO tmp_permissions (id) VALUES (' . implode(') ,(', $batch) . ')'); $batch = []; } } if (count($batch) > 0) { DB::statement('INSERT INTO tmp_permissions (id) VALUES (' . implode(') ,(', $batch) . ')'); } // dump(in_array(329259, $batch)); // $wa = DB::table('tmp_permissions') // ->where('id', 329259)->get(); // dd($wa); $this->tempPermissionFilled = true; $this->tune('Temp table filled'); return $this; } public function deniedEntities(): array { $this->loadPermissions(); return $this->deniedIds; } protected function allowedPostIDs(): array { return $this->allowedPostIDs; } protected function deniedPostIDs(): array { return $this->deniedPostIDs; } public function canRole(): bool { $this->loadPermissions(); return in_array($this->entityType, $this->entityTypesIds); } /** * Grant a permission ad-hoc */ public function grant(Entity $entity): self { $this->granted = true; $this->entityIds[] = $entity->id; $this->tempPermissionFilled = false; return $this; } /** * Was a permission granted? */ public function granted(): bool { return $this->granted; } /** * Load the permissions for posts */ protected function loadPostPermissions(): self { if ($this->loadedPosts) { return $this; } $this->loadedPosts = true; // Get the user's individual and role permissions $roles = $this->userRoles(); $perms = PostPermission::select(['post_id', 'permission']) ->leftJoin('posts as p', 'p.id', 'post_permissions.post_id') ->leftJoin('entities as e', 'e.id', 'p.entity_id') ->where(function ($sub) use ($roles) { $sub->where('user_id', $this->user->id) ->orWhereIn('role_id', $roles); }) ->where('e.campaign_id', $this->campaign->id) ->get(); /** @var PostPermission $perm */ foreach ($perms as $perm) { if ($perm->permission === 2) { $this->deniedPostIDs[] = $perm->post_id; } else { $this->allowedPostIDs[] = $perm->post_id; } } return $this; } /** * Load the permissions of the user (roles and personal permissions) */ private function loadPermissions(): self { if ($this->loadedPermissions) { return $this; } $this->start = microtime(true); $this->loadedPermissions = true; // Valid user: load their roles if ($this->hasUser()) { $this->loadRoles(); $this->loadUserPermissions(); $this->tune('Perms loaded for user'); } // If the user had no loaded roles, we need a public role if (isset($this->loadedRoles) && $this->loadedRoles > 0) { return $this; } $this->loadPublicRole(); $this->tune('Perms loaded with public'); return $this; } public function reset(): self { $this->loadedPermissions = false; unset($this->loadedRoles); $this->admin = false; return $this; } /** * Load the user's roles */ protected function loadRoles(): self { if (isset($this->loadedRoles)) { return $this; } $this->loadedRoles = 0; $roles = UserCache::user($this->user)->roles(); $roleIDs = []; foreach ($roles as $role) { $this->loadedRoles++; // If one of the roles is an admin, we don't need to figure any more stuff, we're good. if ($role['is_admin']) { $this->admin = true; return $this; } $roleIDs[] = $role['id']; } $this->parseRoles($roleIDs); return $this; } /** * Load public role permissions as a fall-back for non-members of the campaign. */ protected function loadPublicRole(): void { /** @var CampaignRole $publicRole */ $publicRole = $this->campaign ->roles() ->where('is_public', true) ->first(); if ($publicRole) { $this->parseRole($publicRole); } } /** * Load the permissions of a role into the service */ protected function parseRole(CampaignRole $role): void { // Loop through the permissions of the role to get any blanket _read permissions on entities $permissions = RolePermission::role($role)->permissions(); $this->publicRoleId = $role->id; foreach ($permissions as $permission) { $this->parseRolePermission($permission); } } /** * Load the permissions of several roles into the service */ protected function parseRoles(array $roleIDs): void { // Loop through the permissions of the role to get any blanket _read permissions on entities $permissions = RolePermission::rolesPermissions($roleIDs); // dump($permissions); // CampaignPermission::whereIn('campaign_role_id', $roleIDs)->get(); foreach ($permissions as $permission) { $this->parseRolePermission($permission); } } /** * Parse a role permission */ protected function parseRolePermission(CampaignPermission $permission) { // Only test permissions whose action is being requested if (isset($this->action) && ! $permission->isAction($this->action)) { return; } if (isset($this->entityType) && ! empty($this->entityType) && $permission->entity_type_id !== $this->entityType) { return; } if (isset($this->entityTypeID) && ! empty($permission->entity_type_id) && $permission->entity_type_id !== $this->entityTypeID) { return; } if (empty($permission->entity_id)) { if (! in_array($permission->entity_type_id, $this->entityTypesIds)) { $this->entityTypesIds[] = $permission->entity_type_id; } } elseif ($permission->access && ! in_array($permission->entity_id, $this->entityIds)) { // This permission targets an entity directly $this->entityIds[] = $permission->entity_id; } elseif (! $permission->access && ! in_array($permission->entity_id, $this->deniedIds)) { // This permission targets an entity directly $this->deniedIds[] = $permission->entity_id; } } /** * Load the user's permissions. */ protected function loadUserPermissions(): void { if ($this->admin) { return; } // If we have a user, they might have individual entity permissions $permissions = CampaignPermission::where('user_id', $this->user->id) ->where('campaign_id', $this->campaign->id) ->get(); foreach ($permissions as $permission) { $this->parseUserPermission($permission); } } /** * Parse a permission */ protected function parseUserPermission(CampaignPermission $permission) { if (isset($this->action) && ! $permission->isAction($this->action)) { return; } // If the permission set is negative, we need to add it to the denied ones too, in case a role of // the user has access to this entity. if ($permission->access) { if (! in_array($permission->entity_id, $this->entityIds)) { $this->entityIds[] = $permission->entity_id; } // If the user was denied through a role but has access through a direct permissions, still allow them if (($key = array_search($permission->entity_id, $this->deniedIds)) !== false) { unset($this->deniedIds[$key]); } return; } if (! in_array($permission->entity_id, $this->deniedIds)) { $this->deniedIds[] = $permission->entity_id; } } protected function tune(string $log): void { // if (!isset($this->campaign)) { // return; // } // if ($this->campaign->id !== 1) { // return; // } // // Log::info($log . ' in ' . round(microtime(true) - $this->start, 3) . 's'); } protected function userRoles(): array { return UserCache::user($this->user) ->roles() ->pluck('id') ->toArray(); } } ================================================ FILE: app/Services/Permissions/RolePermission.php ================================================ permissions[$this->role->id])) { return $this->permissions[$this->role->id]; } return $this->permissions[$this->role->id] = $this->role->permissions; } public function rolesPermissions(array $roles): mixed { $key = implode('-', $roles); if (isset($this->rolesPermissions[$key])) { return $this->rolesPermissions[$key]; } return $this->rolesPermissions[$key] = CampaignPermission::roleIds($roles)->get(); } } ================================================ FILE: app/Services/Permissions/RolePermissionService.php ================================================ type = $type; return $this; } /** * Get the campaign role permissions. First key is the entity type */ public function permissions(): array { $permissions = []; $campaignRolePermissions = []; foreach ($this->role->rolePermissions as $perm) { $campaignRolePermissions[$perm->entity_type_id . '_' . $perm->action] = 1; } $entityActions = [ CampaignPermission::ACTION_READ, CampaignPermission::ACTION_EDIT, CampaignPermission::ACTION_ADD, CampaignPermission::ACTION_DELETE, CampaignPermission::ACTION_POSTS, CampaignPermission::ACTION_PERMS, ]; $icons = [ CampaignPermission::ACTION_READ => [ 'fa-regular fa-eye', 'read', ], CampaignPermission::ACTION_EDIT => [ 'fa-regular fa-pen', 'edit', ], CampaignPermission::ACTION_ADD => [ 'fa-regular fa-plus-square', 'add', ], CampaignPermission::ACTION_DELETE => [ 'fa-regular fa-trash-alt', 'delete', ], CampaignPermission::ACTION_POSTS => [ 'fa-regular fa-file', 'articles', ], CampaignPermission::ACTION_PERMS => [ 'fa-regular fa-cog', 'permission', ], ]; // Public actions if ($this->role->isPublic()) { // $actions = ['read']; $entityActions = [CampaignPermission::ACTION_READ]; } foreach (EntityType::exclude([config('entities.ids.bookmark')])->inCampaign($this->campaign)->get() as $entityType) { foreach ($entityActions as $action) { if (! isset($permissions[$entityType->plural()])) { $permissions[$entityType->plural()] = [ 'entityType' => $entityType, 'permissions' => [], ]; } $key = "{$entityType->id}_{$action}"; $permissions[$entityType->plural()]['permissions'][] = [ 'action' => $action, 'key' => $key, 'icon' => Arr::first($icons[$action]), 'label' => Arr::last($icons[$action]), 'enabled' => isset($campaignRolePermissions[$key]), ]; } } $collator = new \Collator(app()->getLocale()); $collator->asort($permissions); $keys = array_keys($permissions); $collator->sort($keys); $result = []; foreach ($keys as $key) { $result[$key] = $permissions[$key]; } return $result; } /** * Campaign Permissions */ public function campaignPermissions(): array { $permissions = []; $campaignRolePermissions = []; foreach ($this->role->permissions as $perm) { if ($perm->entity_type_id || $perm->isGallery()) { continue; } $campaignRolePermissions['campaign_' . $perm->action] = 1; } $entityActions = [ CampaignPermission::ACTION_MANAGE, CampaignPermission::ACTION_DASHBOARD, CampaignPermission::ACTION_MEMBERS, ]; $icons = [ CampaignPermission::ACTION_MANAGE => [ 'fa-regular fa-cog', 'manage', ], CampaignPermission::ACTION_DASHBOARD => [ 'fa-regular fa-columns', 'dashboard', ], CampaignPermission::ACTION_MEMBERS => [ 'fa-regular fa-users', 'members', ], ]; foreach ($entityActions as $action) { if (! isset($permissions['campaign'])) { $permissions['campaign'] = []; } $key = "campaign_{$action}"; $permissions['campaign'][] = [ 'action' => $action, 'key' => $key, 'icon' => Arr::first($icons[$action]), 'label' => Arr::last($icons[$action]), 'enabled' => isset($campaignRolePermissions[$key]), ]; } return $permissions; } public function galleryPermissions(): array { $permissions = []; $campaignRolePermissions = []; foreach ($this->role->permissions as $perm) { if ($perm->entity_type_id || ! $perm->isGallery()) { continue; } $campaignRolePermissions['campaign_' . $perm->action] = 1; } $entityActions = [ CampaignPermission::ACTION_GALLERY, CampaignPermission::ACTION_GALLERY_BROWSE, CampaignPermission::ACTION_GALLERY_UPLOAD, ]; $icons = [ CampaignPermission::ACTION_GALLERY => [ 'fa-regular fa-cog', 'gallery.manage', ], CampaignPermission::ACTION_GALLERY_BROWSE => [ 'fa-regular fa-eye', 'gallery.browse', ], CampaignPermission::ACTION_GALLERY_UPLOAD => [ 'fa-regular fa-upload', 'gallery.upload', ], ]; foreach ($entityActions as $action) { if (! isset($permissions['campaign'])) { $permissions['campaign'] = []; } $key = "campaign_{$action}"; $permissions['campaign'][] = [ 'action' => $action, 'key' => $key, 'icon' => Arr::first($icons[$action]), 'label' => Arr::last($icons[$action]), 'enabled' => isset($campaignRolePermissions[$key]), ]; } return $permissions; } public function templatePermissions(): array { $permissions = []; $campaignRolePermissions = []; foreach ($this->role->permissions as $perm) { if ($perm->entity_type_id || ! $perm->isTemplate()) { continue; } $campaignRolePermissions['campaign_' . $perm->action] = 1; } $entityActions = [ CampaignPermission::ACTION_TEMPLATES, CampaignPermission::ACTION_POST_TEMPLATES, ]; $icons = [ CampaignPermission::ACTION_TEMPLATES => [ 'fa-regular fa-cog', 'entries', ], CampaignPermission::ACTION_POST_TEMPLATES => [ 'fa-regular fa-cog', 'articles', ], ]; foreach ($entityActions as $action) { if (! isset($permissions['campaign'])) { $permissions['campaign'] = []; } $key = "campaign_{$action}"; $permissions['campaign'][] = [ 'action' => $action, 'key' => $key, 'icon' => Arr::first($icons[$action]), 'label' => Arr::last($icons[$action]), 'enabled' => isset($campaignRolePermissions[$key]), ]; } return $permissions; } public function bookmarkPermissions(): array { $permissions = []; $campaignRolePermissions = []; foreach ($this->role->permissions as $perm) { if ($perm->entity_type_id || ! $perm->isBookmark()) { continue; } $campaignRolePermissions['campaign_' . $perm->action] = 1; } $entityActions = [ CampaignPermission::ACTION_BOOKMARKS, ]; $icons = [ CampaignPermission::ACTION_BOOKMARKS => [ 'fa-regular fa-cog', 'manage', ], ]; foreach ($entityActions as $action) { if (! isset($permissions['campaign'])) { $permissions['campaign'] = []; } $key = "campaign_{$action}"; $permissions['campaign'][] = [ 'action' => $action, 'key' => $key, 'icon' => Arr::first($icons[$action]), 'label' => Arr::last($icons[$action]), 'enabled' => isset($campaignRolePermissions[$key]), ]; } return $permissions; } public function savePermissions(array $permissions = []): void { // Load existing $existing = []; foreach ($this->role->rolePermissions as $permission) { if (empty($permission->entity_type_id)) { $existing['campaign_' . $permission->action] = $permission; continue; } $existing[$permission->entity_type_id . '_' . $permission->action] = $permission; } // Loop on submitted form if (empty($permissions)) { $permissions = []; } foreach ($permissions as $key => $module) { // Check if exists$ if (isset($existing[$key])) { // Do nothing unset($existing[$key]); } else { $action = Str::after($key, '_'); if ($module === 'campaign') { $module = 0; } $this->add($module, (int) $action); } } // Delete existing that weren't updated foreach ($existing as $permission) { // Only delete if it's a "general" and not an entity specific permission if (! is_numeric($permission->entity_id)) { $permission->delete(); } } } /** * Toggle an entity's action permission */ public function toggle(int $action): bool { $perm = $this->role->permissions() ->where('entity_type_id', $this->entityType->id) ->where('action', $action) ->whereNull('entity_id') ->first(); if ($perm) { $perm->delete(); return false; } $this->add($this->entityType->id, $action); return true; } /** * Determine if the loaded role has the permission to do a specific action on the * specified entity type (->type()) */ public function can(Permission $permission): bool // int $action = CampaignPermission::ACTION_READ): bool { return $this->role->permissions ->where('entity_type_id', $this->type) ->whereNull('entity_id') ->where('action', $permission->value) ->where('access', true) ->count() === 1; } /** * Add a campaign permission for the role */ protected function add(int $entityType, int $action): CampaignPermission { if ($entityType === 0) { $entityType = null; } return CampaignPermission::create([ // 'key' => $key, 'campaign_role_id' => $this->role->id, // 'table_name' => $value, 'access' => true, 'action' => $action, 'entity_type_id' => $entityType, // 'campaign_id' => $campaign->id, ]); } } ================================================ FILE: app/Services/Plugins/ImporterService.php ================================================ plugin = $plugin; return $this; } public function options(array $options): self { if (Arr::get($options, 'force_private', false)) { $this->forcePrivate = true; } if (Arr::get($options, 'only_new', false)) { $this->skipUpdates = true; } return $this; } /** * Import a content pack's entities into the campaign. */ public function import() { if (! $this->plugin->isContentPack()) { throw new Exception('not_content_pack'); } // Prepare the uuids for already imported lookups $campaignPlugin = $this->campaignPlugin(); $version = $campaignPlugin->version; $entities = $version->entities()->with('type')->get(); $uuids = $entities->pluck('uuid'); $this->importedEntities = Entity::whereIn('marketplace_uuid', $uuids)->get(); $count = 0; /** * First we create the base model for all entities, or load existing ones from the db */ foreach ($entities as $pluginEntity) { $this->importModel($pluginEntity); } /*dump('Misc Mapping'); dump($this->miscIds); dump('Entity Mapping'); dump($this->entityIds);*/ // Now what we have the base models and mappings, let's go again and post those fields foreach ($entities as $pluginEntity) { $this->importFields($pluginEntity); $count++; } PluginImported::dispatch($campaignPlugin, $this->user); return $count; } protected function importModel(PluginVersionEntity $pluginEntity) { // Updating? /** @var ?Entity $model */ $model = null; /** @var ?Entity $entity */ $entity = $this->importedEntities ->where('marketplace_uuid', $pluginEntity->uuid) ->where('type_id', $pluginEntity->type_id) ->first(); /** @var ?Entity $modifiedEntity */ $modifiedEntity = $this->importedEntities ->where('marketplace_uuid', $pluginEntity->uuid) ->first(); if ($entity) { $this->entityIds[$pluginEntity->id] = $entity->id; $this->miscIds[$pluginEntity->id] = $entity->entity_id; $this->entityTypes[$pluginEntity->id] = $entity->entityType->code; // dump('existing ' . $pluginEntity->uuid); $model = $entity->child; if (! $this->skipUpdates) { $this->updated[] = '' . $entity->name . ''; } else { $this->skippedEntities[] = $pluginEntity->id; } } else { if ($modifiedEntity) { $modifiedEntity->marketplace_uuid = null; $modifiedEntity->save(); } $className = '\App\Models\\' . Str::studly($pluginEntity->type->code); // dump('new ' . $className); /** @var MiscModel $model */ $model = new $className; $model->name = $pluginEntity->name; // $model->entry = $this->$pluginEntity->entry; $model->campaign_id = $this->campaign->id; $model->save(); $entity = $model->entity; $entity->marketplace_uuid = $pluginEntity->uuid; $entity->source = 'plugin'; $entity->save(); $this->miscIds[$pluginEntity->id] = $model->id; $this->entityTypes[$pluginEntity->id] = $model->entity->entityType->code; $this->entityIds[$pluginEntity->id] = $model->entity->id; $this->created[] = '' . $entity->name . ''; } $this->models[$pluginEntity->id] = $model; } protected function importFields(PluginVersionEntity $pluginEntity): void { if (in_array($pluginEntity->id, $this->skippedEntities)) { return; } // dump('Parsing entity ' . $pluginEntity->name . ' #' . $pluginEntity->id . ''); $this->model = $this->models[$pluginEntity->id]; $entityId = $this->getEntityId($pluginEntity->id); // dump("entityId: $entityId"); foreach ($pluginEntity->fields as $field => $value) { $this->importField($field, $value, $pluginEntity); } if ($this->forcePrivate) { $this->model->is_private = true; } $this->importImage($pluginEntity); // Mentions $this->model->entity->entry = preg_replace_callback('`\[entity:(.*?)\]`i', function ($matches) { $id = (int) $matches[1]; if (empty($id) || ! isset($this->entityIds[$id])) { return 'wat'; } return '[' . $this->entityTypes[$id] . ':' . $this->entityIds[$id] . ']'; }, $pluginEntity->entry); $this->model->save(); $this->model->entity->save(); // Relations if (! empty($pluginEntity->related)) { $this->importRelations($pluginEntity, $entityId); } $this->importPosts($pluginEntity, $entityId); } protected function importField(string $field, mixed $value, PluginVersionEntity $pluginEntity): void { // dump("field $field => $value"); // parent mapping if ($field == 'gender') { $field = 'sex'; } // Foreign key if (Str::endsWith($field, '_id')) { $this->importForeign($field, $value); } elseif (in_array($field, ['personality', 'appearance'])) { $this->importBlock($field, $value); } elseif ($field == 'tags') { $this->importTags($value); } elseif ($field == 'tooltip') { $this->importEntityTooltip($value); } elseif ($field == 'type') { $this->importEntityType($value); } elseif ($field == 'is_private' && $this->forcePrivate) { // Skip } else { $this->model->$field = $value; } } protected function importForeign(string $field, mixed $value): void { if ($field == 'race_id' && $this->model instanceof Character) { $this->importCharacterRace($value); return; } elseif ($field == 'family_id' && $this->model instanceof Character) { $this->importCharacterFamily($value); return; } if (empty($value)) { $this->model->$field = null; } elseif (isset($this->miscIds[$value])) { $this->model->$field = $this->miscIds[$value]; } } protected function importEntityTooltip(mixed $value): void { $this->model->entity->tooltip = $value; } protected function importEntityType(mixed $value): void { $this->model->entity->type = $value; } protected function importCharacterRace(mixed $value): void { if (empty($value)) { $this->model->races()->detach(); } elseif (isset($this->miscIds[$value])) { $raceID = $this->miscIds[$value]; $race = Race::find($raceID); if ($race) { $this->model->races()->attach($race); } } } protected function importCharacterFamily(mixed $value): void { if (empty($value)) { $this->model->families()->detach(); } elseif (isset($this->miscIds[$value])) { $raceID = $this->miscIds[$value]; $race = Family::find($raceID); if ($race) { $this->model->families()->attach($race); } } } protected function importRelations(mixed $pluginEntity, mixed $entityId): void { $this->loadRelations($this->model); foreach ($pluginEntity->related as $type => $fields) { foreach ($fields as $uuid => $data) { if ($type == 'relation') { $this->saveRelation($data, $uuid, $entityId); } elseif ($type == 'member') { $this->saveOrganisationMember($data, $uuid, $this->model->id, $pluginEntity); } elseif ($type == 'quest_element') { $this->saveQuestElement($data, $uuid, $this->model->id, $pluginEntity); } else { Log::info('Unknown relation type \'' . $type . '\' for marketplace entity #' . $pluginEntity->id); } } } } /** * Create or update a relation */ protected function saveRelation(array $data, string $uuid, int $ownerId): void { // dump("New relation for $ownerId"); // dump($data); $targetId = $this->getEntityId($data['target']); if (empty($targetId) || empty($data['relation'])) { return; } try { $relation = $this->loadedRelations[$uuid] ?? null; if (empty($relation)) { $relation = new Relation; $relation->owner_id = $ownerId; $relation->marketplace_uuid = $uuid; } $relation->target_id = $targetId; $relation->colour = Arr::get($data, 'colour', null); $relation->attitude = Arr::get($data, 'attitude', null); $relation->relation = Arr::get($data, 'relation', null); $relation->save(); } catch (Exception $e) { Log::error('Invalid relation for owner ' . $ownerId . ' and uuid ' . $uuid . ': ' . $e->getMessage()); } } protected function saveOrganisationMember(array $data, string $uuid, int $characterId, PluginVersionEntity $pluginEntity): void { // Check the target org // dump('adding org member for ' . $characterId); $organisation = $this->miscIds[$data['target']]; if (empty($organisation)) { return; } // Look if already exists try { $member = OrganisationMember::where('character_id', $characterId) ->where('organisation_id', $organisation) ->first(); if (! $member) { $member = new OrganisationMember; $member->character_id = $characterId; $member->organisation_id = $organisation; } $member->role = Arr::get($data, 'role', null); $member->save(); } catch (Exception $e) { Log::error('Invalid org member ' . $uuid . ' for plugin entity #' . $pluginEntity->id . ': ' . $e->getMessage()); } } protected function saveQuestElement(array $data, string $uuid, int $questId, PluginVersionEntity $pluginEntity): void { // dump('importing a quest element.'); // Determine what we're adding $target = $this->entityIds[$data['target']]; // dump("want to add target $target ($targetType) as a $class to $questId"); // Does it exist? try { /** @var QuestElement $element */ $element = QuestElement::where('quest_id', $questId)->where('entity_id', $target)->first(); if (empty($element)) { $element = new QuestElement; $element->quest_id = $questId; $element->entity_id = $target; } $element->role = Arr::get($data, 'role', null); $element->entry = $this->mentions(Arr::get($data, 'description', '')); $element->visibility_id = Visibility::All; $element->save(); } catch (Exception $e) { Log::error('Invalid quest element ' . $uuid . ' for plugin entity #' . $pluginEntity->id . ' (entity #' . $target . '): ' . $e->getMessage()); } } /** * Images are stored in the campaign gallery and need to be mapped to their uuid */ protected function importImage(PluginVersionEntity $entity): self { // Don't do anything if no image or replacing an image (too many false positives) if (empty($entity->image_path) || ! empty($this->model->entity->image_path) || ! empty($this->model->entity->image_uuid)) { return $this; } // Need to download the image from the marketplace's s3 (if possible) try { $imageExt = Str::afterLast($entity->image_path, '.'); // We need to create a new Image to migrate to the new system. Maybe in the future // we can store the marketplace's uuid here and avoid duplicates. $image = new Image; $image->campaign_id = $this->campaign->id; $image->ext = $imageExt; $image->name = $entity->name; $image->visibility_id = Visibility::All; $size = Storage::disk('s3-marketplace')->size($entity->image_path); $image->size = (int) ceil($size / 1024); // kb $image->save(); Storage::writeStream($image->path, Storage::disk('s3-marketplace')->readStream($entity->image_path)); $this->model->entity->image_uuid = $image->id; } catch (Exception $e) { Log::error('Error importing image from ' . $entity->id . ': ' . $e->getMessage()); } return $this; } protected function importBlock(string $block, ?array $values = null): void { if (empty($values)) { return; } /** @var CharacterTrait[] $existing */ $existing = []; foreach ($this->model->characterTraits()->{$block}()->get() as $pers) { $existing[$pers->name] = $pers; } foreach ($values as $name => $value) { if (isset($existing[$name])) { $existing[$name]->entry = $value; $existing[$name]->save(); } else { CharacterTrait::create([ 'character_id' => $this->model->id, 'section_id' => $block == 'appearance' ? CharacterTrait::SECTION_APPEARANCE : CharacterTrait::SECTION_PERSONALITY, 'name' => $name, 'entry' => $value, ]); } } } protected function importTags(?array $values = null): void { if (empty($values)) { return; } $real = []; foreach ($values as $val) { if (! empty($val)) { $real[] = $val; } } if (empty($real)) { return; } // Importing tags for // Get existing tags on this entity $existing = $this->model->entity->tags->pluck('id')->toArray(); foreach ($real as $tag) { // Tag doesn't properly exist, skip if (! isset($this->miscIds[$tag])) { continue; } $target = $this->miscIds[$tag]; if (in_array($target, $existing)) { continue; } $new = new EntityTag; $new->entity_id = $this->model->entity->id; $new->tag_id = $target; $new->save(); } } protected function getEntityId(int $id) { return $this->entityIds[$id]; } /** * Load */ protected function loadRelations(MiscModel $misc): void { $this->loadedRelations = []; /** @var Relation $relation */ foreach ($misc->entity->relationships()->whereNotNull('marketplace_uuid')->get() as $relation) { if (empty($relation->marketplace_uuid)) { continue; } $this->loadedRelations[$relation->marketplace_uuid] = $relation; } } protected function loadPosts(int $entityId): void { $this->loadedPosts = []; $posts = Post::where('entity_id', $entityId) ->whereNotNull('marketplace_uuid') ->get(); /** @var Post $post */ foreach ($posts as $post) { $this->loadedPosts[$post->marketplace_uuid] = $post; } } protected function mentions(string $text): string { return preg_replace_callback('`\[entity:(.*?)\]`i', function ($matches) { $id = (int) $matches[1]; if (empty($id) || ! isset($this->entityIds[$id])) { return 'wat'; } return '[' . $this->entityTypes[$id] . ':' . $this->entityIds[$id] . ']'; }, $text); } /** * List of created entities */ public function created(): string { return (string) implode(', ', $this->created); } /** * List of created entities */ public function updated(): string { return (string) implode(', ', $this->updated); } protected function importPosts(PluginVersionEntity $entity, int $entityId): void { if (empty($entity->posts)) { return; } $this->loadPosts($entityId); foreach ($entity->posts as $uuid => $data) { $post = $this->loadedPosts[$uuid] ?? null; if (empty($post)) { $post = new Post; $post->entity_id = $entityId; $post->marketplace_uuid = $uuid; } $visibility = Visibility::All->value; if (Arr::get($data, 'visibility') == 'admin') { $visibility = Visibility::Admin->value; } elseif (Arr::get($data, 'visibility') == 'admin-self') { $visibility = Visibility::AdminSelf->value; } elseif (Arr::get($data, 'visibility') == 'members') { $visibility = Visibility::Member->value; } elseif (Arr::get($data, 'visibility') == 'self') { $visibility = Visibility::Self->value; } $post->name = Arr::get($data, 'name'); $post->entry = $this->mentions(Arr::get($data, 'entry')); $post->visibility_id = $visibility; $post->save(); } } protected function campaignPlugin(): ?CampaignPlugin { return CampaignPlugin::where('campaign_id', $this->campaign->id) ->where('plugin_id', $this->plugin->id) ->first(); } } ================================================ FILE: app/Services/Posts/Permissions/SavePermissionsService.php ================================================ post->permissions as $perm) { $existing[$perm->isUser() ? 'u_' . $perm->user_id : 'r_' . $perm->role_id] = $perm; } $users = (array) $this->request->post('perm_user', []); $perms = (array) $this->request->post('perm_user_perm', []); $dirty = false; foreach ($users as $key => $user) { if ($user == '$SELECTEDID$') { continue; } $existingKey = 'u_' . $user; if (isset($existing[$existingKey])) { $perm = $existing[$existingKey]; $perm->permission = (int) $perms[$key]; $perm->save(); unset($existing[$existingKey]); $parsed[] = $existingKey; } elseif (! in_array($existingKey, $parsed)) { PostPermission::create([ 'post_id' => $this->post->id, 'user_id' => $user, 'permission' => $perms[$key], ]); $parsed[] = $existingKey; $dirty = true; } } $roles = (array) $this->request->post('perm_role', []); $perms = (array) $this->request->post('perm_role_perm', []); foreach ($roles as $key => $user) { if ($user == '$SELECTEDID$') { continue; } $existingKey = 'r_' . $user; if (isset($existing[$existingKey])) { $perm = $existing[$existingKey]; $perm->permission = (int) $perms[$key]; $perm->save(); unset($existing[$existingKey]); $parsed[] = $existingKey; } elseif (! in_array($existingKey, $parsed)) { PostPermission::create([ 'post_id' => $this->post->id, 'role_id' => $user, 'permission' => $perms[$key], ]); $dirty = true; $parsed[] = $existingKey; } } // Cleanup permissions that are no longer used foreach ($existing as $oldPermission) { $oldPermission->delete(); $dirty = true; } } } ================================================ FILE: app/Services/Posts/PurgeService.php ================================================ count; } /** * @throws Exception */ public function trash(Post $post): void { $post->forceDelete(); $this->count++; } } ================================================ FILE: app/Services/Posts/RecoveryService.php ================================================ post($id); if ($url) { $posts[$id] = $url; } } return $posts; } /** * Restore an entity post. */ protected function post(int $id): mixed { /** @var ?Post $post */ $post = Post::onlyTrashed()->find($id); if (! $post) { return null; } if ($post->entity->deleted_at) { return null; } $post->restore(); $options = ['#post-' . $post->id]; return $post->entity->url('show', $options); } } ================================================ FILE: app/Services/QuickCreator/ProcessService.php ================================================ request->get('name'))); $values = $this->request->all(); // Prepare the data unset($values['names'], $values['_multi'], $values['_target']); // Remove target as we need that for something else if (! empty($values['entry'])) { $values['entry'] = nl2br($values['entry']); } elseif ($this->entityType->id == config('entities.ids.note')) { $values['entry'] = ''; } // Prepare the validator $requestValidator = '\App\Http\Requests\Store' . ucfirst(Str::camel($this->entityType->code)); if ($this->entityType->isCustom()) { $requestValidator = StoreCustomEntity::class; } /** @var StoreCharacter $validator */ $validator = new $requestValidator; // Handle dynamic elements $this->inputFields = $values; $this->dynamicTags() ->dynamicParent($this->entityType) ->dynamicLocations() ->dynamicLocation(); if ($this->entityType->isCharacter()) { $this->dynamicFamilies() ->dynamicRaces(); } $this->loadTemplate(); $values = $this->inputFields; foreach ($names as $name) { if ($name == '') { continue; } $values['name'] = $name; $this->validateEntity($values, $validator->rules()); if ($this->entityType->isCustom()) { $this->entity = new Entity($values); $this->entity->campaign_id = $this->campaign->id; $this->entity->type_id = $this->entityType->id; $this->entity->save(); $this->entitySaveService->save($this->entity, $values); $this->new[] = $this->entity; $this->copyTemplateRelations(); } else { /** @var MiscModel $new */ $new = $this->entityType->getClass(); $new->fill($values); $new->campaign_id = $this->campaign->id; $new->save(); $this->entitySaveService->save($new->entity, $values); $this->relationsFactory->for($new->entity)?->save($new, $values); $this->new[] = $new->entity; $this->entity = $new->entity; $this->copyTemplateRelations(); } } // If no entity was created, we throw the standard error if (empty($this->new)) { $rules = $validator->rules(); $this->validateEntity($values, $rules); } } public function post(): self { $names = explode(PHP_EOL, str_replace("\r", '', $this->request->get('name'))); $values = $this->request->all(); // Prepare the data unset($values['names'], $values['_multi'], $values['_target']); // Remove target as we need that for something else if (! empty($values['entry'])) { $values['entry'] = nl2br($values['entry']); } // Prepare the validator $validator = new StorePost; // Handle dynamic elements $this->inputFields = $values; $values = $this->inputFields; foreach ($names as $name) { if (empty($name)) { continue; } $values['name'] = $name; // If position = 0 the post's position is last, else the post's position is first. $rules = $validator->rules(); $rules['entity_id'] = 'required|integer|exists:entities,id'; $this->validateEntity($values, $rules); if ($values['position'] == 0) { $new = Post::create($values); } else { $entity = Entity::find($values['entity_id']); $entity->posts()->increment('position'); $values['position'] = 1; $new = Post::create($values); } $this->new[] = $new; } // If no entity was created, we throw the standard error if (empty($this->new)) { $rules = $validator->rules(); $this->validateEntity($values, $rules); } return $this; } public function first(): Post|Entity { return Arr::first($this->new); } public function count(): int { return count($this->new); } public function links(): array { $links = []; foreach ($this->new as $new) { if ($new instanceof Post) { $links[] = '' . $new->name . ''; } else { $links[] = '' . $new->name . ''; } } return $links; } /** * Validate an entity's request to make sure data doesn't contain erroneous info */ protected function validateEntity(array $data, array $rules) { return Validator::make( $data, $rules, ); } protected function dynamicLocation(): self { if (! $this->request->has('location_id')) { return $this; } $resolved = $this->resolveNewModels( [$this->request->get('location_id')], Location::class, config('entities.ids.location'), ); $this->inputFields['location_id'] = Arr::first($resolved); return $this; } protected function dynamicTags(): self { if (! $this->request->has('tags') && ! $this->request->has('save-tags')) { return $this; } $this->inputFields['tags'] = $this->resolveNewModels( $this->request->get('tags', []), Tag::class, config('entities.ids.tag'), ); return $this; } protected function dynamicLocations(): self { if (! $this->request->has('locations') && ! $this->request->has('save_locations')) { return $this; } $this->inputFields['locations'] = $this->resolveNewModels( $this->request->get('locations', []), Location::class, config('entities.ids.location'), ); return $this; } protected function dynamicRaces(): self { if (! $this->request->has('races') && ! $this->request->has('save_races')) { return $this; } $this->inputFields['races'] = $this->resolveNewModels( $this->request->get('races', []), Race::class, config('entities.ids.race'), ); return $this; } protected function dynamicFamilies(): self { if (! $this->request->has('families') && ! $this->request->has('save_families')) { return $this; } $this->inputFields['families'] = $this->resolveNewModels( $this->request->get('families', []), Family::class, config('entities.ids.family'), ); return $this; } protected function dynamicParent(EntityType $entityType): self { if (! $this->request->has($entityType->code . '_id')) { return $this; } $value = $this->request->get($entityType->code . '_id', null); if ($value === null || is_numeric($value)) { return $this; } // @phpstan-ignore-next-line $id = $this->createModelFromName($value, get_class($entityType->getClass()), $entityType, $this->campaign); if ($id !== null) { $this->inputFields[$entityType->code . '_id'] = $id; } return $this; } protected function loadTemplate(): self { if (! $this->request->has('template_id')) { return $this; } unset($this->inputFields['template_id']); /** @var Entity $template */ $template = Entity::template()->find($this->request->get('template_id')); if (empty($template)) { return $this; } if ($template->type_id !== $this->entityType->id) { return $this; } if ($this->entityType->isStandard() && $template->isMissingChild()) { return $this; } $this->template = $template; $entityFields = [ 'type', 'parent_id', 'entry', 'image_uuid', 'header_uuid', 'focus_x', 'focus_y', 'tooltip', ]; foreach ($entityFields as $field) { if (empty($template->{$field})) { continue; } if (! empty($this->inputFields[$field])) { continue; } $this->inputFields[$field] = $template->{$field}; } if ($this->entityType->isStandard()) { $this->loadTemplateChild(); } return $this; } protected function loadTemplateChild(): self { // Do we do a if/else per entity type? Or try and play it by fillable? $fillable = $this->template->child->getFillable(); foreach ($fillable as $field) { if (empty($this->template->child->{$field}) || ! empty($this->inputFields[$field])) { continue; } $this->inputFields[$field] = $this->template->child->{$field}; } return $this; } protected function copyTemplateRelations(): self { if (! isset($this->template)) { return $this; } $this->copyService ->source($this->template) ->entity($this->entity) ->force() ->copy(); $this->copyService ->attributes(); if ($this->template->isCharacter()) { $this->copyService->character(); } return $this; } } ================================================ FILE: app/Services/Referrals/JoinService.php ================================================ referral = $referral; return $this; } public function expired(): bool { return $this->referral->revoked_at !== null; } public function flag(): void { session()->put('referral_code', $this->referral->code); } public function referrer(): ?int { if (! session()->has('referral_code')) { return null; } $code = session()->get('referral_code'); session()->forget('referral_code'); $this->referral = Referral::where('code', $code)->first(); return $this->referral?->user_id; } public function event(User $user, ReferralEventType $type): void { if (! isset($this->referral)) { return; } $event = new ReferralEvent; $event->created_by = $user->id; $event->referred_by = $this->referral->user_id; $event->type = $type; $event->save(); } } ================================================ FILE: app/Services/Referrals/ManagementService.php ================================================ user->id)->first(); if (! $code) { return $this->createReferral(); } return $code; } public function users(): int { if (app()->hasDebugModeEnabled()) { return mt_rand(1, 50); } return $this->referrals()->count(); } protected function referrals(): Collection { if (isset($this->referrals)) { return $this->referrals; } return $this->referrals = User::select(['id', 'pledge'])->where('referred_by', $this->user->id)->get(); } public function subscribers(): int { if (app()->hasDebugModeEnabled()) { return mt_rand(0, 10); } return $this->referrals()->whereNotNull('pledge')->count(); } public function badge(): string { $count = $this->users(); if ($count < 5) { return 'I'; } elseif ($count < 12) { return 'II'; } return 'III'; } protected function createReferral(): Referral { while (true) { try { return Referral::create([ 'user_id' => $this->user->id, 'code' => $this->generateCode(), ]); } catch (QueryException $e) { if ($e->getCode() !== '23000') { throw $e; } // If it's a collision, retry } } } protected function generateCode(int $length = 8): string { return Str::random($length); } } ================================================ FILE: app/Services/Report/AccountsReportService.php ================================================ whereBetween('created_at', [$start, $end]) ->count(); if ($newAccounts === 0) { return [ 'new_accounts' => 0, 'accounts_with_entities' => 0, 'entities_created' => 0, ]; } $accountsWithEntities = DB::table('users') ->join('entities', function ($join) { $join->on('entities.created_by', '=', 'users.id') ->where('entities.source', '=', 'user'); }) ->whereBetween('users.created_at', [$start, $end]) ->distinct() ->count('users.id'); $entitiesCreated = DB::table('entities') ->join('users', 'users.id', '=', 'entities.created_by') ->whereBetween('users.created_at', [$start, $end]) ->where('entities.source', 'user') ->count(); return [ 'new_accounts' => $newAccounts, 'accounts_with_entities' => $accountsWithEntities, 'entities_created' => $entitiesCreated, ]; } public function buildTerminalLines(array $current, array $previous): array { return [ $this->formatMetricLine('New Accounts', $current['new_accounts'], $previous['new_accounts']), $this->formatMetricLine('Accounts that Created Entities', $current['accounts_with_entities'], $previous['accounts_with_entities'], $current['new_accounts']), $this->formatMetricLine('Entities Created by New Accounts', $current['entities_created'], $previous['entities_created']), ]; } public function buildDiscordBody(array $current, array $previous): string { return implode("\n", [ $this->formatMetricText('New Accounts', $current['new_accounts'], $previous['new_accounts']), $this->formatMetricText('Accounts that Created Entities', $current['accounts_with_entities'], $previous['accounts_with_entities'], $current['new_accounts']), $this->formatMetricText('Entities Created by New Accounts', $current['entities_created'], $previous['entities_created']), ]); } } ================================================ FILE: app/Services/Report/BaseReportService.php ================================================ text' for section headers and '' for blank lines. */ abstract public function buildTerminalLines(array $current, array $previous): array; abstract public function buildDiscordBody(array $current, array $previous): string; protected function calculateGrowth(int $current, int $previous): float { if ($previous == 0) { return $current > 0 ? 100 : 0; } return (($current - $previous) / $previous) * 100; } protected function getGrowthIndicator(float $growth): string { $color = $growth > 0 ? 'green' : ($growth < 0 ? 'red' : 'yellow'); $arrow = $growth > 0 ? '↑' : ($growth < 0 ? '↓' : '→'); return sprintf('<%s>%s %.1f%%', $color, $arrow, abs($growth)); } protected function formatMetricLine(string $label, int $current, int $previous, ?int $base = null): string { $growth = $this->calculateGrowth($current, $previous); $growthIndicator = $this->getGrowthIndicator($growth); if ($base) { $percentOfBase = $base > 0 ? Number::format(($current / $base) * 100, 1) : '0.0'; return sprintf('%s: %s (%s%%) %s', $label, Number::format($current), $percentOfBase, $growthIndicator); } return sprintf('%s: %s %s', $label, Number::format($current), $growthIndicator); } protected function formatMetricText(string $label, int $current, int $previous, ?int $base = null): string { $growth = $this->calculateGrowth($current, $previous); $growthIndicator = strip_tags($this->getGrowthIndicator($growth)); if ($base) { $percentOfBase = $base > 0 ? Number::format(($current / $base) * 100, 1) : '0.0'; return sprintf('%s: %s (%s%%) %s', $label, Number::format($current), $percentOfBase, $growthIndicator); } return sprintf('%s: %s %s', $label, Number::format($current), $growthIndicator); } } ================================================ FILE: app/Services/Report/ChurnReportService.php ================================================ DB::table('subscription_cancellations') ->whereBetween('created_at', [$start, $end]); $total = $base()->count(); if ($total === 0) { return [ 'total' => 0, 'avg_duration' => 0, 'flagged' => 0, 'by_tier' => collect(), 'by_reason' => collect(), ]; } return [ 'total' => $total, 'avg_duration' => (int) round($base()->avg('duration') ?? 0), 'flagged' => $base()->where('is_flagged', true)->count(), 'by_tier' => $base() ->select('tier', DB::raw('count(*) as total')) ->whereNotNull('tier') ->groupBy('tier') ->orderByDesc('total') ->pluck('total', 'tier'), 'by_reason' => $base() ->select('reason', DB::raw('count(*) as total')) ->whereNotNull('reason') ->groupBy('reason') ->orderByDesc('total') ->pluck('total', 'reason'), ]; } public function buildTerminalLines(array $current, array $previous): array { $lines = [ $this->formatMetricLine('Total Cancellations', $current['total'], $previous['total']), $this->formatMetricLine('Avg Duration (months)', $current['avg_duration'], $previous['avg_duration']), $this->formatMetricLine('Flagged', $current['flagged'], $previous['flagged'], $current['total']), '', 'By Tier:', ]; foreach ($current['by_tier'] as $tier => $count) { $lines[] = $this->formatMetricLine(" {$tier}", $count, $previous['by_tier']->get($tier, 0), $current['total']); } $lines[] = ''; $lines[] = 'By Reason:'; foreach ($current['by_reason'] as $reason => $count) { $lines[] = $this->formatMetricLine(" {$reason}", $count, $previous['by_reason']->get($reason, 0), $current['total']); } return $lines; } public function buildDiscordBody(array $current, array $previous): string { $lines = [ $this->formatMetricText('Total Cancellations', $current['total'], $previous['total']), $this->formatMetricText('Avg Duration (months)', $current['avg_duration'], $previous['avg_duration']), $this->formatMetricText('Flagged', $current['flagged'], $previous['flagged'], $current['total']), '', 'By Tier:', ]; foreach ($current['by_tier'] as $tier => $count) { $lines[] = $this->formatMetricText(" {$tier}", $count, $previous['by_tier']->get($tier, 0), $current['total']); } $lines[] = ''; $lines[] = 'By Reason:'; foreach ($current['by_reason'] as $reason => $count) { $lines[] = $this->formatMetricText(" {$reason}", $count, $previous['by_reason']->get($reason, 0), $current['total']); } return implode("\n", $lines); } } ================================================ FILE: app/Services/Report/OnboardingReportService.php ================================================ whereBetween('created_at', [$start, $end]) ->count(); $campaignIds = DB::table('campaigns') ->whereBetween('created_at', [$start, $end]) ->pluck('id'); if ($campaignIds->isEmpty()) { return [ 'campaigns_created' => 0, 'onboarding_shown' => 0, 'onboarding_completed' => 0, 'onboarding_dismissed' => 0, 'onboarding_abandoned' => 0, 'campaign_choice' => 0, 'worldbuilding_choice' => 0, 'story_choice' => 0, 'skip_choice' => 0, ]; } $onboardingShown = DB::table('campaign_events') ->whereIn('campaign_id', $campaignIds) ->where('event', 'onboarding_shown') ->count(); $onboardingCompleted = DB::table('campaign_events') ->whereIn('campaign_id', $campaignIds) ->where('event', 'onboarding_completed') ->count(); $onboardingDismissed = DB::table('campaign_events') ->whereIn('campaign_id', $campaignIds) ->where('event', 'onboarding_dismissed') ->count(); $completedOrDismissedCampaigns = DB::table('campaign_events') ->whereIn('campaign_id', $campaignIds) ->whereIn('event', ['onboarding_completed', 'onboarding_dismissed']) ->distinct() ->pluck('campaign_id'); $onboardingAbandoned = $onboardingShown - $completedOrDismissedCampaigns->count(); // @phpstan-ignore-next-line $choices = DB::table('campaign_events') ->whereIn('campaign_id', $campaignIds) ->where('event', 'onboarding_completed') ->get() ->pluck('metadata') ->map(function ($metadata) { $decoded = json_decode($metadata, true); return $decoded['choice'] ?? null; }) ->filter() ->countBy(); return [ 'campaigns_created' => $campaignsCreated, 'onboarding_shown' => $onboardingShown, 'onboarding_completed' => $onboardingCompleted, 'onboarding_dismissed' => $onboardingDismissed, 'onboarding_abandoned' => $onboardingAbandoned, 'campaign_choice' => $choices->get('campaign', 0), 'worldbuilding_choice' => $choices->get('worldbuilding', 0), 'story_choice' => $choices->get('story', 0), 'skip_choice' => $choices->get('skip', 0), ]; } public function buildTerminalLines(array $current, array $previous): array { return [ $this->formatMetricLine('Campaigns Created', $current['campaigns_created'], $previous['campaigns_created']), '', 'Onboarding Funnel:', $this->formatMetricLine(' Onboarding Shown', $current['onboarding_shown'], $previous['onboarding_shown'], $current['campaigns_created']), $this->formatMetricLine(' Onboarding Completed', $current['onboarding_completed'], $previous['onboarding_completed'], $current['onboarding_shown']), $this->formatMetricLine(' Onboarding Dismissed', $current['onboarding_dismissed'], $previous['onboarding_dismissed'], $current['onboarding_shown']), $this->formatMetricLine(' Onboarding Abandoned', $current['onboarding_abandoned'], $previous['onboarding_abandoned'], $current['onboarding_shown']), '', 'Choice Breakdown (of completed):', $this->formatMetricLine(' Campaign', $current['campaign_choice'], $previous['campaign_choice'], $current['onboarding_completed']), $this->formatMetricLine(' Worldbuilding', $current['worldbuilding_choice'], $previous['worldbuilding_choice'], $current['onboarding_completed']), $this->formatMetricLine(' Story', $current['story_choice'], $previous['story_choice'], $current['onboarding_completed']), $this->formatMetricLine(' Skip', $current['skip_choice'], $previous['skip_choice'], $current['onboarding_completed']), ]; } public function buildDiscordBody(array $current, array $previous): string { return implode("\n", [ $this->formatMetricText('Campaigns Created', $current['campaigns_created'], $previous['campaigns_created']), '', 'Onboarding Funnel:', $this->formatMetricText(' Onboarding Shown', $current['onboarding_shown'], $previous['onboarding_shown'], $current['campaigns_created']), $this->formatMetricText(' Onboarding Completed', $current['onboarding_completed'], $previous['onboarding_completed'], $current['onboarding_shown']), $this->formatMetricText(' Onboarding Dismissed', $current['onboarding_dismissed'], $previous['onboarding_dismissed'], $current['onboarding_shown']), $this->formatMetricText(' Onboarding Abandoned', $current['onboarding_abandoned'], $previous['onboarding_abandoned'], $current['onboarding_shown']), '', 'Choice Breakdown (of completed):', $this->formatMetricText(' Campaign', $current['campaign_choice'], $previous['campaign_choice'], $current['onboarding_completed']), $this->formatMetricText(' Worldbuilding', $current['worldbuilding_choice'], $previous['worldbuilding_choice'], $current['onboarding_completed']), $this->formatMetricText(' Story', $current['story_choice'], $previous['story_choice'], $current['onboarding_completed']), $this->formatMetricText(' Skip', $current['skip_choice'], $previous['skip_choice'], $current['onboarding_completed']), ]); } } ================================================ FILE: app/Services/Report/WeeklyReportService.php ================================================ join('campaigns', 'campaigns.created_by', '=', 'users.id') ->whereBetween('users.created_at', [$start, $end]) ->whereNull('campaigns.deleted_at') ->distinct() ->count('users.id'); $newUsers = DB::table('users') ->whereBetween('created_at', [$start, $end]) ->count(); // Organic: campaign creators with no referral source $organic = DB::table('users') ->join('campaigns', 'campaigns.created_by', '=', 'users.id') ->whereBetween('users.created_at', [$start, $end]) ->whereNull('campaigns.deleted_at') ->whereNull('users.referred_by') ->distinct() ->count('users.id'); // Onboarding completed: campaign creators with onboarding_completed event $onboarding = DB::table('users') ->join('campaigns', 'campaigns.created_by', '=', 'users.id') ->join('campaign_events', function ($join) { $join->on('campaign_events.created_by', '=', 'users.id') ->where('campaign_events.event', '=', 'onboarding_completed'); }) ->whereBetween('users.created_at', [$start, $end]) ->whereNull('campaigns.deleted_at') ->distinct() ->count('users.id'); // 3+ Entities: campaign creators who created 3 or more user entities $entities3Plus = DB::table('users') ->join('campaigns', 'campaigns.created_by', '=', 'users.id') ->whereBetween('users.created_at', [$start, $end]) ->whereNull('campaigns.deleted_at') ->whereIn('users.id', function ($query) { $query->select('created_by') ->from('entities') ->where('source', 'user') ->whereNull('deleted_at') ->groupBy('created_by') ->havingRaw('COUNT(*) >= 3'); }) ->distinct() ->count('users.id'); // Second Login: campaign creators who logged in after their registration $secondLogin = DB::table('users') ->join('campaigns', 'campaigns.created_by', '=', 'users.id') ->whereBetween('users.created_at', [$start, $end]) ->whereNull('campaigns.deleted_at') ->whereNotNull('users.last_login_at') ->whereColumn('users.last_login_at', '>', 'users.created_at') ->distinct() ->count('users.id'); // Subscribed: campaign creators with an active subscription $subscribed = DB::table('users') ->join('campaigns', 'campaigns.created_by', '=', 'users.id') ->join('subscriptions', 'subscriptions.user_id', '=', 'users.id') ->whereBetween('users.created_at', [$start, $end]) ->whereNull('campaigns.deleted_at') ->distinct() ->count('users.id'); // Engagement $weeklyActiveUsers = DB::table('users') ->whereBetween('last_login_at', [$start, $end]) ->whereRaw('last_login_at > created_at') ->count(); $activeCampaigns = DB::table('campaigns') ->whereBetween('updated_at', [$start, $end]) ->whereNull('deleted_at') ->count(); $entitiesCreated = DB::table('entities') ->where('source', 'user') ->whereBetween('created_at', [$start, $end]) ->whereNull('deleted_at') ->count(); return [ 'campaign_creators' => $campaignCreators, 'organic' => $organic, 'referred' => max(0, $campaignCreators - $organic), 'invited_only' => max(0, $newUsers - $campaignCreators), 'onboarding' => $onboarding, 'entities_3plus' => $entities3Plus, 'second_login' => $secondLogin, 'subscribed' => $subscribed, 'weekly_active_users' => $weeklyActiveUsers, 'active_campaigns' => $activeCampaigns, 'entities_created' => $entitiesCreated, ]; } public function buildTerminalLines(array $current, array $previous): array { return [ '🚀 ACTIVATION FUNNEL', 'Campaign Creators → Onboarding → 3+ Entities → Second Login → Subscribe', $this->formatFunnelRow($current), '', sprintf( 'Note: %s creators = %s organic + %s referred', Number::format($current['campaign_creators']), Number::format($current['organic']), Number::format($current['referred']) ), sprintf(' (Excludes %s invited-only users)', Number::format($current['invited_only'])), '', 'vs Last Week:', $this->formatFunnelRow($previous), '', $this->buildTrendsLine($current, $previous), '', str_repeat('━', 26), '📈 ENGAGEMENT', $this->formatMetricLine('Weekly Active Users', $current['weekly_active_users'], $previous['weekly_active_users']), $this->formatMetricLine('Active Campaigns', $current['active_campaigns'], $previous['active_campaigns']), $this->formatMetricLine('Entities Created', $current['entities_created'], $previous['entities_created']), ]; } public function buildDiscordBody(array $current, array $previous): string { return implode("\n", [ '🚀 **ACTIVATION FUNNEL**', 'Campaign Creators → Onboarding → 3+ Entities → Second Login → Subscribe', $this->formatFunnelRow($current), '', sprintf( 'Note: %s creators = %s organic + %s referred', Number::format($current['campaign_creators']), Number::format($current['organic']), Number::format($current['referred']) ), sprintf(' (Excludes %s invited-only users)', Number::format($current['invited_only'])), '', 'vs Last Week:', $this->formatFunnelRow($previous), '', strip_tags($this->buildTrendsLine($current, $previous)), '', str_repeat('━', 26), '📈 **ENGAGEMENT**', $this->formatMetricText('Weekly Active Users', $current['weekly_active_users'], $previous['weekly_active_users']), $this->formatMetricText('Active Campaigns', $current['active_campaigns'], $previous['active_campaigns']), $this->formatMetricText('Entities Created', $current['entities_created'], $previous['entities_created']), ]); } private function formatFunnelRow(array $stats): string { $base = $stats['campaign_creators']; return sprintf( '%s → %s (%s%%) → %s (%s%%) → %s (%s%%) → %s (%s%%)', Number::format($base), Number::format($stats['onboarding']), $this->pct($stats['onboarding'], $base), Number::format($stats['entities_3plus']), $this->pct($stats['entities_3plus'], $base), Number::format($stats['second_login']), $this->pct($stats['second_login'], $base), Number::format($stats['subscribed']), $this->pct($stats['subscribed'], $base) ); } private function buildTrendsLine(array $current, array $previous): string { $parts = [ 'Signups ' . $this->getGrowthIndicator($this->calculateGrowth($current['campaign_creators'], $previous['campaign_creators'])), 'Onboarding ' . $this->getGrowthIndicator($this->calculateGrowth($current['onboarding'], $previous['onboarding'])), 'Entities ' . $this->getGrowthIndicator($this->calculateGrowth($current['entities_3plus'], $previous['entities_3plus'])), 'Second Login ' . $this->getGrowthIndicator($this->calculateGrowth($current['second_login'], $previous['second_login'])), 'Conversion ' . $this->getGrowthIndicator($this->calculateGrowth($current['subscribed'], $previous['subscribed'])), ]; return 'Trends: ' . implode(' | ', $parts); } private function pct(int $value, int $base): string { if ($base === 0) { return '0.0'; } return Number::format(($value / $base) * 100, 1); } } ================================================ FILE: app/Services/Search/AttributeSearchService.php ================================================ request->get('q') ?? ''); $attributes = $this->entity->attributes() ->whereLike('name', '%' . $term . '%') ->get(); $results = []; /** @var Attribute $attribute */ foreach ($attributes as $attribute) { $results[] = [ 'id' => $attribute->id, 'name' => $attribute->name, 'value' => $attribute->value, ]; } return $results; } } ================================================ FILE: app/Services/Search/CampaignSearchService.php ================================================ campaign->roles() ->search($query) ->get(['name', 'id']) ->toArray(); } /** * List of members in a campaign * * @param string|null $query Search term */ public function members(?string $query = null): array { $members = $this->campaign->members()->search($query)->get(); $result = []; /** @var CampaignUser $member */ foreach ($members as $member) { $result[] = [ 'id' => $member->user->id, 'text' => $member->user->name, 'image' => $member->user->hasAvatar() ? $member->user->getAvatarUrl() : null, ]; } return $result; } } ================================================ FILE: app/Services/Search/EntitySearchService.php ================================================ limit = $limit; return $this; } public function pages(): int { return $this->pages; } public function page(int $page): self { $this->page = $page; return $this; } /** * Send search request */ public function search(?string $term = null, ?string $term2 = null): array { // Get results from Meilisearch $client = new Client(config('scout.meilisearch.host'), config('scout.meilisearch.key')); $client->getKeys(); $queries = [ (new SearchQuery) ->setIndexUid('entities') ->setQuery($term) ->setFilter(['campaign_id = ' . $this->campaign->id]) ->setAttributesToRetrieve(['id', 'entity_id', 'type']) ->setPage($this->page) ->setLimit($this->limit) ->setHitsPerPage($this->limit), ]; if ($term2) { $queries[] = (new SearchQuery) ->setIndexUid('entities') ->setQuery($term2) ->setFilter(['campaign_id = ' . $this->campaign->id]) ->setAttributesToRetrieve(['id', 'entity_id', 'type']) ->setPage($this->page) ->setLimit($this->limit) ->setHitsPerPage($this->limit); } $results = $client->multiSearch($queries); if ($term2) { $entities = array_merge($results['results'][0]['hits'], $results['results'][1]['hits']); } else { $entities = $results['results'][0]['hits']; } $this->pages = $results['results'][0]['totalPages']; return $this->process($entities)->fetch(); } /** * Process results to fetch entities from db * * @param array $results Search term */ protected function process(array $results = []): self { foreach ($results as $result) { if ($result['type'] == 'quest_element') { $id = Str::afterLast($result['id'], '_'); $this->questElementIds[$result['entity_id']] = $id; // dd($result); } elseif ($result['type'] == 'timeline_element') { $id = Str::afterLast($result['id'], '_'); $this->timelineElementIds[$result['entity_id']] = $id; // dd($result); } elseif ($result['type'] == 'post') { $id = Str::afterLast($result['id'], '_'); $this->postIds[$result['entity_id']] = $id; // dd($result); } elseif ($result['type'] == 'attribute') { $id = Str::afterLast($result['id'], '_'); $this->attributeIds[$result['entity_id']] = $id; // dd($result); } else { $this->ids[$result['entity_id']] = $result['entity_id']; } } // If the search also threw the entity as a possible result don't bother loading the other models $this->attributeIds = array_diff_key($this->attributeIds, $this->ids); $this->timelineElementIds = array_diff_key($this->timelineElementIds, $this->ids); $this->questElementIds = array_diff_key($this->questElementIds, $this->ids); $this->postIds = array_diff_key($this->postIds, $this->ids); return $this; } /** * Fetch entities from DB */ protected function fetch(): array { $posts = Post::select(['id', 'entity_id']) ->with('entity') ->has('entity') ->whereIn('id', $this->postIds) ->get(); $attributes = Attribute::select(['id', 'entity_id']) ->with('entity') ->has('entity') ->whereIn('id', $this->attributeIds) ->get(); $questElements = QuestElement::select(['id', 'quest_id']) ->with(['quest', 'quest.entity']) ->has('quest.entity') ->whereIn('id', $this->questElementIds) ->get(); $timelineElements = TimelineElement::select(['id', 'timeline_id']) ->with(['timeline', 'timeline.entity']) ->has('timeline.entity') ->whereIn('id', $this->timelineElementIds) ->get(); // Get entities from db $entities = Entity::select('id', 'name') ->whereIn('id', $this->ids) ->orderBy('name') ->get(); // Process entities for output $output = []; foreach ($entities as $entity) { $output[$entity->id] = [ 'id' => $entity->id, 'entity' => $entity->name, 'url' => $entity->url(), ]; } foreach ($attributes as $attribute) { $output[$attribute->entity->id] = [ 'id' => $attribute->entity->id, 'entity' => $attribute->entity->name, 'url' => $attribute->entity->url(), ]; } foreach ($posts as $post) { $output[$post->entity->id] = [ 'id' => $post->entity->id, 'entity' => $post->entity->name, 'url' => $post->entity->url(), ]; } foreach ($questElements as $questElement) { $output[$questElement->quest->entity->id] = [ 'id' => $questElement->quest->entity->id, 'entity' => $questElement->quest->name, 'url' => $questElement->quest->entity->url(), ]; } foreach ($timelineElements as $timelineElement) { $output[$timelineElement->timeline->entity->id] = [ 'id' => $timelineElement->timeline->entity->id, 'entity' => $timelineElement->timeline->entity->name, 'url' => $timelineElement->timeline->entity->url(), ]; } return $output; } } ================================================ FILE: app/Services/Search/LiveSearchService.php ================================================ request->get('q') ?? ''); $excludes = $this->request->has('exclude') ? $this->request->get('exclude') : null; $with = ['image', 'entityType']; if ($this->request->filled('with-family')) { $with[] = 'character'; $with[] = 'character.families'; } $this->query = Entity::inTypes($this->entityType->id); if (! empty($excludes)) { $this->query->whereNotIn('id', [$excludes]); } if ($this->entityType->isStandard()) { $with[] = Str::camel($this->entityType->code); } $this->query->with($with); $this->order($term); $entities = $this->query ->limit(10) ->get(); $onlyEntity = $this->request->filled('entity'); $list = []; $child = Str::camel($this->entityType->code); /** @var Entity $entity */ foreach ($entities as $entity) { if ($this->entityType->isStandard() && empty($entity->{$child})) { continue; } $format = [ 'id' => $this->entityType->isCustom() || $onlyEntity ? $entity->id : $entity->{$child}->id, 'entity_id' => $entity->id, 'name' => $entity->name, 'text' => $entity->name, ]; if ($entity->isTag() && $entity->tag->hasColour()) { $format['colour'] = $entity->tag->colour; $format['colour_style'] = $entity->tag->colourStyle(); } $format['image'] = Avatar::entity($entity)->fallback()->size(40)->thumbnail(); $format['url'] = $entity->url(); if ($this->request->filled('with-family')) { $families = $entity->character->families->pluck('name')->toarray(); if (! empty($families)) { $format['text'] .= ' (' . implode(', ', $families) . ')'; } } $list[] = $format; } return $list; } } ================================================ FILE: app/Services/Search/MapGroupSearchService.php ================================================ map = $map; return $this; } public function search(): array { $term = mb_trim($this->request->get('q') ?? ''); $excludes = $this->request->has('exclude') ? $this->request->get('exclude') : null; $this->query = $this->map->groups()->getQuery(); if (! empty($excludes)) { $this->query->whereNotIn('id', [$excludes]); } $this->order($term); $groups = $this->query ->limit(10) ->get(); $list = []; /** @var MapGroup $group */ foreach ($groups as $group) { $format = [ 'id' => $group->id, 'name' => $group->name, 'text' => $group->name, ]; $list[] = $format; } return $list; } } ================================================ FILE: app/Services/Search/MentionService.php ================================================ prepare() ->entities() ->posts() ->attributes() ->new() ->data(); } public function load(): array { $results = []; if ($this->request->filled('entities')) { $entities = Entity::with(['entityType', 'aliases'])->whereIn('id', $this->request->get('entities'))->get(); foreach ($entities as $entity) { if ($entity->entityType->isStandard() && ! $entity->child) { continue; } $results[] = $this->formatEntity($entity); } } if ($this->request->filled('posts')) { $posts = Post::has('entity')->whereIn('id', $this->request->get('posts'))->get(); foreach ($posts as $post) { $results[] = [ 'type' => 'post', 'id' => $post->id, 'name' => $post->name, 'inject' => "[post:{$post->id}]", ]; } } return $results; } protected function prepare(): self { if ($this->request->has('entity')) { $entity = Entity::find(['id' => $this->request->get('entity')])->first(); if ($entity) { $this->entity($entity); } } $this->term = mb_trim(Str::replace('_', ' ', $this->request->get('q'))); $this->strippedTerm = mb_ltrim($this->term, '='); return $this; } protected function entities(): self { // Figure out what kind of entities we want. $availableEntityTypes = $this->entityTypeService ->campaign($this->campaign) ->available() ->pluck('id') ->toArray(); $query = Entity::inTypes($availableEntityTypes)->whereNull('archived_at'); $query ->select(['entities.*', 'ea.id as alias_id', 'ea.name as alias_name']) ->distinct() ->leftJoin('entity_assets as ea', function ($join) { $join->on('ea.entity_id', '=', 'entities.id'); if (Str::startsWith($this->term, '=')) { $join->where('ea.name', $this->strippedTerm); } else { $join->where('ea.name', 'like', '%' . $this->term . '%'); } $join->where('ea.type_id', EntityAssetType::alias); }) ->where(function ($sub) { if (Str::startsWith($this->term, '=')) { $sub->where('entities.name', $this->strippedTerm) ->orWhere('ea.name', $this->strippedTerm); } else { $sub->where('entities.name', 'like', '%' . $this->term . '%') ->orWhere('ea.name', 'like', '%' . $this->term . '%'); } }); // Exact name match comes first // Only do this when the input string is utf8 $cleanTerm = preg_replace("/[^a-zA-Z0-9_\-\.\s]/", '', $this->strippedTerm); if (mb_strlen($cleanTerm, 'UTF-8') === mb_strlen($cleanTerm)) { $escapedTerm = preg_replace('/&/', '\\&', preg_quote($cleanTerm)); $query->orderByRaw('FIELD(entities.name, ?) DESC', [$cleanTerm]); if ($this->campaign->boosted()) { $query->orderByRaw('FIELD(ea.name, ?) DESC', [$cleanTerm]); } // Name word-start match, so when looking for 'Morley', entities named 'Momorley' appear at the end $query->orderByRaw('entities.name RLIKE ? DESC', ["[[:<:]]{$escapedTerm}"]); if ($this->campaign->boosted()) { $query->orderByRaw('ea.name RLIKE ? DESC', ["[[:<:]]{$escapedTerm}"]); } // Partial name match $query->orderByRaw('entities.name LIKE ? DESC', ["%{$cleanTerm}%"]); if ($this->campaign->boosted()) { $query->orderByRaw('ea.name LIKE ? DESC', ["%{$cleanTerm}%"]); } } $with = ['image', 'entityType', 'aliases']; $query ->with($with) ->limit($this->limit); $this->data['entities'] = []; /** @var Entity $entity */ foreach ($query->get() as $entity) { $this->addEntity($entity); } return $this; } protected function addEntity(Entity $entity): void { // Force having a child for "ghost" entities. if ($entity->entityType->isStandard() && ! $entity->child) { return; } $this->data['entities'][] = $this->formatEntity($entity); } protected function formatEntity(Entity $entity): array { $mention = '[' . $entity->entityType->code . ':' . $entity->id . ']'; // @phpstan-ignore-next-line if ($entity->alias_id) { $mention = '[' . $entity->entityType->code . ':' . $entity->id . '|alias:' . $entity->alias_id . ']'; } return [ 'id' => $entity->id, 'name' => $entity->name, 'type' => $entity->entityType->name(), 'is_private' => $entity->is_private, 'image' => Avatar::entity($entity)->fallback()->size(32)->thumbnail(), 'url' => $entity->url(), 'preview' => route('entities.preview', [$this->campaign, $entity]), 'mention' => $mention, 'alias_name' => $entity->alias_name ?? null, 'alias_id' => $entity->alias_id ?? null, 'aliases' => $entity->aliases->map(fn ($alias) => [ 'id' => $alias->id, 'name' => $alias->name, ])->toArray(), ]; } protected function attributes(): self { if (! isset($this->entity)) { return $this; } $query = $this->entity->entityAttributes(); if (Str::startsWith($this->term, '=')) { $query->where('attributes.name', $this->strippedTerm); } else { $query->where('attributes.name', 'like', '%' . $this->term . '%'); } $attributes = $query->limit($this->limit)->get(); if ($attributes->isEmpty()) { return $this; } $this->data['attributes'] = []; foreach ($attributes as $attribute) { $this->addAttribute($attribute); } return $this; } protected function addAttribute(Attribute $attribute): void { $this->data['attributes'][] = [ 'id' => $attribute->id, 'type' => 'post', 'name' => $attribute->name, 'value' => $attribute->value, 'inject' => "{attribute:{$attribute->id}}", ]; } protected function posts(): self { if (! isset($this->entity)) { return $this; } $query = $this->entity->posts(); if (Str::startsWith($this->term, '=')) { $query->where('posts.name', $this->strippedTerm); } else { $query->where('posts.name', 'like', '%' . $this->term . '%'); } $posts = $query->limit($this->limit)->get(); if ($posts->isEmpty()) { return $this; } $this->data['posts'] = []; foreach ($posts as $post) { $this->addPost($post); } return $this; } protected function addPost(Post $post): void { $this->data['posts'][] = [ 'type' => 'post', 'id' => $post->id, 'name' => $post->name, 'inject' => "[post:{$post->id}]", ]; } protected function new(): self { $options = []; if (! isset($this->user)) { return $this; } $available = $this->newService ->campaign($this->campaign) ->user($this->user) ->available(); // Re-order alphabetically and in groups of custom vs default $available = $available->sortBy(fn (EntityType $a) => $a->isStandard() . '.' . $a->name()); $this->data['new'] = []; foreach ($available as $entityType) { $this->data['new'][] = [ 'inject' => '[new:' . $entityType->code . '|' . $this->term . ']', 'type' => __('crud.titles.new', ['module' => $entityType->name()]), 'name' => $this->term, ]; } return $this; } protected function data(): array { return $this->data; } } ================================================ FILE: app/Services/Search/RecentService.php ================================================ recentEntityIds(); if (empty($recentIds)) { return []; } $orderedIds = implode(',', $recentIds); $entities = Entity::whereIn('id', $recentIds) ->with(['image', 'entityType']) ->orderByRaw("FIELD(id, {$orderedIds})") ->get(); $recent = []; /** @var Entity $entity */ foreach ($entities as $entity) { $recent[] = $this->formatForLookup($entity); } return $recent; } /** * Format an entity for the lookup/search/recent dropdown * Todo: switch to a trait and share with SearchService */ protected function formatForLookup(Entity $entity): array { return [ 'id' => $entity->id, 'name' => $entity->name, 'is_private' => $entity->is_private, 'image' => Avatar::entity($entity)->fallback()->size(64)->thumbnail(), 'link' => $entity->url(), 'type' => $entity->entityType->name(), 'preview' => route('entities.preview', [$this->campaign, $entity]), ]; } public function logView(Entity $entity): void { $recents = $original = $this->recentEntityIds(); $recents = array_diff($recents, [$entity->id]); $recents = [$entity->id, ...$recents]; // Limit the array to five $recents = array_splice($recents, 0, 5); if ($recents == $original) { return; } $key = $this->recentEntityCacheKey(); cache()->put($key, $recents, 7 * 86400); } protected function recentEntityIds(): array { $key = $this->recentEntityCacheKey(); if (! cache()->has($key)) { return []; } return (array) cache()->get($key); } protected function recentEntityCacheKey(): string { return 'recent_c' . $this->campaign->id . '_u' . $this->user->id; } public function bookmarks(): array { $bookmarks = []; $links = Bookmark::active()->with(['entity', 'dashboard', 'target', 'entityType'])->ordered()->get(); /** @var Bookmark $link */ foreach ($links as $link) { if (! $link->valid($this->campaign)) { continue; } $bookmarks[] = $this->formatBookmark($link); } return $bookmarks; } /** * Extract usable data from the bookmark */ protected function formatBookmark(Bookmark $link): array { return [ 'url' => $this->routingService->campaign($this->campaign)->bookmark($link)->url(), 'icon' => $link->iconClass(), 'text' => $link->name, ]; } public function indexes(): array { $types = $this->orderedTypes(); $indexes = []; foreach ($types as $entityType) { $indexes[] = [ 'name' => $entityType->plural(), 'icon' => $entityType->icon(), 'url' => Breadcrumb::campaign($this->campaign)->entityType($entityType)->index(), ]; } return $indexes; } protected function orderedTypes(): Collection { return $this->entityTypeService ->campaign($this->campaign) ->exclude(config('entities.ids.bookmark')) ->ordered(); } } ================================================ FILE: app/Services/Search/TemplateSearchService.php ================================================ request->get('q') ?? ''); $excludes = $this->request->has('exclude') ? $this->request->get('exclude') : null; $with = ['image', 'entityType']; $this->query = Entity::inTypes($this->entityType->id); if (! empty($excludes)) { $this->query->whereNotIn('id', [$excludes]); } if ($this->entityType->isStandard()) { $with[] = Str::camel($this->entityType->code); } $this->query->with($with); $this->order($term); // @phpstan-ignore-next-line $entities = $this->query ->template() ->limit(10) ->get(); $list = []; /** @var Entity $entity */ foreach ($entities as $entity) { if ($this->entityType->isStandard() && empty($entity->{$this->entityType->code})) { continue; } $format = [ 'id' => $entity->id, 'entity_id' => $entity->id, 'name' => $entity->name, 'text' => $entity->name, ]; $format['image'] = Avatar::entity($entity)->fallback()->size(40)->thumbnail(); $format['url'] = $entity->url(); $list[] = $format; } return $list; } } ================================================ FILE: app/Services/SearchService.php ================================================ name) */ protected bool $full = false; /** * Thumb size */ protected int $thumbSize = 64; /** * Set to true to return new entity options */ protected bool $new = false; /** * Set to true to return posts */ protected bool $posts = false; protected Collection $pages; public function __construct( protected EntityTypeService $entityTypeService, protected NewService $newService ) {} /** * The search term as requested by the user */ public function term(?string $term = null): self { $this->term = $term; return $this; } /** * Sets the service to return data in the "v2" format, used for the header lookup */ public function v2(): self { $this->v2 = true; return $this; } public function thumb(int $size): self { $this->thumbSize = $size; return $this; } /** * The search entity type as requested by the user */ public function type(?int $type = null): self { if (! empty($type)) { $this->onlyTypes = [$type]; } return $this; } public function new(bool $new = false): self { $this->new = $new; return $this; } public function posts(bool $posts = true): self { $this->posts = $posts; return $this; } public function limit(int $limit = 10): self { $this->limit = $limit; return $this; } public function exclude($types): self { $this->excludedTypes = is_array($types) ? $types : [$types]; return $this; } public function excludeIds($ids): self { if (empty($ids)) { $this->excludeIds = []; return $this; } if (! is_array($ids)) { $ids = [$ids]; } $this->excludeIds = $ids; return $this; } public function only(null|array|string $types = null): self { if (empty($types)) { $this->onlyTypes = []; return $this; } $this->onlyTypes = is_array($types) ? $types : [$types]; return $this; } /** * Set the result as full (live search, mentions) */ public function full(): self { $this->full = true; return $this; } /** * List of entities matching the request */ public function find() { // Figure out what kind of entities we want. $availableEntityTypes = $this->entityTypeService ->campaign($this->campaign) ->available() ->pluck('id') ->toArray(); // If a list of types are provided, use those if (! empty($this->onlyTypes)) { $availableEntityTypes = $this->onlyTypes; } // If a list of excluded types are provided, remove them from the results if (! empty($this->excludedTypes)) { $availableEntityTypes = array_diff($availableEntityTypes, $this->excludedTypes); } $cleanTerm = mb_ltrim(str_replace('_', ' ', $this->term), '='); $query = Entity::inTypes($availableEntityTypes)->whereNull('archived_at'); if (empty($this->term)) { $query->orderBy('updated_at', 'DESC'); } else { if ($this->campaign->boosted()) { $query ->select(['entities.*', 'ea.id as alias_id', 'ea.name as alias_name']) ->distinct() ->leftJoin('entity_assets as ea', function ($join) use ($cleanTerm) { $join->on('ea.entity_id', '=', 'entities.id'); if (Str::startsWith($this->term, '=')) { $join->where('ea.name', $cleanTerm); } else { $join->where('ea.name', 'like', '%' . $this->term . '%'); } $join->where('ea.type_id', EntityAssetType::alias); }) ->where(function ($sub) use ($cleanTerm) { if (Str::startsWith($this->term, '=')) { $sub->where('entities.name', $cleanTerm) ->orWhere('ea.name', $cleanTerm); } else { $sub->where('entities.name', 'like', '%' . $this->term . '%') ->orWhere('ea.name', 'like', '%' . $this->term . '%'); } }); } else { if (Str::startsWith($this->term, '=')) { $query->where('name', mb_ltrim($this->term, '=')); } else { $query->where('name', 'like', '%' . $this->term . '%'); } } // Exact name match comes first // Only do this when the input string is utf8 $cleanTerm = preg_replace("/[^a-zA-Z0-9_\-\.\s]/", '', $cleanTerm); if (mb_strlen($cleanTerm, 'UTF-8') === mb_strlen($cleanTerm)) { $escapedTerm = preg_replace('/&/', '\\&', preg_quote($cleanTerm)); $query->orderByRaw('FIELD(entities.name, ?) DESC', [$cleanTerm]); if ($this->campaign->boosted()) { $query->orderByRaw('FIELD(ea.name, ?) DESC', [$cleanTerm]); } // Name word-start match, so when looking for 'Morley', entities named 'Momorley' appear at the end $query->orderByRaw('entities.name RLIKE ? DESC', ["[[:<:]]{$escapedTerm}"]); if ($this->campaign->boosted()) { $query->orderByRaw('ea.name RLIKE ? DESC', ["[[:<:]]{$escapedTerm}"]); } // Partial name match $query->orderByRaw('entities.name LIKE ? DESC', ["%{$cleanTerm}%"]); if ($this->campaign->boosted()) { $query->orderByRaw('ea.name LIKE ? DESC', ["%{$cleanTerm}%"]); } } } if (! empty($this->excludeIds)) { $query->whereNotIn('entities.id', $this->excludeIds); } $with = ['image', 'entityType']; if ($this->posts) { $with[] = 'posts'; } $query ->with($with) ->limit($this->limit); $searchResults = $foundEntityIds = []; /** @var Entity $model */ foreach ($query->get() as $model) { /** @var ?MiscModel $child */ // Force having a child for "ghost" entities. if ($model->entityType->isStandard()) { $child = $model->child; if ($child === null || in_array($model->id, $foundEntityIds)) { continue; } } if ($this->v2) { $searchResults[] = $this->formatForLookup($model); continue; } $img = ''; if ($model->hasImage()) { $img = ' '; } $parsedName = str_replace([''', '&'], ['\'', '&'], $model->name); $parsedNameAlias = $parsedName; // @phpstan-ignore-next-line if ($model->alias_name) { $parsedNameAlias = $parsedName . ' - ' . str_replace([''', '&'], ['\'', '&'], e($model->alias_name)); } if (! $this->full) { $searchResults[] = [ 'id' => $model->id, 'text' => $parsedName . ' (' . $model->entityType->name() . ')', ]; continue; } $searchResults[] = [ 'id' => $model->id, 'fullname' => $parsedNameAlias, 'image' => $img, 'name' => $parsedName, 'type' => $model->entityType->name(), 'model_type' => $model->entityType->code, 'url' => $model->url(), 'alias_id' => $model->alias_id, // @phpstan-ignore-line 'advanced_mention' => Mentions::advancedMentionHelper($model->name), 'advanced_mention_alias' => $model->alias_name ? Mentions::advancedMentionHelper($model->alias_name) : null, ]; $foundEntityIds[] = $model->id; // If the result is a map, also add its explore page as a result. // @phpstan-ignore-next-line if (! $this->posts && ! $this->new && $model->isMap() && $model->child->explorable()) { $searchResults[] = [ 'id' => $model->id, 'fullname' => $parsedName, 'image' => $img, 'name' => $parsedName, 'type' => __('maps.actions.explore'), 'model_type' => $model->entityType->code, 'url' => $model->child->getLink('explore'), 'alias_id' => $model->alias_id, // @phpstan-ignore-line 'advanced_mention' => Mentions::advancedMentionHelper($model->name), 'advanced_mention_alias' => $model->alias_name ? Mentions::advancedMentionHelper($model->alias_name) : null, ]; } if (! $this->posts) { continue; } foreach ($model->posts as $post) { $postName = str_replace([''', '&'], ['\'', '&'], $post->name); $searchResults[] = [ 'id' => $post->id, 'fullname' => $postName, 'image' => null, 'name' => $postName, 'type' => '', 'model_type' => 'post', 'url' => route('entities.show', [$this->campaign, $model, '#post-' . $post->id]), 'alias_id' => null, 'advanced_mention' => Mentions::advancedMentionHelper($post->name), 'advanced_mention_alias' => null, ]; } } if (! $this->new) { if ($this->v2) { return [ 'entities' => $searchResults, 'pages' => $this->pages(), ]; } return $searchResults; } elseif (empty($searchResults)) { return $this->newOptions(); } $lowerCleanTerm = mb_strtolower($cleanTerm); foreach ($searchResults as $result) { if (mb_strtolower($result['name']) == $lowerCleanTerm) { return $searchResults; } } // @phpstan-ignore-next-line return array_merge(array_values($searchResults), array_values($this->newOptions())); } /** * List of months in the calendars */ public function monthList(): array { $searchResults = []; // Load up the calendars of a campaign to get the month names // Todo: this can load any calendar regardless of permission $calendars = Calendar::get(); foreach ($calendars as $calendar) { $months = $calendar->months(); foreach ($months as $month) { if ((! empty($this->term) && str_contains($month['name'], $this->term)) || empty($this->term)) { $searchResults[] = [ 'fullname' => $month['name'], 'name' => $month['name'] . ' (' . $calendar->name . ')', ]; } } } return $searchResults; } /** * List of elements that can be created on the fly */ protected function newOptions(): array { $options = []; if (! isset($this->user)) { return $options; } $term = str_replace('_', ' ', $this->term); $available = $this->newService->campaign($this->campaign)->user($this->user)->available(); // Re-order alphabetically and in groups of custom vs default $available = $available->sortBy(fn (EntityType $a) => $a->isStandard() . '.' . $a->name()); foreach ($available as $entityType) { $options[] = [ 'new' => true, 'inject' => '[new:' . $entityType->code . '|' . $term . ']', 'fullname' => $term, 'type' => __('crud.titles.new', ['module' => $entityType->name()]), 'text' => $term, 'name' => $term, ]; } return $options; } /** * Format an entity for the lookup/search/recent dropdown * Todo: switch to a trait and share with SearchService */ protected function formatForLookup(Entity $entity): array { $mention = '[' . $entity->entityType->code . ':' . $entity->id . ']'; // @phpstan-ignore-next-line if ($entity->alias_id) { $mention = '[' . $entity->entityType->code . ':' . $entity->id . '|alias:' . $entity->alias_id . ']'; } return [ 'id' => $entity->id, 'name' => $entity->name, 'is_private' => $entity->is_private, 'image' => Avatar::entity($entity)->fallback()->size($this->thumbSize)->thumbnail(), 'link' => $entity->url(), 'type' => $entity->entityType->name(), 'preview' => route('entities.preview', [$this->campaign, $entity]), 'mention' => $mention, 'aliases' => $entity->aliases->map(fn ($alias) => [ 'id' => $alias->id, 'name' => $alias->name, ])->toArray(), ]; } protected function pages(): Collection { $this->pages = new Collection; if (empty($this->term)) { return $this->pages; } // Fill data with hardcoded pages and roles $this ->addCampaignPage('crud.tabs.overview', 'overview') ->addCampaignPage('campaigns.show.tabs.achievements', 'campaign.achievements') ->addCampaignPage('campaigns.show.tabs.stats', 'campaign.stats'); if (isset($this->user)) { $this ->addCampaignPage('campaigns.show.tabs.members', 'campaign_users.index', 'members') ->addCampaignPage('campaigns.show.tabs.defaults', 'campaign-defaults', 'update') ->addCampaignPage('campaigns.show.tabs.roles', 'campaign_roles.index', 'roles') ->addCampaignPage('campaigns/applications.title', 'applications.index', 'applications') ->addCampaignPage('campaigns.show.tabs.modules', 'campaign.modules') ->addCampaignPage('campaigns/categories.tab', 'campaign.modules') ->addCampaignPage('campaigns.show.tabs.recovery', 'recovery', 'update') ->addCampaignPage('campaigns.show.tabs.styles', 'campaign_styles.index', 'update') ->addCampaignPage('campaigns.show.tabs.export', 'campaign.export') ->addCampaignPage('campaigns.show.tabs.import', 'campaign.import') ->addCampaignPage('campaigns.show.tabs.webhooks', 'webhooks.index', 'webhooks'); } if (config('marketplace.enabled')) { $this->addCampaignPage('campaigns.show.tabs.plugins', 'campaign_plugins.index'); } $this->pages ->add(['name' => __('footer.plugins'), 'url' => config('marketplace.url')]) ->add(['name' => __('footer.documentation'), 'url' => 'https://docs.kanka.io/en/latest/index.html']) ->add(['name' => __('front.features.api.link'), 'url' => route('larecipe.index')]); if (isset($this->user)) { $this->pages ->add(['name' => __('settings.menu.premium'), 'url' => route('settings.premium')]) ->add(['name' => __('Dark mode'), 'url' => route('settings.appearance', ['highlight' => 'dark'])]) ->add(['name' => __('billing/menu.payment-method'), 'url' => route('billing.payment-method')]) ->add(['name' => __('billing/menu.history'), 'url' => route('billing.history')]); } $this->addCampaignRoles(); return $this->pages->filter(function ($page) { return Str::contains(mb_strtolower($page['name']), mb_strtolower($this->term)); }); } protected function addCampaignPage(string $name, string $route, ?string $perm = null): self { if (! empty($perm) && (! isset($this->user) || ! $this->user->can($perm, $this->campaign))) { return $this; } $this->pages->add(['name' => __($name), 'url' => route($route, [$this->campaign])]); return $this; } protected function addCampaignRoles(): self { if (! isset($this->user) || ! $this->user->can('roles', $this->campaign)) { return $this; } foreach ($this->campaign->roles as $role) { $this->pages->add(['name' => $role->name . ' (' . __('campaigns.invites.fields.role') . ')', 'url' => route($role->url('show'), [$this->campaign, $role])]); } return $this; } } ================================================ FILE: app/Services/Spotlights/ApplyService.php ================================================ campaign->id) ->where('status', '<>', SpotlightContentStatus::rejected) ->first(); } public function save() { $this->content = $this->content(); if (isset($this->content) && ! $this->content->isDraft()) { return; } $this->fill(); $this->content->save(); } public function apply() { $this->content = $this->content(); $this->fill(); $this->content->status = SpotlightContentStatus::applied; $this->content->save(); SpotlightSubmitted::dispatch($this->content); } public function retract() { $this->content = $this->content(); if (empty($this->content)) { return; } if ($this->content->status !== SpotlightContentStatus::applied) { return; } $this->content->status = SpotlightContentStatus::draft; $this->content->save(); } public function fill() { if (empty($this->content)) { $this->content = new SpotlightContent; $this->content->campaign_id = $this->campaign->id; $this->content->status = SpotlightContentStatus::draft; } $fields = $this->request->only([ 'time', 'world', 'proud', 'inspiration', 'stories', 'kanka', 'share', ]); foreach ($fields as $key => $value) { $fields[$key] = Purify::clean($value); } $this->content->content_json = $fields; } } ================================================ FILE: app/Services/StarterService.php ================================================ $this->user->name]); $request = [ 'name' => $name, 'entry' => '', 'excerpt' => '', 'settings' => ['default-name' => $name], ]; $this->campaign = $this->createService ->user($this->user) ->data($request) ->create(); EntityPermission::campaign($this->campaign); CampaignCache::campaign($this->campaign); UserCache::campaign($this->campaign); $this->entities(); $this->dashboard(); CampaignEvent::create([ 'campaign_id' => $this->campaign->id, 'created_by' => $this->user->id, 'event' => 'campaign_created', ]); session()->put('onboarding', 1); return $this->campaign; } public function bind(): self { return $this; } public function entities() { CampaignLocalization::forceCampaign($this->campaign); request()->route()->setParameter('campaign', $this->campaign); EntityCache::campaign($this->campaign); CharacterCache::campaign($this->campaign); // Generate locations $kingdom = new Location([ 'name' => __('starter.kingdom.name'), 'campaign_id' => $this->campaign->id, 'is_private' => false, ]); $kingdom->save(); $kingdom->entity->update([ 'type' => __('starter.kingdom.type'), 'entry' => '

    ' . __('starter.kingdom.description') . '

    ', 'source' => 'onboarding', ]); Post::create([ 'entity_id' => $kingdom->entity->id, 'name' => __('starter.kingdom.recent.title'), 'entry' => '
      ' . '
    1. ' . __('starter.kingdom.recent.first') . '
    2. ' . '
    3. ' . __('starter.kingdom.recent.second') . '
    4. ' . '
    ', ]); Attribute::create([ 'entity_id' => $kingdom->entity->id, 'name' => __('starter.kingdom.features.pop.name'), 'value' => __('starter.kingdom.features.pop.value'), 'is_pinned' => true, 'type_id' => AttributeType::Standard, ]); Attribute::create([ 'entity_id' => $kingdom->entity->id, 'name' => __('starter.kingdom.features.exp.name'), 'value' => __('starter.kingdom.features.exp.value'), 'is_pinned' => true, 'type_id' => AttributeType::Standard, ]); Attribute::create([ 'entity_id' => $kingdom->entity->id, 'name' => __('starter.kingdom.features.gov.name'), 'value' => __('starter.kingdom.features.gov.value'), 'is_pinned' => true, 'type_id' => AttributeType::Standard, ]); $city = new Location([ 'name' => __('starter.city.name'), 'location_id' => $kingdom->id, 'campaign_id' => $this->campaign->id, 'is_private' => false, ]); $city->save(); $city->entity->update([ 'source' => 'onboarding', 'type' => __('starter.city.type'), 'entry' => '

    ' . __('starter.city.description') . '

    ', ]); Post::create([ 'entity_id' => $city->entity->id, 'name' => __('starter.city.districts.title'), 'entry' => '
      ' . '
    1. ' . __('starter.city.districts.first') . '
    2. ' . '
    3. ' . __('starter.city.districts.second') . '
    4. ' . '
    5. ' . __('starter.city.districts.third') . '
    6. ' . '
    7. ' . __('starter.city.districts.fourth') . '
    8. ' . '
    ', ]); Post::create([ 'entity_id' => $city->entity->id, 'name' => __('starter.city.locations.title'), 'entry' => '
      ' . '
    1. ' . __('starter.city.locations.first') . '
    2. ' . '
    3. ' . __('starter.city.locations.second') . '
    4. ' . '
    5. ' . __('starter.city.locations.third') . '
    6. ' . '
    ', ]); Attribute::create([ 'entity_id' => $kingdom->entity->id, 'name' => __('starter.kingdom.features.capital.name'), 'value' => '[entity:' . $city->entity->id . ']', 'is_pinned' => true, 'type_id' => AttributeType::Standard, ]); // Generate characters $james = new Character([ 'name' => __('starter.character1.name'), 'age' => __('starter.character1.age'), 'campaign_id' => $this->campaign->id, 'is_private' => false, 'is_appearance_pinned' => true, 'is_personality_pinned' => true, ]); $james->save(); $james->entity->locations()->sync([$city->id]); CharacterTrait::create([ 'character_id' => $james->id, 'name' => __('starter.character1.physical.build.name'), 'entry' => __('starter.character1.physical.build.value'), 'section_id' => CharacterTrait::SECTION_APPEARANCE, ]); CharacterTrait::create([ 'character_id' => $james->id, 'name' => __('starter.character1.physical.features.name'), 'entry' => __('starter.character1.physical.features.value'), 'section_id' => CharacterTrait::SECTION_APPEARANCE, ]); CharacterTrait::create([ 'character_id' => $james->id, 'name' => __('starter.character1.personality.trait1.name'), 'entry' => __('starter.character1.personality.trait1.value'), 'section_id' => CharacterTrait::SECTION_PERSONALITY, ]); CharacterTrait::create([ 'character_id' => $james->id, 'name' => __('starter.character1.personality.trait2.name'), 'entry' => __('starter.character1.personality.trait2.value'), 'section_id' => CharacterTrait::SECTION_PERSONALITY, ]); CharacterTrait::create([ 'character_id' => $james->id, 'name' => __('starter.character1.personality.trait2.name'), 'entry' => __('starter.character1.personality.trait2.value'), 'section_id' => CharacterTrait::SECTION_PERSONALITY, ]); $james->entity->update([ 'source' => 'onboarding', 'entry' => '

    ' . __('starter.character1.description.template') . '

    ' . '

    ' . __('starter.character1.description.intro') . '

    ' . '

    ' . __('starter.character1.description.tip') . '

    ', ]); Post::create([ 'entity_id' => $james->entity->id, 'name' => __('starter.character1.background.title'), 'entry' => '
      ' . '
    1. ' . __('starter.character1.background.loc') . '
    2. ' . '
    3. ' . __('starter.character1.background.cur') . '
    4. ' . '
    5. ' . __('starter.character1.background.seeking') . '
    6. ' . '
    ', ]); $irwie = new Character([ 'name' => __('starter.character2.name'), 'age' => __('starter.character1.age'), 'campaign_id' => $this->campaign->id, 'is_private' => false, 'is_appearance_pinned' => true, 'is_personality_pinned' => true, ]); $irwie->save(); $irwie->entity->locations()->sync([$city->id]); $irwie->entity->update([ 'source' => 'onboarding', 'entry' => '

    ' . __('starter.character2.description.first', ['mention' => '[entity:' . $james->entity->id . ']']) . '

    ' . '

    ' . __('starter.character2.description.second') . '

    ', ]); CharacterTrait::create([ 'character_id' => $irwie->id, 'name' => __('starter.character1.physical.build.name'), 'entry' => __('starter.character1.physical.build.value'), 'section_id' => CharacterTrait::SECTION_APPEARANCE, ]); CharacterTrait::create([ 'character_id' => $irwie->id, 'name' => __('starter.character1.physical.features.name'), 'entry' => __('starter.character1.physical.features.value'), 'section_id' => CharacterTrait::SECTION_APPEARANCE, ]); CharacterTrait::create([ 'character_id' => $irwie->id, 'name' => __('starter.character1.personality.trait1.name'), 'entry' => __('starter.character1.personality.trait1.value'), 'section_id' => CharacterTrait::SECTION_PERSONALITY, ]); CharacterTrait::create([ 'character_id' => $irwie->id, 'name' => __('starter.character1.personality.trait2.name'), 'entry' => __('starter.character1.personality.trait2.value'), 'section_id' => CharacterTrait::SECTION_PERSONALITY, ]); CharacterTrait::create([ 'character_id' => $irwie->id, 'name' => __('starter.character1.personality.trait2.name'), 'entry' => __('starter.character1.personality.trait2.value'), 'section_id' => CharacterTrait::SECTION_PERSONALITY, ]); Post::create([ 'entity_id' => $irwie->entity->id, 'name' => __('starter.character2.skills.title'), 'entry' => '
      ' . '
    1. ' . __('starter.character2.skills.first') . '
    2. ' . '
    3. ' . __('starter.character2.skills.second') . '
    4. ' . '
    5. ' . __('starter.character2.skills.third') . '
    6. ' . '
    ', ]); Relation::create([ 'owner_id' => $irwie->entity->id, 'target_id' => $james->entity->id, 'relation' => __('starter.character2.relation'), 'campaign_id' => $this->campaign->id, ]); } /** * Setup the new campaign's dashboard */ protected function dashboard() { $position = 0; // Recent widget $widget = new CampaignDashboardWidget([ 'campaign_id' => $this->campaign->id, 'widget' => Widget::Recent, 'width' => 4, 'position' => $position++, ]); $widget->save(); $widget = new CampaignDashboardWidget([ 'campaign_id' => $this->campaign->id, 'widget' => Widget::Onboarding, 'width' => 4, 'position' => $position++, ]); $widget->save(); $widget = new CampaignDashboardWidget([ 'campaign_id' => $this->campaign->id, 'widget' => Widget::Help, 'width' => 4, 'position' => $position++, ]); $widget->save(); } } ================================================ FILE: app/Services/Submenus/AbilitySubmenu.php ================================================ entity->child; $items['second']['entities'] = [ 'name' => __('entities.entries'), 'route' => 'abilities.entities', 'count' => $ability->entityAbilities()->count(), ]; return $items; } } ================================================ FILE: app/Services/Submenus/BaseSubmenu.php ================================================ items = $items; return $this; } } ================================================ FILE: app/Services/Submenus/CalendarSubmenu.php ================================================ entity->child; // @phpstan-ignore-next-line $count = $calendar->calendarEvents()->whereHas('remindable')->count(); $items = []; if ($count > 0) { $items['second']['events'] = [ 'name' => __('crud.tabs.reminders'), 'route' => 'calendars.events', 'count' => $count, ]; } return $items; } } ================================================ FILE: app/Services/Submenus/CharacterSubmenu.php ================================================ user) && $this->user->can('update', $this->entity); $items['second']['profile'] = [ 'name' => __('entities/profile.show.tab_name'), 'route' => 'entities.profile', 'entity' => true, 'button' => $canEdit ? [ 'url' => $this->entity->url('edit'), 'icon' => 'fa-regular fa-pencil', 'tooltip' => __('entities/profile.actions.edit_profile'), ] : null, ]; // @phpstan-ignore-next-line $count = $this->entity->child->organisationMemberships()->has('organisation')->has('organisation.entity')->count(); if ($this->campaign->enabled('organisations') && ($count > 0 || $canEdit)) { $items['second']['organisations'] = [ 'name' => Module::plural(config('entities.ids.organisation'), 'entities.organisations'), 'route' => 'characters.organisations', 'count' => $count, ]; } return $items; } } ================================================ FILE: app/Services/Submenus/CreatureSubmenu.php ================================================ entity->child; if (config('services.stripe.enabled') && config('limits.campaigns.premium')) { $items['second']['tree'] = [ 'name' => __('families.show.tabs.tree'), 'route' => 'families.family-tree', ]; } return $items; } } ================================================ FILE: app/Services/Submenus/ItemSubmenu.php ================================================ entity->child; $inventoryCount = $item->inventories()->with('item')->has('entity')->count(); if ($inventoryCount > 0) { $items['second']['inventories'] = [ 'name' => __('items.show.tabs.inventories'), 'route' => 'items.inventories', 'count' => $inventoryCount, ]; } return $items; } } ================================================ FILE: app/Services/Submenus/JournalSubmenu.php ================================================ entity->child; $count = $location->setRelation('entity', $this->entity)->allCharacters()->count(); if ($this->campaign->enabled('characters') && $count > 0) { $items['second']['characters'] = [ 'name' => Module::plural(config('entities.ids.character'), 'entities.characters'), 'route' => 'locations.characters', 'count' => $count, ]; } return $items; } } ================================================ FILE: app/Services/Submenus/MapSubmenu.php ================================================ entity->child; if (isset($this->user) && $this->user->can('update', $this->entity)) { $items['second']['layers'] = [ 'name' => __('maps.panels.layers'), 'route' => 'maps.map_layers.index', 'count' => $map->layers->count(), ]; $items['second']['groups'] = [ 'name' => __('maps.panels.groups'), 'route' => 'maps.map_groups.index', 'count' => $map->groups->count(), ]; $items['second']['markers'] = [ 'name' => __('maps.panels.markers'), 'route' => 'maps.map_markers.index', 'count' => $map->markers->count(), ]; } return $items; } } ================================================ FILE: app/Services/Submenus/NoteSubmenu.php ================================================ entity->child; $count = $model->allMembersCount(); if ($this->campaign->enabled('characters') && $count > 0) { $items['second']['members'] = [ 'name' => __('organisations.fields.members'), 'route' => 'organisations.members', 'count' => $count, ]; } return $items; } } ================================================ FILE: app/Services/Submenus/QuestSubmenu.php ================================================ entity->child; $count = $model->elements()->with('entity')->has('entity')->count(); $items['second']['elements'] = [ 'name' => __('quests.show.tabs.elements'), 'route' => 'quests.quest_elements.index', 'count' => $count, ]; return $items; } } ================================================ FILE: app/Services/Submenus/RaceSubmenu.php ================================================ default() ->custom() ->ordered(); } protected function default(): self { $this->items['first']['story'] = [ 'name' => __('crud.tabs.overview'), 'route' => 'entities.show', 'entity' => true, 'button' => isset($this->user) && $this->user->can('update', $this->entity) ? [ 'url' => route('entities.story.reorder', [$this->campaign, $this->entity]), 'icon' => 'fa-regular fa-arrow-up-arrow-down', 'tooltip' => __('entities/story.reorder.icon_tooltip'), ] : null, ]; $childCount = $this->entity->children()->count(); if ($childCount > 0) { $this->items['second']['children'] = [ 'name' => __('entities/children.title'), 'route' => 'entities.children', 'count' => $childCount, 'entity' => true, ]; } // Each entity can have relations $this->items['first']['relations'] = [ 'name' => __('entries/tabs.relations'), 'route' => 'entities.relations.index', 'count' => $this->entity->relationships()->has('target')->count(), 'entity' => true, 'icon' => 'fa-regular fa-users', ]; // Each entity can have abilities if ($this->campaign->enabled('abilities') && ! $this->entity->isAbility()) { $this->items['third']['abilities'] = [ 'name' => Module::plural(config('entities.ids.ability'), 'crud.tabs.abilities'), 'route' => 'entities.entity_abilities.index', 'count' => 0, // $this->entity->abilities()->has('ability')->count(), 'entity' => true, 'icon' => 'ra ra-fire-symbol', ]; } if ($this->campaign->enabled('calendars')) { $this->items['third']['reminders'] = [ 'name' => __('crud.tabs.reminders'), 'route' => 'entities.reminders.index', 'count' => 0, // $this->entity->abilities()->has('ability')->count(), 'entity' => true, 'icon' => 'ra ra-sun-moon', ]; } if ($this->campaign->enabled('entity_attributes')) { $this->items['third']['attributes'] = [ 'name' => __('entries/tabs.properties'), 'route' => 'entities.attributes', 'entity' => true, 'icon' => '', 'perm' => 'view-attributes', ]; } // Each entity can have an inventory if ($this->campaign->enabled('inventories')) { $this->items['third']['inventory'] = [ 'name' => __('crud.tabs.inventory'), 'route' => 'entities.inventory', 'count' => 0, // $this->entity->inventories()->has('item')->count(), 'entity' => true, 'icon' => 'ra ra-round-bottom-flask', ]; } // Each entity can have assets if ($this->campaign->enabled('media') && $this->entity->hasFiles()) { $this->items['third']['assets'] = [ 'name' => __('entries/tabs.media'), 'route' => 'entities.entity_assets.index', 'count' => $this->entity->assets()->withoutAliases()->count(), 'entity' => true, 'icon' => 'fa-regular fa-file', ]; } // Check if and how many times entity has been mentioned $mentionsCount = $this->entity->mentionsCount(); if (isset($this->user) && $mentionsCount > 0) { $this->items['fourth']['mentions'] = [ 'name' => __('crud.tabs.mentions'), 'route' => 'entities.mentions', 'entity' => true, 'count' => $mentionsCount, ]; } // Permissions for the admin? if (isset($this->user) && $this->user->can('permissions', $this->entity)) { $this->items['fourth']['permissions'] = [ 'name' => __('crud.tabs.permissions'), 'route' => 'entities.permissions', 'entity' => true, 'ajax' => true, 'id' => 'entity-permissions-link', 'icon' => 'lock', ]; } return $this; } protected function custom(): self { if ($this->entity->entityType->isCustom()) { return $this->customEntityType(); } // Get the custom one based on the model name? $className = ucfirst($this->entity->entityType->code); $submenuName = 'App\Services\Submenus\\' . $className . 'Submenu'; try { /** @var CharacterSubmenu $object */ $object = app()->make($submenuName); if (isset($this->user)) { $object->user($this->user); } $object->entity($this->entity)->campaign($this->campaign); foreach ($object->extra() as $section => $sectionItems) { $this->items[$section] = array_merge($this->items[$section] ?? [], $sectionItems); } } catch (\Exception $e) { // Some modules like convos have no submenu } return $this; } protected function customEntityType(): self { /** @var CustomSubmenu $service */ $service = app()->make(CustomSubmenu::class); $service->entity($this->entity)->campaign($this->campaign); foreach ($service->extra() as $section => $sectionItems) { $this->items[$section] = array_merge($this->items[$section] ?? [], $sectionItems); } return $this; } protected function ordered(): array { $this->ordered = []; if (Arr::has($this->items, 'first')) { $this->ordered[] = $this->items['first']; } if (Arr::has($this->items, 'second')) { $this->ordered[] = $this->items['second']; } if (Arr::has($this->items, 'third')) { $sortedItems = array_combine(array_keys($this->items['third']), array_column($this->items['third'], 'name')); foreach ($sortedItems as $key => $item) { $sortedItems[$key] = $item; } $collator = new \Collator(app()->getLocale()); $collator->asort($sortedItems); $sortedMenuItems = []; foreach ($sortedItems as $key => $item) { $sortedMenuItems[$key] = $this->items['third'][$key]; } $this->ordered[] = $sortedMenuItems; } if (Arr::has($this->items, 'fourth')) { $this->ordered[] = $this->items['fourth']; } return $this->ordered; } } ================================================ FILE: app/Services/Submenus/TagSubmenu.php ================================================ entity->child; return $items; } } ================================================ FILE: app/Services/Submenus/TimelineSubmenu.php ================================================ entity->child; if (isset($this->user) && $this->user->can('update', $this->entity)) { $items['second']['eras'] = [ 'name' => __('timelines.fields.eras'), 'route' => 'timelines.timeline_eras.index', 'count' => $model->eras->count(), ]; $items['second']['reorder'] = [ 'name' => __('crud.actions.reorder'), 'route' => 'timelines.reorder', ]; } return $items; } } ================================================ FILE: app/Services/Subscribers/HallOfFameService.php ================================================ has($cacheKey)) { // return cache()->get($cacheKey); } $subscribers = [ 'elemental' => [], 'wyvern' => [], 'owlbear' => [], 'goblin' => [], 'kobold' => [], ]; /** @var ?Role $role */ $role = Role::where(['name' => Pledge::ROLE])->first(); // No subscriber role? Local instance or not properly set up. Let's just avoid throwing an error. if ($role === null) { return $subscribers; } $users = User::select(['pledge', 'name', 'settings']) ->leftJoin('user_roles as ur', function ($join) use ($role) { $join->on('ur.user_id', '=', 'users.id') ->where('ur.role_id', $role->id); }) ->whereNotNull('ur.role_id') ->orderBy('users.name', 'ASC') ->get(); /** @var User $user */ foreach ($users as $user) { if (empty($user->pledge) || $user->pledge === Pledge::KOBOLD) { continue; } if (Arr::get($user, 'settings.hide_subscription', false)) { continue; } $subscribers[mb_strtolower($user->pledge)][] = $user->name; } // Cache for a day cache()->set($cacheKey, $subscribers, 3600 * 24); return $subscribers; } } ================================================ FILE: app/Services/Subscription/CancellationService.php ================================================ request = $request; return $this; } public function cancel(): void { if (! $this->user->subscribed('kanka')) { throw new TranslatableException('subscription/cancellation.errors.not_subscribed'); } try { $this->user->subscription('kanka')->cancel(); } catch (\Exception $e) { // On local machines, subs get dropped from stripe and it causes issues $this->user->subscription('kanka')->delete(); } $this->user->log(UserAction::subCancel); if ($this->webhook) { return; } $cancellation = SubscriptionCancellation::create([ 'user_id' => $this->user->id, 'reason' => $this->request->reason, 'secondary' => $this->request->reason_secondary, 'custom' => $this->request->reason_custom, 'tier' => $this->user->pledge ?? 'Owlbear', 'duration' => $this->user->subscription('kanka')->created_at->diffInDays(Carbon::now()), ]); // Anything that can fail, send to a queue SubscriptionCancelEmailJob::dispatch($cancellation); // Dispatch the job when the subscription actually ends SubscriptionEndJob::dispatch($this->user) ->delay( $this->user->subscription('kanka')->ends_at ); } } ================================================ FILE: app/Services/Subscription/CouponService.php ================================================ code = strip_tags(mb_trim($code, ' ')); return $this; } public function tier(Tier $tier): self { $this->tier = $tier; return $this; } public function check(): array { if (empty($this->code)) { return [ 'valid' => false, 'error' => 'Empty code', ]; } try { Stripe::setApiKey(config('cashier.secret')); // We have to look at all codes with this coupon this way, because the retrieve method // expects a stripe_id $promos = PromotionCode::all(['code' => $this->code, 'active' => true]); if ($promos->count() !== 1) { return $this->error(__('subscriptions/promos.errors.invalid')); } /** @var PromotionCode $promo */ $promo = $promos->first(); if (! $promo->active) { return $this->error(__('subscriptions/promos.errors.inactive')); } // Check restrictions if ($promo->restrictions) { // Some promos are only for first time subscribers // @phpstan-ignore-next-line if ($promo->restrictions->first_time_transaction) { if ($this->user->subscriptions->count()) { return $this->error(__('subscriptions/promos.errors.only-new')); } } } // We have a valid coupon return [ 'promo' => $promo, 'valid' => $promo->active, 'promotion' => $promo->id, 'coupon' => $promo->coupon->id, 'discount' => __('settings.subscription.coupon.percent_off', ['percent' => $promo->coupon->percent_off]), 'price' => $this->price($promo), ]; } catch (Exception $e) { return $this->error($e->getMessage()); } } protected function price($promo): string { $price = $this->tier->price($this->user->currency(), PricingPeriod::Yearly); $discount = round($price * ($promo->coupon->percent_off / 100), 2); $newPrice = $price - $discount; return '' . Number::format($price, 2) . ' ' . Number::format($newPrice, 2); } protected function error(mixed $error): array { return [ 'valid' => false, 'error' => $error, ]; } } ================================================ FILE: app/Services/Subscription/FreeTrialEndService.php ================================================ ids; } /** * Find users with expired subscriptions and dispatch a cleanup job for each one * * @param bool $dispatch set as false to not dispatch the job, just listing the expired subscriptions * in the admin job log. */ public function run(bool $dispatch = true): int { $this->dispatch = $dispatch; $this->endFreeTrials(); return $this->count; } protected function endFreeTrials(): void { $subscriptions = Subscription::with(['user', 'user.boosts', 'user.boosts.campaign']) ->where('stripe_id', 'like', 'manual_sub%') ->where('stripe_status', 'canceled') ->whereLike('stripe_price', 'trial_%') ->whereDate('ends_at', '=', Carbon::now()->startOfDay()) ->get(); if ($subscriptions->count() === 0) { return; } $this->logs[] = 'Free trials'; foreach ($subscriptions as $subscription) { $this->process($subscription); } } protected function process(Subscription $subscription): void { /** @var User $user */ $user = $subscription->user; $this->logs[] = 'User ' . $user->name . ' (' . $user->id . '): ' . $subscription->ends_at; if ($this->dispatch) { $this->ids[] = $user->id; SubscriptionEndJob::dispatch($user, false, true); } $this->count++; } } ================================================ FILE: app/Services/Subscription/PayPalRenewalService.php ================================================ tier = $tier; return $this; } public function process(): mixed { $currency = 'USD'; if ($this->user->billedInEur()) { $currency = 'EUR'; } $price = $this->tier->yearly; $provider = new PayPal; $provider->setApiCredentials(config('paypal')); $provider->getAccessToken(); return $provider->createOrder([ 'intent' => 'CAPTURE', 'application_context' => [ 'return_url' => route('paypal.renew-success'), 'cancel_url' => route('paypal.renew-cancel'), ], 'purchase_units' => [ 0 => [ 'reference_id' => $this->tier->name, 'amount' => [ 'currency_code' => $currency, 'value' => $price, ], ], ], ]); } public function renew(Tier $tier): void { /** @var Subscription $sub */ $sub = $this->user->subscriptions()->where('stripe_price', 'like', 'paypal_%')->first(); $sub->ends_at = $sub->ends_at->addYear(); $sub->stripe_price = 'paypal_' . $tier->name; $sub->save(); $this->user->pledge = $tier->name; $this->user->save(); $this->user->log(UserAction::subPaypalRenew); PaypalRenewedJob::dispatch($this->user->id); } } ================================================ FILE: app/Services/Subscription/PaymentMethodService.php ================================================ defaultPaymentMethod(); if ($defaultPaymentMethod instanceof PaymentMethod) { /** @var Card $card */ $card = $defaultPaymentMethod->asStripePaymentMethod()->card; $expiresAt = Carbon::createFromDate($card->exp_year, $card->exp_month)->endOfMonth(); $user->card_expires_at = $expiresAt; } else { $user->card_expires_at = null; } $user->saveQuietly(); $user->log($action); } } ================================================ FILE: app/Services/Subscription/SubscriptionEndService.php ================================================ ids; } /** * Find users with a pledge but no valid subscription and dispatch a cleanup job for each one * * @param bool $dispatch set as false to not dispatch the job, just listing the expired subscriptions * in the admin job log. */ public function run(bool $dispatch = true): int { $this->dispatch = $dispatch; $users = User::with(['boosts', 'boosts.campaign']) ->whereNotNull('pledge') ->where('pledge', '!=', '') ->where('settings', 'not like', '%patreon%') ->whereNull('banned_until') ->whereDoesntHave('subscriptions', function ($query) { $query->where('stripe_status', 'active') ->orWhere('stripe_status', 'trialing') ->orWhere('stripe_status', 'past_due') ->orWhereDate('ends_at', '>=', Carbon::today()->toDateString()); }) ->get(); foreach ($users as $user) { $this->process($user); } return $this->count; } protected function process(User $user): void { $this->logs[] = 'User ' . $user->name . ' (' . $user->id . ')'; if ($this->dispatch) { SubscriptionEndJob::dispatch($user); $this->ids[] = $user->id; } $this->count++; } } ================================================ FILE: app/Services/Subscription/TrialService.php ================================================ removeFlag(); $this->start(); } protected function start(): void { $this->user->roles()->syncWithoutDetaching([5]); $this->user->pledge = 'Owlbear'; $this->user->save(); $sub = new Subscription; $sub->user_id = $this->user->id; $sub->type = 'kanka'; $sub->stripe_id = 'manual_sub_' . uniqid(); $sub->stripe_status = 'canceled'; $sub->stripe_price = 'trial_' . $this->user->pledge; $sub->quantity = 1; $sub->ends_at = now()->addDays(16)->startOfDay(); $sub->save(); $flag = new UserFlag; $flag->user_id = $this->user->id; $flag->flag = UserFlags::startTrial; $flag->save(); TrialAcceptedEmailJob::dispatch($this->user); } protected function removeFlag(): void { $this ->user ->flags() ->freeTrial() ->delete(); session()->remove('kanka.freeTrial'); } } ================================================ FILE: app/Services/SubscriptionService.php ================================================ tier = $tier; return $this; } public function period(PricingPeriod $period): self { $this->period = $period; return $this; } public function yearly(): self { $this->period = PricingPeriod::Yearly; return $this; } public function monthly(): self { $this->period = PricingPeriod::Monthly; return $this; } public function webhook(): self { $this->webhook = true; return $this; } public function request(array $request): self { $this->request = $request; return $this; } public function coupon(?string $coupon = null): self { if ($this->period === PricingPeriod::Yearly && ! empty($coupon)) { $this->coupon = $coupon; } return $this; } /** * When the stripe API calls us, we get a plan_id that needs to be transformed into a tier and tierprice */ public function plan(string $plan): self { // Some weird edge cases in prod need mapping if ($plan === 'price_1IRIwTDInN4WlDnRJJU53rej') { $plan = config('subscription.owlbear.usd.monthly'); } /** @var ?TierPrice $price */ $price = TierPrice::where('stripe_id', $plan)->first(); $this->tier = $price->tier; return $this; } /** * Change plans */ public function change(): self { // Update the user's payment plan $paymentMethodID = Arr::get($this->request, 'payment_id'); $this->user->addPaymentMethod($paymentMethodID); $this->user->updateDefaultPaymentMethod($paymentMethodID); // Save the expiration date on the user for alerts about expiring cards $payment = $this->user->defaultPaymentMethod(); if ($payment instanceof PaymentMethod) { /** @var Card $card */ $card = $payment->asStripePaymentMethod()->card; $expiresAt = Carbon::createFromDate($card->exp_year, $card->exp_month)->endOfMonth(); $this->user->card_expires_at = $expiresAt; $this->user->save(); // Check that someone isn't using a VPN if (app()->isProduction() && $this->user->currency() === 'brl' && $card->country !== 'BR') { throw (new TranslatableException('subscription.errors.invalid_card_country.brl'))->setOptions(['email' => '' . config('app.email') . '']); } } // Subscribe $this->subscribe($paymentMethodID); return $this; } /** * @throws IncompletePayment */ public function subscribe(string $paymentID): self { // New subscriber if (! $this->user->subscribed('kanka')) { $this->user->newSubscription('kanka', $this->tierPrice()->stripe_id) ->withCoupon($this->coupon ?? null) ->create($paymentID); $this->user->log(UserAction::subNew); return $this; } // If going down from elemental to owlbear, keep it as is until the current billing period if ($this->downgrading()) { $this->user->subscription('kanka')->swap($this->tierPrice()->stripe_id); $this->user->log(UserAction::subDowngrade); } else { $this->user->subscription('kanka')->swapAndInvoice($this->tierPrice()->stripe_id); $this->user->log(UserAction::subUpgrade); } return $this; } public function renew(): void { $this->user->subscription('kanka')->resume(); } /** * Set up the user's pledge, role, discord */ public function finish(): self { // If the user is cancelling through the interface, don't do anything else if ($this->cancelled) { return $this; } // If downgrading, send admins an email, and let stripe deal with the rest. A user update hook will be thrown // when the user really changes. Probably? if (! $this->webhook && $this->downgrading()) { SubscriptionDowngradedEmailJob::dispatch( $this->user, Arr::get($this->request, 'reason'), Arr::get($this->request, 'reason_custom') ); $this->user->log(UserAction::subDowngrade); return $this; } // Determine if the pledge was changed or not $new = ! $this->upgrading(); // Add the necessary roles and pledge data $this->user->pledge = $this->tier->name; $this->user->update(['pledge' => $this->tier->name]); // We're so far, good. Let's add the user to the subscriber group $role = Role::where('name', '=', Pledge::ROLE)->first(); if ($role && ! $this->user->hasRole(Pledge::ROLE)) { $this->user->roles()->attach($role->id); } // Anything that can fail, send to the queue DiscordRoleJob::dispatch($this->user)->delay(now()->addSeconds($new ? 0 : 30)); MailSettingsChangeJob::dispatch($this->user); // If Stripe is confirming that a sub is renewed, we don't want to do anything more if ($this->renewal()) { return $this; } // Don't send emails when called from the webhook if (! $this->webhook) { if ($this->userConvertedFromFreeTrial()) { Converted::dispatch($this->user); } else { SubscriptionCreatedEmailJob::dispatch($this->user, $this->period, $new); WelcomeSubscriptionEmailJob::dispatch($this->user, $this->tier); } // Save the new sub value if (isset($this->tier)) { $this->subscriptionValue = $this->tierPrice()->cost; } } return $this; } /** * Get the status of the user's subscription */ public function status(): int { if (! $this->user->subscribed('kanka')) { return self::STATUS_UNSUBSCRIBED; } elseif ($this->user->subscription('kanka')->onGracePeriod()) { return self::STATUS_GRACE; } elseif ($this->user->subscription('kanka')->canceled()) { return self::STATUS_CANCELLED; } return self::STATUS_SUBSCRIBED; } /** * Get the tier amount */ public function amount(): string { $amount = $this->tierPrice()->cost; return Number::format($amount, 2); } /** * Get the user's current plan */ public function currentPlan(): ?TierPrice { if (! $this->user->subscribed('kanka')) { return null; } $price = $this->user->subscription('kanka')->stripe_price; /** @var TierPrice $tier */ $tier = TierPrice::where('stripe_id', $price)->first(); if (empty($tier)) { return null; } return $tier; } /** * Cancel the user's subscription to Kanka */ public function canceled(): bool { return $this->cancelled; } /** * Get the subscription value */ public function subscriptionValue(): int { return (int) $this->subscriptionValue; } /** * Determine if a user is downgrading */ public function downgrading(): bool { // Elemental downgrading -> owl or wyv if ($this->user->isElemental() && in_array($this->tier->name, [Pledge::OWLBEAR, Pledge::WYVERN])) { return true; } // Wyvern downgrading to owl if ($this->user->isWyvern() && $this->toOwlbear()) { return true; } // Cancelling return isset($this->tier) && $this->tier->name === Pledge::KOBOLD; } /** * Determine if a user is upgrading their plan to a higher tier */ protected function upgrading(): bool { if ($this->user->pledge == Pledge::OWLBEAR && in_array($this->tier->name, [Pledge::WYVERN, Pledge::ELEMENTAL])) { return true; } return (bool) ($this->user->pledge == Pledge::WYVERN && $this->tier->name == Pledge::ELEMENTAL); } /** * Determine if the process is renewing the user or not */ protected function renewal(): bool { // If we're not in a webhook, it's not possible to be an auto-renewal if (! $this->webhook) { return false; } // Check if the user's active sub is from before the current date /** @var ?Subscription $sub */ $sub = Subscription::where('user_id', $this->user->id)->where('stripe_status', 'active')->first(); if ($sub === null) { return false; } return $sub->created_at->lessThan(Carbon::yesterday()); } /** * If the target tier is owlbear */ protected function toOwlbear(): bool { return $this->tier->name == Pledge::OWLBEAR; } /** * Determine if the user is only limited to paypal subscriptions */ public function isLimited(): bool { $countries = ['EG']; return $this->user->logs() ->where('type_id', UserAction::login) ->whereIn('country', $countries) ->count() > 0; } protected function isYearly(): bool { return $this->period === PricingPeriod::Yearly; } public function tierPrice(): TierPrice { if (isset($this->tierPrice)) { return $this->tierPrice; } return $this->tierPrice = TierPrice::where('tier_id', $this->tier->id) ->where('currency', $this->user->currency()) ->where('period', $this->isYearly() ? PricingPeriod::Yearly->value : PricingPeriod::Monthly->value) ->first(); } protected function userConvertedFromFreeTrial(): bool { return $this->user->flags() ->where('flag', UserFlags::startTrial) ->whereDate('created_at', '>=', Carbon::now()->subDays(30)) ->exists(); } } ================================================ FILE: app/Services/SubscriptionUpgradeService.php ================================================ tier = $tier; return $this; } public function period(PricingPeriod $pricingPeriod): self { $this->period = $pricingPeriod; return $this; } public function upgradePrice(): float|int { $monthly = true; $price = $this->tier->price($this->user->currency(), $this->period); if (! $this->user->subscribed('kanka') || $this->user->hasManualSubscription()) { return $price; } if ($this->onYearlyPlan()) { $monthly = false; } // Calculate the current subscription price $code = 'owlbear'; if ($this->user->isElemental()) { $code = 'elemental'; if (! $monthly) { return 0; } } elseif ($this->user->isWyvern()) { $code = 'wyvern'; } $this->tier = Tier::where('code', $code)->first(); $oldPrice = $this->tier->price( $this->user->currency(), $monthly ? PricingPeriod::Monthly : PricingPeriod::Yearly ); $endPeriod = $this->endPeriod(); if ($this->period === PricingPeriod::Yearly) { // Prorated Cost = (New Tier Cost - Old Tier Cost) x (Number of Days Remaining / Number of Days in a Full Year) // If going from monthly to yearly, we divide the current sub up on a month $duration = $monthly ? 31 : 365; $price = round(($price - ($oldPrice)) * ($endPeriod->diffInDays(Carbon::now(), true) / $duration), 2); // @phpstan-ignore-next-line } elseif ($monthly && $this->period === PricingPeriod::Monthly) { // Prorated Cost = (New Tier Cost - Old Tier Cost) x (Number of Days Remaining / Total Days in the Month) // dump($price); // dump($oldPrice); // dump($endPeriod->format('Y.m.d')); // dump($endPeriod->diffInDays(Carbon::now(), true)); // dump($endPeriod->diffInDays(Carbon::now(), true)/ 31); $price = round(($price - ($oldPrice)) * ($endPeriod->diffInDays(Carbon::now(), true) / 31), 2); } elseif ($this->period === PricingPeriod::Monthly) { // Switching from a yearly plan to a monthly plan, this gets interesting $remaining = 365; $price = round(($price - ($oldPrice)) * ($endPeriod->diffInDays(Carbon::now(), true) / $remaining), 2); } return max(0, $price); } protected function endPeriod(): Carbon { // Stripe provides us with this information easily if (! $this->user->hasPayPal()) { return Carbon::createFromTimestamp($this->user->subscription('kanka')->asStripeSubscription()->current_period_end); } // For paypal, we need the subscription's end date return $this->user->subscription('kanka')->ends_at; } protected function onYearlyPlan(): bool { if ($this->user->hasPayPal()) { return true; } // Todo: move to tiers table? $prices = array_merge( config('subscription.owlbear.yearly'), config('subscription.wyvern.yearly'), config('subscription.elemental.yearly'), ); return $this->user->subscribedToPrice($prices, 'kanka'); } } ================================================ FILE: app/Services/TOC/TocSlugify.php ================================================ used = []; $this->slugger = $slugger ?: new AsciiSlugger; } /** * Slugify */ public function makeSlug(string $string): string { $slugged = $this->slugger->slug($string)->lower()->toString(); $count = 1; $orig = $slugged; while (in_array($slugged, $this->used)) { $slugged = $orig . '-' . $count; $count++; } $this->used[] = $slugged; return $slugged; } public function reset(): void { $this->used = []; } } ================================================ FILE: app/Services/TagService.php ================================================ entities as $entity) { $entity->tags()->detach($tag->id); $entity->tags()->attach($newTag->id); } } public function transferPosts(Tag $tag, Tag $newTag): void { foreach ($tag->posts as $post) { $post->tags()->detach($tag->id); $post->tags()->attach($newTag->id); } } } ================================================ FILE: app/Services/TimelineService.php ================================================ timeline = $timeline; return $this; } public function reorderElements(TimelineElement $timelineElement, bool $replace = false) { // First position. If replacing, start where the current one is gone $position = $timelineElement->position; if (! $replace) { $position++; } // Reorder the position of following elements $elements = $timelineElement->era ->elements() ->where('position', '>=', $timelineElement->position) ->where('id', '!=', $timelineElement->id) ->orderBy('position') ->get(); foreach ($elements as $element) { $element->position = $position; $element->save(); $position++; } } public function reorder(ReorderTimeline $request): bool { $ids = $request->get('timeline_era'); $elementIds = $request->get('timeline_element'); if (empty($ids)) { return false; } $position = 1; foreach ($ids as $id) { /** @var ?TimelineEra $era */ $era = TimelineEra::find($id); if ($era === null || $era->timeline_id !== $this->timeline->id) { continue; } $era->position = $position; $era->save(); $position++; // Reorder elements $elements = Arr::get($elementIds, $id, []); if (empty($elements)) { continue; } $elementPosition = 1; // dump($elements); foreach ($elements as $elementId) { /** @var ?TimelineElement $element */ $element = TimelineElement::find($elementId); if ($element === null || $element->timeline_id !== $this->timeline->id) { continue; } // Reposition // dump("Reposition $element->name (# $element->id) to $elementPosition"); $element->position = $elementPosition; $element->save(); $elementPosition++; } } // dd('w'); return true; } } ================================================ FILE: app/Services/Tracking/DatalayerService.php ================================================ 'visitor', 'userGroup' => $this->userGroup(), 'userTier' => null, 'userSubbed' => false, 'route' => $this->route(), 'newAccount' => $this->newAccount ? '1' : '0', 'newSubscriber' => $this->newSubscriber ? '1' : '0', 'userID' => null, ], $this->additional); if (isset($this->user)) { $data['userType'] = 'registered'; $data['userTier'] = ! empty($this->user->pledge) ? $this->user->pledge : null; $data['userSubbed'] = ! empty($this->user->pledge) ? 'true' : 'false'; $data['userID'] = $this->user->id; if ($this->newCancelledSubscriber) { $data['newCancelled'] = '1'; } if ($this->newAccount || $this->newSubscriber) { $data['userEmail'] = $this->user->email; } } // We only track if ads are shown or hidden on page that are set up to actually serve ads if (AdCache::canHaveAds()) { $data['showAds'] = $this->showAds(); } return json_encode($data); } protected function showAds(): bool { if (isset($this->campaign) && $this->campaign->boosted()) { return false; // } elseif (!AdCache::canHaveAds()) { // return false; } elseif (! isset($this->user)) { return true; } elseif ($this->user->isSubscriber()) { return false; } return $this->user->created_at->diffInHours(Carbon::now()) > 24; } public function userGroup(): string { if (isset($this->group)) { return $this->group; } // Set in session? Use that if (session()->has('user_group')) { $this->group = session()->get('user_group'); return $this->group; } if (isset($this->user)) { $this->group = $this->user->id % 2 == 0 ? 'a' : 'b'; return $this->group; } // Unlogged user, use one from the session $this->group = mt_rand(0, 1) === 0 ? 'a' : 'b'; session()->put('user_group', $this->group); return $this->group; } public function groupB(): bool { return $this->userGroup() === 'b'; } public function add(string $key, mixed $value): self { $this->additional[$key] = $value; return $this; } protected function route(): string { if (empty($this->request->route())) { return ''; } return (string) $this->request->route()->getName(); } /** * Set the new subscriber as true */ public function newSubscriber(): self { $this->newSubscriber = true; return $this; } /** * Trigger the user as being newly cancelled */ public function newCancelledSubscriber(): self { $this->newCancelledSubscriber = true; return $this; } /** * Set the new account as true */ public function newAccount(): self { $this->newAccount = true; return $this; } } ================================================ FILE: app/Services/TroubleshootingService.php ================================================ __('assistance.placeholders.campaign'), ]; foreach ($this->adminCampaigns() as $id => $name) { $campaigns[$id] = $name; } return $campaigns; } /** * Generate a unique token for the kanka team to join a campaign * * @throws TranslatableException */ public function generate(): AdminInvite { // Already has a token? $exists = AdminInvite::check($this->campaign->id)->first(); if ($exists) { throw (new TranslatableException('helpers.troubleshooting.errors.token_exists')) ->setOptions(['campaign' => $this->campaign->name]); } $token = new AdminInvite; $token->created_by = $this->user->id; $token->campaign_id = $this->campaign->id; $token->token = Str::uuid(); $token->save(); return $token; } protected function adminCampaigns(): array { $campaigns = []; return $this ->user ->campaignRoles() ->where('campaign_roles.is_admin', 1) ->leftJoin('campaigns', 'campaigns.id', '=', 'campaign_roles.campaign_id') ->has('campaign') ->pluck('campaigns.name', 'campaigns.id') ->toArray(); } } ================================================ FILE: app/Services/TutorialService.php ================================================ user ->tutorials() ->whereIn('code', $this->tutorials) ->delete(); UserCache::user($this->user)->clear(); return $this; } /** * Save that a user dismissed a tutorial */ public function track(string $code): self { if (UserCache::dismissedTutorial($code)) { return $this; } $code = Purify::clean($code); if (! $this->valid($code) && ! Str::startsWith($code, ['releases_', 'banner_'])) { return $this; } try { $tutorial = new Tutorial; $tutorial->user_id = $this->user->id; $tutorial->code = $code; $tutorial->save(); UserCache::clear(); } catch (Exception $e) { // Someone double-clicked with a slow internet return $this; } return $this; } protected function valid(string $code): bool { return in_array($code, $this->tutorials); } } ================================================ FILE: app/Services/UserAuthenticatedService.php ================================================ get('login_redirect'); if (! empty($redirectTo)) { session()->remove('login_redirect'); // 2FA redirects are handled by the OTP middleware if (config('google2fa.enabled')) { session(['2fa_redirect' => $redirectTo]); } return redirect()->to($redirectTo); } return redirect()->route('home'); } } ================================================ FILE: app/Services/Users/CampaignService.php ================================================ put('campaign_id', $this->campaign->id); $this->user->last_campaign_id = $this->campaign->id; $this->user->saveQuietly(); return $this; } public function last(): self { if (! isset($this->user)) { return $this; } $last = $this->user->lastCampaign; if (! $last) { return $this; } return $this->campaign($last)->set(); } public function next(): self { // Switch to the next available campaign? $member = CampaignUser::where('user_id', $this->user->id)->first(); if ($member && $member->campaign) { // Just switch to the first one available. return $this->campaign($member->campaign)->set(); } else { // Need to create a new campaign session()->forget('campaign_id'); } return $this; } /** * List of user campaigns thar aren't the current one */ public function campaigns(): array { return $this ->user ->campaigns() ->whereNotIn('campaign_id', [$this->campaign->id]) ->pluck('campaigns.name', 'campaigns.id') ->toArray(); } /** * List of campaigns the user is the owner and last member of. This is used for the purge warning emails */ public function flaggedCampaigns(): array { $campaigns = []; /** @var Campaign[] $userCampaigns */ $userCampaigns = $this->user->campaigns()->with(['roles', 'roles.users'])->get(); foreach ($userCampaigns as $campaign) { /** @var ?CampaignRole $adminRole */ $adminRole = $campaign->roles->where('is_admin', true)->first(); if (! $adminRole) { continue; } // If the user isn't in the admin $isAdmin = false; foreach ($adminRole->users as $member) { if ($member->user_id === $this->user->id) { $isAdmin = true; } } if (! $isAdmin || $adminRole->users->count() > 1) { continue; } // The user is the only admin $campaigns[] = $campaign; } return $campaigns; } } ================================================ FILE: app/Services/Users/CleanupService.php ================================================ removeCampaigns() ->removeFollows() ->removeFeatureRequests() ->removeWorldbuilding() ->removeAvatar() ->cleanCache() ->removeNewsletter(); return $this; } protected function removeCampaigns(): self { // Log::info('Services/Users/CleanupService', ['deleting', ['user' => $this->user->id]]); $members = CampaignUser::where('user_id', $this->user->id) ->with(['campaign', 'campaign.members']) ->has('campaign') ->get(); foreach ($members as $member) { $member->delete(); // Delete a campaign if no one is left in it. Since we did the "with", it's cached, hence checking on 1 if ($member->campaign->members->count() <= 1) { Images::model($member->campaign)->field('image')->cleanup(); $member->campaign->forceDelete(); Deleted::dispatch($member->campaign, $this->user); } } return $this; } protected function removeFollows(): self { $followers = CampaignFollower::where('user_id', $this->user->id)->with('campaign')->get(); foreach ($followers as $follower) { // Log::info('Removing follower', ['follower' => $follower->id]); $follower->delete(); } return $this; } protected function removeFeatureRequests(): self { Feature::where('created_by', $this->user->id)->where('upvote_count', '<', 10)->where('status_id', FeatureStatus::Approved)->delete(); return $this; } protected function removeWorldbuilding(): self { CommunityEventEntry::where('created_by', $this->user->id)->delete(); return $this; } protected function removeAvatar(): self { if ($this->user->hasAvatar()) { Images::model($this->user)->field('avatar')->cleanup(); } return $this; } protected function cleanCache(): self { UserCache::user($this->user) ->clearName() ->clear(); return $this; } protected function removeNewsletter(): self { // If the user was subscribed to the newsletter, unsubscribe them if (app()->isProduction() && ! empty($this->user->hasNewsletter())) { UnsubscribeUser::dispatch($this->user->email); } return $this; } } ================================================ FILE: app/Services/Users/CurrencyService.php ================================================ countryService = $countryService; } /** * Build a list of currencies available to the user */ public function availableCurrencies(): array { // USD and EUR are always available $currencies = [ 'usd' => __('settings.subscription.currencies.usd'), 'eur' => __('settings.subscription.currencies.eur'), ]; // Brazil if ($this->countryService->getCountry() === 'BR' || $this->user->currency() === 'brl') { $currencies['brl'] = __('settings.subscription.currencies.brl'); } return $currencies; } public function setDefaultCurrency(): void { // If the user has defined their preferred currency, we use that if (Arr::has($this->user->settings, 'currency')) { return; } // If the user has a subscription, use that if ($this->user->subscribed('kanka')) { $id = $this->user->subscription('kanka')->stripe_price; $price = TierPrice::stripe($id)->first(); if ($price) { $this->save($price->currency); return; } } $country = $this->countryService->getCountry(); $europe = [ // EuroZone 'AT', 'BE', 'HR', 'CY', 'EE', 'FI', 'FR', 'DE', 'GR', 'IE', 'IT', 'LV', 'LT', 'LU', 'MT', 'NL', 'PT', 'SI', 'SK', 'ES', // Pegged to the EUR 'DK', ]; $currency = null; if ($country === 'BR') { $currency = 'brl'; } elseif (in_array($country, $europe)) { $currency = 'eur'; } // Not one of our cases, let it default to USD if (empty($currency)) { return; } $this->save($currency); } protected function save(string $currency): void { $settings = $this->user->settings; $settings['currency'] = $currency; $this->user->settings = $settings; $this->user->saveQuietly(); } } ================================================ FILE: app/Services/Users/EmailValidationService.php ================================================ user->id)->first(); if ($token && $token->is_valid) { return; } $flag = UserFlag::where('user_id', $this->user->id) ->where('flag', UserFlags::email->value) ->first(); // If we've already notified the user, no need to notify them again if ($flag) { return; } $flag = new UserFlag; $flag->user_id = $this->user->id; $flag->flag = UserFlags::email; $flag->save(); $token = new UserValidation; $token->token = Str::uuid(); $token->user_id = $this->user->id; $token->is_valid = false; $token->save(); EmailValidationJob::dispatch($this->user, $token); } } ================================================ FILE: app/Services/Users/OfferTrialService.php ================================================ ids; } public function run(): int { $this->find(); return count($this->ids); } protected function find(): void { $ids = json_decode(Storage::disk('local')->get('promo.json'), true); // $ids = []; $users = User::select('users.id') ->where('last_login_at', '>', now()->subMonths(3)) ->leftJoin('user_flags', 'user_flags.user_id', '=', 'users.id') ->where(function ($query) { $query->whereNull('pledge')->orWhere('pledge', ''); }) ->whereNull('user_flags.id') ->whereNull('users.booster_count') ->whereIn('users.id', $ids) ->get(); foreach ($users as $user) { $this->flag($user); } } protected function flag(User $user): void { $this->ids[] = $user->id; $flag = new UserFlag; $flag->user_id = $user->id; $flag->flag = UserFlags::freeTrial; $flag->save(); } } ================================================ FILE: app/Services/Users/PurgeService.php ================================================ date = $date; return $this; } public function real(): self { $this->dry = false; return $this; } public function limit(int $limit): self { $this->limit = $limit; return $this; } /** * Accounts that haven't logged in during two years and have no campaign whatsoever */ public function empty(): int { $this->reset(); User::select('users.*') ->leftJoin('campaign_user as cu', 'cu.user_id', 'users.id') ->where(function ($sub) { $sub->whereNull('last_login_at') ->orWhereDate('last_login_at', '<=', $this->date); }) ->whereDate('users.created_at', '<=', $this->date) ->where(function ($sub) { $sub->where('users.pledge', '') ->orWhereNull('users.pledge'); }) ->whereNull('cu.id') ->whereNull('users.stripe_id') ->chunk(500, function ($users) { echo "New chunk\n"; if ($this->count >= $this->limit) { return false; } /** @var User $user */ foreach ($users as $user) { if ($this->count >= $this->limit) { return false; } $this->count++; if (! $this->dry) { DeleteUser::dispatch($user); } } }); return $this->count; } /** * Accounts that haven't logged in during two years and only have a campaign with the boilerplate stuff */ public function example(): int { // $this->reset(); if ($this->count >= $this->limit) { return 0; } User::distinct() ->select('users.id') ->leftJoin('campaign_user as cu', 'cu.user_id', 'users.id') /*->with([ 'campaigns' => function ($sub) { $sub->select('campaigns.id'); }, 'campaigns.users' => function ($sub) { $sub->select('users.id'); } ])*/ ->where(function ($sub) { $sub->whereNull('last_login_at') ->orWhereDate('last_login_at', '<=', $this->date); })->whereDate('users.created_at', '<=', $this->date) ->where(function ($sub) { $sub->where('users.pledge', '') ->orWhereNull('users.pledge'); }) ->where(DB::raw('(select count(cu2.id) from campaign_user as cu2 where cu2.user_id = users.id)'), '=', 1) ->where(DB::raw('(select count(cu3.id) from campaign_user as cu3 where cu3.campaign_id = cu.campaign_id)'), '=', 1) ->where(DB::raw('(select count(e.id) from entities as e where e.created_by = users.id and e.deleted_at is null)'), '<', 7) ->whereNull('users.stripe_id') ->chunk(2000, function ($users) { echo "New chunk\n"; if ($this->count >= $this->limit) { return false; } /** @var User $user */ foreach ($users as $user) { if ($this->count >= $this->limit) { return false; } $this->count++; if (! $this->dry) { DeleteUser::dispatch($user); } } }); return $this->count; } protected function reset(): void { $this->count = 0; } /** * Inactive users for > 18 months, warn them of their upcoming deletion */ public function firstWarning(): int { if ($this->count >= $this->limit) { return 0; } $cutoff = config('purge.users.first.inactivity'); $this->date = Carbon::now()->subMonths($cutoff); User::distinct() ->select('users.id') ->leftJoin('user_flags as f', function ($sub) { return $sub ->on('f.user_id', 'users.id') ->where('f.flag', UserFlags::firstWarning->value); }) ->where(function ($sub) { $sub->whereNull('last_login_at') ->orWhereDate('last_login_at', '<=', $this->date); }) ->whereDate('users.created_at', '<=', $this->date) ->where(function ($sub) { $sub->where('users.pledge', '') ->orWhereNull('users.pledge'); }) ->whereNull('f.id') ->whereNull('users.stripe_id') ->limit($this->limit) ->chunk(1000, function ($users) { if ($this->count >= $this->limit) { return false; } foreach ($users as $user) { if ($this->count >= $this->limit) { return false; } // Add a flag for this user if (! $this->dry) { $flag = new UserFlag; $flag->user_id = $user->id; $flag->flag = UserFlags::firstWarning; $flag->save(); FirstWarningJob::dispatch($user->id); } $this->warnedIds[] = $user->id; $this->count++; } }); JobLog::create([ 'name' => 'users:purge', 'result' => 'First warning: ' . implode(', ', $this->warnedIds), ]); $this->warnedIds = []; return $this->count; } public function secondWarning(): int { $this->reset(); if ($this->count >= $this->limit) { return 0; } // First warning send 23 days ago $cutoff = Carbon::now() ->subDays(config('purge.users.first.limit')) ->addDays(config('purge.users.second.limit')); User::distinct() ->select('users.id') ->leftJoin('user_flags as f', function ($sub) { return $sub ->on('f.user_id', 'users.id') ->where('f.flag', UserFlags::firstWarning->value); }) ->leftJoin('user_flags as f2', function ($sub) { return $sub ->on('f2.user_id', 'users.id') ->where('f2.flag', UserFlags::secondWarning->value); }) ->where(function ($sub) { $sub->where('users.pledge', '') ->orWhereNull('users.pledge'); }) ->where('f.created_at', '<=', $cutoff) ->whereNull('f2.id') ->whereNull('users.stripe_id') ->limit($this->limit) ->chunk(1000, function ($users) { if ($this->count >= $this->limit) { return false; } foreach ($users as $user) { if ($this->count >= $this->limit) { return false; } // Add a flag for this user if (! $this->dry) { $flag = new UserFlag; $flag->user_id = $user->id; $flag->flag = UserFlags::secondWarning; $flag->save(); SecondWarningJob::dispatch($user->id); } $this->warnedIds[] = $user->id; $this->count++; } }); JobLog::create([ 'name' => 'users:purge', 'result' => 'Second warning: ' . implode(', ', $this->warnedIds), ]); $this->warnedIds = []; return $this->count; } /** * Permanently delete users who haven't logged in and contributed in a long time */ public function purge(): int { $this->reset(); if ($this->count >= $this->limit) { return 0; } // Warning 2 sent 7 days ago $cutoff = Carbon::now() ->subDays(config('purge.users.second.limit')); User::distinct() ->select('users.id') ->leftJoin('user_flags as f', function ($sub) { return $sub ->on('f.user_id', 'users.id') ->where('f.flag', UserFlags::secondWarning->value); }) ->where(function ($sub) { $sub->where('users.pledge', '') ->orWhereNull('users.pledge'); }) ->where('f.created_at', '<=', $cutoff) ->whereNull('users.stripe_id') ->limit($this->limit) ->chunkById(1000, function ($users) { if ($this->count >= $this->limit) { return false; } /** @var User $user */ foreach ($users as $user) { if ($this->count >= $this->limit) { return false; } // Add a flag for this user if (! $this->dry) { DeleteUser::dispatch($user); } $this->warnedIds[$user->id] = $user->email; $this->count++; } }, 'users.id', 'id'); JobLog::create([ 'name' => 'users:purge', 'result' => 'Purged: ' . collect($this->warnedIds) ->map(fn ($v, $k) => "$k:$v") ->implode(', '), ]); $this->warnedIds = []; return $this->count; } } ================================================ FILE: app/Services/Users/UserLogService.php ================================================ count; } public function anonymize(): self { $cutoff = config('logging.anonymize'); $this->count = UserLog::whereDate('created_at', Carbon::today()->subDays($cutoff)->format('Y-m-d')) ->update(['ip' => null, 'country' => null]); return $this; } } ================================================ FILE: app/Services/WebhookService.php ================================================ campaign); // Todo: move all of this to a service so it can be tested $webhooks = Webhook::active($this->campaign->id, $action)->with('tags')->get(); $entityTags = $this->entity->tags()->pluck('tags.id')->all(); foreach ($webhooks as $webhook) { if ($this->isInvalid($webhook, $entityTags)) { continue; } if ($webhook->type == 1) { $data = Str::replace( ['{name}', '{who}', '{url}'], [$this->entity->name, $this->user->name, route('entities.show', [$this->campaign->id, $this->entity])], $webhook->message ); } else { CampaignLocalization::forceCampaign($this->entity->campaign); $data = [ 'event' => [ 'id' => uniqid(), 'type' => $webhook->typeKey(), 'webhook_id' => $webhook->id, 'timestamp' => time(), ], 'entity' => new EntityResource($this->entity), ]; } if ($webhook->shortUrl() == 'discord') { if ($webhook->type == 2) { $data = json_encode($data); } $embeds = [ 'title' => $this->entity->name, 'description' => strval($data), 'color' => config('discord.color'), 'url' => route('entities.show', [$this->campaign->id, $this->entity]), 'author' => [ 'name' => 'Kanka Webhooks', ], ]; if ($this->entity->hasImage(true)) { $embeds['thumbnail'] = [ 'url' => Avatar::entity($this->entity)->size(192)->thumbnail(), ]; } $data = [ 'embeds' => [ $embeds, ], ]; } try { Http::post($webhook->url, $data); } catch (Exception $e) { // Don't do anything with failures } } } public function test(Webhook $webhook) { if ($webhook->type == 1) { $data = Str::replace( ['{name}', '{who}', '{url}'], ['Thaelia', $this->user->name, route('locations.index', [$this->campaign])], $webhook->message ); } else { $data = [ 'event' => [ 'id' => uniqid(), 'type' => $webhook->typeKey(), 'webhook_id' => $webhook->id, 'timestamp' => time(), ], 'entity' => '{"id": 1,"name":"Thaelia","entry":"\n

    Lorem Ipsum.

    \n","image":"{path}","image_full":"{url}","image_thumb":"{url}","has_custom_image":false,"is_private":true,"location_id": null,"entity_id": 5,"tags":[],"created_at":"2019-01-30T00:01:44.000000Z","created_by": 1,"updated_at":"2019-08-29T13:48:54.000000Z","updated_by":1,"location_id":4,"type":"Kingdom"}', ]; } if ($webhook->shortUrl() == 'discord') { if ($webhook->type == 2) { $data = json_encode($data); } $embeds = [ 'title' => 'Thaelia', 'description' => strval($data), 'color' => config('discord.color'), 'url' => route('locations.index', [$this->campaign]), 'author' => [ 'name' => 'Kanka Webhooks', ], ]; $data = [ 'embeds' => [ $embeds, ], ]; } try { Http::post($webhook->url, $data); } catch (Exception $e) { // Don't do anything with failures } } protected function isInvalid(Webhook $webhook, array $entityTags): bool { // Check if the entity is private or the webhook supports private entities. if ($this->entity->is_private && $webhook->skipPrivate()) { return true; } // Check that entity has at least one of the tags the webhook has. if ($webhook->tags()->count() === 0) { return false; } $tags = $webhook->tags()->pluck('tags.id')->all(); return (bool) (empty(array_intersect($entityTags, $tags))); } } ================================================ FILE: app/Services/Whiteboards/ApiService.php ================================================ whiteboard = $whiteboard; return $this; } public function load(): array { $this->data['name'] = $this->whiteboard->name; $this->loadShapes(); $this->loadEntities(); $this->loadImages(); $this->translations(); $this->urls(); $this->interactive(); $this->fixData(); return $this->data; } protected function loadShapes(): self { $this->data['data'] = $this->whiteboard->data ?? []; /** @var WhiteboardShape[]|Collection $shapes */ $shapes = $this->whiteboard->shapes()->with(['whiteboard', 'whiteboard.campaign'])->get(); $this->data['data'] = ShapeResource::collection($shapes); // Collect image UUIDs from image shapes foreach ($shapes as $shape) { if ($shape->isImage()) { $uuid = Arr::get($shape->shape, 'uuid'); if ($uuid) { $this->images[] = $uuid; } } elseif ($shape->isEntity()) { $entity = Arr::get($shape->shape, 'entity_id'); if ($entity) { $this->entityIds[] = $entity; } } } return $this; } protected function loadImages(): void { $this ->loadGallery(); } protected function translations(): void { $this->data['i18n'] = [ 'close' => __('crud.actions.close'), 'save' => __('crud.save'), 'create' => __('crud.create'), 'delete' => __('crud.permissions.actions.delete'), 'add-square' => __('whiteboards/draw.actions.add-square'), 'add-circle' => __('whiteboards/draw.actions.add-circle'), 'add-entity' => __('whiteboards/draw.actions.add-entity'), 'add-image' => __('whiteboards/draw.actions.add-image'), 'start-drawing' => __('whiteboards/draw.actions.start-drawing'), 'end-drawing' => __('whiteboards/draw.actions.end-drawing'), 'color' => __('whiteboards/draw.fields.color'), 'thin-stroke' => __('whiteboards/draw.pen.thin-stroke'), 'large-stroke' => __('whiteboards/draw.pen.large-stroke'), 'push-to-front' => __('whiteboards/draw.actions.push-to-front'), 'push-to-back' => __('whiteboards/draw.actions.push-to-back'), 'lock' => __('whiteboards/draw.actions.lock'), 'unlock' => __('whiteboards/draw.actions.unlock'), 'duplicate' => __('whiteboards/draw.actions.duplicate'), 'copy-success' => __('whiteboards/draw.toast.copy.success'), 'paste-error' => __('whiteboards/draw.toast.paste.error'), // Reset 'reset-helper' => __('whiteboards/draw.reset.helper'), 'reset' => __('crud.actions.reset'), 'reset-title' => __('whiteboards/draw.reset.title'), // General UI 'back' => __('whiteboards/draw.actions.back'), // Entity search 'entity-search' => __('whiteboards/draw.entity-search.title'), 'search-placeholder' => __('whiteboards/draw.entity-search.placeholder'), // Browse stuff 'cancel' => __('crud.cancel'), 'remove' => __('crud.remove'), 'url' => __('gallery.actions.url'), 'gallery' => __('gallery.actions.gallery'), 'unauthorized' => __('gallery.download.errors.unauthorized'), 'browse' => [ 'title' => __('gallery.browse.title'), 'layouts' => [ 'small' => __('gallery.browse.layouts.small'), 'large' => __('gallery.browse.layouts.large'), ], 'search' => [ 'placeholder' => __('gallery.browse.search.placeholder'), ], 'unauthorized' => __('gallery.browse.unauthorized'), ], 'cta_title' => __('gallery.cta.title'), 'cta_action' => __('gallery.cta.action'), 'cta_helper' => __('gallery.cta.helper', [ 'premium-campaign' => '' . __('concept.premium-campaign') . '', 'size' => Number::format(config('limits.gallery.premium') / (1024 * 1024), 2), ]), 'qq-keyboard-shortcut' => __('crud.keyboard-shortcut', ['code' => 'N']), // Errors 'websocket-server-unavailable' => __('whiteboards/draw.errors.websockets.unavailable'), 'error-connecting-websocket' => __('whiteboards/draw.errors.websockets.error'), 'websocket-disconnected' => __('whiteboards/draw.errors.websockets.disconnected'), 'role-edit' => __('whiteboards/draw.roles.edit'), 'role-view' => __('whiteboards/draw.roles.view'), ]; } protected function loadEntities(): self { $entities = Entity::select('id', 'name', 'image_path', 'image_uuid', 'type_id') ->with(['image', 'entityType']) ->whereIn('id', $this->entityIds) ->get(); /** @var Entity $entity */ foreach ($entities as $entity) { $this->data['entities'][$entity->id] = new EntityResource($entity)->campaign($this->campaign); $this->data['images'][$entity->id] = Avatar::entity($entity)->size(256)->fallback()->thumbnail(); } return $this; } protected function loadGallery(): self { /** @var Image[] $images */ $images = Image::whereIn('id', $this->images) ->get(); foreach ($images as $image) { $this->data['images'][$image->id] = $image->url(); } return $this; } protected function fixData(): void { foreach ($this->data['data'] as $id => $shape) { if (! isset($shape['scaleX'])) { $shape['scaleX'] = 1; } if (! isset($shape['scaleY'])) { $shape['scaleY'] = 1; } $this->data['data'][$id] = $shape; } } protected function urls(): void { $this->data['urls'] = [ 'overview' => route('entities.show', [$this->campaign, $this->whiteboard->entity]), 'creator' => route('entity-creator.selection', $this->campaign), ]; } protected function interactive(): void { $pusher = config('broadcasting.connections.reverb.key'); if (empty($pusher) || ! isset($this->user)) { return; } if (! $this->user->can('view', $this->whiteboard->entity)) { return; } $this->data['interactive'] = [ 'key' => $pusher, 'host' => config('broadcasting.connections.reverb.options.host'), 'port' => config('broadcasting.connections.reverb.options.port'), 'scheme' => config('broadcasting.connections.reverb.options.scheme'), ]; } } ================================================ FILE: app/Services/Whiteboards/Shapes/PersistanceService.php ================================================ shape = $shape; return $this; } public function whiteboard(Whiteboard $whiteboard): self { $this->whiteboard = $whiteboard; return $this; } public function create(): WhiteboardShape { $this->shape = new WhiteboardShape; $this->shape->whiteboard_id = $this->whiteboard->id; $this->cleanData(); $this->shape->save(); $this->points(); return $this->shape; } public function save(): void { $data = $this->request->only([ 'x', 'y', 'width', 'height', 'scale_x', 'scale_y', 'group_id', 'rotation', 'is_locked', 'z_index', ]); $this->shape->fill($data); $this->fillShape(); $this->shape->save(); } public function addStroke(): ?WhiteboardStroke { if (! $this->shape->isDrawing()) { return null; } if (! $this->request->filled('points')) { return null; } $stroke = new WhiteboardStroke; $stroke->shape_id = $this->shape->id; $stroke->color = $this->request->get('fill', '#cccccc'); $stroke->width = $this->request->get('strokeWidth', 1); $stroke->points = $this->pack($this->request->get('points')); $stroke->save(); return $stroke; } protected function cleanData(): void { $data = $this->request->except('shape'); $this->shape->fill($data); $this->shape->shape = []; // Do the shape stuff $this->fillShape(); } protected function fillShape(): void { $this->fill = $this->shape->shape; if ($this->shape->isRectangle()) { $this->colour(); } elseif ($this->shape->isCircle()) { $this->colour(); } elseif ($this->shape->isText()) { $this->colour() ->text(); } elseif ($this->shape->isImage()) { $this->gallery(); } elseif ($this->shape->isEntity()) { $this->entity() ->colour(); } elseif ($this->shape->isDrawing()) { } $this->shape->shape = $this->fill; } protected function colour(): self { if ($this->request->filled('fill')) { $this->fill['fill'] = $this->request->get('fill'); } return $this; } protected function text(): self { if ($this->request->filled('text')) { $this->fill['text'] = $this->request->get('text'); } if ($this->request->filled('fontSize')) { $this->fill['fontSize'] = $this->request->get('fontSize'); } return $this; } protected function gallery(): self { if ($this->request->filled('uuid')) { $this->fill['uuid'] = $this->request->get('uuid'); } return $this; } protected function entity(): self { if ($this->request->filled('entity_id')) { $this->fill['entity_id'] = $this->request->get('entity_id'); } return $this; } protected function points(): void { if (! $this->shape->isDrawing()) { return; } if (! $this->request->filled('children')) { return; } $children = $this->request->get('children'); foreach ($children as $data) { $stroke = new WhiteboardStroke; $stroke->shape_id = $this->shape->id; $stroke->color = Arr::get($data, 'fill', '#cccccc'); $stroke->width = Arr::get($data, 'strokeWidth', 1); $stroke->points = $this->pack($data['points']); $stroke->save(); } } /** * Pack an array of points into a binary blob string */ protected function pack(array $points, int $scale = 1000): string { $bin = ''; $count = count($points); if ($count % 2 !== 0) { throw new InvalidArgumentException('Point array must have even length'); } for ($i = 0; $i < $count; $i += 2) { $x = (int) round($points[$i] * $scale); $y = (int) round($points[$i + 1] * $scale); $bin .= pack('q', $x); $bin .= pack('q', $y); } return $bin; } } ================================================ FILE: app/Support/HtmlPurifier/CalcStyleDefinition.php ================================================ caseSensitive = $caseSensitive; } /** * @param string $string * @param \HTMLPurifier_Config $config * @param \HTMLPurifier_Context $context * @return bool|string */ public function validate($string, $config, $context) { $string = mb_trim($string); if (! $this->caseSensitive) { // we may want to do full case-insensitive libraries $string = ctype_lower($string) ? $string : mb_strtolower($string); } if (Str::contains($string, ['&', '<'])) { return false; } $result = preg_match('`calc\((.*)\)`', $string); return $result ? $string : false; } /** * I have no idea what this is for, sorry. * * @param string $string In form of comma-delimited list of case-insensitive * valid values. Example: "foo,bar,baz". Prepend "s:" to make * case-sensitive * @return HTMLPurifier_AttrDef_Enum */ public function make($string) { if (mb_strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') { $string = mb_substr($string, 2); $sensitive = true; } else { $sensitive = false; } $values = explode(',', $string); return new HTMLPurifier_AttrDef_Enum($values, $sensitive); } } ================================================ FILE: app/Traits/AdminPolicyTrait.php ================================================ cachedAdminPolicy)) { return $this->cachedAdminPolicy; } $this->cachedAdminPolicy = false; $campaign = CampaignLocalization::getCampaign(); /** @var CampaignRole[] $roles */ $roles = $user->campaignRoles->where('campaign_id', $campaign->id); foreach ($roles as $role) { if ($role->is_admin) { $this->cachedAdminPolicy = true; } } return $this->cachedAdminPolicy; } } ================================================ FILE: app/Traits/ApiRequest.php ================================================ environment('testing'); if (! $isApi || ! (request()->isMethod('put') || request()->isMethod('patch'))) { return $rules; } foreach ($rules as $field => $rule) { if (! is_string($rule)) { if (($key = array_search('required', $rule, true)) !== false) { unset($rule[$key]); $rules[$field] = $rule; } continue; } // Remove any required| rule, and remove any alone | $rules[$field] = mb_trim( str_replace('required|', '', $rule), '|' ); } if ($except) { // Do something with this? } return $rules; } } ================================================ FILE: app/Traits/BulkControllerTrait.php ================================================ bulk) && ! empty($this->bulk)) { return new $this->bulk; } if ($modelClass !== null) { $bulkClass = 'App\Datagrids\Bulks\\' . Str::studly(Str::singular($modelClass->getTable())) . 'Bulk'; } else { // @phpstan-ignore-next-line $model = new $this->model; $bulkClass = 'App\Datagrids\Bulks\\' . Str::studly(Str::singular($model->getTable())) . 'Bulk'; } if (class_exists($bulkClass)) { return new $bulkClass; } return new DefaultBulk; } } ================================================ FILE: app/Traits/CalendarAware.php ================================================ calendar = $calendar; return $this; } } ================================================ FILE: app/Traits/CampaignAware.php ================================================ campaign = $campaign; return $this; } } ================================================ FILE: app/Traits/Controllers/HasDatagrid.php ================================================ with('rows', $this->rows) ->with('campaign', $this->campaign) ->render(); $deletes = view('layouts.datagrid.delete-forms') ->with('models', Datagrid::deleteForms()) ->with('params', Datagrid::getActionParams()) ->with('campaign', $this->campaign) ->render(); $data = [ 'success' => true, 'html' => $html, 'deletes' => $deletes, ]; return response()->json($data); } } ================================================ FILE: app/Traits/Controllers/HasNested.php ================================================ request->get('n'); if (auth()->guest()) { Session::put($key, $new); } else { $settings = auth()->user()->settings; if (auth()->check() && Arr::get($settings, $key) !== $new) { $settings = auth()->user()->settings; if ($new) { unset($settings[$key]); } else { $settings[$key] = false; } auth()->user()->settings = $settings; auth()->user()->updateQuietly(); } } return $new; } } ================================================ FILE: app/Traits/Controllers/HasSubview.php ================================================ with([ 'fullview' => $view, 'model' => $model, 'entity' => $model instanceof Entity ? $model : $model->entity, 'campaign' => $this->campaign, 'rows' => $this->rows, 'mode' => $this->descendantsMode(), ]); } protected function filterToDirect(): bool { return $this->descendantsMode() === Descendants::Direct; } protected function filterToAll(): bool { return $this->descendantsMode() === Descendants::All; } protected function descendantsMode(): Descendants { if (isset($this->descendantsMode)) { return $this->descendantsMode; } if (request()->has('m')) { if (request()->get('m') == Descendants::All->value) { return $this->descendantsMode = Descendants::All; } elseif (request()->get('m') == Descendants::Direct->value) { return $this->descendantsMode = Descendants::Direct; } } return $this->descendantsMode = $this->campaign->defaultDescendantsMode(); } } ================================================ FILE: app/Traits/CreatesEntityFromName.php ================================================ $classname */ protected function createModelFromName(string $name, string $classname, EntityType $entityType, Campaign $campaign, bool $returnEntityId = false): ?int { $name = mb_trim(Purify::clean($name)); if (empty($name)) { return null; } if (! auth()->user()->can('create', [$entityType, $campaign])) { return null; } /** @var MiscModel $model */ $model = new $classname([ 'name' => $name, 'campaign_id' => $campaign->id, 'is_private' => false, ]); return DB::transaction(function () use ($model, $returnEntityId): int { $model->saveQuietly(); $model->createEntity(); return $returnEntityId ? $model->entity->id : $model->id; }); } /** * For each value in the array, if it is a non-numeric string, create a new entity with that name. * Returns an array of model IDs ready for relationship syncing. * * @param array $values * @param class-string $classname * @return array */ public function resolveNewModels(array $values, string $classname, int $entityTypeId): array { $campaign = $entityType = null; $resolved = []; foreach ($values as $value) { if ($value === null) { continue; } if (is_numeric($value)) { $resolved[] = (int) $value; continue; } if ($campaign === null) { $campaign = CampaignLocalization::getCampaign(); $entityType = $campaign->getEntityTypes()->firstWhere('id', $entityTypeId); } $name = Str::startsWith($value, 'new:') ? Str::substr($value, 4) : $value; $id = $this->createModelFromName($name, $classname, $entityType, $campaign); if ($id !== null) { $resolved[] = $id; } } return $resolved; } /** * Sanitize, authorize, and create a new custom Entity from a name string. * Returns the new entity's ID, or null if creation is not possible. */ protected function createEntityFromName(string $name, EntityType $entityType, Campaign $campaign): ?int { $name = mb_trim(Purify::clean($name)); if (empty($name)) { return null; } if (! auth()->user()->can('create', [$entityType, $campaign])) { return null; } $entity = new Entity([ 'name' => $name, 'campaign_id' => $campaign->id, 'is_private' => false, ]); $entity->type_id = $entityType->id; $entity->save(); return $entity->id; } } ================================================ FILE: app/Traits/EntityAware.php ================================================ entity = $entity; return $this; } } ================================================ FILE: app/Traits/EntityTypeAware.php ================================================ entityType = $entityType; return $this; } } ================================================ FILE: app/Traits/ExportableTrait.php ================================================ baseExportData() ->entityExportData() ->foreignExportData(); return json_encode($this->exportData); } protected function baseExportData(): self { if (! isset($this->exportFields)) { $this->exportData = $this->toArray(); return $this; } $this->exportData = []; $baseFields = [ 'id', 'name', 'created_at', 'updated_at', 'is_private', ]; foreach ($this->exportFields as $field) { if ($field !== 'base') { $value = $this->$field; $this->exportData[$field] = $value instanceof \BackedEnum ? $value->value : $value; continue; } foreach ($baseFields as $baseField) { // @phpstan-ignore-next-line $this->exportData[$baseField] = $this->$baseField; } } // Parent relationship is now on entities.parent_id, exported via entity export return $this; } protected function entityExportData(): self { if (isset($this->entity) && $this->entity) { $this->exportData['entity'] = $this->entity->export(); } return $this; } public function exportRelations(): array { // @phpstan-ignore-next-line if (! property_exists($this, 'foreignExport')) { return []; } return $this->foreignExport; } protected function foreignExportData(): self { foreach ($this->exportRelations() as $foreign) { $this->exportData[$foreign] = []; foreach ($this->$foreign as $model) { try { if (method_exists($model, 'exportFields')) { $foreignData = []; foreach ($model->exportFields() as $field) { $foreignData[$field] = $model->$field; } $this->exportData[$foreign][] = $foreignData; } else { $this->exportData[$foreign][] = $model->toArray(); } } catch (Exception $e) { throw new Exception("Unknown relation '{$foreign}' on model " . get_class($this) . '(' . $e->getMessage() . ')'); } } } return $this; } } ================================================ FILE: app/Traits/GuestAuthTrait.php ================================================ entityType->isStandard() && $entity->isMissingChild()) { abort(403); } if (auth()->check()) { $this->authorize('view', $entity); } else { $this->authorizeEntityForGuest(Permission::View, $entity); } } /** * Secondary Authentication for Guest users * * @return void */ protected function authorizeEntityForGuest(Permission $permission, ?Entity $entity) { // If the misc model is null ($entity->child), the user has no valid access if ($entity === null) { abort(403); } // @phpstan-ignore-next-line $permission = EntityPermission::entity($entity)->campaign($this->campaign)->can($permission); // @phpstan-ignore-next-line if ($this->campaign->id != $entity->campaign_id || ! $permission) { // Raise an error abort(403); } } } ================================================ FILE: app/Traits/HasJobLog.php ================================================ $this->signature, 'result' => $data, ]); } } ================================================ FILE: app/Traits/MentionTrait.php ================================================ $type) { $options = explode('|', $segments[2][$id]); // Force numbers in case someone copy-pasts mentions with tags $id = Str::numbers(Arr::first($options)); $key = $type . '.' . $id; $data = [ 'type' => $type, 'id' => $id, ]; if (count($options) > 1) { // Skip the first segment unset($options[0]); foreach ($options as $option) { $subSegments = explode(':', $option); if (count($subSegments) === 1) { $data['text'] = Arr::first($subSegments); continue; } $type = Arr::first($subSegments); $value = Arr::last($subSegments); if ($type == 'page') { $data['page'] = $value; } } } $mentions[$key] = $data; } return $mentions; } /** * Extract the Images from a text */ public function extractImages(?string $text = null): array { $images = []; preg_match_all('/data-gallery-id="[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}"/i', $text, $segments); foreach ($segments[0] as $key => $type) { $id = mb_substr($type, 17, -1); if (! in_array($id, $images)) { $images[$key] = $id; } } return $images; } /** * Extract the formatting for a mention */ protected function extractData(array $matches): array { $segments = explode('|', $matches[2]); // The first block should always be type:id $id = (int) Arr::first($segments); $type = $matches[1]; $data = [ 'type' => $type, 'id' => (int) $id, ]; // Nothing else, we can go back if (count($segments) < 2) { return $data; } // Skip the first segment unset($segments[0]); foreach ($segments as $option) { $subSegments = explode(':', $option); if (count($subSegments) === 1) { $data['text'] = Arr::first($subSegments); $data['custom'] = true; continue; } $type = Arr::first($subSegments); $value = Arr::last($subSegments); if (in_array($type, ['page', 'field', 'transclude'])) { $data[$type] = mb_strtolower($value); $data['custom'] = true; } elseif (in_array($type, ['anchor', 'params', 'tooltip'])) { $data[$type] = $value; $data['custom'] = true; } elseif ($type == 'alias') { $data['alias'] = (int) $value; $data['custom'] = true; } } return $data; } } ================================================ FILE: app/Traits/OrderableTrait.php ================================================ orderTrigger)) { return $this->handleNoToken($query, $defaultField); } $field = str_replace($this->orderTrigger, '', $data); $direction = 'ASC'; if (! empty($field) && ! Str::contains($field, '/')) { $segments = explode('.', $field); if (count($segments) > 1) { $relationName = $segments[0]; // Make sure the relationship exists if (method_exists($this, $relationName)) { return $query; } $relation = $this->{$relationName}(); $foreignName = $relation->getQuery()->getQuery()->from; return $query ->select($this->getTable() . '.*') ->with($relationName) ->leftJoin($foreignName . ' as f', 'f.id', $this->getTable() . '.' . $relation->getForeignKeyName()) ->orderBy(str_replace($relationName, 'f', $field), $direction); } elseif ($data == 'events/date') { return $query ->orderBy($this->getTable() . '.year', $direction) ->orderBy($this->getTable() . '.month', $direction) ->orderBy($this->getTable() . '.day', $direction); } else { return $query->orderBy($this->getTable() . '.' . $field, $direction); } } return $query; } protected function handleNoToken(Builder $query, string $defaultField): Builder { if ($defaultField == 'name' && isset($this->orderDefaultField)) { $defaultField = $this->orderDefaultField; } $defaultDir = $this->orderDefaultDir ?? 'asc'; if ($defaultField == 'events/date') { return $query ->orderBy('year', $defaultDir) ->orderBy('month', $defaultDir) ->orderBy('day', $defaultDir); } return $query->orderBy($defaultField, $defaultDir); } } ================================================ FILE: app/Traits/PostAware.php ================================================ post = $post; return $this; } } ================================================ FILE: app/Traits/RequestAware.php ================================================ request = $request; return $this; } } ================================================ FILE: app/Traits/ResolvesNewForeignEntities.php ================================================ [ModelClass, entityTypeId]. * Override this method directly for fields that don't follow the {snake}_id convention. * * @return array */ protected function newEntityFields(): array { $fields = []; foreach ($this->foreignEntityFields ?? [] as $field) { $key = str_replace('_id', '', $field); $fields[$field] = ['App\\Models\\' . Str::studly($key), config("entities.ids.{$key}")]; } return $fields; } /** * When true, parent_id accepts a new entity name and resolves to an entity ID * of the same type as the form request (e.g. StoreLocation creates a Location). * * @return array */ protected function newEntityParentFields(): array { if (! ($this->foreignEntityParent ?? false)) { return []; } $typeCode = Str::snake(Str::after(class_basename($this), 'Store')); $entityTypeId = config("entities.ids.{$typeCode}"); if (empty($entityTypeId)) { return []; } return [ 'parent_id' => ['App\\Models\\' . Str::studly($typeCode), $entityTypeId], ]; } protected function prepareForValidation(): void { $this->resolveNewForeignEntities($this); } /** * Resolve any typed-in entity names in FK fields into real IDs on the given request. * Called directly by EditController when form requests are not DI-injected. */ public function resolveNewForeignEntities(Request $request): void { foreach ($this->newEntityFields() as $field => [$classname, $entityTypeId]) { $value = $request->input($field); if (empty($value) || is_numeric($value)) { continue; } $name = Str::startsWith($value, 'new:') ? Str::substr($value, 4) : $value; // Always replace the string — with the new ID on success, or null so // the nullable rule can pass cleanly rather than failing on "integer". $resolved = $this->createNewForeignEntity($name, $classname, $entityTypeId); $request->merge([$field => $resolved]); } foreach ($this->newEntityParentFields() as $field => [$classname, $entityTypeId]) { $value = $request->input($field); if (empty($value) || is_numeric($value)) { continue; } $name = Str::startsWith($value, 'new:') ? Str::substr($value, 4) : $value; $resolved = $this->createNewForeignEntityAsEntityId($name, $classname, $entityTypeId); $request->merge([$field => $resolved]); } } /** * Return only the fields from newEntityFields() that were resolved during prepareForValidation(). * Used by EditController to sync resolved IDs back into the original request. * * @return array */ public function resolvedFields(): array { return array_intersect_key($this->all(), array_merge($this->newEntityFields(), $this->newEntityParentFields())); } protected function createNewForeignEntity(string $value, string $classname, int $entityTypeId): ?int { // AJAX calls are validation-only pre-flight requests; skip creation to avoid duplicates. if (empty($value) || request()->ajax()) { return null; } $campaign = CampaignLocalization::getCampaign(); $entityType = $campaign->getEntityTypes()->firstWhere('id', $entityTypeId); return $this->createModelFromName($value, $classname, $entityType, $campaign); } protected function createNewForeignEntityAsEntityId(string $value, string $classname, int $entityTypeId): ?int { // AJAX calls are validation-only pre-flight requests; skip creation to avoid duplicates. if (empty($value) || request()->ajax()) { return null; } $campaign = CampaignLocalization::getCampaign(); $entityType = $campaign->getEntityTypes()->firstWhere('id', $entityTypeId); return $this->createModelFromName($value, $classname, $entityType, $campaign, true); } } ================================================ FILE: app/Traits/RoleAware.php ================================================ role = $role; return $this; } } ================================================ FILE: app/Traits/Search/Orderable.php ================================================ query->orderBy('updated_at', 'DESC'); } else { if (Str::startsWith($term, '=')) { $this->query->where('name', mb_ltrim($term, '=')); } else { $this->query->where('name', 'like', "%{$term}%"); } } } } ================================================ FILE: app/Traits/UserAware.php ================================================ user = $user; return $this; } public function userless(): self { // @phpstan-ignore-next-line unset($this->user); return $this; } public function hasUser(): bool { return $this->user !== null; } } ================================================ FILE: app/View/Components/Ad.php ================================================ check()) { $this->user = auth()->user(); } return view('components.ad'); } /** * Determine if ads should be displayed */ public function shouldRender(): bool { // If we don't have free enabled, then we don't have any ads to show $provider = config('ads.provider'); if (empty($provider)) { return false; } // If requesting a section that isn't set up, don't show $key = 'ads.' . $provider . '.tags.' . $this->section; if (! empty($this->section) && empty(config($key))) { // dump("Unknown ad tag " . $key); return false; } if (! AdCache::canHaveAds()) { // Using the adless middleware to define routes that have no ads (ie settings) return false; } // Parameter to force ads to be displayed if (request()->has('_showads')) { return true; } // shouldRender is called before render() so we need to re-read the user if (auth()->check()) { $this->user = auth()->user(); } if (isset($this->user)) { // Subscribed users don't have ads if ($this->user->isSubscriber()) { return false; } // User has been created less than 24 hours ago if ($this->user->created_at->diffInHours(Carbon::now()) < 24) { return false; } } // Premium campaigns don't have ads displayed to their members return ! empty($this->campaign) && ! $this->campaign->boosted(); } } ================================================ FILE: app/View/Components/Ads/Native.php ================================================ check()) { $this->user = auth()->user(); } } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { if (! $this->noAds()) { return ''; } $this->ad = AdCache::get(); return view('components.ads.native'); } protected function noAds(): bool { // No admin panel set up, no ads possible, since the admin project provides the tables if (! config('app.admin')) { return false; } // If we provided an ad test, override that if (request()->has('_adtest') && auth()->user()->hasRole('admin')) { return AdCache::test($this->section, request()->get('_adtest')); } // If the section has no ads, don't try and show anything if (! AdCache::has($this->section)) { return false; } // Force ads displayed if (request()->get('_boost') === '0') { return true; } if (isset($this->user)) { // Subscribed users don't have ads if ($this->user->isSubscriber()) { return false; } // User has been created less than 24 hours ago if ($this->user->created_at->diffInHours(Carbon::now()) < 24) { return false; } } // Premium campaigns don't either have ads displayed to their members return isset($this->campaign) && ! $this->campaign->boosted(); } } ================================================ FILE: app/View/Components/Alert.php ================================================ with('colour', $this->colour()); } public function colour(): string { if ($this->type === 'default') { return ''; } return 'badge-' . $this->type; } } ================================================ FILE: app/View/Components/Box/Footer.php ================================================ type === 'danger') { if ($this->outline) { return 'btn2 btn-error btn-outline'; } return 'btn2 btn-error'; } elseif ($this->type === 'primary') { if ($this->outline) { return 'btn2 btn-primary btn-outline'; } return 'btn2 btn-primary'; } elseif ($this->type === 'secondary') { if ($this->outline) { return 'btn2 btn-secondary btn-outline'; } return 'btn2 btn-secondary'; } elseif ($this->type === 'ghost') { return 'btn2 btn-ghost'; } if ($this->outline) { return ''; } // Default return ''; } } ================================================ FILE: app/View/Components/Buttons/Confirm.php ================================================ with('colours', $this->colour()) ->with('sizes', $this->size()) ->with('element', $this->dismiss == 'dialog' ? 'a' : 'button'); } protected function size(): string { if ($this->size === 'sm') { return 'btn-sm'; } return ''; } } ================================================ FILE: app/View/Components/Buttons/ConfirmDelete.php ================================================ with('enabled', $this->enabled()) ->with('image', $this->image()); } protected function enabled(): bool { if ($this->entityType->isStandard()) { return $this->campaign->enabled($this->entityType); } return $this->entityType->isEnabled(); } protected function image(): string { if (! empty($this->thumbnail)) { return Img::crop(96, 96)->url($this->thumbnail); } return ''; } } ================================================ FILE: app/View/Components/CharacterSheet.php ================================================ plugin->version->updated_at->gt('2021-03-30 17:00:00')) { $this->renderer = app()->make(Blade::class); } else { $this->renderer = app()->make(Custom::class); } $this->renderer->campaign($campaign)->entity($entity)->plugin($plugin); } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.character-sheet'); } } ================================================ FILE: app/View/Components/Checkbox.php ================================================ text = $text; $this->dataProperties = $data; } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.checkbox'); } } ================================================ FILE: app/View/Components/Dashboards/Widgets/Selection.php ================================================ date)) { return ''; } $this->loadUserFormat(); try { $original = new Carbon($this->date); $this->formattedDate = $original->isoFormat($this->format); } catch (Exception $e) { $this->formattedDate = $this->date; } if ($this->string) { return $this->formattedDate; } return view('components.date') ->with('formatted', $this->formattedDate); } public function format(): string { return $this->format; } /** * Load the user's format if logged in */ private function loadUserFormat(): void { // Get the user's date format if they have a custom one if (auth()->guest() || empty(auth()->user()->dateformat)) { return; } $this->format = mb_strtoupper(auth()->user()->dateformat); $this->format = Str::replace(['M', 'D'], ['MM', 'DD'], $this->format); } } ================================================ FILE: app/View/Components/Dialog/Article.php ================================================ id)) { $this->id = uniqid(); } } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.dialog'); } } ================================================ FILE: app/View/Components/Dropdowns/Divider.php ================================================ dataProperties = $data; if ($this->shortcut !== null) { $userAgent = request()->header('User-Agent', ''); if (Str::contains($userAgent, 'Macintosh') || Str::contains($userAgent, 'Mac OS')) { $this->shortcut = str_ireplace('ctrl', '⌘', $this->shortcut); } } $this->shortcut = Str::replace('Shift', '', $this->shortcut); } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.dropdowns.item'); } } ================================================ FILE: app/View/Components/Dropdowns/Section.php ================================================ with('items', $this->items()); } protected function items(): array { /** @var SubmenuService $service */ $service = app()->make(SubmenuService::class); if (auth()->check()) { $service->user(auth()->user()); } return $service ->campaign($this->campaign) ->entity($this->entity) ->items(); } } ================================================ FILE: app/View/Components/Entities/Thumbnail.php ================================================ entity)) { return ''; } return view('components.entity-link'); } public function post(): string { if (! empty($this->post)) { return '#post-' . $this->post; } return ''; } } ================================================ FILE: app/View/Components/FaqElement.php ================================================ method = mb_strtoupper($method); foreach ($config as $k => $v) { if (! property_exists($this, $k)) { continue; } $this->$k = $v; } } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.form'); } public function extra(): ?string { if (empty($this->extra)) { return null; } $extra = []; foreach ($this->extra as $k => $v) { $extra[] = $k . '="' . $v . '"'; } return implode(' ', $extra); } public function action(): string { if (! is_array($this->action)) { return route($this->action); } $parameters = array_slice($this->action, 1); if (array_keys($this->action) === [0, 1]) { $parameters = head($parameters); } return route($this->action[0], $parameters); } } ================================================ FILE: app/View/Components/Forms/Field.php ================================================ field = $field; $this->label = $label; $this->required = $required; $this->tooltip = $tooltip; $this->hidden = $hidden; $this->helper = $helper; $this->link = $link; $this->css = $css; } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.forms.field'); } } ================================================ FILE: app/View/Components/Forms/Foreign.php ================================================ id = ! empty($id) ? $id : $name . '_' . uniqid(); } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { $canNew = $this->createPermission(); if (! empty($this->selected)) { if (is_array($this->selected)) { $this->options = $this->selected; } elseif ($this->selected instanceof Model) { // @phpstan-ignore-next-line $this->options[$this->selected->id] = $this->selected->name; } } if ($this->parent) { $this->label = __('crud.fields.parent'); $this->placeholder = __('crud.placeholders.parent'); } elseif (! empty($this->key)) { if (empty($this->label)) { $this->label = ! empty($this->entityTypeID) ? __('entities.' . $this->key) : __('crud.fields.' . $this->key); if (! empty($this->entityTypeID)) { $this->label = Module::singular($this->entityTypeID, $this->label); } } if (empty($this->placeholder)) { $key = 'crud.placeholders.' . $this->key; $this->placeholder = __($key); if (! empty($this->entityTypeID)) { $mod = Module::singular($this->entityTypeID); if (! empty($mod)) { $this->placeholder = __('crud.placeholders.fallback', ['module' => Module::singular($this->entityTypeID, $this->label)]); } } if ($this->placeholder === $key) { $this->placeholder = __('crud.placeholders.search'); } } } else { if (empty($this->label)) { $this->label = ''; } if (empty($this->placeholder)) { $this->placeholder = ''; } } return view('components.forms.foreign') ->with('canNew', $canNew); } protected function createPermission(): bool { if (! $this->allowNew || auth()->guest()) { return false; } if (! empty($this->entityType)) { return auth()->user()->can('create', [$this->entityType, $this->campaign]); } $this->entityType = $this->campaign->getEntityTypes()->where('id', $this->entityTypeID)->first(); return auth()->user()->can('create', [$this->entityType, $this->campaign]); } } ================================================ FILE: app/View/Components/Forms/Select.php ================================================ name); if ($old !== null) { $this->selected = $old; } elseif (! empty($this->placeholder)) { $this->selected = ''; $this->options = ['' => $this->placeholder] + $this->options; } return view('components.forms.select'); } public function fieldId(): string { if (! empty($this->id)) { return $this->id; } return $this->autoId ?? $this->autoId = uniqid($this->name); } public function isSelected(mixed $value): bool { if (empty($this->selected)) { return empty($value); } if (is_array($this->selected)) { return in_array($value, $this->selected); } if (is_int($this->selected)) { return $value == $this->selected; } if ($this->selected instanceof \BackedEnum) { return $value == $this->selected->value; } // Always force values to lower to avoid thinking return mb_strtolower($value) == mb_strtolower($this->selected); } public function optionAttributes(string $key): array { return $this->optionAttributes[$key] ?? []; } } ================================================ FILE: app/View/Components/Forms/Tags.php ================================================ id = $id ?? 'tags_' . uniqid(); $this->label = $label ?? __('entities.tags'); $this->dropdownParent = $dropdownParent ?? '#app'; } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { if ( $this->allowNew && ! auth()->user()->can('create', [ $this->campaign->getEntityTypes()->firstWhere('id', config('entities.ids.tag')), $this->campaign, ]) ) { $this->allowNew = false; } $this->prepareOptions(); return view('components.forms.tags') ->with('campaign', $this->campaign) ->with('tags', $this->tags); } protected function prepareOptions(): void { $this->tags = []; if (! empty($this->model) && $this->model instanceof Entity) { /** @var Tag $tag */ foreach ($this->model->tags()->with('entity')->get() as $tag) { if ($tag->entity) { $this->tags[$tag->id] = $tag; } } } elseif (! empty($this->model) && ! empty($this->model->entity) && ! $this->model instanceof Post) { /** @var Tag $tag */ foreach ($this->model->entity->tags()->with('entity')->get() as $tag) { if ($tag->entity) { $this->tags[$tag->id] = $tag; } } } elseif (! empty($this->model) && ($this->model instanceof CampaignDashboardWidget || $this->model instanceof Post || $this->model instanceof Bookmark || $this->model instanceof Webhook)) { /** @var Tag $tag */ foreach ($this->model->tags()->with('entity')->get() as $tag) { $this->tags[$tag->id] = $tag; } } elseif (! empty($this->options) && is_array($this->options)) { foreach ($this->options as $tagId) { if (! empty($tagId) && is_numeric($tagId)) { $tag = Tag::find($tagId); if ($tag && $tag->entity) { $this->tags[$tag->id] = $tag; } } } } elseif (empty($this->model) && $this->enableAuto) { $tags = Tag::autoApplied()->with('entity')->get(); /** @var Tag $tag */ foreach ($tags as $tag) { if ($tag && $tag->entity) { $this->tags[$tag->id] = $tag; } } } } } ================================================ FILE: app/View/Components/Forms/VisibilityPicker.php ================================================ id = $id ?? uniqid('visibility-'); } public function render(): View|Closure|string { return view('components.forms.visibility-picker', [ 'adminUrl' => route('campaigns.campaign_roles.admin', $this->campaign), 'adminName' => $this->campaign->adminRoleName(), 'entityName' => $this->entity->name, 'iconMap' => $this->buildIconMap(), ]); } /** * @return array */ protected function buildIconMap(): array { return [ Visibility::All->value => 'fa-regular fa-eye', Visibility::Admin->value => 'fa-regular fa-lock', Visibility::AdminSelf->value => 'fa-regular fa-user-lock', Visibility::Self->value => 'fa-regular fa-user-secret', Visibility::Member->value => 'fa-regular fa-users', ]; } } ================================================ FILE: app/View/Components/Forms/VisibilityPickerField.php ================================================ id = $id ?? uniqid('visibility-field-'); } public function render(): View|Closure|string { return view('components.forms.visibility-picker-field', [ 'adminUrl' => route('campaigns.campaign_roles.admin', $this->campaign), 'adminName' => $this->campaign->adminRoleName(), 'iconMap' => $this->buildIconMap(), 'visibilityKeys' => $this->buildVisibilityKeys(), ]); } /** * @return array */ protected function buildIconMap(): array { return [ Visibility::All->value => 'fa-regular fa-eye', Visibility::Admin->value => 'fa-regular fa-lock', Visibility::AdminSelf->value => 'fa-regular fa-user-lock', Visibility::Self->value => 'fa-regular fa-user-secret', Visibility::Member->value => 'fa-regular fa-users', ]; } /** * @return array */ protected function buildVisibilityKeys(): array { return [ Visibility::All->value => 'all', Visibility::Admin->value => 'admin', Visibility::AdminSelf->value => 'admin-self', Visibility::Self->value => 'self', Visibility::Member->value => 'member', ]; } } ================================================ FILE: app/View/Components/Grid.php ================================================ with('title', $this->title) ->with('html', $this->html); } } ================================================ FILE: app/View/Components/Hero.php ================================================ class = $this->map($this->class); return view('components.icon'); } /** * Map icon shortcuts into their fontawesome or other elements */ protected function map(string $class): string { return match ($class) { 'map' => 'fa-regular fa-map', 'check' => 'fa-regular fa-check', 'trash' => 'fa-regular fa-trash-can', 'plus' => 'fa-regular fa-plus', 'question' => 'fa-regular fa-question-circle', 'save' => 'fa-regular fa-save', 'pencil' => 'fa-regular fa-pencil', 'cog' => 'fa-regular fa-cog', 'copy' => 'fa-regular fa-copy', 'edit' => 'fa-regular fa-edit', 'premium' => 'fa-regular fa-gem', 'lock' => 'fa-regular fa-lock', 'filter' => 'fa-regular fa-filter', 'load' => 'fa-solid fa-spinner fa-spin', 'arrow' => 'fa-regular fa-arrow-right', 'permissions' => 'fa-regular fa-user-shield', 'attributes' => 'fa-regular fa-rectangle-list', 'link' => 'fa-regular fa-external-link', 'sort' => 'fa-regular fa-grip-vertical', default => $class, }; } } ================================================ FILE: app/View/Components/InfoBox.php ================================================ post->visibleTags()->isEmpty()) { return ''; } return view('components.posts.tags') ->with('tags', $this->post->visibleTags()); } } ================================================ FILE: app/View/Components/PremiumCta.php ================================================ check() && auth()->user()->hasBoosterNomenclature(); $amount = 4.99; $currency = 'US$'; if (auth()->check()) { if (auth()->user()->billedInBrl()) { $amount = 19.99; } } return view('components.premium-cta') ->with('legacy', $legacy) ->with('currency', $currency) ->with('amount', $amount); } } ================================================ FILE: app/View/Components/PremiumCtaFooter.php ================================================ link = $link; if (! preg_match('#^https?://#i', $this->link)) { $this->link = 'https://' . $this->link; } } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.profile.social-link'); } public function domain(): string { $host = parse_url($this->link, PHP_URL_HOST); return $host ? preg_replace('/^www\./i', '', $host) : ''; } } ================================================ FILE: app/View/Components/Reorder/Child.php ================================================ segment($segment); if ($current == $menu) { return ' active'; } return null; } } ================================================ FILE: app/View/Components/Sidebar/Bookmark.php ================================================ routingService ->campaign($this->campaign) ->bookmark($this->bookmark) ->url(); return view('components.sidebar.element-link') ->with('url', $url) ->with('icon', $this->bookmark->iconClass()) ->with('text', $this->bookmark->name) ->with('class', ''); } } ================================================ FILE: app/View/Components/Sidebar/Campaign.php ================================================ [ null, 'dashboard', 'dashboard-setup', ], 'characters' => [ 'characters', ], 'conversations' => [ 'conversations', 'conversation_messages', ], 'events' => [ 'events', ], 'families' => [ 'families', ], 'items' => [ 'items', ], 'journals' => [ 'journals', ], 'locations' => [ 'locations', ], 'maps' => [ 'maps', ], 'notes' => [ 'notes', ], 'organisations' => [ 'organisations', 'organisation_member', ], 'other' => [ 'releases', 'team', ], 'quests' => [ 'quests', ], 'calendars' => [ 'calendars', ], 'releases' => [ 'releases', ], 'team' => [ 'team', ], 'attribute_templates' => [ 'attribute_templates', ], 'tags' => [ 'tags', ], 'timelines' => [ 'timelines', ], 'dice_rolls' => [ 'dice_rolls', 'dice_roll_results', ], 'bookmarks' => [ 'bookmarks', ], 'races' => [ 'races', ], 'creatures' => [ 'creatures', ], 'abilities' => [ 'abilities', ], 'whiteboards' => [ 'whiteboards', ], 'relations' => [ 'relations', ], 'history' => [ 'history', ], 'gallery' => [ 'gallery', ], ]; protected array $bookmarks; /** * Create a new component instance. */ public function __construct( public \App\Models\Campaign $campaign, public ?Entity $entity, protected SetupService $sidebar) { $sidebar ->campaign($campaign) ->request(request()); $this->prepareBookmarks(); } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { $this->layout = $this->sidebar ->campaign($this->campaign) ->layout(); return view('components.sidebar.campaign') ->with('layout', $this->layout); } public function active(string $menu = '', string $class = 'active'): ?string { if (empty($this->rules[$menu])) { return null; } if (request()->has('bookmark')) { return null; } foreach ($this->rules[$menu] as $rule) { if (request()->segment(3) == $rule) { return " {$class}"; } } // Entities? It's complicated /** @var ?Entity $entity */ $entity = request()->route('entity'); if ($entity) { // Custom modules are currently dealt with "bookmarks" (we need to change that in the future) // so we need this check in case someone makes a custom module named "races" again if ($entity->entityType->isStandard() && $entity->entityType->pluralCode() == $menu) { return " {$class}"; } } $entityType = request()->route('entityType'); if ($entityType && $entityType->pluralCode() == $menu) { return " {$class}"; } return null; } /** * Prepare the quick links by figuring out where they will be rendered */ public function prepareBookmarks(): void { $this->bookmarks = []; // Bookmarks module is not activated on the campaign, no need to go further if (! $this->campaign->enabled('bookmarks')) { return; } $bookmarks = $this->campaign->bookmarks()->active()->ordered()->with(['target' => function ($sub) { return $sub->select('id', 'type_id', 'entity_id'); }, 'entityType', 'target.entityType'])->get(); /** @var Bookmark $bookmark */ foreach ($bookmarks as $bookmark) { if ($bookmark->entityType && $bookmark->entityType->isCustom() && ! $bookmark->entityType->isEnabled()) { continue; } $parent = 'bookmarks'; if (! empty($bookmark->parent)) { $parent = $bookmark->parent; } $this->bookmarks[$parent][] = $bookmark; } } /** * Get the quick links for a specified section/parent */ public function bookmarks(?string $parent = null): array { if (! $this->hasBookmarks($parent)) { return []; } return $this->bookmarks[$parent]; } /** * Determine if a section has quick links in it */ public function hasBookmarks(string $parent): bool { return array_key_exists($parent, $this->bookmarks) && ! empty($this->bookmarks[$parent]); } } ================================================ FILE: app/View/Components/Sidebar/Element.php ================================================ url)) { $view = 'text'; } return view('components.sidebar.element-' . $view); } } ================================================ FILE: app/View/Components/Sidebar/Profile.php ================================================ segment(3), $options)) { return 'active'; } return null; } } ================================================ FILE: app/View/Components/Since.php ================================================ loadUserFormat(); try { $this->formattedDate = $this->date->isoFormat($this->dateFormat . ($this->withTime ? ' HH:mm:ss' : null)); } catch (Exception $e) { $this->formattedDate = $this->date->isoFormat($this->withTime ? 'LL HH:mm:ss' : 'LL'); } return view('components.since') ->with('formatted', $this->formattedDate); } public function elapsed(): string { if ($this->date->diffInMonths() <= 2) { return $this->date->diffForHumans(); } return $this->date->isoFormat($this->dateFormat); } /** * Load the user's format if logged in */ private function loadUserFormat(): void { // Get the user's date format if they have a custom one if (auth()->guest() || empty(auth()->user()->dateformat)) { return; } $this->dateFormat = mb_strtoupper(auth()->user()->dateformat); $this->dateFormat = Str::replace(['M', 'D'], ['MM', 'DD'], $this->dateFormat); } } ================================================ FILE: app/View/Components/Tab/Nav.php ================================================ with('css', $this->css()) ->with('inlineStyle', $this->tag->colourStyle()); } protected function css(): string { $classes = [ 'badge', ]; if ($this->tag->hasColour()) { $classes[] = $this->tag->colourClass(); } else { $classes[] = 'color-tag'; } return implode(' ', $classes); } } ================================================ FILE: app/View/Components/Toggles/FilterButton.php ================================================ id = uniqid($code . '-'); } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { if ($this->auth && ! auth()->check()) { return ''; } if (auth()->check() && UserCache::dismissedTutorial($this->code)) { return ''; } return view('components.tutorial'); } } ================================================ FILE: app/View/Components/Users/Avatar.php ================================================ campaign = $campaign; $this->widget = $widget; $this->entityString = $entityString; } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.widgets.filtered-link') ->with('isLink', $this->isLink()) ->with('title', $this->title()) ->with('url', $this->url()); } protected function title(): string { if (! empty($this->widget->conf('text'))) { return $this->widget->conf('text'); } $title = ''; if (! empty($this->entityString)) { $title = __($this->entityString) . ' - '; } $title .= __('dashboards/widgets/recent.name'); return $title; } protected function url(): ?string { if (! $this->isLink()) { return null; } $parameters = [ 'campaign' => $this->campaign, 'tags' => $this->widget->tags->pluck('id')->toArray(), ] + $this->widget->filterOptions(); return route(Str::plural($this->widget->conf('entity')) . '.index', $parameters); } protected function isLink(): bool { return ! empty($this->widget->conf('entity')); } } ================================================ FILE: app/View/Components/Widgets/Forms/Advanced.php ================================================ campaign = $campaign; $this->widget = $widget; $this->entity = $entity; } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.widgets.previews.body'); } } ================================================ FILE: app/View/Components/Widgets/Previews/Head.php ================================================ campaign = $campaign; $this->widget = $widget; $this->entity = $entity; } /** * Get the view / contents that represent the component. */ public function render(): View|Closure|string { return view('components.widgets.previews.head') ->with('images', $this->headerImage()) ->with('customName', $this->customName()); } protected function headerImage(): ?array { if ($this->widget->conf('entity-header') && $this->campaign->boosted() && $this->entity->header_image) { return [ 'wide_xl' => $this->entity->thumbnail(800, 450, 'header_image'), 'wide_sm' => $this->entity->thumbnail(480, 270, 'header_image'), 'square_xl' => $this->entity->thumbnail(800, 800, 'header_image'), 'square_sm' => $this->entity->thumbnail(480, 480, 'header_image'), ]; } elseif ($this->widget->conf('entity-header') && $this->campaign->boosted() && $this->entity->header) { return [ 'wide_xl' => $this->entity->header->getUrl(800, 450), 'wide_sm' => $this->entity->header->getUrl(480, 270), 'square_xl' => $this->entity->header->getUrl(800, 800), 'square_sm' => $this->entity->header->getUrl(480, 480), ]; } elseif ($this->entity->hasImage()) { return [ 'wide_xl' => Avatar::entity($this->entity)->size(800, 450)->thumbnail(), 'wide_sm' => Avatar::entity($this->entity)->size(480, 270)->thumbnail(), 'square_xl' => Avatar::entity($this->entity)->size(800, 800)->thumbnail(), 'square_sm' => Avatar::entity($this->entity)->size(480, 480)->thumbnail(), ]; } return null; } protected function customName(): string { if (empty($this->widget->conf('text'))) { return ''; } return str_replace('{name}', $this->entity->name, $this->widget->conf('text')); } } ================================================ FILE: app/View/Components/WordCount.php ================================================ make(Illuminate\Contracts\Console\Kernel::class); $status = $kernel->handle( $input = new Symfony\Component\Console\Input\ArgvInput, new Symfony\Component\Console\Output\ConsoleOutput ); /* |-------------------------------------------------------------------------- | Shutdown The Application |-------------------------------------------------------------------------- | | Once Artisan has finished running, we will fire off the shutdown events | so that any final work may be done by the application before we shut | down the process. This is the last thing to happen to the request. | */ $kernel->terminate($input, $status); exit($status); ================================================ FILE: boost.json ================================================ { "agents": [ "claude_code" ], "guidelines": true, "herd_mcp": false, "mcp": true, "sail": true, "skills": [ "livewire-development", "pest-testing", "tailwindcss-development" ] } ================================================ FILE: bootstrap/app.php ================================================ singleton( Illuminate\Contracts\Http\Kernel::class, Kernel::class ); $app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class ); $app->singleton( ExceptionHandler::class, Handler::class ); /* |-------------------------------------------------------------------------- | Return The Application |-------------------------------------------------------------------------- | | This script returns the application instance. The instance is given to | the calling script so we can separate the building of the instances | from the actual running of the application and sending responses. | */ return $app; ================================================ FILE: bootstrap/cache/.gitignore ================================================ * !.gitignore ================================================ FILE: composer.json ================================================ { "name": "owlchester/kanka", "description": "The Kanka RPG and worldbuilding tool", "keywords": [ "campaign", "rpg", "worldbuilding", "kanka", "dnd" ], "license": "proprietary", "type": "project", "require": { "php": "^8.4", "ext-dom": "*", "ext-json": "*", "ext-zip": "*", "aws/aws-sdk-php": "^3.276", "bacon/bacon-qr-code": "^3.0", "binarytorch/larecipe": "^2.0", "caseyamcl/toc": "^4.0", "chriskonnertz/string-calc": "^2.0", "dompdf/dompdf": "^3.0", "enshrined/svg-sanitize": "^0.22", "guzzlehttp/guzzle": "^7.0.1", "ilestis/kanka-dnd5e-monster": "^5.0", "intervention/image": "^3.10", "laravel/cashier": "^15.0", "laravel/framework": "^11.0", "laravel/pail": "^1.1", "laravel/passport": "^13.0", "laravel/reverb": "^1.0", "laravel/scout": "^10.5", "laravel/socialite": "^5.0", "laravel/ui": "^4.6", "league/flysystem-aws-s3-v3": "^3.5", "league/html-to-markdown": "^5.1", "livewire/livewire": "^3.4", "mailerlite/mailerlite-php": "^1.0", "mcamara/laravel-localization": "^2.0", "meilisearch/meilisearch-php": "^1.4", "orhanerday/open-ai": "^4.7", "owlchester/laravel-translation-manager": "^12", "pragmarx/google2fa-laravel": "^2.3", "predis/predis": "^2.0", "pusher/pusher-php-server": "^7.2", "sentry/sentry-laravel": "^4.0", "sergej-kurakin/diceroller": "^2.0", "spatie/laravel-backup": "^9.0", "srmklive/paypal": "^3.0", "staudenmeir/laravel-adjacency-list": "^1.0", "staudenmeir/laravel-cte": "^1.0", "stechstudio/laravel-zipstream": "^5.0", "stevebauman/purify": "^6.3", "symfony/http-client": "^7.2", "symfony/mailgun-mailer": "^7.1" }, "require-dev": { "barryvdh/laravel-debugbar": "^3.1", "barryvdh/laravel-ide-helper": "^3.0", "fakerphp/faker": "^1.22", "google/analytics-data": "*", "larastan/larastan": "^3.0", "laravel/boost": "^2.0", "laravel/pint": "^1.10", "laravel/sail": "^1.14", "laravel/tinker": "^3.0", "mockery/mockery": "^1.4.4", "nunomaduro/collision": "^8.1", "pestphp/pest": "^3", "pestphp/pest-plugin-laravel": "^3", "phpmd/phpmd": "^2.13", "spatie/laravel-ignition": "^2.9", "squizlabs/php_codesniffer": "^3.4" }, "autoload": { "psr-4": { "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } }, "extra": { "laravel": { "dont-discover": [ "laravel/telescope" ] } }, "scripts": { "phpcs": "phpcs --standard=phpcs.xml", "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], "post-create-project-cmd": [ "@php artisan key:generate" ], "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover" ], "test:types": "phpstan analyse --ansi --memory-limit 1G", "test:types-clear": "phpstan clear-result-cache", "test:pest": "vendor/bin/pest", "post-update-cmd": [ "Illuminate\\Foundation\\ComposerScripts::postUpdate", "@php artisan ide-helper:generate", "@php artisan ide-helper:meta" ] }, "config": { "preferred-install": "dist", "sort-packages": true, "optimize-autoloader": true, "allow-plugins": { "pestphp/pest-plugin": true, "php-http/discovery": true } } } ================================================ FILE: config/ads.php ================================================ env('AD_PROVIDER'), 'nitro' => [ 'enabled' => ! empty(env('NITRO_SITE')), 'site' => env('NITRO_SITE'), 'tags' => [ 'video' => 'ads-nitro-video', 'leaderboard' => 'ads-nitro-leaderboard', 'siderail_right' => 'yes', 'siderail_left' => 'yes', ], ], 'freestar' => [ 'enabled' => ! empty(env('FREESTAR_SITE')), 'site' => env('FREESTAR_SITE'), 'tags' => [ 'incontent' => 'kanka_incontent_reusable', 'siderail_right' => 'kanka_siderail_right_1', 'siderail_left' => 'kanka_siderail_left', 'leaderboard' => 'kanka_leaderboard_atf', ], ], ]; ================================================ FILE: config/app.php ================================================ env('APP_NAME', 'Miscellany'), 'email' => env('APP_EMAIL', 'you@example.com'), /* |-------------------------------------------------------------------------- | Application Environment |-------------------------------------------------------------------------- | | This value determines the "environment" your application is currently | running in. This may determine how you prefer to configure various | services your application utilizes. Set this in your ".env" file. | */ 'env' => env('APP_ENV', 'production'), /* |-------------------------------------------------------------------------- | Application Debug Mode |-------------------------------------------------------------------------- | | When your application is in debug mode, detailed error messages with | stack traces will be shown on every error that occurs within your | application. If disabled, a simple generic error page is shown. | */ 'debug' => env('APP_DEBUG', false), /* * If the app is hosted along the admin, will enable the community aspects of Kanka. */ 'admin' => env('APP_ADMIN', false), /* |-------------------------------------------------------------------------- | Application URL |-------------------------------------------------------------------------- | | This URL is used by the console to properly generate URLs when using | the Artisan command line tool. You should set this to the root of | your application so that it is used when running Artisan tasks. | */ 'url' => env('APP_URL', 'http://localhost'), 'asset_url' => env('ASSET_URL'), 'site_name' => env('APP_SITE_NAME', 'my self hosted worldbuilding app'), /* |-------------------------------------------------------------------------- | Application Timezone |-------------------------------------------------------------------------- | | Here you may specify the default timezone for your application, which | will be used by the PHP date and date-time functions. We have gone | ahead and set this to a sensible default for you out of the box. | */ 'timezone' => 'UTC', /* |-------------------------------------------------------------------------- | Application Locale Configuration |-------------------------------------------------------------------------- | | The application locale determines the default locale that will be used | by the translation service provider. You are free to set this value | to any of the locales which will be supported by the application. | */ 'locale' => 'en', /* |-------------------------------------------------------------------------- | Application Fallback Locale |-------------------------------------------------------------------------- | | The fallback locale determines the locale to use when the current one | is not available. You may change the value to correspond to any of | the language folders that are provided through your application. | */ 'fallback_locale' => 'en', /* |-------------------------------------------------------------------------- | Encryption Key |-------------------------------------------------------------------------- | | This key is used by the Illuminate encrypter service and should be set | to a random, 32 character string, otherwise these encrypted strings | will not be safe. Please do this before deploying an application! | */ 'key' => env('APP_KEY'), 'cipher' => 'AES-256-CBC', /* |-------------------------------------------------------------------------- | Logging Configuration |-------------------------------------------------------------------------- | | Here you may configure the log settings for your application. Out of | the box, Laravel uses the Monolog PHP logging library. This gives | you a variety of powerful log handlers / formatters to utilize. | | Available Settings: "single", "daily", "syslog", "errorlog" | */ 'log' => env('APP_LOG', 'single'), 'log_level' => env('APP_LOG_LEVEL', 'debug'), 'version' => env('APP_VERSION', '@develop'), 'ignore_develop_warning' => env('APP_IGNORE_DEVELOP_WARNING', false), /* * Determine if cron job results need to be logged in the database */ 'log_jobs' => env('APP_LOG_JOBS', false), /* |------------------------------------------- | API Version |------------------------------------------- | | This value is the version of your api. It's used when there's no specified | version on the routes, so it will take this as the default, or current. */ 'api_latest' => '1', 'force_https' => env('APP_FORCE_HTTPS', false), 'lazy' => env('APP_LAZY', false), /** * Default user country fallback, used for local development to simulate a user from a specific country */ 'default_country' => env('DEFAULT_COUNTRY', 'CH'), /** * Leaflet version. */ 'leaflet_source' => env('LEAFLET_SOURCE', '1.9.4'), 'leaflet_css' => env('LEAFLET_CSS', 'sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY='), 'leaflet_js' => env('LEAFLET_JS', 'sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo='), /* |-------------------------------------------------------------------------- | Autoloaded Service Providers |-------------------------------------------------------------------------- | | The service providers listed here will be automatically loaded on the | request to your application. Feel free to add your own services to | this array to grant expanded functionality to your applications. | */ 'providers' => [ /* * Laravel Framework Service Providers... */ AuthServiceProvider::class, BroadcastServiceProvider::class, BusServiceProvider::class, CacheServiceProvider::class, ConsoleSupportServiceProvider::class, CookieServiceProvider::class, DatabaseServiceProvider::class, EncryptionServiceProvider::class, FilesystemServiceProvider::class, FoundationServiceProvider::class, HashServiceProvider::class, MailServiceProvider::class, NotificationServiceProvider::class, PaginationServiceProvider::class, PipelineServiceProvider::class, QueueServiceProvider::class, RedisServiceProvider::class, PasswordResetServiceProvider::class, SessionServiceProvider::class, // Illuminate\Translation\TranslationServiceProvider::class, ValidationServiceProvider::class, ViewServiceProvider::class, PassportServiceProvider::class, /* * Package Service Providers... */ PurifyServiceProvider::class, SocialiteServiceProvider::class, ManagerServiceProvider::class, TranslationServiceProvider::class, // Illuminate\Translation\TranslationServiceProvider::class, // RichanFongdasen\EloquentBlameable\ServiceProvider::class, ServiceProvider::class, /* * Application Service Providers... */ AppServiceProvider::class, App\Providers\AuthServiceProvider::class, AvatarServiceProvider::class, App\Providers\BroadcastServiceProvider::class, EventServiceProvider::class, RouteServiceProvider::class, CampaignLocalizationServiceProvider::class, MentionsServiceProvider::class, BreadcrumbServiceProvider::class, App\Providers\CacheServiceProvider::class, ImgServiceProvider::class, AttributesServiceProvider::class, DashboardServiceProvider::class, DatalayerServiceProvider::class, DatagridRendererProvider::class, PermissionsServiceProvider::class, EntitySetupServiceProvider::class, ModuleServiceProvider::class, ApiLogServiceProvider::class, DomainServiceProvider::class, LimitServiceProvider::class, ImporterServiceProvider::class, ], /* |-------------------------------------------------------------------------- | Class Aliases |-------------------------------------------------------------------------- | | This array of class aliases will be registered when this application | is started. However, feel free to register as many as you wish as | the aliases are "lazy" loaded so they don't hinder performance. | */ 'aliases' => [ 'App' => Illuminate\Support\Facades\App::class, 'Artisan' => Artisan::class, 'Auth' => Auth::class, 'Blade' => Blade::class, 'Broadcast' => Broadcast::class, 'Bus' => Bus::class, 'Cache' => Cache::class, 'Config' => Config::class, 'Cookie' => Cookie::class, 'Crypt' => Crypt::class, 'DB' => DB::class, 'Eloquent' => Model::class, 'Event' => Event::class, 'File' => File::class, 'Gate' => Gate::class, 'Hash' => Hash::class, 'Lang' => Lang::class, 'Log' => Log::class, 'Mail' => Mail::class, 'Notification' => Notification::class, 'Password' => Password::class, 'Queue' => Queue::class, 'Redirect' => Redirect::class, 'Redis' => Redis::class, 'Request' => Request::class, 'Response' => Response::class, 'Route' => Route::class, 'Schema' => Schema::class, 'Session' => Session::class, 'Storage' => Storage::class, 'URL' => URL::class, 'Validator' => Validator::class, 'View' => View::class, 'Vite' => Vite::class, // Third party 'Purify' => Purify::class, 'Socialite' => Socialite::class, 'Image' => Image::class, 'LaravelLocalization' => LaravelLocalization::class, // Kanka 'Avatar' => Avatar::class, 'CampaignLocalization' => CampaignLocalization::class, 'EntityPermission' => EntityPermission::class, 'Mentions' => Mentions::class, 'Breadcrumb' => Breadcrumb::class, 'FormCopy' => FormCopy::class, 'EntityCache' => EntityCache::class, 'CampaignCache' => CampaignCache::class, 'AdCache' => AdCache::class, 'UserCache' => UserCache::class, 'Img' => Img::class, 'Attributes' => Attributes::class, 'Datagrid' => Datagrid::class, 'EntitySetup' => EntitySetup::class, 'Google2FA' => Facade::class, 'ApiLog' => ApiLog::class, 'Domain' => Domain::class, 'Limit' => Limit::class, 'ImportIdMapper' => ImportIdMapper::class, ], ]; ================================================ FILE: config/attribute-templates.php ================================================ [ 'dnd5emonster' => Template::class, ], ]; ================================================ FILE: config/auth.php ================================================ [ 'guard' => 'web', 'passwords' => 'users', ], /* |-------------------------------------------------------------------------- | Authentication Guards |-------------------------------------------------------------------------- | | Next, you may define every authentication guard for your application. | Of course, a great default configuration has been defined for you | here which uses session storage and the Eloquent user provider. | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | Supported: "session", "token" | */ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ], /* |-------------------------------------------------------------------------- | User Providers |-------------------------------------------------------------------------- | | All authentication drivers have a user provider. This defines how the | users are actually retrieved out of your database or other storage | mechanisms used by this application to persist your user's data. | | If you have multiple user tables or models you may configure multiple | sources which represent each model / table. These sources may then | be assigned to any extra authentication guards you have defined. | | Supported: "database", "eloquent" | */ 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => User::class, ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], /* |-------------------------------------------------------------------------- | Resetting Passwords |-------------------------------------------------------------------------- | | You may specify multiple password reset configurations if you have more | than one user table or model in the application and you want to have | separate password reset settings based on the specific user types. | | The expire time is the number of minutes that the reset token should be | considered valid. This security feature keeps tokens short-lived so | they have less time to be guessed. You may change this as needed. | */ 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, ], ], /** * If set to false, no users can register */ 'register_enabled' => env('APP_REGISTRATION_ENABLED', true), /** * If set to true, show a dropdown of users for the login instead of a text field */ 'user_list' => env('APP_LOGIN_LIST', false), /** * If set to true user registration will be prefilled with random data. */ 'fast_registration' => env('ACCOUNT_FAST_REGISTRATION', false), 'recaptcha' => [ 'enabled' => ! empty(env('RECAPTCHA_KEY')), 'key' => env('RECAPTCHA_KEY'), 'secret' => env('RECAPTCHA_SECRET'), ], ]; ================================================ FILE: config/backup.php ================================================ [ /* * The name of this application. You can use this name to monitor * the backups. */ 'name' => env('APP_NAME', 'laravel-backup'), 'source' => [ 'files' => [ /* * The list of directories and files that will be included in the backup. */ 'include' => [ // base_path('config'), ], /* * These directories and files will be excluded from the backup. * * Directories used by the backup process will automatically be excluded. */ 'exclude' => [ base_path('vendor'), base_path('node_modules'), ], /* * Determines if symlinks should be followed. */ 'follow_links' => false, /* * Determines if it should avoid unreadable folders. */ 'ignore_unreadable_directories' => false, /* * This path is used to make directories in resulting zip-file relative * Set to `null` to include complete absolute path * Example: base_path() */ 'relative_path' => null, ], /* * The names of the connections to the databases that should be backed up * MySQL, PostgreSQL, SQLite and Mongo databases are supported. * * The content of the database dump may be customized for each connection * by adding a 'dump' key to the connection settings in config/database.php. * E.g. * 'mysql' => [ * ... * 'dump' => [ * 'excludeTables' => [ * 'table_to_exclude_from_backup', * 'another_table_to_exclude' * ] * ], * ], * * If you are using only InnoDB tables on a MySQL server, you can * also supply the useSingleTransaction option to avoid table locking. * * E.g. * 'mysql' => [ * ... * 'dump' => [ * 'useSingleTransaction' => true, * ], * ], * * For a complete list of available customization options, see https://github.com/spatie/db-dumper */ 'databases' => [ 'replica', ], ], /* * The database dump can be compressed to decrease diskspace usage. * * Out of the box Laravel-backup supplies * Spatie\DbDumper\Compressors\GzipCompressor::class. * * You can also create custom compressor. More info on that here: * https://github.com/spatie/db-dumper#using-compression * * If you do not want any compressor at all, set it to null. */ 'database_dump_compressor' => GzipCompressor::class, /* * The file extension used for the database dump files. * * If not specified, the file extension will be .archive for MongoDB and .sql for all other databases * The file extension should be specified without a leading . */ 'database_dump_file_extension' => '', 'destination' => [ /* * The filename prefix used for the backup zip file. */ 'filename_prefix' => '', /* * The disk names on which the backups will be stored. */ 'disks' => [ env('BACKUP_DISK', 'local'), ], ], /* * The directory where the temporary files will be stored. */ 'temporary_directory' => storage_path('app/backup-temp'), /* * The password to be used for archive encryption. * Set to `null` to disable encryption. */ 'password' => env('BACKUP_ARCHIVE_PASSWORD'), /* * The encryption algorithm to be used for archive encryption. * You can set it to `null` or `false` to disable encryption. * * When set to 'default', we'll use ZipArchive::EM_AES_256 if it is * available on your system. */ 'encryption' => 'default', ], /* * You can get notified when specific events occur. Out of the box you can use 'mail' and 'slack'. * For Slack you need to install laravel/slack-notification-channel. * * You can also use your own notification classes, just make sure the class is named after one of * the `Spatie\Backup\Events` classes. */ 'notifications' => [ 'notifications' => [ BackupHasFailedNotification::class => ['mail'], UnhealthyBackupWasFoundNotification::class => ['mail'], CleanupHasFailedNotification::class => ['mail'], BackupWasSuccessfulNotification::class => [], HealthyBackupWasFoundNotification::class => ['mail'], CleanupWasSuccessfulNotification::class => [], ], /* * Here you can specify the notifiable to which the notifications should be sent. The default * notifiable will use the variables specified in this config file. */ 'notifiable' => Notifiable::class, 'mail' => [ 'to' => 'hello@kanka.io', 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'hello@kanka.io'), 'name' => env('MAIL_FROM_NAME', 'Kanka Backup'), ], ], 'slack' => [ 'webhook_url' => '', /* * If this is set to null the default channel of the webhook will be used. */ 'channel' => null, 'username' => null, 'icon' => null, ], ], /* * Here you can specify which backups should be monitored. * If a backup does not meet the specified requirements the * UnHealthyBackupWasFound event will be fired. */ 'monitor_backups' => [ [ 'name' => env('APP_NAME', 'laravel-backup'), 'disks' => [env('BACKUP_DISK', 'local')], 'health_checks' => [ MaximumAgeInDays::class => 1, MaximumStorageInMegabytes::class => 2000000, ], ], /* [ 'name' => 'name of the second app', 'disks' => ['local', 's3'], 'health_checks' => [ \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumAgeInDays::class => 1, \Spatie\Backup\Tasks\Monitor\HealthChecks\MaximumStorageInMegabytes::class => 5000, ], ], */ ], 'cleanup' => [ /* * The strategy that will be used to cleanup old backups. The default strategy * will keep all backups for a certain amount of days. After that period only * a daily backup will be kept. After that period only weekly backups will * be kept and so on. * * No matter how you configure it the default strategy will never * delete the newest backup. */ 'strategy' => DefaultStrategy::class, 'default_strategy' => [ /* * The number of days for which backups must be kept. */ 'keep_all_backups_for_days' => 20, /* * The number of days for which daily backups must be kept. */ 'keep_daily_backups_for_days' => 16, /* * The number of weeks for which one weekly backup must be kept. */ 'keep_weekly_backups_for_weeks' => 8, /* * The number of months for which one monthly backup must be kept. */ 'keep_monthly_backups_for_months' => 4, /* * The number of years for which one yearly backup must be kept. */ 'keep_yearly_backups_for_years' => 2, /* * After cleaning up the backups remove the oldest backup until * this amount of megabytes has been reached. */ 'delete_oldest_backups_when_using_more_megabytes_than' => 100000, ], ], ]; ================================================ FILE: config/billing.php ================================================ env('APP_BILLING_STREET'), 'location' => env('APP_BILLING_LOCATION'), 'country' => env('APP_BILLING_COUNTRY'), ]; ================================================ FILE: config/blameable.php ================================================ null, /* |-------------------------------------------------------------------------- | User Model Definition |-------------------------------------------------------------------------- | | Please specify a user model that should be used to setup `creator` | and `updater` relationship. | */ 'user' => User::class, /* |-------------------------------------------------------------------------- | The `createdBy` attribute |-------------------------------------------------------------------------- | | Please define an attribute to use when recording the creator | identifier. | */ 'createdBy' => 'created_by', /* |-------------------------------------------------------------------------- | The `updatedBy` attribute |-------------------------------------------------------------------------- | | Please define an attribute to use when recording the updater | identifier. | */ 'updatedBy' => 'updated_by', /* |-------------------------------------------------------------------------- | The `deletedBy` attribute |-------------------------------------------------------------------------- | | Please define an attribute to use when recording the user | identifier who deleted the record. This feature would only work | if you are using SoftDeletes in your model. | */ 'deletedBy' => 'deleted_by', ]; ================================================ FILE: config/bragi.php ================================================ [ 'admin' => env('BRAGI_ADMIN_LIMIT', 99), 'elemental' => env('BRAGI_ELEMENTAL_LIMIT', 50), 'wyvern' => env('BRAGI_WYVERN_LIMIT', 25), 'all' => 0, ], 'limit' => [ 'prompt' => 180, ], 'backstory' => [ 'temperature' => 0.9, 'presence_penalty' => 0.5, 'frequency_penalty' => 0.2, /** * AI model to use. * * Available models: * "text-babbage-001" * "text-curie-001" * "text-ada-001" * "text-davinci-003" * "gpt-3.5-turbo" */ 'engine' => env('OPEN_AI_ENGINE', 'gpt-3.5-turbo'), /** * Max number of tokens used for generating a backstory */ 'tokens' => 400, ], /** * Decimal point after 0 of a chance of being a disciple of Kankappy. */ 'kankappy' => 5, ]; ================================================ FILE: config/broadcasting.php ================================================ env('BROADCAST_DRIVER', 'null'), /* |-------------------------------------------------------------------------- | Broadcast Connections |-------------------------------------------------------------------------- | | Here you may define all of the broadcast connections that will be used | to broadcast events to other systems or over websockets. Samples of | each available type of connection are provided inside this array. | */ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), ], ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ], 'log' => [ 'driver' => 'log', ], 'null' => [ 'driver' => 'null', ], ], ]; ================================================ FILE: config/cache.php ================================================ env('CACHE_DRIVER', 'file'), /* |-------------------------------------------------------------------------- | Cache Stores |-------------------------------------------------------------------------- | | Here you may define all of the cache "stores" for your application as | well as their drivers. You may even define multiple stores for the | same cache driver to group types of items stored in your caches. | */ 'stores' => [ 'apc' => [ 'driver' => 'apc', ], 'array' => [ 'driver' => 'array', ], 'database' => [ 'driver' => 'database', 'table' => 'cache', 'connection' => null, ], 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), ], 'memcached' => [ 'driver' => 'memcached', 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 'sasl' => [ env('MEMCACHED_USERNAME'), env('MEMCACHED_PASSWORD'), ], 'options' => [ // Memcached::OPT_CONNECT_TIMEOUT => 2000, ], 'servers' => [ [ 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 'port' => env('MEMCACHED_PORT', 11211), 'weight' => 100, ], ], ], 'redis' => [ 'driver' => 'redis', 'connection' => 'app_cache', ], ], /* |-------------------------------------------------------------------------- | Cache Key Prefix |-------------------------------------------------------------------------- | | When utilizing a RAM based store such as APC or Memcached, there might | be other applications utilizing the same cache. So, we'll specify a | value to get prefixed to all our keys so we can avoid collisions. | */ 'prefix' => env( 'CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache' ), ]; ================================================ FILE: config/cdn.php ================================================ env('CDN_UGC_URL'), ]; ================================================ FILE: config/colours.php ================================================ [ 'light-blue', // primary 'aqua', // info 'green', // success 'red', // danger 'yellow', 'grey', // gray 'navy', 'teal', 'purple', 'orange', 'maroon', 'black', 'pink', 'brown', ], 'mappings' => [ // 'blue' => 'primary', // 'lightblue' => 'info', // 'green' => 'success', // 'red' => 'danger', 'grey' => 'gray', ], ]; ================================================ FILE: config/cors.php ================================================ ['api/*', 'public/*', '1.0/*'], /* * Matches the request method. `[*]` allows all methods. */ 'allowed_methods' => ['*'], /* * Matches the request origin. `[*]` allows all origins. */ 'allowed_origins' => ['*'], /* * Matches the request origin with, similar to `Request::is()` */ 'allowed_origins_patterns' => [], /* * Sets the Access-Control-Allow-Headers response header. `[*]` allows all headers. */ 'allowed_headers' => ['*'], /* * Sets the Access-Control-Expose-Headers response header. */ 'exposed_headers' => [ 'X-RateLimit-Remaining', 'X-RateLimit-Limit', ], /* * Sets the Access-Control-Max-Age response header. */ 'max_age' => false, /* * Sets the Access-Control-Allow-Credentials header. */ 'supports_credentials' => false, ]; ================================================ FILE: config/database.php ================================================ env('DB_CONNECTION', 'mysql'), /* |-------------------------------------------------------------------------- | Database Connections |-------------------------------------------------------------------------- | | Here are each of the database connections setup for your application. | Of course, examples of configuring each database platform that is | supported by Laravel is shown below to make development simple. | | | All database work in Laravel is done through the PHP PDO facilities | so make sure you have the driver for your particular database of | choice installed on your machine before you begin development. | */ 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'database' => env('DB_DATABASE', database_path('database.sqlite')), 'prefix' => '', ], 'mysql' => [ 'driver' => 'mariadb', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => false, 'sticky' => true, 'engine' => null, 'dump' => [ 'dump_binary_path' => env('DB_BINARY_DUMP_PATH', '/usr/bin/'), 'add_extra_option' => env('DB_BINARY_DUMP_OPTIONS', '--single-transaction --skip-lock-tables --column-statistics=0'), ], ], 'replica' => [ 'driver' => 'mariadb', 'host' => env('DB_REPLICA_HOST', env('DB_HOST', '127.0.0.1')), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => false, 'sticky' => true, 'engine' => null, 'dump' => [ 'dump_binary_path' => env('DB_BINARY_DUMP_PATH', '/usr/bin/'), 'add_extra_option' => env('DB_BINARY_DUMP_OPTIONS', '--single-transaction --skip-lock-tables --column-statistics=0'), ], ], 'logs' => [ 'driver' => 'mariadb', 'read' => [ 'host' => env('DB_HOST_RW', env('DB_HOST', '127.0.0.1')), ], 'write' => [ 'host' => env('DB_HOST_RW', env('DB_HOST', '127.0.0.1')), ], 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_LOGS_DATABASE', 'logs'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'strict' => false, 'engine' => null, 'dump' => [ 'dump_binary_path' => env('DB_BINARY_DUMP_PATH'), 'add_extra_option' => '--single-transaction --skip-lock-tables', ], ], 'pgsql' => [ 'driver' => 'pgsql', 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '5432'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', 'schema' => 'public', 'sslmode' => 'prefer', ], 'sqlsrv' => [ 'driver' => 'sqlsrv', 'host' => env('DB_HOST', 'localhost'), 'port' => env('DB_PORT', '1433'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', 'prefix' => '', ], ], /* |-------------------------------------------------------------------------- | Migration Repository Table |-------------------------------------------------------------------------- | | This table keeps track of all the migrations that have already run for | your application. Using this information, we can determine which of | the migrations on disk haven't actually been run in the database. | */ 'migrations' => 'migrations', /* |-------------------------------------------------------------------------- | Redis Databases |-------------------------------------------------------------------------- | | Redis is an open source, fast, and advanced key-value store that also | provides a richer set of commands than a typical key-value systems | such as APC or Memcached. Laravel makes it easy to dig right in. | */ 'redis' => [ 'client' => env('REDIS_CLIENT', 'predis'), // Used for the queue 'default' => [ 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => 0, 'read_write_timeout' => -1, ], 'app_cache' => [ 'host' => env('REDIS_HOST', '127.0.0.1'), 'password' => env('REDIS_PASSWORD', null), 'port' => env('REDIS_PORT', 6379), 'database' => 1, 'read_write_timeout' => -1, ], ], ]; ================================================ FILE: config/debugbar.php ================================================ env('DEBUGBAR_ENABLED', null), 'except' => [ ], /* |-------------------------------------------------------------------------- | Storage settings |-------------------------------------------------------------------------- | | DebugBar stores data for session/ajax requests. | You can disable this, so the debugbar stores data in headers/session, | but this can cause problems with large data collectors. | By default, file storage (in the storage folder) is used. Redis and PDO | can also be used. For PDO, run the package migrations first. | */ 'storage' => [ 'enabled' => true, 'driver' => 'file', // redis, file, pdo, custom 'path' => storage_path('debugbar'), // For file driver 'connection' => null, // Leave null for default connection (Redis/PDO) 'provider' => '', // Instance of StorageInterface for custom driver ], /* |-------------------------------------------------------------------------- | Vendors |-------------------------------------------------------------------------- | | Vendor files are included by default, but can be set to false. | This can also be set to 'js' or 'css', to only include javascript or css vendor files. | Vendor files are for css: font-awesome (including fonts) and highlight.js (css files) | and for js: jquery and and highlight.js | So if you want syntax highlighting, set it to true. | jQuery is set to not conflict with existing jQuery scripts. | */ 'include_vendors' => true, /* |-------------------------------------------------------------------------- | Capture Ajax Requests |-------------------------------------------------------------------------- | | The Debugbar can capture Ajax requests and display them. If you don't want this (ie. because of errors), | you can use this option to disable sending the data through the headers. | | Optionally, you can also send ServerTiming headers on ajax requests for the Chrome DevTools. */ 'capture_ajax' => true, 'add_ajax_timing' => false, /* |-------------------------------------------------------------------------- | Custom Error Handler for Deprecated warnings |-------------------------------------------------------------------------- | | When enabled, the Debugbar shows deprecated warnings for Symfony components | in the Messages tab. | */ 'error_handler' => false, /* |-------------------------------------------------------------------------- | Clockwork integration |-------------------------------------------------------------------------- | | The Debugbar can emulate the Clockwork headers, so you can use the Chrome | Extension, without the server-side code. It uses Debugbar collectors instead. | */ 'clockwork' => false, /* |-------------------------------------------------------------------------- | DataCollectors |-------------------------------------------------------------------------- | | Enable/disable DataCollectors | */ 'collectors' => [ 'phpinfo' => true, // Php version 'messages' => true, // Messages 'time' => true, // Time Datalogger 'memory' => true, // Memory usage 'exceptions' => true, // Exception displayer 'log' => true, // Logs from Monolog (merged in messages if enabled) 'db' => true, // Show database (PDO) queries and bindings 'views' => true, // Views with their data 'route' => true, // Current route information 'auth' => true, // Display Laravel authentication status 'gate' => true, // Display Laravel Gate checks 'session' => true, // Display session data 'symfony_request' => true, // Only one can be enabled.. 'mail' => true, // Catch mail messages 'laravel' => false, // Laravel version and environment 'events' => false, // All events fired 'default_request' => false, // Regular or special Symfony request logger 'logs' => false, // Add the latest log messages 'files' => false, // Show the included files 'config' => false, // Display config settings 'cache' => false, // Display cache events ], /* |-------------------------------------------------------------------------- | Extra options |-------------------------------------------------------------------------- | | Configure some DataCollectors | */ 'options' => [ 'auth' => [ 'show_name' => true, // Also show the users name/email in the debugbar ], 'db' => [ 'with_params' => true, // Render SQL with the parameters substituted 'backtrace' => true, // Use a backtrace to find the origin of the query in your files. 'timeline' => false, // Add the queries to the timeline 'explain' => [ // Show EXPLAIN output on queries 'enabled' => false, 'types' => ['SELECT'], // ['SELECT', 'INSERT', 'UPDATE', 'DELETE']; for MySQL 5.6.3+ ], 'hints' => true, // Show hints for common mistakes ], 'mail' => [ 'full_log' => false, ], 'views' => [ 'data' => false, // Note: Can slow down the application, because the data can be quite large.. ], 'route' => [ 'label' => true, // show complete route on bar ], 'logs' => [ 'file' => null, ], 'cache' => [ 'values' => true, // collect cache values ], ], /* |-------------------------------------------------------------------------- | Inject Debugbar in Response |-------------------------------------------------------------------------- | | Usually, the debugbar is added just before , by listening to the | Response after the App is done. If you disable this, you have to add them | in your template yourself. See http://phpdebugbar.com/docs/rendering.html | */ 'inject' => true, /* |-------------------------------------------------------------------------- | DebugBar route prefix |-------------------------------------------------------------------------- | | Sometimes you want to set route prefix to be used by DebugBar to load | its resources from. Usually the need comes from misconfigured web server or | from trying to overcome bugs like this: http://trac.nginx.org/nginx/ticket/97 | */ 'route_prefix' => '_debugbar', /* |-------------------------------------------------------------------------- | DebugBar route domain |-------------------------------------------------------------------------- | | By default DebugBar route served from the same domain that request served. | To override default domain, specify it as a non-empty value. */ 'route_domain' => null, ]; ================================================ FILE: config/discord.php ================================================ getenv('DISCORD_CLIENT_ID'), 'client_secret' => getenv('DISCORD_SECRET'), 'channel_id' => getenv('DISCORD_CHANNEL_ID'), 'bot_secret' => getenv('DISCORD_BOT_SECRET'), 'bot_token' => getenv('DISCORD_BOT_TOKEN'), 'webhooks' => [ 'features' => env('DISCORD_WEBHOOK_FEATURES'), 'admin' => env('DISCORD_WEBHOOK_ADMIN'), ], 'color' => env('DISCORD_COLOR', '7506394'), 'roles' => [ 'owlbear' => 435813101506527233, 'wyvern' => 805183678153621514, 'elemental' => 501452722273386496, ], ]; ================================================ FILE: config/domains.php ================================================ env('API_DOMAIN', 'api.kanka.io'), 'app' => env('APP_DOMAIN', 'app.kanka.io'), 'front' => env('FRONT_DOMAIN', 'kanka.io'), 'importer' => env('IMPORTER_DOMAIN', 'https://import.kanka.io'), ]; ================================================ FILE: config/dompdf.php ================================================ false, // Throw an Exception on warnings from dompdf 'orientation' => 'portrait', 'defines' => [ /** * The location of the DOMPDF font directory * * The location of the directory where DOMPDF will store fonts and font metrics * Note: This directory must exist and be writable by the webserver process. * *Please note the trailing slash.* * * Notes regarding fonts: * Additional .afm font metrics can be added by executing load_font.php from command line. * * Only the original "Base 14 fonts" are present on all pdf viewers. Additional fonts must * be embedded in the pdf file or the PDF may not display correctly. This can significantly * increase file size unless font subsetting is enabled. Before embedding a font please * review your rights under the font license. * * Any font specification in the source HTML is translated to the closest font available * in the font directory. * * The pdf standard "Base 14 fonts" are: * Courier, Courier-Bold, Courier-BoldOblique, Courier-Oblique, * Helvetica, Helvetica-Bold, Helvetica-BoldOblique, Helvetica-Oblique, * Times-Roman, Times-Bold, Times-BoldItalic, Times-Italic, * Symbol, ZapfDingbats. */ 'font_dir' => storage_path('fonts/'), // advised by dompdf (https://github.com/dompdf/dompdf/pull/782) /** * The location of the DOMPDF font cache directory * * This directory contains the cached font metrics for the fonts used by DOMPDF. * This directory can be the same as DOMPDF_FONT_DIR * * Note: This directory must exist and be writable by the webserver process. */ 'font_cache' => storage_path('fonts/'), /** * The location of a temporary directory. * * The directory specified must be writeable by the webserver process. * The temporary directory is required to download remote images and when * using the PFDLib back end. */ 'temp_dir' => sys_get_temp_dir(), /** * ==== IMPORTANT ==== * * dompdf's "chroot": Prevents dompdf from accessing system files or other * files on the webserver. All local files opened by dompdf must be in a * subdirectory of this directory. DO NOT set it to '/' since this could * allow an attacker to use dompdf to read any files on the server. This * should be an absolute path. * This is only checked on command line call by dompdf.php, but not by * direct class use like: * $dompdf = new DOMPDF(); $dompdf->load_html($htmldata); $dompdf->render(); $pdfdata = $dompdf->output(); */ 'chroot' => realpath(base_path()), /** * Whether to enable font subsetting or not. */ 'enable_font_subsetting' => true, /** * The PDF rendering backend to use * * Valid settings are 'PDFLib', 'CPDF' (the bundled R&OS PDF class), 'GD' and * 'auto'. 'auto' will look for PDFLib and use it if found, or if not it will * fall back on CPDF. 'GD' renders PDFs to graphic files. {@link * Canvas_Factory} ultimately determines which rendering class to instantiate * based on this setting. * * Both PDFLib & CPDF rendering backends provide sufficient rendering * capabilities for dompdf, however additional features (e.g. object, * image and font support, etc.) differ between backends. Please see * {@link PDFLib_Adapter} for more information on the PDFLib backend * and {@link CPDF_Adapter} and lib/class.pdf.php for more information * on CPDF. Also see the documentation for each backend at the links * below. * * The GD rendering backend is a little different than PDFLib and * CPDF. Several features of CPDF and PDFLib are not supported or do * not make any sense when creating image files. For example, * multiple pages are not supported, nor are PDF 'objects'. Have a * look at {@link GD_Adapter} for more information. GD support is * experimental, so use it at your own risk. * * @link http://www.pdflib.com * @link http://www.ros.co.nz/pdf * @link http://www.php.net/image */ 'pdf_backend' => 'CPDF', /** * PDFlib license key * * If you are using a licensed, commercial version of PDFlib, specify * your license key here. If you are using PDFlib-Lite or are evaluating * the commercial version of PDFlib, comment out this setting. * * @link http://www.pdflib.com * * If pdflib present in web server and auto or selected explicitely above, * a real license code must exist! */ // "DOMPDF_PDFLIB_LICENSE" => "your license key here", /** * html target media view which should be rendered into pdf. * List of types and parsing rules for future extensions: * http://www.w3.org/TR/REC-html40/types.html * screen, tty, tv, projection, handheld, print, braille, aural, all * Note: aural is deprecated in CSS 2.1 because it is replaced by speech in CSS 3. * Note, even though the generated pdf file is intended for print output, * the desired content might be different (e.g. screen or projection view of html file). * Therefore allow specification of content here. */ 'default_media_type' => 'screen', /** * The default paper size. * * North America standard is "letter"; other countries generally "a4" * * @see CPDF_Adapter::PAPER_SIZES for valid sizes ('letter', 'legal', 'A4', etc.) */ 'default_paper_size' => 'a4', /** * The default font family * * Used if no suitable fonts can be found. This must exist in the font folder. * * @var string */ 'default_font' => 'dejavu sans, dejavu sans condensed, dejavu sans mono, dejavu serif, dejavu serif condensed', /** * Image DPI setting * * This setting determines the default DPI setting for images and fonts. The * DPI may be overridden for inline images by explictly setting the * image's width & height style attributes (i.e. if the image's native * width is 600 pixels and you specify the image's width as 72 points, * the image will have a DPI of 600 in the rendered PDF. The DPI of * background images can not be overridden and is controlled entirely * via this parameter. * * For the purposes of DOMPDF, pixels per inch (PPI) = dots per inch (DPI). * If a size in html is given as px (or without unit as image size), * this tells the corresponding size in pt. * This adjusts the relative sizes to be similar to the rendering of the * html page in a reference browser. * * In pdf, always 1 pt = 1/72 inch * * Rendering resolution of various browsers in px per inch: * Windows Firefox and Internet Explorer: * SystemControl->Display properties->FontResolution: Default:96, largefonts:120, custom:? * Linux Firefox: * about:config *resolution: Default:96 * (xorg screen dimension in mm and Desktop font dpi settings are ignored) * * Take care about extra font/image zoom factor of browser. * * In images, size in pixel attribute, img css style, are overriding * the real image dimension in px for rendering. * * @var int */ 'dpi' => 96, /** * Enable inline PHP * * If this setting is set to true then DOMPDF will automatically evaluate * inline PHP contained within tags. * * Enabling this for documents you do not trust (e.g. arbitrary remote html * pages) is a security risk. Set this option to false if you wish to process * untrusted documents. * * @var bool */ 'enable_php' => false, /** * Enable inline Javascript * * If this setting is set to true then DOMPDF will automatically insert * JavaScript code contained within tags. * * @var bool */ 'enable_javascript' => true, /** * Enable remote file access * * If this setting is set to true, DOMPDF will access remote sites for * images and CSS files as required. * This is required for part of test case www/test/image_variants.html through www/examples.php * * Attention! * This can be a security risk, in particular in combination with DOMPDF_ENABLE_PHP and * allowing remote access to dompdf.php or on allowing remote html code to be passed to * $dompdf = new DOMPDF(, $dompdf->load_html(..., * This allows anonymous users to download legally doubtful internet content which on * tracing back appears to being downloaded by your server, or allows malicious php code * in remote html pages to be executed by your server with your account privileges. * * @var bool */ 'enable_remote' => true, /** * A ratio applied to the fonts height to be more like browsers' line height */ 'font_height_ratio' => 1.1, /** * Use the more-than-experimental HTML5 Lib parser */ 'enable_html5_parser' => false, ], ]; ================================================ FILE: config/entities.php ================================================ env('APP_ENTITY_HARD_DELETE', 30), 'hard_delete_posts' => env('APP_ENTITY_HARD_DELETE_POSTS', 30), 'logs' => env('APP_ENTITY_FULL_LOGS', 30), 'logs_delete' => env('APP_ENTITY_LOGS_DELETE', 365), // Experimental flag 'custom' => env('APP_CUSTOM_ENTITIES', false), 'ids' => [ 'character' => 1, 'family' => 2, 'location' => 3, 'organisation' => 4, 'item' => 5, 'note' => 6, 'event' => 7, 'calendar' => 8, 'race' => 9, 'quest' => 10, 'journal' => 11, 'tag' => 12, 'dice_roll' => 13, 'conversation' => 14, 'attribute_template' => 15, 'ability' => 16, 'map' => 17, 'timeline' => 18, 'bookmark' => 19, 'creature' => 20, 'whiteboard' => (int) env('APP_WHITEBOARD_ID', 10000), ], 'classes' => [ 'ability' => 'App\Models\Ability', 'character' => 'App\Models\Character', 'calendar' => 'App\Models\Calendar', 'conversation' => 'App\Models\Conversation', 'creature' => 'App\Models\Creature', 'event' => 'App\Models\Event', 'family' => 'App\Models\Family', 'item' => 'App\Models\Item', 'journal' => 'App\Models\Journal', 'location' => 'App\Models\Location', 'map' => 'App\Models\Map', 'note' => 'App\Models\Note', 'organisation' => 'App\Models\Organisation', 'quest' => 'App\Models\Quest', 'race' => 'App\Models\Race', 'tag' => 'App\Models\Tag', 'timeline' => 'App\Models\Timeline', 'attribute_template' => 'App\Models\AttributeTemplate', 'dice_roll' => 'App\Models\DiceRoll', 'bookmark' => 'App\Models\Bookmark', 'relation' => 'App\Models\Relation', ], 'classes-plural' => [ 'abilities' => 'App\Models\Ability', 'characters' => 'App\Models\Character', 'calendars' => 'App\Models\Calendar', 'conversations' => 'App\Models\Conversation', 'creatures' => 'App\Models\Creature', 'events' => 'App\Models\Event', 'families' => 'App\Models\Family', 'items' => 'App\Models\Item', 'journals' => 'App\Models\Journal', 'locations' => 'App\Models\Location', 'maps' => 'App\Models\Map', 'notes' => 'App\Models\Note', 'organisations' => 'App\Models\Organisation', 'quests' => 'App\Models\Quest', 'races' => 'App\Models\Race', 'tags' => 'App\Models\Tag', 'timelines' => 'App\Models\Timeline', 'attribute_templates' => 'App\Models\AttributeTemplate', 'dice_rolls' => 'App\Models\DiceRoll', 'bookmarks' => 'App\Models\Bookmark', 'relations' => 'App\Models\Relation', ], ]; ================================================ FILE: config/filesystems.php ================================================ env('FILESYSTEM_DRIVER', 'public'), /* |-------------------------------------------------------------------------- | Filesystem Disks |-------------------------------------------------------------------------- | | Here you may configure as many filesystem "disks" as you wish, and you | may even configure multiple disks of the same driver. Defaults have | been setup for each driver as an example of the required options. | | Supported Drivers: "local", "ftp", "s3", "rackspace" | */ 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), ], 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL') . '/storage', 'visibility' => 'public', ], 's3' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), // Root folder is prefixed outside of prod 'root' => env('APP_ENV') != 'production' ? env('APP_ENV') : null, 'visibility' => 'public', // Url for including the assets in the browser 'url' => env('AWS_URL_S3', env('AWS_URL')), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env( 'AWS_USE_PATH_STYLE_ENDPOINT', false, ), ], 'backup' => [ 'driver' => 's3', 'key' => env('HETZNER_S3_ACCESS_KEY_ID'), 'secret' => env('HETZNER_S3_ACCESS_KEY_SECRET'), 'region' => env('HETZNER_S3_REGION'), 'bucket' => env('S3_BUCKET_BACKUP'), 'root' => env('APP_ENV'), 'endpoint' => env('HETZNER_S3_ENDPOINT'), 'use_path_style_endpoint' => true, 'retries' => [ 'mode' => 'adaptive', // handles backoff automatically 'max' => 10, ], 'options' => [ 'part_size' => 1024 * 1024 * 500, ], 'http' => [ 'timeout' => 0, // No timeout for the request 'connect_timeout' => 10, // Time to wait for a connection ], 'throw' => false, 'visibility' => 'private', ], 'export' => [ 'driver' => 's3', 'key' => env('HETZNER_S3_ACCESS_KEY_ID'), 'secret' => env('HETZNER_S3_ACCESS_KEY_SECRET'), 'region' => env('HETZNER_S3_REGION'), 'bucket' => env('S3_BUCKET_EXPORT'), 'root' => env('APP_ENV'), 'endpoint' => env('HETZNER_S3_ENDPOINT'), 'use_path_style_endpoint' => true, ], 's3-assets' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET_APP'), 'visibility' => 'public', 'url' => env('AWS_URL_ASSETS', env('AWS_URL')), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env( 'AWS_USE_PATH_STYLE_ENDPOINT', false, ), ], 's3-marketplace' => [ 'driver' => 's3', 'key' => env('HETZNER_S3_ACCESS_KEY_ID'), 'secret' => env('HETZNER_S3_ACCESS_KEY_SECRET'), 'region' => env('HETZNER_S3_REGION'), 'bucket' => env('AWS_BUCKET_MARKETPLACE'), 'endpoint' => env('HETZNER_S3_ENDPOINT'), 'root' => env('APP_ENV') != 'production' ? env('APP_ENV') : null, 'use_path_style_endpoint' => true, ], /** * On production, we use cloudfront in front of the default s3 */ 'cloudfront' => [ 'driver' => 's3', 'key' => env('AWS_ACCESS_KEY_ID'), 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION'), 'bucket' => env('AWS_BUCKET'), 'visibility' => 'public', // Url for including the assets in the browser 'url' => env('AWS_CLOUDFRONT'), 'endpoint' => env('AWS_ENDPOINT'), 'use_path_style_endpoint' => env( 'AWS_USE_PATH_STYLE_ENDPOINT', false, ), ], ], ]; ================================================ FILE: config/fontawesome.php ================================================ getenv('FONTAWESOME_KIT'), 'search' => 'https://fontawesome.com/search', ]; ================================================ FILE: config/front.php ================================================ env('APP_FRONT_FEATURED_CAMPAIGNS'), ]; ================================================ FILE: config/google2fa.php ================================================ env('OTP_ENABLED', false), /* * Lifetime in minutes. * * In case you need your users to be asked for a new one time passwords from time to time. */ 'lifetime' => env('OTP_LIFETIME', 0), // 0 = eternal /* * Renew lifetime at every new request. */ 'keep_alive' => env('OTP_KEEP_ALIVE', true), /* * Auth container binding. */ 'auth' => 'auth', /* * Guard. */ 'guard' => '', /* * 2FA verified session var. */ 'session_var' => 'google2fa', /* * One Time Password request input name. */ 'otp_input' => 'one_time_password', /* * One Time Password Window. */ 'window' => 1, /* * Forbid user to reuse One Time Passwords. */ 'forbid_old_passwords' => false, /* * User's table column for google2fa secret. */ 'otp_secret_column' => 'google2fa_secret', /* * One Time Password View. */ 'view' => 'auth.otp', /* * One Time Password error message. */ 'error_messages' => [ 'wrong_otp' => "The 'One Time Password' typed was wrong.", 'cannot_be_empty' => 'One Time Password cannot be empty.', 'unknown' => 'An unknown error has occurred. Please try again.', ], /* * Throw exceptions or just fire events? */ 'throw_exceptions' => env('OTP_THROW_EXCEPTION', true), /* * Which image backend to use for generating QR codes? * * Supports imagemagick, svg and eps */ 'qrcode_image_backend' => Constants::QRCODE_IMAGE_BACKEND_SVG, ]; ================================================ FILE: config/ignition.php ================================================ env('IGNITION_EDITOR', 'phpstorm'), /* |-------------------------------------------------------------------------- | Theme |-------------------------------------------------------------------------- | | Here you may specify which theme Ignition should use. | | Supported: "light", "dark", "auto" | */ 'theme' => env('IGNITION_THEME', 'auto'), /* |-------------------------------------------------------------------------- | Sharing |-------------------------------------------------------------------------- | | You can share local errors with colleagues or others around the world. | Sharing is completely free and doesn't require an account on Flare. | | If necessary, you can completely disable sharing below. | */ 'enable_share_button' => env('IGNITION_SHARING_ENABLED', false), /* |-------------------------------------------------------------------------- | Register Ignition commands |-------------------------------------------------------------------------- | | Ignition comes with an additional make command that lets you create | new solution classes more easily. To keep your default Laravel | installation clean, this command is not registered by default. | | You can enable the command registration below. | */ 'register_commands' => env('REGISTER_IGNITION_COMMANDS', false), /* |-------------------------------------------------------------------------- | Solution Providers |-------------------------------------------------------------------------- | | List of solution providers that should be loaded. You may specify additional | providers as fully qualified class names. | */ 'solution_providers' => [ // from spatie/ignition BadMethodCallSolutionProvider::class, MergeConflictSolutionProvider::class, UndefinedPropertySolutionProvider::class, // from spatie/laravel-ignition IncorrectValetDbCredentialsSolutionProvider::class, MissingAppKeySolutionProvider::class, DefaultDbNameSolutionProvider::class, TableNotFoundSolutionProvider::class, MissingImportSolutionProvider::class, InvalidRouteActionSolutionProvider::class, ViewNotFoundSolutionProvider::class, RunningLaravelDuskInProductionProvider::class, MissingColumnSolutionProvider::class, UnknownValidationSolutionProvider::class, MissingMixManifestSolutionProvider::class, MissingViteManifestSolutionProvider::class, MissingLivewireComponentSolutionProvider::class, UndefinedViewVariableSolutionProvider::class, GenericLaravelExceptionSolutionProvider::class, OpenAiSolutionProvider::class, SailNetworkSolutionProvider::class, UnknownMysql8CollationSolutionProvider::class, UnknownMariadbCollationSolutionProvider::class, ], /* |-------------------------------------------------------------------------- | Ignored Solution Providers |-------------------------------------------------------------------------- | | You may specify a list of solution providers (as fully qualified class | names) that shouldn't be loaded. Ignition will ignore these classes | and possible solutions provided by them will never be displayed. | */ 'ignored_solution_providers' => [ MissingPackageSolutionProvider::class, ], /* |-------------------------------------------------------------------------- | Runnable Solutions |-------------------------------------------------------------------------- | | Some solutions that Ignition displays are runnable and can perform | various tasks. Runnable solutions are enabled when your app has | debug mode enabled. You may also fully disable this feature. | */ 'enable_runnable_solutions' => env('IGNITION_ENABLE_RUNNABLE_SOLUTIONS'), /* |-------------------------------------------------------------------------- | Remote Path Mapping |-------------------------------------------------------------------------- | | If you are using a remote dev server, like Laravel Homestead, Docker, or | even a remote VPS, it will be necessary to specify your path mapping. | | Leaving one, or both of these, empty or null will not trigger the remote | URL changes and Ignition will treat your editor links as local files. | | "remote_sites_path" is an absolute base path for your sites or projects | in Homestead, Vagrant, Docker, or another remote development server. | | Example value: "/home/vagrant/Code" | | "local_sites_path" is an absolute base path for your sites or projects | on your local computer where your IDE or code editor is running on. | | Example values: "/Users//Code", "C:\Users\\Documents\Code" | */ 'remote_sites_path' => env('IGNITION_REMOTE_SITES_PATH', base_path()), 'local_sites_path' => env('IGNITION_LOCAL_SITES_PATH', ''), /* |-------------------------------------------------------------------------- | Housekeeping Endpoint Prefix |-------------------------------------------------------------------------- | | Ignition registers a couple of routes when it is enabled. Below you may | specify a route prefix that will be used to host all internal links. | */ 'housekeeping_endpoint_prefix' => '_ignition', /* |-------------------------------------------------------------------------- | Settings File |-------------------------------------------------------------------------- | | Ignition allows you to save your settings to a specific global file. | | If no path is specified, a file with settings will be saved to the user's | home directory. The directory depends on the OS and its settings but it's | typically `~/.ignition.json`. In this case, the settings will be applied | to all of your projects where Ignition is used and the path is not | specified. | | However, if you want to store your settings on a project basis, or you | want to keep them in another directory, you can specify a path where | the settings file will be saved. The path should be an existing directory | with correct write access. | For example, create a new `ignition` folder in the storage directory and | use `storage_path('ignition')` as the `settings_file_path`. | | Default value: '' (empty string) */ 'settings_file_path' => '', /* |-------------------------------------------------------------------------- | Recorders |-------------------------------------------------------------------------- | | Ignition registers a couple of recorders when it is enabled. Below you may | specify a recorders will be used to record specific events. | */ 'recorders' => [ DumpRecorder::class, JobRecorder::class, LogRecorder::class, QueryRecorder::class, ], /* * When a key is set, we'll send your exceptions to Open AI to generate a solution */ 'open_ai_key' => env('IGNITION_OPEN_AI_KEY'), /* |-------------------------------------------------------------------------- | Include arguments |-------------------------------------------------------------------------- | | Ignition show you stack traces of exceptions with the arguments that were | passed to each method. This feature can be disabled here. | */ 'with_stack_frame_arguments' => true, /* |-------------------------------------------------------------------------- | Argument reducers |-------------------------------------------------------------------------- | | Ignition show you stack traces of exceptions with the arguments that were | passed to each method. To make these variables more readable, you can | specify a list of classes here which summarize the variables. | */ 'argument_reducers' => [ BaseTypeArgumentReducer::class, ArrayArgumentReducer::class, StdClassArgumentReducer::class, EnumArgumentReducer::class, ClosureArgumentReducer::class, DateTimeArgumentReducer::class, DateTimeZoneArgumentReducer::class, SymphonyRequestArgumentReducer::class, ModelArgumentReducer::class, CollectionArgumentReducer::class, StringableArgumentReducer::class, ], ]; ================================================ FILE: config/image.php ================================================ 'gd', ]; ================================================ FILE: config/imports.php ================================================ env('CAMPAIGN_IMPORTS_PRUNE', 90), ]; ================================================ FILE: config/laravel-translation-manager.php ================================================ 'en', /** * Specify any additional locales that you want to be shown even if they have no translation files or translations in the database * * @type array of strings */ 'locales' => [ // 'ca', 'en', 'en-US', 'de', 'es', 'fr', 'hu', 'it', 'nl', 'pt-BR', 'ru', 'sk', // 'he', // 'hr', 'pl', // 'el', // 'tr', // 'gl', // 'nb', // 'sv', // 'zh-CN', // 'uk', ], /** * Specify locales that you want to show in the web interface, if empty or not provided then all locales in the database * will be shown * * @type array of strings */ 'show_locales' => [ 'en', 'fr', 'de', 'es', 'it', ], /** * Specify the prefix used for all cookies, session data and cache persistence. * * @type string */ 'persistent_prefix' => 'g2Lu2pyz8QcVrxhL32eN', /** * Enable management of translations beyond just editing and command line manipulations * * @type bool */ 'admin_enabled' => true, /** * use cookies to store user locale * * @type bool */ 'use_cookies' => false, /** * Inplace edit mode * 1 - Using trans(), noEditTrans(), ifEditTrans() in all templates * 2 - Only by modifying application template (disables ifEditTrans and reverts trans() to default functionality) */ 'inplace_edit_mode' => 2, /** * Enable management of translations for editors by locales * * Only applies to users that are not translation admins * * If not defined then by locale access is disabled and all locales can be * modified by any editor. * * @type bool */ 'user_locales_enabled' => true, /** * Enable markdown translation to html on the fly * * With this option set to a suffix string, all keys that end in this suffix, will * have their text converted to HTML via facade \Markdown::convertToHtml(translationText) * graham-cambell/Laravel-Markdown package serves this purpose well. * * The resulting HTML text will be saved in a key, with the suffix removed. * * This conversion is ONLY done when the translation for the markdown key is modified * via the web UI. Keys already in the translation files or in the database are assumed to * be properly converted. * * Set this to a reasonable suffix that will not conflict with normal keys in your application * and with laravel translation key convention. * * Do not use '.md' or anything with a period. It denotes a nested array key for translations. * Do not use multi-byte characters in the suffix. non-multi-byte string operations are used in manipulating the key. * Do not use keys that will cause issues in jQuery because keys are used to create jQueries with keys assumed to be valid element id's * Do not use colons since these are used to delimit package translation groups * You get the picture. Try a suffix out and if it works for you great. If not try another one * * Some options that work are: '-md', '--md', '-md-' * * @type string */ 'markdown_key_suffix' => '', /** * No Longer used. Must implement 'ltm-editors-list' ability that will return true * if the user can manage per locale access with an array of objects with: id, email, * and name fields that correspond to the list of users to be displayed in the web UI * to allow the current admin user to manage per locale access. */ // 'user_list_provider' => null, /** * Specify export formatting options: * * PRESERVE_EMPTY_ARRAYS - preserve first level translations that are empty arrays * USE_QUOTES - use " instead of ' for wrapping strings * USE_HEREDOC - use <<<'TEXT' for wrapping string that contain \n * USE_SHORT_ARRAY - use [] instead of array() for arrays * SORT_KEYS - alphabetically sort keys withing an array * * @type string | array */ 'export_format' => [ 'PRESERVE_EMPTY_ARRAYS', // 'USE_QUOTES', 'USE_HEREDOC', 'USE_SHORT_ARRAY', 'SORT_KEYS', ], /** * Enable mismatch dashboard * * @type bool */ 'mismatch_enabled' => false, /** * Exclude specific groups from Laravel Translation Manager. * This is useful if, for example, you want to avoid editing the official Laravel language files. * * @type array */ 'exclude_groups' => [ 'banners', 'reminders', 'validation', 'validation-inline', 'pagination', 'passwords', 'backup::notifications', 'faq', 'tutorials.actions', 'tutorials.home', 'tutorials.characters', 'openai', 'front.testimonials', ], /** * Exclude specific groups from Laravel Translation Manager in page edit mode. * This is useful for groups that are used exclusively for non-display strings like page titles and emails * * @type array */ 'exclude_page_edit_groups' => [ // 'page-titles', 'reminders', 'validation', 'pagination', 'passwords', ], /** * determines whether missing keys are logged * * @type bool */ 'log_missing_keys' => false, /** * determines whether usage of keys is logged, requires missing keys to be logged too * * @type bool */ 'log_key_usage_info' => false, /** * determines one out of how many user sessions will have a chance to log missing keys * since the operation hits the database for every missing key you can limit this by setting a * higher number depending on the traffic load to your site. * * @type int * * 1 - means every user * 10 - means 1 in 10 users * 100 - 1 in a 100 users * 1000 .... */ 'missing_keys_lottery' => 0, // 1 in 100 of users will have the missing translation keys logged. /** * @type int 0 - as usual, write out files and set status for translations to SAVED, * * 1 - on publish will only copy value to saved_value in the database and set the status to SAVED_CACHED * and add the changed keys to the translator cache so that the correct translation will be used. Used to * publish translations on production cluster or where write access to lang directory is not available. * * 2 - write out files but act as if doing in database publish only, this setting is useful for accessing * translations from a local dev server to a production database for the purpose of updating translation files * for deployment. Lets you create the translation files but leaves the translations in the database in a state * where they will continue to be served up with the latest published version, not the outdated file versions. * * to be used by clustered systems where the translation files are determined at deployment and publishing * on one system does no good to the rest of the cluster. */ 'indatabase_publish' => 0, /** * @type array list of alternate database connections and their properties indexed by app()->environment() value, * default connection settings are taken from config, so only add alternate connections * * If user_list_connection is missing, null or empty then the connection will also be used * to obtain the user list for user locale management, otherwise the given connection name will be user * to obtain the user list when managing translations on this connection. * * description is used to display the connection name, default connection is displayed as 'default' in * the web interface. * * indatabase_publish of: * 0 - means use files only. * 1 - means use cache for publishing modifications. No files are written out * 2 - means use cache for publishing modifications but also write out the files. * useful for publishing to files while leaving all flags in the database as * they would be after publishing only to cache. */ 'db_connections' => [ // 'local' => array( // 'mysql_prd' => array( // 'description' => 'production', // 'indatabase_publish' => 2, // ), // ), ], /** * used to provide an alternate default connection name for translation * tables * * @type string connection name to use for the default connection * * if blank, null or not defined then default connection will be used. */ 'default_connection' => null, /** * used to provide the Yandex key for use in automatic Yandex translations * * @type string Yandex translation key * * This key is free to obtain and use but is required to enable Yandex translations. Visit: https://tech.yandex.com/translate/ */ 'yandex_translator_key' => '', /** * used to provide configuration on where the translation files are stored and where to write them out. * * This configuration provides the difference in layout between Laravel 4 & 5. * It also can be used to include language files from vendor directories without having to export them to the * standard lang/ directory for those cases where these files are modified directly so that a git commit can * be done in-place. * * It can also be used to include lang/ files from projects in the workbench subdirectory for Laravel 4.2 * or Laravel 5 even though Laravel 5 does not support workbench subdirectory the same way it does in * version 4.2 but it is easy to configure composer.json to include * * array keys: * * 'path' - the root path for this element's language definitions, Any placeholder values placeholder * will take the name from the part inside {} and the value from the actual path found on the disk. * * * 'lang' - defines the location of the application's standard language files relative to the root * of the application. Laravel 4.2 '/app/lang', 5.x '/resources/lang'. * * 'packages' - defines the location of the package override directory * * 'workbench' - defines the location of the packages you are including in the project which are also * the source of the package that you are developing. * * * values: if the value is a string then it is assumed to be a 'file' spec, if it is an array then the keys * in the array should correspond to expected values as below. Any missing keys will be added as * follows: * * lang - 'db_group' => '{group}' * - 'root' => '' * (4.2) - 'file' => '/app/lang/{locale}/{group}' * (5.x) - 'file' => '/resources/lang/{locale}/{group}' * * packages - 'db_group' => '{package}::{group}' * - 'root' => '' * (4.2) - 'file' => '/app/lang/packages/{locale}/{package}/{group}' * (5.x) - 'file' => '/resources/lang/vendor/{package}/{locale}/{group}' * * workbench - 'db_group' => 'wbn:{vendor}.{package}::{group}' * - 'root' => '/workbench/{vendor}/{package}' * - 'include' => '/' // which means all vendor/package combinations * (4.2) - 'file' => 'src/lang/{locale}/{group}' * (5.x) - 'file' => 'resources/lang/{locale}/{group}' * * vendor - 'db_group' => 'vnd:{vendor}.{package}::{group}' * - 'root' => '/vendor/{vendor}/{package}' * - 'include' => [] // which means no vendor/package combinations will be included * (4.2) - 'file' => 'src/lang/{locale}/{group}' * (5.x) - 'file' => 'resources/lang/{locale}/{group}' * * * The above sections have other defaults that will be added when the config file is processed. The other * sections will only have their 'include' converted from a string to an array, or an empty array * will be added * * array values can have the following: * * 'root' - the path spec for the path common between versions and packages * * 'file' - the path spec, when appended to 'root' that will result in the language file once .php * is appended to it. see above * * The combined 'root' and 'file' strings are appended, and the resulting path searched with * {vendor}, {package}, {group}, {locale} being variables that will match any directory at that * position and the actual directory name will be used as the value for the corresponding variable * in deriving the 'db_group' value. The last part of the path is expected to be a variable of the * form {name} and its value will be the name of the file (minus the .php extension). If the last * element in the path is {group} then it will also contain . separated sub-directories. Any other * named element will only match files. * * 'include' - valid for all types other than 'lang' and 'packages', it is a string or an array * defining package combinations to include, each * element in the array is assumed to be {vendor}/{package}, if either {vendor} or {package} is * missing or * then all such sub-directories will be included. Example: * * 'vsch/' - will include all packages under vsch/ * * '/laravel-translation-manager' - all packages of that name regardless of the vendor will be * included. * * '/' - will include all vendors and packages. * * NOTE: if the array is empty then no vendors will be included. This is the default for * 'vendors' and '/' default for 'workbench' which will include language files * for all workbench packages. * * @type array * * Please read above before changing. */ 'language_dirs' => [ 'lang' => '/lang/{locale}/{group}', 'packages' => '/lang/vendor/{package}/{locale}/{group}', 'workbench' => [ 'include' => '*/*', 'root' => '/workbench/{vendor}/{package}', 'files' => 'lang/{locale}/{group}', ], 'vendor' => [ 'include' => [], 'root' => '/vendor/{vendor}/{package}', 'files' => 'lang/{locale}/{group}', ], /* * add packages that need special mapping to their language files because they don't use the standard Laravel * layout for the version that you are using or just plain not Laravel layout. Add '__merge' key with names of * sections where the package should be attempted to be merged. * * These will be merged with vendor or workbench type to get the rest of the config information. * The sections will be checked in the order listed in the __merge entry. The first section whose include * accepts the vendor/package used here, will be used. All the other sections, listed in __merge, will be ignored. * * Since vendor section requires opt-in, it is listed first, if this package is included then * it will be a vendor type. * * NOTE: Regardless of whether the directory exists or not under vendor if the vendor section includes this * package, then it will be expected to be in the vendor directory. If it is not then no language files will be * loaded for it. Therefore only include in vendor section if it is not actually located in workbench. */ /* * This one requires a very different definition. The file names are the locale.php, therefore more guts are * exposed when defining the mapping of this one. Including a hard-coded value for the {group} since the only * other option is to replace Lang dir with {group}, but then the group will be called Lang, seems a bit out of * place from the rest of the packages. So there is a way to just hard code the group to any string. */ 'nesbot/carbon' => [ '__merge' => ['vendor', 'workbench'], 'files' => 'src/Carbon/Lang/{locale}', 'vars' => [ '{group}' => 'carbon', ], ], ], /** * Provide the prefix for the root of the zip file * if a path from language_dirs does not start with this prefix then language files exported * for that part will include the full path. Therefore define the most common root path * / means application root. */ 'zip_root' => '/resources', 'disable-react-ui' => true, 'disable-react-ui-link' => true, ]; ================================================ FILE: config/laravellocalization.php ================================================ [ // 'ace' => ['name' => 'Achinese', 'script' => 'Latn', 'native' => 'Aceh', 'regional' => ''], // 'af' => ['name' => 'Afrikaans', 'script' => 'Latn', 'native' => 'Afrikaans', 'regional' => 'af_ZA'], // 'agq' => ['name' => 'Aghem', 'script' => 'Latn', 'native' => 'Aghem', 'regional' => ''], // 'ak' => ['name' => 'Akan', 'script' => 'Latn', 'native' => 'Akan', 'regional' => 'ak_GH'], // 'an' => ['name' => 'Aragonese', 'script' => 'Latn', 'native' => 'aragonés', 'regional' => 'an_ES'], // 'cch' => ['name' => 'Atsam', 'script' => 'Latn', 'native' => 'Atsam', 'regional' => ''], // 'gn' => ['name' => 'Guaraní', 'script' => 'Latn', 'native' => 'Avañe’ẽ', 'regional' => ''], // 'ae' => ['name' => 'Avestan', 'script' => 'Latn', 'native' => 'avesta', 'regional' => ''], // 'ay' => ['name' => 'Aymara', 'script' => 'Latn', 'native' => 'aymar aru', 'regional' => 'ay_PE'], // 'az' => ['name' => 'Azerbaijani (Latin)', 'script' => 'Latn', 'native' => 'azərbaycanca', 'regional' => 'az_AZ'], // 'id' => ['name' => 'Indonesian', 'script' => 'Latn', 'native' => 'Bahasa Indonesia', 'regional' => 'id_ID'], // 'ms' => ['name' => 'Malay', 'script' => 'Latn', 'native' => 'Bahasa Melayu', 'regional' => 'ms_MY'], // 'bm' => ['name' => 'Bambara', 'script' => 'Latn', 'native' => 'bamanakan', 'regional' => ''], // 'jv' => ['name' => 'Javanese (Latin)', 'script' => 'Latn', 'native' => 'Basa Jawa', 'regional' => ''], // 'su' => ['name' => 'Sundanese', 'script' => 'Latn', 'native' => 'Basa Sunda', 'regional' => ''], // 'bh' => ['name' => 'Bihari', 'script' => 'Latn', 'native' => 'Bihari', 'regional' => ''], // 'bi' => ['name' => 'Bislama', 'script' => 'Latn', 'native' => 'Bislama', 'regional' => ''], // 'nb' => ['name' => 'Norwegian Bokmål', 'script' => 'Latn', 'native' => 'Bokmål', 'regional' => 'nb_NO'], // 'bs' => ['name' => 'Bosnian', 'script' => 'Latn', 'native' => 'bosanski', 'regional' => 'bs_BA'], // 'br' => ['name' => 'Breton', 'script' => 'Latn', 'native' => 'brezhoneg', 'regional' => 'br_FR'], 'ca' => ['name' => 'Catalan', 'script' => 'Latn', 'native' => 'Català', 'regional' => 'ca_ES'], // 'ch' => ['name' => 'Chamorro', 'script' => 'Latn', 'native' => 'Chamoru', 'regional' => ''], // 'ny' => ['name' => 'Chewa', 'script' => 'Latn', 'native' => 'chiCheŵa', 'regional' => ''], // 'kde' => ['name' => 'Makonde', 'script' => 'Latn', 'native' => 'Chimakonde', 'regional' => ''], // 'sn' => ['name' => 'Shona', 'script' => 'Latn', 'native' => 'chiShona', 'regional' => ''], // 'co' => ['name' => 'Corsican', 'script' => 'Latn', 'native' => 'corsu', 'regional' => ''], // 'cy' => ['name' => 'Welsh', 'script' => 'Latn', 'native' => 'Cymraeg', 'regional' => 'cy_GB'], // 'da' => ['name' => 'Danish', 'script' => 'Latn', 'native' => 'dansk', 'regional' => 'da_DK'], // 'se' => ['name' => 'Northern Sami', 'script' => 'Latn', 'native' => 'davvisámegiella', 'regional' => 'se_NO'], 'de' => ['name' => 'German', 'script' => 'Latn', 'native' => 'Deutsch', 'regional' => 'de_DE'], // 'luo' => ['name' => 'Luo', 'script' => 'Latn', 'native' => 'Dholuo', 'regional' => ''], // 'nv' => ['name' => 'Navajo', 'script' => 'Latn', 'native' => 'Diné bizaad', 'regional' => ''], // 'dua' => ['name' => 'Duala', 'script' => 'Latn', 'native' => 'duálá', 'regional' => ''], // 'et' => ['name' => 'Estonian', 'script' => 'Latn', 'native' => 'eesti', 'regional' => 'et_EE'], // 'na' => ['name' => 'Nauru', 'script' => 'Latn', 'native' => 'Ekakairũ Naoero', 'regional' => ''], // 'guz' => ['name' => 'Ekegusii', 'script' => 'Latn', 'native' => 'Ekegusii', 'regional' => ''], 'en' => ['name' => 'English', 'script' => 'Latn', 'native' => 'UK English', 'regional' => 'en_GB'], // 'en-AU' => ['name' => 'Australian English', 'script' => 'Latn', 'native' => 'Australian English', 'regional' => 'en_AU'], // 'en-GB' => ['name' => 'British English', 'script' => 'Latn', 'native' => 'British English', 'regional' => 'en_GB'], 'en-US' => ['name' => 'U.S. English', 'script' => 'Latn', 'native' => 'U.S. English', 'regional' => 'en_US'], 'es' => ['name' => 'Spanish', 'script' => 'Latn', 'native' => 'Español', 'regional' => 'es_ES'], // 'eo' => ['name' => 'Esperanto', 'script' => 'Latn', 'native' => 'esperanto', 'regional' => ''], // 'eu' => ['name' => 'Basque', 'script' => 'Latn', 'native' => 'euskara', 'regional' => 'eu_ES'], // 'ewo' => ['name' => 'Ewondo', 'script' => 'Latn', 'native' => 'ewondo', 'regional' => ''], // 'ee' => ['name' => 'Ewe', 'script' => 'Latn', 'native' => 'eʋegbe', 'regional' => ''], // 'fil' => ['name' => 'Filipino', 'script' => 'Latn', 'native' => 'Filipino', 'regional' => 'fil_PH'], 'fr' => ['name' => 'French', 'script' => 'Latn', 'native' => 'Français', 'regional' => 'fr_FR'], // 'fr-CA' => ['name' => 'Canadian French', 'script' => 'Latn', 'native' => 'français canadien', 'regional' => 'fr_CA'], // 'fy' => ['name' => 'Western Frisian', 'script' => 'Latn', 'native' => 'frysk', 'regional' => 'fy_DE'], // 'fur' => ['name' => 'Friulian', 'script' => 'Latn', 'native' => 'furlan', 'regional' => 'fur_IT'], // 'fo' => ['name' => 'Faroese', 'script' => 'Latn', 'native' => 'føroyskt', 'regional' => 'fo_FO'], // 'gaa' => ['name' => 'Ga', 'script' => 'Latn', 'native' => 'Ga', 'regional' => ''], // 'ga' => ['name' => 'Irish', 'script' => 'Latn', 'native' => 'Gaeilge', 'regional' => 'ga_IE'], 'gl' => ['name' => 'Galician', 'script' => 'Latn', 'native' => 'Galego', 'regional' => 'gl_ES'], // 'gv' => ['name' => 'Manx', 'script' => 'Latn', 'native' => 'Gaelg', 'regional' => 'gv_GB'], // 'sm' => ['name' => 'Samoan', 'script' => 'Latn', 'native' => 'Gagana fa’a Sāmoa', 'regional' => ''], // 'gl' => ['name' => 'Galician', 'script' => 'Latn', 'native' => 'galego', 'regional' => 'gl_ES'], // 'ki' => ['name' => 'Kikuyu', 'script' => 'Latn', 'native' => 'Gikuyu', 'regional' => ''], // 'gd' => ['name' => 'Scottish Gaelic', 'script' => 'Latn', 'native' => 'Gàidhlig', 'regional' => 'gd_GB'], // 'ha' => ['name' => 'Hausa', 'script' => 'Latn', 'native' => 'Hausa', 'regional' => 'ha_NG'], // 'bez' => ['name' => 'Bena', 'script' => 'Latn', 'native' => 'Hibena', 'regional' => ''], // 'ho' => ['name' => 'Hiri Motu', 'script' => 'Latn', 'native' => 'Hiri Motu', 'regional' => ''], 'hr' => ['name' => 'Croatian', 'script' => 'Latn', 'native' => 'Hrvatski', 'regional' => 'hr_HR'], // 'bem' => ['name' => 'Bemba', 'script' => 'Latn', 'native' => 'Ichibemba', 'regional' => 'bem_ZM'], // 'io' => ['name' => 'Ido', 'script' => 'Latn', 'native' => 'Ido', 'regional' => ''], // 'ig' => ['name' => 'Igbo', 'script' => 'Latn', 'native' => 'Igbo', 'regional' => 'ig_NG'], // 'rn' => ['name' => 'Rundi', 'script' => 'Latn', 'native' => 'Ikirundi', 'regional' => ''], // 'ia' => ['name' => 'Interlingua', 'script' => 'Latn', 'native' => 'interlingua', 'regional' => 'ia_FR'], // 'iu-Latn' => ['name' => 'Inuktitut (Latin)', 'script' => 'Latn', 'native' => 'Inuktitut', 'regional' => 'iu_CA'], // 'sbp' => ['name' => 'Sileibi', 'script' => 'Latn', 'native' => 'Ishisangu', 'regional' => ''], // 'nd' => ['name' => 'North Ndebele', 'script' => 'Latn', 'native' => 'isiNdebele', 'regional' => ''], // 'nr' => ['name' => 'South Ndebele', 'script' => 'Latn', 'native' => 'isiNdebele', 'regional' => 'nr_ZA'], // 'xh' => ['name' => 'Xhosa', 'script' => 'Latn', 'native' => 'isiXhosa', 'regional' => 'xh_ZA'], // 'zu' => ['name' => 'Zulu', 'script' => 'Latn', 'native' => 'isiZulu', 'regional' => 'zu_ZA'], 'it' => ['name' => 'Italian', 'script' => 'Latn', 'native' => 'Italiano', 'regional' => 'it_IT'], // 'ik' => ['name' => 'Inupiaq', 'script' => 'Latn', 'native' => 'Iñupiaq', 'regional' => 'ik_CA'], // 'dyo' => ['name' => 'Jola-Fonyi', 'script' => 'Latn', 'native' => 'joola', 'regional' => ''], // 'kea' => ['name' => 'Kabuverdianu', 'script' => 'Latn', 'native' => 'kabuverdianu', 'regional' => ''], // 'kaj' => ['name' => 'Jju', 'script' => 'Latn', 'native' => 'Kaje', 'regional' => ''], // 'mh' => ['name' => 'Marshallese', 'script' => 'Latn', 'native' => 'Kajin M̧ajeļ', 'regional' => 'mh_MH'], // 'kl' => ['name' => 'Kalaallisut', 'script' => 'Latn', 'native' => 'kalaallisut', 'regional' => 'kl_GL'], // 'kln' => ['name' => 'Kalenjin', 'script' => 'Latn', 'native' => 'Kalenjin', 'regional' => ''], // 'kr' => ['name' => 'Kanuri', 'script' => 'Latn', 'native' => 'Kanuri', 'regional' => ''], // 'kcg' => ['name' => 'Tyap', 'script' => 'Latn', 'native' => 'Katab', 'regional' => ''], // 'kw' => ['name' => 'Cornish', 'script' => 'Latn', 'native' => 'kernewek', 'regional' => 'kw_GB'], // 'naq' => ['name' => 'Nama', 'script' => 'Latn', 'native' => 'Khoekhoegowab', 'regional' => ''], // 'rof' => ['name' => 'Rombo', 'script' => 'Latn', 'native' => 'Kihorombo', 'regional' => ''], // 'kam' => ['name' => 'Kamba', 'script' => 'Latn', 'native' => 'Kikamba', 'regional' => ''], // 'kg' => ['name' => 'Kongo', 'script' => 'Latn', 'native' => 'Kikongo', 'regional' => ''], // 'jmc' => ['name' => 'Machame', 'script' => 'Latn', 'native' => 'Kimachame', 'regional' => ''], // 'rw' => ['name' => 'Kinyarwanda', 'script' => 'Latn', 'native' => 'Kinyarwanda', 'regional' => 'rw_RW'], // 'asa' => ['name' => 'Kipare', 'script' => 'Latn', 'native' => 'Kipare', 'regional' => ''], // 'rwk' => ['name' => 'Rwa', 'script' => 'Latn', 'native' => 'Kiruwa', 'regional' => ''], // 'saq' => ['name' => 'Samburu', 'script' => 'Latn', 'native' => 'Kisampur', 'regional' => ''], // 'ksb' => ['name' => 'Shambala', 'script' => 'Latn', 'native' => 'Kishambaa', 'regional' => ''], // 'swc' => ['name' => 'Congo Swahili', 'script' => 'Latn', 'native' => 'Kiswahili ya Kongo', 'regional' => ''], // 'sw' => ['name' => 'Swahili', 'script' => 'Latn', 'native' => 'Kiswahili', 'regional' => 'sw_KE'], // 'dav' => ['name' => 'Dawida', 'script' => 'Latn', 'native' => 'Kitaita', 'regional' => ''], // 'teo' => ['name' => 'Teso', 'script' => 'Latn', 'native' => 'Kiteso', 'regional' => ''], // 'khq' => ['name' => 'Koyra Chiini', 'script' => 'Latn', 'native' => 'Koyra ciini', 'regional' => ''], // 'ses' => ['name' => 'Songhay', 'script' => 'Latn', 'native' => 'Koyraboro senni', 'regional' => ''], // 'mfe' => ['name' => 'Morisyen', 'script' => 'Latn', 'native' => 'kreol morisien', 'regional' => ''], // 'ht' => ['name' => 'Haitian', 'script' => 'Latn', 'native' => 'Kreyòl ayisyen', 'regional' => 'ht_HT'], // 'kj' => ['name' => 'Kuanyama', 'script' => 'Latn', 'native' => 'Kwanyama', 'regional' => ''], // 'ksh' => ['name' => 'Kölsch', 'script' => 'Latn', 'native' => 'Kölsch', 'regional' => ''], // 'ebu' => ['name' => 'Kiembu', 'script' => 'Latn', 'native' => 'Kĩembu', 'regional' => ''], // 'mer' => ['name' => 'Kimîîru', 'script' => 'Latn', 'native' => 'Kĩmĩrũ', 'regional' => ''], // 'lag' => ['name' => 'Langi', 'script' => 'Latn', 'native' => 'Kɨlaangi', 'regional' => ''], // 'lah' => ['name' => 'Lahnda', 'script' => 'Latn', 'native' => 'Lahnda', 'regional' => ''], // 'la' => ['name' => 'Latin', 'script' => 'Latn', 'native' => 'latine', 'regional' => ''], // 'lv' => ['name' => 'Latvian', 'script' => 'Latn', 'native' => 'latviešu', 'regional' => 'lv_LV'], // 'to' => ['name' => 'Tongan', 'script' => 'Latn', 'native' => 'lea fakatonga', 'regional' => ''], // 'lt' => ['name' => 'Lithuanian', 'script' => 'Latn', 'native' => 'lietuvių', 'regional' => 'lt_LT'], // 'li' => ['name' => 'Limburgish', 'script' => 'Latn', 'native' => 'Limburgs', 'regional' => 'li_BE'], // 'ln' => ['name' => 'Lingala', 'script' => 'Latn', 'native' => 'lingála', 'regional' => ''], // 'lg' => ['name' => 'Ganda', 'script' => 'Latn', 'native' => 'Luganda', 'regional' => 'lg_UG'], // 'luy' => ['name' => 'Oluluyia', 'script' => 'Latn', 'native' => 'Luluhia', 'regional' => ''], // 'lb' => ['name' => 'Luxembourgish', 'script' => 'Latn', 'native' => 'Lëtzebuergesch', 'regional' => 'lb_LU'], 'hu' => ['name' => 'Hungarian', 'script' => 'Latn', 'native' => 'Magyar', 'regional' => 'hu_HU'], // 'mgh' => ['name' => 'Makhuwa-Meetto', 'script' => 'Latn', 'native' => 'Makua', 'regional' => ''], // 'mg' => ['name' => 'Malagasy', 'script' => 'Latn', 'native' => 'Malagasy', 'regional' => 'mg_MG'], // 'mt' => ['name' => 'Maltese', 'script' => 'Latn', 'native' => 'Malti', 'regional' => 'mt_MT'], // 'mtr' => ['name' => 'Mewari', 'script' => 'Latn', 'native' => 'Mewari', 'regional' => ''], // 'mua' => ['name' => 'Mundang', 'script' => 'Latn', 'native' => 'Mundang', 'regional' => ''], // 'mi' => ['name' => 'Māori', 'script' => 'Latn', 'native' => 'Māori', 'regional' => 'mi_NZ'], 'nl' => ['name' => 'Dutch', 'script' => 'Latn', 'native' => 'Nederlands', 'regional' => 'nl_NL'], // 'nmg' => ['name' => 'Kwasio', 'script' => 'Latn', 'native' => 'ngumba', 'regional' => ''], // 'yav' => ['name' => 'Yangben', 'script' => 'Latn', 'native' => 'nuasue', 'regional' => ''], // 'nn' => ['name' => 'Norwegian Nynorsk', 'script' => 'Latn', 'native' => 'nynorsk', 'regional' => 'nn_NO'], // 'oc' => ['name' => 'Occitan', 'script' => 'Latn', 'native' => 'occitan', 'regional' => 'oc_FR'], // 'ang' => ['name' => 'Old English', 'script' => 'Runr', 'native' => 'Old English', 'regional' => ''], // 'xog' => ['name' => 'Soga', 'script' => 'Latn', 'native' => 'Olusoga', 'regional' => ''], // 'om' => ['name' => 'Oromo', 'script' => 'Latn', 'native' => 'Oromoo', 'regional' => 'om_ET'], // 'ng' => ['name' => 'Ndonga', 'script' => 'Latn', 'native' => 'OshiNdonga', 'regional' => ''], // 'hz' => ['name' => 'Herero', 'script' => 'Latn', 'native' => 'Otjiherero', 'regional' => ''], // 'uz-Latn' => ['name' => 'Uzbek (Latin)', 'script' => 'Latn', 'native' => 'oʼzbekcha', 'regional' => 'uz_UZ'], // 'nds' => ['name' => 'Low German', 'script' => 'Latn', 'native' => 'Plattdüütsch', 'regional' => 'nds_DE'], 'pl' => ['name' => 'Polish', 'script' => 'Latn', 'native' => 'Polski', 'regional' => 'pl_PL'], // 'pt' => ['name' => 'Portuguese', 'script' => 'Latn', 'native' => 'português', 'regional' => 'pt_PT'], 'pt-BR' => ['name' => 'Brazilian Portuguese', 'script' => 'Latn', 'native' => 'Português do Brasil', 'regional' => 'pt-BR'], // 'ff' => ['name' => 'Fulah', 'script' => 'Latn', 'native' => 'Pulaar', 'regional' => 'ff_SN'], // 'pi' => ['name' => 'Pahari-Potwari', 'script' => 'Latn', 'native' => 'Pāli', 'regional' => ''], // 'aa' => ['name' => 'Afar', 'script' => 'Latn', 'native' => 'Qafar', 'regional' => 'aa_ER'], // 'ty' => ['name' => 'Tahitian', 'script' => 'Latn', 'native' => 'Reo Māohi', 'regional' => ''], // 'ksf' => ['name' => 'Bafia', 'script' => 'Latn', 'native' => 'rikpa', 'regional' => ''], // 'ro' => ['name' => 'Romanian', 'script' => 'Latn', 'native' => 'română', 'regional' => 'ro_RO'], // 'cgg' => ['name' => 'Chiga', 'script' => 'Latn', 'native' => 'Rukiga', 'regional' => ''], // 'rm' => ['name' => 'Romansh', 'script' => 'Latn', 'native' => 'rumantsch', 'regional' => ''], // 'qu' => ['name' => 'Quechua', 'script' => 'Latn', 'native' => 'Runa Simi', 'regional' => ''], // 'nyn' => ['name' => 'Nyankole', 'script' => 'Latn', 'native' => 'Runyankore', 'regional' => ''], // 'ssy' => ['name' => 'Saho', 'script' => 'Latn', 'native' => 'Saho', 'regional' => ''], // 'sc' => ['name' => 'Sardinian', 'script' => 'Latn', 'native' => 'sardu', 'regional' => 'sc_IT'], // 'de-CH' => ['name' => 'Swiss High German', 'script' => 'Latn', 'native' => 'Schweizer Hochdeutsch', 'regional' => 'de_CH'], // 'gsw' => ['name' => 'Swiss German', 'script' => 'Latn', 'native' => 'Schwiizertüütsch', 'regional' => ''], // 'trv' => ['name' => 'Taroko', 'script' => 'Latn', 'native' => 'Seediq', 'regional' => ''], // 'seh' => ['name' => 'Sena', 'script' => 'Latn', 'native' => 'sena', 'regional' => ''], // 'nso' => ['name' => 'Northern Sotho', 'script' => 'Latn', 'native' => 'Sesotho sa Leboa', 'regional' => 'nso_ZA'], // 'st' => ['name' => 'Southern Sotho', 'script' => 'Latn', 'native' => 'Sesotho', 'regional' => 'st_ZA'], // 'tn' => ['name' => 'Tswana', 'script' => 'Latn', 'native' => 'Setswana', 'regional' => 'tn_ZA'], // 'sq' => ['name' => 'Albanian', 'script' => 'Latn', 'native' => 'shqip', 'regional' => 'sq_AL'], // 'sid' => ['name' => 'Sidamo', 'script' => 'Latn', 'native' => 'Sidaamu Afo', 'regional' => 'sid_ET'], // 'ss' => ['name' => 'Swati', 'script' => 'Latn', 'native' => 'Siswati', 'regional' => 'ss_ZA'], 'sk' => ['name' => 'Slovak', 'script' => 'Latn', 'native' => 'Slovenský', 'regional' => 'sk_SK'], // 'sl' => ['name' => 'Slovene', 'script' => 'Latn', 'native' => 'slovenščina', 'regional' => 'sl_SI'], // 'so' => ['name' => 'Somali', 'script' => 'Latn', 'native' => 'Soomaali', 'regional' => 'so_SO'], // 'sr-Latn' => ['name' => 'Serbian (Latin)', 'script' => 'Latn', 'native' => 'Srpski', 'regional' => 'sr_RS'], // 'sh' => ['name' => 'Serbo-Croatian', 'script' => 'Latn', 'native' => 'srpskohrvatski', 'regional' => ''], // 'fi' => ['name' => 'Finnish', 'script' => 'Latn', 'native' => 'suomi', 'regional' => 'fi_FI'], // 'sv' => ['name' => 'Swedish', 'script' => 'Latn', 'native' => 'svenska', 'regional' => 'sv_SE'], // 'sg' => ['name' => 'Sango', 'script' => 'Latn', 'native' => 'Sängö', 'regional' => ''], // 'tl' => ['name' => 'Tagalog', 'script' => 'Latn', 'native' => 'Tagalog', 'regional' => 'tl_PH'], // 'tzm-Latn' => ['name' => 'Central Atlas Tamazight (Latin)', 'script' => 'Latn', 'native' => 'Tamazight', 'regional' => ''], // 'kab' => ['name' => 'Kabyle', 'script' => 'Latn', 'native' => 'Taqbaylit', 'regional' => 'kab_DZ'], // 'twq' => ['name' => 'Tasawaq', 'script' => 'Latn', 'native' => 'Tasawaq senni', 'regional' => ''], // 'shi' => ['name' => 'Tachelhit (Latin)', 'script' => 'Latn', 'native' => 'Tashelhit', 'regional' => ''], // 'nus' => ['name' => 'Nuer', 'script' => 'Latn', 'native' => 'Thok Nath', 'regional' => ''], // 'vi' => ['name' => 'Vietnamese', 'script' => 'Latn', 'native' => 'Tiếng Việt', 'regional' => 'vi_VN'], // 'tg-Latn' => ['name' => 'Tajik (Latin)', 'script' => 'Latn', 'native' => 'tojikī', 'regional' => 'tg_TJ'], // 'lu' => ['name' => 'Luba-Katanga', 'script' => 'Latn', 'native' => 'Tshiluba', 'regional' => 've_ZA'], // 've' => ['name' => 'Venda', 'script' => 'Latn', 'native' => 'Tshivenḓa', 'regional' => ''], // 'tw' => ['name' => 'Twi', 'script' => 'Latn', 'native' => 'Twi', 'regional' => ''], // 'tr' => ['name' => 'Turkish', 'script' => 'Latn', 'native' => 'Türkçe', 'regional' => 'tr_TR'], // 'ale' => ['name' => 'Aleut', 'script' => 'Latn', 'native' => 'Unangax tunuu', 'regional' => ''], // 'ca-valencia' => ['name' => 'Valencian', 'script' => 'Latn', 'native' => 'valencià', 'regional' => ''], // 'vai-Latn' => ['name' => 'Vai (Latin)', 'script' => 'Latn', 'native' => 'Viyamíĩ', 'regional' => ''], // 'vo' => ['name' => 'Volapük', 'script' => 'Latn', 'native' => 'Volapük', 'regional' => ''], // 'fj' => ['name' => 'Fijian', 'script' => 'Latn', 'native' => 'vosa Vakaviti', 'regional' => ''], // 'wa' => ['name' => 'Walloon', 'script' => 'Latn', 'native' => 'Walon', 'regional' => 'wa_BE'], // 'wae' => ['name' => 'Walser', 'script' => 'Latn', 'native' => 'Walser', 'regional' => 'wae_CH'], // 'wen' => ['name' => 'Sorbian', 'script' => 'Latn', 'native' => 'Wendic', 'regional' => ''], // 'wo' => ['name' => 'Wolof', 'script' => 'Latn', 'native' => 'Wolof', 'regional' => 'wo_SN'], // 'ts' => ['name' => 'Tsonga', 'script' => 'Latn', 'native' => 'Xitsonga', 'regional' => 'ts_ZA'], // 'dje' => ['name' => 'Zarma', 'script' => 'Latn', 'native' => 'Zarmaciine', 'regional' => ''], // 'yo' => ['name' => 'Yoruba', 'script' => 'Latn', 'native' => 'Èdè Yorùbá', 'regional' => 'yo_NG'], // 'de-AT' => ['name' => 'Austrian German', 'script' => 'Latn', 'native' => 'Österreichisches Deutsch', 'regional' => 'de_AT'], // 'is' => ['name' => 'Icelandic', 'script' => 'Latn', 'native' => 'íslenska', 'regional' => 'is_IS'], // 'cs' => ['name' => 'Czech', 'script' => 'Latn', 'native' => 'čeština', 'regional' => 'cs_CZ'], // 'bas' => ['name' => 'Basa', 'script' => 'Latn', 'native' => 'Ɓàsàa', 'regional' => ''], // 'mas' => ['name' => 'Masai', 'script' => 'Latn', 'native' => 'ɔl-Maa', 'regional' => ''], // 'haw' => ['name' => 'Hawaiian', 'script' => 'Latn', 'native' => 'ʻŌlelo Hawaiʻi', 'regional' => ''], // 'el' => ['name' => 'Greek', 'script' => 'Grek', 'native' => 'Ελληνικά', 'regional' => 'el_GR'], // 'uz' => ['name' => 'Uzbek (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Ўзбек', 'regional' => 'uz_UZ'], // 'az-Cyrl' => ['name' => 'Azerbaijani (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Азәрбајҹан', 'regional' => 'uz_UZ'], // 'ab' => ['name' => 'Abkhazian', 'script' => 'Cyrl', 'native' => 'Аҧсуа', 'regional' => ''], // 'os' => ['name' => 'Ossetic', 'script' => 'Cyrl', 'native' => 'Ирон', 'regional' => 'os_RU'], // 'ky' => ['name' => 'Kyrgyz', 'script' => 'Cyrl', 'native' => 'Кыргыз', 'regional' => 'ky_KG'], // 'sr' => ['name' => 'Serbian (Cyrillic)', 'script' => 'Cyrl', 'native' => 'Српски', 'regional' => 'sr_RS'], // 'av' => ['name' => 'Avaric', 'script' => 'Cyrl', 'native' => 'авар мацӀ', 'regional' => ''], // 'ady' => ['name' => 'Adyghe', 'script' => 'Cyrl', 'native' => 'адыгэбзэ', 'regional' => ''], // 'ba' => ['name' => 'Bashkir', 'script' => 'Cyrl', 'native' => 'башҡорт теле', 'regional' => ''], // 'be' => ['name' => 'Belarusian', 'script' => 'Cyrl', 'native' => 'беларуская', 'regional' => 'be_BY'], // 'bg' => ['name' => 'Bulgarian', 'script' => 'Cyrl', 'native' => 'български', 'regional' => 'bg_BG'], // 'kv' => ['name' => 'Komi', 'script' => 'Cyrl', 'native' => 'коми кыв', 'regional' => ''], // 'mk' => ['name' => 'Macedonian', 'script' => 'Cyrl', 'native' => 'македонски', 'regional' => 'mk_MK'], // 'mn' => ['name' => 'Mongolian (Cyrillic)', 'script' => 'Cyrl', 'native' => 'монгол', 'regional' => 'mn_MN'], // 'ce' => ['name' => 'Chechen', 'script' => 'Cyrl', 'native' => 'нохчийн мотт', 'regional' => 'ce_RU'], 'ru' => ['name' => 'Russian', 'script' => 'Cyrl', 'native' => 'Pусский', 'regional' => 'ru_RU'], // 'sah' => ['name' => 'Yakut', 'script' => 'Cyrl', 'native' => 'саха тыла', 'regional' => ''], // 'tt' => ['name' => 'Tatar', 'script' => 'Cyrl', 'native' => 'татар теле', 'regional' => 'tt_RU'], // 'tg' => ['name' => 'Tajik (Cyrillic)', 'script' => 'Cyrl', 'native' => 'тоҷикӣ', 'regional' => 'tg_TJ'], // 'tk' => ['name' => 'Turkmen', 'script' => 'Cyrl', 'native' => 'түркменче', 'regional' => 'tk_TM'], // 'uk' => ['name' => 'Ukrainian', 'script' => 'Cyrl', 'native' => 'українська', 'regional' => 'uk_UA'], // 'cv' => ['name' => 'Chuvash', 'script' => 'Cyrl', 'native' => 'чӑваш чӗлхи', 'regional' => 'cv_RU'], // 'cu' => ['name' => 'Church Slavic', 'script' => 'Cyrl', 'native' => 'ѩзыкъ словѣньскъ', 'regional' => ''], // 'kk' => ['name' => 'Kazakh', 'script' => 'Cyrl', 'native' => 'қазақ тілі', 'regional' => 'kk_KZ'], // 'hy' => ['name' => 'Armenian', 'script' => 'Armn', 'native' => 'Հայերեն', 'regional' => 'hy_AM'], // 'yi' => ['name' => 'Yiddish', 'script' => 'Hebr', 'native' => 'ייִדיש', 'regional' => 'yi_US'], // 'he' => ['name' => 'Hebrew', 'script' => 'Hebr', 'native' => 'עברית', 'regional' => 'he_IL'], // 'ug' => ['name' => 'Uyghur', 'script' => 'Arab', 'native' => 'ئۇيغۇرچە', 'regional' => 'ug_CN'], // 'ur' => ['name' => 'Urdu', 'script' => 'Arab', 'native' => 'اردو', 'regional' => 'ur_PK'], // 'ar' => ['name' => 'Arabic', 'script' => 'Arab', 'native' => 'العربية', 'regional' => 'ar_AE'], // 'uz-Arab' => ['name' => 'Uzbek (Arabic)', 'script' => 'Arab', 'native' => 'اۉزبېک', 'regional' => ''], // 'tg-Arab' => ['name' => 'Tajik (Arabic)', 'script' => 'Arab', 'native' => 'تاجیکی', 'regional' => 'tg_TJ'], // 'sd' => ['name' => 'Sindhi', 'script' => 'Arab', 'native' => 'سنڌي', 'regional' => 'sd_IN'], // 'fa' => ['name' => 'Persian', 'script' => 'Arab', 'native' => 'فارسی', 'regional' => 'fa_IR'], // 'pa-Arab' => ['name' => 'Punjabi (Arabic)', 'script' => 'Arab', 'native' => 'پنجاب', 'regional' => 'pa_IN'], // 'ps' => ['name' => 'Pashto', 'script' => 'Arab', 'native' => 'پښتو', 'regional' => 'ps_AF'], // 'ks' => ['name' => 'Kashmiri (Arabic)', 'script' => 'Arab', 'native' => 'کأشُر', 'regional' => 'ks_IN'], // 'ku' => ['name' => 'Kurdish', 'script' => 'Arab', 'native' => 'کوردی', 'regional' => 'ku_TR'], // 'dv' => ['name' => 'Divehi', 'script' => 'Thaa', 'native' => 'ދިވެހިބަސް', 'regional' => 'dv_MV'], // 'ks-Deva' => ['name' => 'Kashmiri (Devaganari)', 'script' => 'Deva', 'native' => 'कॉशुर', 'regional' => 'ks_IN'], // 'kok' => ['name' => 'Konkani', 'script' => 'Deva', 'native' => 'कोंकणी', 'regional' => 'kok_IN'], // 'doi' => ['name' => 'Dogri', 'script' => 'Deva', 'native' => 'डोगरी', 'regional' => 'doi_IN'], // 'ne' => ['name' => 'Nepali', 'script' => 'Deva', 'native' => 'नेपाली', 'regional' => ''], // 'pra' => ['name' => 'Prakrit', 'script' => 'Deva', 'native' => 'प्राकृत', 'regional' => ''], // 'brx' => ['name' => 'Bodo', 'script' => 'Deva', 'native' => 'बड़ो', 'regional' => 'brx_IN'], // 'bra' => ['name' => 'Braj', 'script' => 'Deva', 'native' => 'ब्रज भाषा', 'regional' => ''], // 'mr' => ['name' => 'Marathi', 'script' => 'Deva', 'native' => 'मराठी', 'regional' => 'mr_IN'], // 'mai' => ['name' => 'Maithili', 'script' => 'Tirh', 'native' => 'मैथिली', 'regional' => 'mai_IN'], // 'raj' => ['name' => 'Rajasthani', 'script' => 'Deva', 'native' => 'राजस्थानी', 'regional' => ''], // 'sa' => ['name' => 'Sanskrit', 'script' => 'Deva', 'native' => 'संस्कृतम्', 'regional' => 'sa_IN'], // 'hi' => ['name' => 'Hindi', 'script' => 'Deva', 'native' => 'हिन्दी', 'regional' => 'hi_IN'], // 'as' => ['name' => 'Assamese', 'script' => 'Beng', 'native' => 'অসমীয়া', 'regional' => 'as_IN'], // 'bn' => ['name' => 'Bengali', 'script' => 'Beng', 'native' => 'বাংলা', 'regional' => 'bn_BD'], // 'mni' => ['name' => 'Manipuri', 'script' => 'Beng', 'native' => 'মৈতৈ', 'regional' => 'mni_IN'], // 'pa' => ['name' => 'Punjabi (Gurmukhi)', 'script' => 'Guru', 'native' => 'ਪੰਜਾਬੀ', 'regional' => 'pa_IN'], // 'gu' => ['name' => 'Gujarati', 'script' => 'Gujr', 'native' => 'ગુજરાતી', 'regional' => 'gu_IN'], // 'or' => ['name' => 'Oriya', 'script' => 'Orya', 'native' => 'ଓଡ଼ିଆ', 'regional' => 'or_IN'], // 'ta' => ['name' => 'Tamil', 'script' => 'Taml', 'native' => 'தமிழ்', 'regional' => 'ta_IN'], // 'te' => ['name' => 'Telugu', 'script' => 'Telu', 'native' => 'తెలుగు', 'regional' => 'te_IN'], // 'kn' => ['name' => 'Kannada', 'script' => 'Knda', 'native' => 'ಕನ್ನಡ', 'regional' => 'kn_IN'], // 'ml' => ['name' => 'Malayalam', 'script' => 'Mlym', 'native' => 'മലയാളം', 'regional' => 'ml_IN'], // 'si' => ['name' => 'Sinhala', 'script' => 'Sinh', 'native' => 'සිංහල', 'regional' => 'si_LK'], // 'th' => ['name' => 'Thai', 'script' => 'Thai', 'native' => 'ไทย', 'regional' => 'th_TH'], // 'lo' => ['name' => 'Lao', 'script' => 'Laoo', 'native' => 'ລາວ', 'regional' => 'lo_LA'], // 'bo' => ['name' => 'Tibetan', 'script' => 'Tibt', 'native' => 'པོད་སྐད་', 'regional' => 'bo_IN'], // 'dz' => ['name' => 'Dzongkha', 'script' => 'Tibt', 'native' => 'རྫོང་ཁ', 'regional' => 'dz_BT'], // 'my' => ['name' => 'Burmese', 'script' => 'Mymr', 'native' => 'မြန်မာဘာသာ', 'regional' => 'my_MM'], // 'ka' => ['name' => 'Georgian', 'script' => 'Geor', 'native' => 'ქართული', 'regional' => 'ka_GE'], // 'byn' => ['name' => 'Blin', 'script' => 'Ethi', 'native' => 'ብሊን', 'regional' => 'byn_ER'], // 'tig' => ['name' => 'Tigre', 'script' => 'Ethi', 'native' => 'ትግረ', 'regional' => 'tig_ER'], // 'ti' => ['name' => 'Tigrinya', 'script' => 'Ethi', 'native' => 'ትግርኛ', 'regional' => 'ti_ET'], // 'am' => ['name' => 'Amharic', 'script' => 'Ethi', 'native' => 'አማርኛ', 'regional' => 'am_ET'], // 'wal' => ['name' => 'Wolaytta', 'script' => 'Ethi', 'native' => 'ወላይታቱ', 'regional' => 'wal_ET'], // 'chr' => ['name' => 'Cherokee', 'script' => 'Cher', 'native' => 'ᏣᎳᎩ', 'regional' => ''], // 'iu' => ['name' => 'Inuktitut (Canadian Aboriginal Syllabics)', 'script' => 'Cans', 'native' => 'ᐃᓄᒃᑎᑐᑦ', 'regional' => 'iu_CA'], // 'oj' => ['name' => 'Ojibwa', 'script' => 'Cans', 'native' => 'ᐊᓂᔑᓈᐯᒧᐎᓐ', 'regional' => ''], // 'cr' => ['name' => 'Cree', 'script' => 'Cans', 'native' => 'ᓀᐦᐃᔭᐍᐏᐣ', 'regional' => ''], // 'km' => ['name' => 'Khmer', 'script' => 'Khmr', 'native' => 'ភាសាខ្មែរ', 'regional' => 'km_KH'], // 'mn-Mong' => ['name' => 'Mongolian (Mongolian)', 'script' => 'Mong', 'native' => 'ᠮᠣᠨᠭᠭᠣᠯ ᠬᠡᠯᠡ', 'regional' => 'mn_MN'], // 'shi-Tfng' => ['name' => 'Tachelhit (Tifinagh)', 'script' => 'Tfng', 'native' => 'ⵜⴰⵎⴰⵣⵉⵖⵜ', 'regional' => ''], // 'tzm' => ['name' => 'Central Atlas Tamazight (Tifinagh)','script' => 'Tfng', 'native' => 'ⵜⴰⵎⴰⵣⵉⵖⵜ', 'regional' => ''], // 'yue' => ['name' => 'Yue', 'script' => 'Hant', 'native' => '廣州話', 'regional' => 'yue_HK'], // 'ja' => ['name' => 'Japanese', 'script' => 'Jpan', 'native' => '日本語', 'regional' => 'ja_JP'], // 'zh' => ['name' => 'Chinese (Simplified)', 'script' => 'Hans', 'native' => '简体中文', 'regional' => 'zh_CN'], // 'zh-Hant' => ['name' => 'Chinese (Traditional)', 'script' => 'Hant', 'native' => '繁體中文', 'regional' => 'zh_CN'], // 'ii' => ['name' => 'Sichuan Yi', 'script' => 'Yiii', 'native' => 'ꆈꌠꉙ', 'regional' => ''], // 'vai' => ['name' => 'Vai (Vai)', 'script' => 'Vaii', 'native' => 'ꕙꔤ', 'regional' => ''], // 'jv-Java' => ['name' => 'Javanese (Javanese)', 'script' => 'Java', 'native' => 'ꦧꦱꦗꦮ', 'regional' => ''], // 'ko' => ['name' => 'Korean', 'script' => 'Hang', 'native' => '한국어', 'regional' => 'ko_KR'], ], // Negotiate for the user locale using the Accept-Language header if it's not defined in the URL? // If false, system will take app.php locale attribute 'useAcceptLanguageHeader' => true, // If LaravelLocalizationRedirectFilter is active and hideDefaultLocaleInURL // is true, the url would not have the default application language // // IMPORTANT - When hideDefaultLocaleInURL is set to true, the unlocalized root is treated as the applications default locale "app.locale". // Because of this language negotiation using the Accept-Language header will NEVER occur when hideDefaultLocaleInURL is true. // 'hideDefaultLocaleInURL' => false, // If you want to display the locales in particular order in the language selector you should write the order here. // CAUTION: Please consider using the appropriate locale code otherwise it will not work // Example: 'localesOrder' => ['es','en'], 'localesOrder' => [], ]; ================================================ FILE: config/larecipe.php ================================================ [ 'route' => '/api-docs', 'path' => '/resources/api-docs', 'landing' => 'overview', ], /* |-------------------------------------------------------------------------- | Documentation Versions |-------------------------------------------------------------------------- | | Here you may specify and set the versions and the default (latest) one | of your documentation's versions where you can redirect the user to. | Just make sure that the default version is in the published list. | | */ 'versions' => [ 'default' => '1.0', 'published' => [ '1.0', ], ], /* |-------------------------------------------------------------------------- | Documentation Settings |-------------------------------------------------------------------------- | | These options configure the additional behaviors of your documentation | where you can limit the access to only authenticated users in your | system. It is false initially so that guests can view your docs. | | You may also specify links to show under the auth dropdown menu. | Logout link will show by default. | | */ 'settings' => [ 'auth' => false, ], /* |-------------------------------------------------------------------------- | Cache |-------------------------------------------------------------------------- | | Obviously rendering markdown at the back-end is costly especially if | the rendered files are massive. Thankfully, caching is considered | as a good option to speed up your app when having high traffic. | | Caching period unit: minutes | */ 'cache' => [ 'enabled' => false, 'period' => 60, ], /* |-------------------------------------------------------------------------- | Search |-------------------------------------------------------------------------- | | Here you can add configure the search functionality of your docs. | You can choose the default engine of your search from the list | However, you can also enable/disable the search's visibility | | Supported Search Engines: 'algolia', 'internal' | */ 'search' => [ 'enabled' => false, 'default' => 'algolia', 'engines' => [ 'internal' => [ 'index' => ['h2', 'h3'], ], 'algolia' => [ 'key' => '', 'index' => '', ], ], ], /* |-------------------------------------------------------------------------- | Documentation Repository |-------------------------------------------------------------------------- | | This is an optional config you can set in order to show an external link | to your documentation's repository if you have one. Once you set the | value of the url, LaRecipe automatically will show the nav button. | | */ 'repository' => [ 'provider' => 'github', 'url' => 'https://github.com/owlchester/kanka', ], /* |-------------------------------------------------------------------------- | Appearance |-------------------------------------------------------------------------- | | Here you can add configure the appearance of your docs. For example, | you can swap the default logo to custom one that matches your Id | Also, you can change the theme of your docs if you prefer that | | Supported Themes: 'light', 'dark' | */ 'ui' => [ 'fav' => '/favicon.ico', // e.g.: /fav.png 'code_theme' => 'dark', 'show_side_bar' => true, 'colors' => [ 'primary' => '#787AF6', 'secondary' => '#2b9cf2', ], ], /* |-------------------------------------------------------------------------- | SEO |-------------------------------------------------------------------------- | | These options configure the SEO settings of your docs. You can set the | author, the description and the keywords. Also, LaRecipe by default | sets the canonical link to the viewed page's link automatically. | | */ 'seo' => [ 'author' => 'Kanka', 'description' => 'Free online worldbuilding and campaign management tool', 'keywords' => 'Kanka api documentation integration discord app', 'og' => [ 'title' => 'Kanka API Docs', 'type' => 'article', 'url' => 'kanka.io', 'image' => '', 'description' => 'Kanka\'s API documentation', ], ], /* |-------------------------------------------------------------------------- | Forum |-------------------------------------------------------------------------- | | Giving a chance to your users to post their questions or feedback | directly on your docs, is pretty nice way to engage them more. | However, you can also enable/disable the forum's visibility. | | Supported Services: 'disqus' | */ 'forum' => [ 'enabled' => false, 'default' => 'disqus', 'services' => [ 'disqus' => [ 'site_name' => '', // yoursite.disqus.com ], ], ], /* |-------------------------------------------------------------------------- | Components and Packages |-------------------------------------------------------------------------- | | Once you create a new asset or theme, its directory will be | published under `larecipe-components` folder. However, If | you want a different location, feel free to change it. | | */ 'packages' => [ 'path' => 'larecipe-components', ], ]; ================================================ FILE: config/limits.php ================================================ [ 'premium' => env('APP_PREMIUM', false), /** * Limits in place for standard campaigns. Premium campaigns are unlimited */ 'members' => env('APP_MEMBER_LIMIT', 10), 'roles' => env('APP_ROLE_LIMIT', 3), 'bookmarks' => env('APP_BOOKMARK_LIMIT', 3), /** * Entities have a limited number of files (a type of entity_asset) available on each entity */ 'files' => [ 'standard' => env('APP_FILE_LIMIT', 3), 'premium' => env('APP_PREMIUM_FILE_LIMIT', 20), ], /** * Number of custom modules allowed per subscription tier. */ 'modules' => [ 'premium' => env('APP_MODULE_LIMIT', 5), 'wyvern' => env('APP_WYVERN_MODULE_LIMIT', 10), 'elemental' => env('APP_ELEMENTAL_MODULE_LIMIT', 20), ], 'export' => 6, // hours after which exports get deleted 'maps' => [ // Maximum number of groups per map 'groups' => [ 'standard' => 1, 'premium' => 20, ], 'layers' => [ 'standard' => 1, 'premium' => 20, ], ], 'logs' => [ 'standard' => 7, 'premium' => 180, ], 'aliases' => env('APP_ALIAS_LIMIT', 2), 'web' => 10, ], /** * Default file upload size for standard user, in MB */ 'filesize' => [ 'image' => [ 'standard' => env('APP_IMAGE_SIZE_MB', 3), 'owlbear' => env('APP_IMAGE_SIZE_OWLBEAR_MB', 10), 'wyvern' => env('APP_IMAGE_SIZE_WYVERN_MB', 25), 'elemental' => env('APP_IMAGE_SIZE_ELEMENTAL_MB', 100), ], 'map' => env('APP_MAP_SIZE_MB', 5), ], 'gallery' => [ 'standard' => env('APP_GALLERY_STANDARD', 150 * 1024), 'premium' => env('APP_GALLERY_PREMIUM', 3 * 1024 * 1024), 'wyvern' => env('APP_GALLERY_WYVERN', 10 * 1024 * 1024), 'elemental' => env('APP_GALLERY_ELEMENTAL', 25 * 1024 * 1024), // 'premium' => 20 * 1024, ], 'pagination' => env('APP_PAGINATION', 15), 'api' => [ // Throttling values of requests per minute before a 421 "back down" response is thrown 'throttle' => [ 'subscriber' => env('API_THROTTLE_SUBSCRIBER_LIMIT', 90), 'default' => env('API_THROTTLE_LIMIT', 30), ], ], ]; ================================================ FILE: config/livewire.php ================================================ 'App\\Livewire', /* |--------------------------------------------------------------------------- | View Path |--------------------------------------------------------------------------- | | This value is used to specify where Livewire component Blade templates are | stored when running file creation commands like `artisan make:livewire`. | It is also used if you choose to omit a component's render() method. | */ 'view_path' => resource_path('views/livewire'), /* |--------------------------------------------------------------------------- | Layout |--------------------------------------------------------------------------- | The view that will be used as the layout when rendering a single component | as an entire page via `Route::get('/post/create', CreatePost::class);`. | In this case, the view returned by CreatePost will render into $slot. | */ 'layout' => 'components.layouts.app', /* |--------------------------------------------------------------------------- | Lazy Loading Placeholder |--------------------------------------------------------------------------- | Livewire allows you to lazy load components that would otherwise slow down | the initial page load. Every component can have a custom placeholder or | you can define the default placeholder view for all components below. | */ 'lazy_placeholder' => null, /* |--------------------------------------------------------------------------- | Temporary File Uploads |--------------------------------------------------------------------------- | | Livewire handles file uploads by storing uploads in a temporary directory | before the file is stored permanently. All file uploads are directed to | a global endpoint for temporary storage. You may configure this below: | */ 'temporary_file_upload' => [ 'disk' => env('LIVEWIRE_TEMP_DISK', 's3'), // Example: 'local', 's3' | Default: 'default' 'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) 'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp' 'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1' 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs... 'png', 'gif', 'bmp', 'svg', 'wav', 'mp4', 'mov', 'avi', 'wmv', 'mp3', 'm4a', 'jpg', 'jpeg', 'mpga', 'webp', 'wma', ], 'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated... ], /* |--------------------------------------------------------------------------- | Render On Redirect |--------------------------------------------------------------------------- | | This value determines if Livewire will run a component's `render()` method | after a redirect has been triggered using something like `redirect(...)` | Setting this to true will render the view once more before redirecting | */ 'render_on_redirect' => false, /* |--------------------------------------------------------------------------- | Eloquent Model Binding |--------------------------------------------------------------------------- | | Previous versions of Livewire supported binding directly to eloquent model | properties using wire:model by default. However, this behavior has been | deemed too "magical" and has therefore been put under a feature flag. | */ 'legacy_model_binding' => false, /* |--------------------------------------------------------------------------- | Auto-inject Frontend Assets |--------------------------------------------------------------------------- | | By default, Livewire automatically injects its JavaScript and CSS into the | and of pages containing Livewire components. By disabling | this behavior, you need to use @livewireStyles and @livewireScripts. | */ 'inject_assets' => true, /* |--------------------------------------------------------------------------- | Navigate (SPA mode) |--------------------------------------------------------------------------- | | By adding `wire:navigate` to links in your Livewire application, Livewire | will prevent the default link handling and instead request those pages | via AJAX, creating an SPA-like effect. Configure this behavior here. | */ 'navigate' => [ 'show_progress_bar' => true, 'progress_bar_color' => '#2299dd', ], /* |--------------------------------------------------------------------------- | HTML Morph Markers |--------------------------------------------------------------------------- | | Livewire intelligently "morphs" existing HTML into the newly rendered HTML | after each update. To make this process more reliable, Livewire injects | "markers" into the rendered Blade surrounding @if, @class & @foreach. | */ 'inject_morph_markers' => true, /* |--------------------------------------------------------------------------- | Pagination Theme |--------------------------------------------------------------------------- | | When enabling Livewire's pagination feature by using the `WithPagination` | trait, Livewire will use Tailwind templates to render pagination views | on the page. If you want Bootstrap CSS, you can specify: "bootstrap" | */ 'pagination_theme' => 'tailwind', ]; ================================================ FILE: config/logging.php ================================================ env('LOG_CHANNEL', 'daily'), /* |-------------------------------------------------------------------------- | Log Channels |-------------------------------------------------------------------------- | | Here you may configure the log channels for your application. Out of | the box, Laravel uses the Monolog PHP logging library. This gives | you a variety of powerful log handlers / formatters to utilize. | | Available Drivers: "single", "daily", "slack", "syslog", | "errorlog", "monolog", | "custom", "stack" | */ 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => ['single'], ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => 'debug', 'days' => 7, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => 'critical', ], 'papertrail' => [ 'driver' => 'monolog', 'level' => 'debug', 'handler' => SyslogUdpHandler::class, 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), ], ], 'stderr' => [ 'driver' => 'monolog', 'handler' => StreamHandler::class, 'with' => [ 'stream' => 'php://stderr', ], ], 'syslog' => [ 'driver' => 'syslog', 'level' => 'debug', ], 'errorlog' => [ 'driver' => 'errorlog', 'level' => 'debug', ], ], 'enabled' => env('DB_LOGS_DATABASE', false), 'anonymize' => 30, 'prune_months' => 6, ]; ================================================ FILE: config/mail.php ================================================ env('MAIL_DRIVER', 'smtp'), /* |-------------------------------------------------------------------------- | SMTP Host Address |-------------------------------------------------------------------------- | | Here you may provide the host address of the SMTP server used by your | applications. A default option is provided that is compatible with | the Mailgun mail service which will provide reliable deliveries. | */ 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), /* |-------------------------------------------------------------------------- | SMTP Host Port |-------------------------------------------------------------------------- | | This is the SMTP port used by your application to deliver e-mails to | users of the application. Like the host we have set this value to | stay compatible with the Mailgun e-mail application by default. | */ 'port' => env('MAIL_PORT', 587), /* |-------------------------------------------------------------------------- | Global "From" Address |-------------------------------------------------------------------------- | | You may wish for all e-mails sent by your application to be sent from | the same address. Here, you may specify a name and address that is | used globally for all e-mails that are sent by your application. | */ 'from' => [ 'address' => env('MAIL_FROM_ADDRESS', 'me@example.com'), 'name' => env('MAIL_FROM_NAME', 'Kanka.io'), ], /* |-------------------------------------------------------------------------- | E-Mail Encryption Protocol |-------------------------------------------------------------------------- | | Here you may specify the encryption protocol that should be used when | the application send e-mail messages. A sensible default using the | transport layer security protocol should provide great security. | */ 'encryption' => env('MAIL_ENCRYPTION', 'tls'), /* |-------------------------------------------------------------------------- | SMTP Server Username |-------------------------------------------------------------------------- | | If your SMTP server requires a username for authentication, you should | set it here. This will get used to authenticate with your server on | connection. You may also set the "password" value below this one. | */ 'username' => env('MAIL_USERNAME'), 'password' => env('MAIL_PASSWORD'), /* |-------------------------------------------------------------------------- | Sendmail System Path |-------------------------------------------------------------------------- | | When using the "sendmail" driver to send e-mails, we will need to know | the path to where Sendmail lives on this server. A default path has | been provided here, which will work well on most of your systems. | */ 'sendmail' => '/usr/sbin/sendmail -bs', /* |-------------------------------------------------------------------------- | Markdown Mail Settings |-------------------------------------------------------------------------- | | If you are using Markdown based email rendering, you may configure your | theme and component paths here, allowing you to customize the design | of the emails. Or, you may simply stick with the Laravel defaults! | */ 'markdown' => [ 'theme' => 'default', 'paths' => [ resource_path('views/vendor/mail'), ], ], 'verify_peer' => false, ]; ================================================ FILE: config/mailerlite.php ================================================ env('MAILERLITE_API_TOKEN'), 'groups' => [ 'all' => 84086713298715839, 'subs' => 84439423159109338, 'new' => 129565403592525427, ], ]; ================================================ FILE: config/marketplace.php ================================================ ! empty(env('APP_MARKETPLACE_URL', null)), /* |------------------------------------------- | Marketplace url |------------------------------------------- | | This value tells us the url for the marketplace */ 'url' => env('APP_MARKETPLACE_URL', 'https://plugins.kanka.io'), ]; ================================================ FILE: config/openai.php ================================================ env('OPEN_AI_SECRET', 0), /** * Custom URL */ 'custom_url' => env('OPEN_AI_URL', ''), /** * Number of tokens to use for prompt generation. */ 'tokens' => 650, ]; ================================================ FILE: config/passport.php ================================================ 'web', /* |-------------------------------------------------------------------------- | Encryption Keys |-------------------------------------------------------------------------- | | Passport uses encryption keys while generating secure access tokens for | your application. By default, the keys are stored as local files but | can be set via environment variables when that is more convenient. | */ 'private_key' => env('PASSPORT_PRIVATE_KEY'), 'public_key' => env('PASSPORT_PUBLIC_KEY'), /* |-------------------------------------------------------------------------- | Passport Database Connection |-------------------------------------------------------------------------- | | By default, Passport's models will utilize your application's default | database connection. If you wish to use a different connection you | may specify the configured name of the database connection here. | */ 'connection' => env('PASSPORT_CONNECTION'), ]; ================================================ FILE: config/paypal.php ================================================ env('PAYPAL_MODE') !== null, // Can only be 'sandbox' Or 'live'. If empty or invalid, 'live' will be used. 'mode' => env('PAYPAL_MODE', 'sandbox'), 'sandbox' => [ 'client_id' => env('PAYPAL_SANDBOX_CLIENT_ID', ''), 'client_secret' => env('PAYPAL_SANDBOX_CLIENT_SECRET', ''), // Used for testing Adaptive Payments API in sandbox mode 'app_id' => 'APP-80W284485P519543T', ], 'live' => [ 'client_id' => env('PAYPAL_LIVE_CLIENT_ID', ''), 'client_secret' => env('PAYPAL_LIVE_CLIENT_SECRET', ''), 'app_id' => '', ], // Can only be 'Sale', 'Authorization' or 'Order' 'payment_action' => env('PAYPAL_PAYMENT_ACTION', 'Sale'), 'currency' => env('PAYPAL_CURRENCY', 'USD'), // Change this accordingly for your application, after payment is done PayPal IPN service sends notification // to notify_url (or it can be set in PayPal profile). https://developer.paypal.com/api/nvp-soap/ipn/ht-ipn/ 'notify_url' => env('PAYPAL_NOTIFY_URL', ''), // force gateway language i.e. it_IT, es_ES, en_US ... (for express checkout only) 'locale' => env('PAYPAL_LOCALE', 'en_US'), // Validate SSL when creating api client. 'validate_ssl' => env('PAYPAL_VALIDATE_SSL', true), ]; ================================================ FILE: config/purge.php ================================================ [ 'first' => [ 'inactivity' => 18, 'limit' => 30, ], 'second' => [ 'limit' => 7, ], ], 'hard_delete' => env('APP_CAMPAIGN_HARD_DELETE', 24), ]; ================================================ FILE: config/purify.php ================================================ 'default', /* |-------------------------------------------------------------------------- | Config sets |-------------------------------------------------------------------------- | | Here you may configure various sets of configuration for differentiated use of HTMLPurifier. | A specific set of configuration can be applied by calling the "config($name)" method on | a Purify instance. Feel free to add/remove/customize these attributes as you wish. | | Documentation: http://htmlpurifier.org/live/configdoc/plain.html | | Core.Encoding The encoding to convert input to. | HTML.Doctype Doctype to use during filtering. | HTML.Allowed The allowed HTML Elements with their allowed attributes. | HTML.ForbiddenElements The forbidden HTML elements. Elements that are listed in this | string will be removed, however their content will remain. | CSS.AllowedProperties The Allowed CSS properties. | AutoFormat.AutoParagraph Newlines are converted in to paragraphs whenever possible. | AutoFormat.RemoveEmpty Remove empty elements that contribute no semantic information to the document. | */ 'configs' => [ 'default' => [ 'Core.Encoding' => 'utf-8', 'HTML.Doctype' => 'HTML 4.01 Transitional', /* |-------------------------------------------------------------------------- | HTML.Allowed |-------------------------------------------------------------------------- | | The allowed HTML Elements with their allowed attributes. | | http://htmlpurifier.org/live/configdoc/plain.html#HTML.Allowed | */ 'HTML.Allowed' => '' /** Titles */ . 'h1[class|style|id|title],' . 'h2[class|style|id|title],' . 'h3[class|style|id|title],' . 'h4[class|style|id|title],' . 'h5[class|style|id|title],' . 'h6[class|style|id|title],' /** General elements */ . 'div[class|style|id|align|role|title],' . 'p[class|style|id|dir|align],' . 'span[class|style|id|dir],' . 'a[href|class|style|target|rel|title|id|role|data-toggle|data-html|data-dropdown|data-pulse|data-animate|data-tooltip|data-title|data-entity-type],' . 'br[class|style],' . 'i[class|style],u[class|style],' . 'img[src|style|alt|width|height|class|title|id|data-gallery-id|data-uuid],' . 'hr[class|style|id|title],' /** Text blocks */ . 'pre[class|style|title|id],' . 'blockquote[cite|class|style|id],' . 'code[class|style|id],' /** Lists **/ . 'ul[class|style|id|role|data-type],' . 'ol[class|style|id|role|start|type],' . 'li[class|style|id|role|value|data-type|data-checked],' . 'dl[class|style|id],dt[class|style|id],dd[class|style|id],' /** Task lists */ . 'label[class|style],' . 'input[type|checked|disabled],' /** Misc elements */ . 'mark[class|style],' . 'cite[class|style],' . 'q[cite|class|style],' . 'caption[class|style|id|title],' . 'acronym[title|class|style],' . 'abbr[title|class|style],' . 'font[class|style|color],' . 'summary[class|style|id],' . 'details[class|style|id|open],' . 'figure[class|style|id|title],figcaption[class|style|id|title],' /** Iframe, combined with the domain whitelist */ . 'iframe[src|width|height|style|class|scrolling|id],' /** Formatting */ . 'ins[class|style],del[class|style],' . 'sup[class|style],sub[class|style],' . 'big,small,' . 's[class|style],strong[class|style],em[class|style],b[class|style],strike,' /** Tables */ . 'table[class|style|summary|border|cellpadding|cellspacing|id|width],' . 'tbody[class|style|id],' . 'thead[class|style|id],' . 'tfoot[class|style|id],' . 'colgroup,' . 'col[style|class],' . 'tr[class|style|id],' . 'colgroup,' . 'col[style|class],' . 'td[class|style|abbr|colspan|rowspan|colwidth|title|align|valign],' . 'th[class|style|abbr|colspan|rowspan|colwidth|title|align|valign],', /* |-------------------------------------------------------------------------- | HTML.ForbiddenElements |-------------------------------------------------------------------------- | | The forbidden HTML elements. Elements that are listed in | this string will be removed, however their content will remain. | | For example if 'p' is inside the string, the string: '

    Test

    ', | | Will be cleaned to: 'Test' | | http://htmlpurifier.org/live/configdoc/plain.html#HTML.ForbiddenElements | */ 'HTML.ForbiddenElements' => '', /* |-------------------------------------------------------------------------- | CSS.AllowedProperties |-------------------------------------------------------------------------- | | The Allowed CSS properties. | | http://htmlpurifier.org/live/configdoc/plain.html#CSS.AllowedProperties | */ 'CSS.AllowedProperties' => '' /** Typography */ . 'font,font-size,font-weight,font-style,font-family,' . 'text-decoration,text-align,text-indent,text-transform,' . 'line-height,letter-spacing,word-spacing,white-space,' . 'vertical-align,color,' /** Backgrounds */ . 'background-color,' /** Box model */ . 'width,height,min-width,min-height,max-width,max-height,' . 'margin,margin-top,margin-right,margin-bottom,margin-left,' . 'padding,padding-top,padding-right,padding-bottom,padding-left,' /** Borders */ . 'border,border-collapse,border-style,border-color,border-width,' . 'border-top,border-right,border-bottom,border-left,' /** Layout */ . 'float,list-style-type', /* |-------------------------------------------------------------------------- | AutoFormat.AutoParagraph |-------------------------------------------------------------------------- | | The Allowed CSS properties. | | This directive turns on auto-paragraphing, where double | newlines are converted in to paragraphs whenever possible. | | http://htmlpurifier.org/live/configdoc/plain.html#AutoFormat.AutoParagraph | */ 'AutoFormat.AutoParagraph' => false, /* |-------------------------------------------------------------------------- | AutoFormat.RemoveEmpty |-------------------------------------------------------------------------- | | When enabled, HTML Purifier will attempt to remove empty | elements that contribute no semantic information to the document. | | http://htmlpurifier.org/live/configdoc/plain.html#AutoFormat.RemoveEmpty | */ 'AutoFormat.RemoveEmpty' => false, // 'AutoFormat.RemoveEmpty.Predicate' => ['iframe' => false], // To allow max-width and max-height on images. This might cause imageattacks? 'HTML.MaxImgLength' => null, 'CSS.MaxImgLength' => null, // Allow links that target blank 'Attr.AllowedFrameTargets' => ['_blank'], // Allow setting IDs for anchors 'Attr.EnableID' => true, // Iframes to vimeo and youtube // 'HTML.SafeIframe' => true, // 'URI.SafeIframeRegexp' => '%^(https?:)?//(www\.youtube(?:-nocookie)?\.com/embed/|player\.vimeo\.com/video/)%', 'Filter.YouTube' => true, 'HTML.SafeIframe' => true, 'URI.SafeIframeRegexp' => '%^(https?:)?//(' . "www\.youtube(?:-nocookie)?\.com/embed/|" . "player\.vimeo\.com/video/|" . "open\.spotify\.com/embed|" . 'docs.google.com/|' . 'drive.google.com/|' . 'www.google.com/maps/embed|' . 'calendar.google.com/calendar/embed|' . 'snazzymaps.com/embed|' . 'w.soundcloud.com/player/|' . "www\.dndbeyond\.com/|" . "www\.aonprd\.com/|" . "2e\.aonprd\.com/|" . "www\.aonsrd\.com/|" . "p3d\.in/e/|" . "api\.mapbox\.com/|" . 'app.box.com/embed/' . "discord\.com/|" . "discord\.gg/|" . "bardly\.io/|" . "view\.genially\.com/|" . ')%', ], 'tooltips' => [ //

    ', '', '', ''); var vTable = new TableResultAction(cell, TableResultAction.where.Row, TableResultAction.requestAction.Add, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentTr).closest('table')[0]); var actions = vTable.getActionList(); for (var idCell = 0; idCell < actions.length; idCell++) { var currentCell = actions[idCell]; var tdAttributes = this.recoverAttributes(currentCell.baseCell); switch (currentCell.action) { case TableResultAction.resultAction.AddCell: html.append('' + dom.blank + ''); break; case TableResultAction.resultAction.SumSpanCount: { if (position === 'top') { var baseCellTr = currentCell.baseCell.parent; var isTopFromRowSpan = (!baseCellTr ? 0 : currentCell.baseCell.closest('tr').rowIndex) <= currentTr[0].rowIndex; if (isTopFromRowSpan) { var newTd = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
    ').append(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('' + dom.blank + '').removeAttr('rowspan')).html(); html.append(newTd); break; } } var rowspanNumber = parseInt(currentCell.baseCell.rowSpan, 10); rowspanNumber++; currentCell.baseCell.setAttribute('rowSpan', rowspanNumber); } break; } } if (position === 'top') { currentTr.before(html); } else { var cellHasRowspan = cell.rowSpan > 1; if (cellHasRowspan) { var lastTrIndex = currentTr[0].rowIndex + (cell.rowSpan - 2); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentTr).parent().find('tr')[lastTrIndex]).after(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(html)); return; } currentTr.after(html); } } /** * Add a new col * * @param {WrappedRange} rng * @param {String} position (left/right) * @return {Node} */ }, { key: "addCol", value: function addCol(rng, position) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); var row = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('tr'); var rowsGroup = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(row).siblings(); rowsGroup.push(row); var vTable = new TableResultAction(cell, TableResultAction.where.Column, TableResultAction.requestAction.Add, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(row).closest('table')[0]); var actions = vTable.getActionList(); for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) { var currentCell = actions[actionIndex]; var tdAttributes = this.recoverAttributes(currentCell.baseCell); switch (currentCell.action) { case TableResultAction.resultAction.AddCell: if (position === 'right') { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentCell.baseCell).after('' + dom.blank + ''); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentCell.baseCell).before('' + dom.blank + ''); } break; case TableResultAction.resultAction.SumSpanCount: if (position === 'right') { var colspanNumber = parseInt(currentCell.baseCell.colSpan, 10); colspanNumber++; currentCell.baseCell.setAttribute('colSpan', colspanNumber); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(currentCell.baseCell).before('' + dom.blank + ''); } break; } } } /* * Copy attributes from element. * * @param {object} Element to recover attributes. * @return {string} Copied string elements. */ }, { key: "recoverAttributes", value: function recoverAttributes(el) { var resultStr = ''; if (!el) { return resultStr; } var attrList = el.attributes || []; for (var i = 0; i < attrList.length; i++) { if (attrList[i].name.toLowerCase() === 'id') { continue; } if (attrList[i].specified) { resultStr += ' ' + attrList[i].name + '=\'' + attrList[i].value + '\''; } } return resultStr; } /** * Delete current row * * @param {WrappedRange} rng * @return {Node} */ }, { key: "deleteRow", value: function deleteRow(rng) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); var row = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('tr'); var cellPos = row.children('td, th').index(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell)); var rowPos = row[0].rowIndex; var vTable = new TableResultAction(cell, TableResultAction.where.Row, TableResultAction.requestAction.Delete, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(row).closest('table')[0]); var actions = vTable.getActionList(); for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) { if (!actions[actionIndex]) { continue; } var baseCell = actions[actionIndex].baseCell; var virtualPosition = actions[actionIndex].virtualTable; var hasRowspan = baseCell.rowSpan && baseCell.rowSpan > 1; var rowspanNumber = hasRowspan ? parseInt(baseCell.rowSpan, 10) : 0; switch (actions[actionIndex].action) { case TableResultAction.resultAction.Ignore: continue; case TableResultAction.resultAction.AddCell: { var nextRow = row.next('tr')[0]; if (!nextRow) { continue; } var cloneRow = row[0].cells[cellPos]; if (hasRowspan) { if (rowspanNumber > 2) { rowspanNumber--; nextRow.insertBefore(cloneRow, nextRow.cells[cellPos]); nextRow.cells[cellPos].setAttribute('rowSpan', rowspanNumber); nextRow.cells[cellPos].innerHTML = ''; } else if (rowspanNumber === 2) { nextRow.insertBefore(cloneRow, nextRow.cells[cellPos]); nextRow.cells[cellPos].removeAttribute('rowSpan'); nextRow.cells[cellPos].innerHTML = ''; } } } continue; case TableResultAction.resultAction.SubtractSpanCount: if (hasRowspan) { if (rowspanNumber > 2) { rowspanNumber--; baseCell.setAttribute('rowSpan', rowspanNumber); if (virtualPosition.rowIndex !== rowPos && baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; } } else if (rowspanNumber === 2) { baseCell.removeAttribute('rowSpan'); if (virtualPosition.rowIndex !== rowPos && baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; } } } continue; case TableResultAction.resultAction.RemoveCell: // Do not need remove cell because row will be deleted. continue; } } row.remove(); } /** * Delete current col * * @param {WrappedRange} rng * @return {Node} */ }, { key: "deleteCol", value: function deleteCol(rng) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); var row = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('tr'); var cellPos = row.children('td, th').index(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell)); var vTable = new TableResultAction(cell, TableResultAction.where.Column, TableResultAction.requestAction.Delete, external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(row).closest('table')[0]); var actions = vTable.getActionList(); for (var actionIndex = 0; actionIndex < actions.length; actionIndex++) { if (!actions[actionIndex]) { continue; } switch (actions[actionIndex].action) { case TableResultAction.resultAction.Ignore: continue; case TableResultAction.resultAction.SubtractSpanCount: { var baseCell = actions[actionIndex].baseCell; var hasColspan = baseCell.colSpan && baseCell.colSpan > 1; if (hasColspan) { var colspanNumber = baseCell.colSpan ? parseInt(baseCell.colSpan, 10) : 0; if (colspanNumber > 2) { colspanNumber--; baseCell.setAttribute('colSpan', colspanNumber); if (baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; } } else if (colspanNumber === 2) { baseCell.removeAttribute('colSpan'); if (baseCell.cellIndex === cellPos) { baseCell.innerHTML = ''; } } } } continue; case TableResultAction.resultAction.RemoveCell: dom.remove(actions[actionIndex].baseCell, true); continue; } } } /** * create empty table element * * @param {Number} rowCount * @param {Number} colCount * @return {Node} */ }, { key: "createTable", value: function createTable(colCount, rowCount, options) { var tds = []; var tdHTML; for (var idxCol = 0; idxCol < colCount; idxCol++) { tds.push('
    '); } tdHTML = tds.join(''); var trs = []; var trHTML; for (var idxRow = 0; idxRow < rowCount; idxRow++) { trs.push('' + tdHTML + ''); } trHTML = trs.join(''); var $table = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
    ', '', '', '', '
    ', ' 'allowed' => [ 'a', 'i', 'b', 'strong', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div', 'span', 'table', 'tr', 'th', 'td', 'img', ], ], ], /* |-------------------------------------------------------------------------- | HTMLPurifier definitions |-------------------------------------------------------------------------- | | Here you may specify a class that augments the HTML definitions used by | HTMLPurifier. Additional HTML5 definitions are provided out of the box. | When specifying a custom class, make sure it implements the interface: | | \Stevebauman\Purify\Definitions\Definition | | Note that these definitions are applied to every Purifier instance. | | Documentation: http://htmlpurifier.org/docs/enduser-customize.html | */ 'definitions' => CustomDefinitions::class, 'css-definitions' => CustomCssDefinitions::class, /* |-------------------------------------------------------------------------- | Serializer location |-------------------------------------------------------------------------- | | The location where HTMLPurifier can store its temporary serializer files. | The filepath should be accessible and writable by the web server. | A good place for this is in the framework's own storage path. | */ 'serializer' => [ 'disk' => env('FILESYSTEM_DRIVER', 'local'), 'path' => 'purify', 'cache' => FilesystemDefinitionCache::class, ], // 'serializer' => [ // 'driver' => env('CACHE_DRIVER', 'file'), // 'cache' => \Stevebauman\Purify\Cache\CacheDefinitionCache::class, // ], ]; ================================================ FILE: config/queue.php ================================================ env('QUEUE_DRIVER', 'redis'), /* |-------------------------------------------------------------------------- | Queue Connections |-------------------------------------------------------------------------- | | Here you may configure the connection information for each server that | is used by your application. A default configuration has been added | for each back-end shipped with Laravel. You are free to add more. | */ 'connections' => [ 'sync' => [ 'driver' => 'sync', ], 'database' => [ 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', 'retry_after' => 90, ], 'beanstalkd' => [ 'driver' => 'beanstalkd', 'host' => 'localhost', 'queue' => 'default', 'retry_after' => 90, ], 'sqs' => [ 'driver' => 'sqs', 'key' => 'your-public-key', 'secret' => 'your-secret-key', 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', 'queue' => 'your-queue-name', 'region' => 'us-east-1', ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('REDIS_QUEUE', 'default'), 'retry_after' => 90, ], // For heavy jobs (no timeout) like map chunking 'heavy' => [ 'driver' => 'redis', 'connection' => 'default', 'queue' => env('REDIS_HEAVY_QUEUE', 'heavy'), 'retry_after' => 300, ], ], /* |-------------------------------------------------------------------------- | Failed Queue Jobs |-------------------------------------------------------------------------- | | These options configure the behavior of failed queue job logging so you | can control which database and table are used to store the jobs that | have failed. You may change them to any database / table you wish. | */ 'failed' => [ 'database' => env('DB_CONNECTION', 'mysql'), 'table' => 'failed_jobs', ], ]; ================================================ FILE: config/reverb.php ================================================ env('REVERB_SERVER', 'reverb'), /* |-------------------------------------------------------------------------- | Reverb Servers |-------------------------------------------------------------------------- | | Here you may define details for each of the supported Reverb servers. | Each server has its own configuration options that are defined in | the array below. You should ensure all the options are present. | */ 'servers' => [ 'reverb' => [ 'host' => env('REVERB_SERVER_HOST', '0.0.0.0'), 'port' => env('REVERB_SERVER_PORT', 8080), 'path' => env('REVERB_SERVER_PATH', ''), 'hostname' => env('REVERB_HOST'), 'options' => [ 'tls' => [], ], 'max_request_size' => env('REVERB_MAX_REQUEST_SIZE', 10_000), 'scaling' => [ 'enabled' => env('REVERB_SCALING_ENABLED', false), 'channel' => env('REVERB_SCALING_CHANNEL', 'reverb'), 'server' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'port' => env('REDIS_PORT', '6379'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'database' => env('REDIS_DB', '0'), 'timeout' => env('REDIS_TIMEOUT', 60), ], ], 'pulse_ingest_interval' => env('REVERB_PULSE_INGEST_INTERVAL', 15), 'telescope_ingest_interval' => env('REVERB_TELESCOPE_INGEST_INTERVAL', 15), ], ], /* |-------------------------------------------------------------------------- | Reverb Applications |-------------------------------------------------------------------------- | | Here you may define how Reverb applications are managed. If you choose | to use the "config" provider, you may define an array of apps which | your server will support, including their connection credentials. | */ 'apps' => [ 'provider' => 'config', 'apps' => [ [ 'key' => env('REVERB_APP_KEY'), 'secret' => env('REVERB_APP_SECRET'), 'app_id' => env('REVERB_APP_ID'), 'options' => [ 'host' => env('REVERB_HOST'), 'port' => env('REVERB_PORT', 443), 'scheme' => env('REVERB_SCHEME', 'https'), 'useTLS' => env('REVERB_SCHEME', 'https') === 'https', ], 'allowed_origins' => ['*'], 'ping_interval' => env('REVERB_APP_PING_INTERVAL', 60), 'activity_timeout' => env('REVERB_APP_ACTIVITY_TIMEOUT', 30), 'max_connections' => env('REVERB_APP_MAX_CONNECTIONS'), 'max_message_size' => env('REVERB_APP_MAX_MESSAGE_SIZE', 10_000), ], ], ], ]; ================================================ FILE: config/scout.php ================================================ env('SCOUT_DRIVER', 'algolia'), /* |-------------------------------------------------------------------------- | Index Prefix |-------------------------------------------------------------------------- | | Here you may specify a prefix that will be applied to all search index | names used by Scout. This prefix may be useful if you have multiple | "tenants" or applications sharing the same search infrastructure. | */ 'prefix' => env('SCOUT_PREFIX', ''), /* |-------------------------------------------------------------------------- | Queue Data Syncing |-------------------------------------------------------------------------- | | This option allows you to control if the operations that sync your data | with your search engines are queued. When this is set to "true" then | all automatic data syncing will get queued for better performance. | */ // SET TO TRUE TO USE QUEUE 'queue' => env('SCOUT_QUEUE', false), /* |-------------------------------------------------------------------------- | Database Transactions |-------------------------------------------------------------------------- | | This configuration option determines if your data will only be synced | with your search indexes after every open database transaction has | been committed, thus preventing any discarded data from syncing. | */ 'after_commit' => false, /* |-------------------------------------------------------------------------- | Chunk Sizes |-------------------------------------------------------------------------- | | These options allow you to control the maximum chunk size when you are | mass importing data into the search engine. This allows you to fine | tune each of these chunk sizes based on the power of the servers. | */ 'chunk' => [ 'searchable' => 10000, 'unsearchable' => 10000, ], /* |-------------------------------------------------------------------------- | Soft Deletes |-------------------------------------------------------------------------- | | This option allows to control whether to keep soft deleted records in | the search indexes. Maintaining soft deleted records can be useful | if your application still needs to search for the records later. | */ 'soft_delete' => false, /* |-------------------------------------------------------------------------- | Identify User |-------------------------------------------------------------------------- | | This option allows you to control whether to notify the search engine | of the user performing the search. This is sometimes useful if the | engine supports any analytics based on this application's users. | | Supported engines: "algolia" | */ 'identify' => env('SCOUT_IDENTIFY', false), /* |-------------------------------------------------------------------------- | Algolia Configuration |-------------------------------------------------------------------------- | | Here you may configure your Algolia settings. Algolia is a cloud hosted | search engine which works great with Scout out of the box. Just plug | in your application ID and admin API key to get started searching. | */ 'algolia' => [ 'id' => env('ALGOLIA_APP_ID', ''), 'secret' => env('ALGOLIA_SECRET', ''), ], /* |-------------------------------------------------------------------------- | Meilisearch Configuration |-------------------------------------------------------------------------- | | Here you may configure your Meilisearch settings. Meilisearch is an open | source search engine with minimal configuration. Below, you can state | the host and key information for your own Meilisearch installation. | | See: https://www.meilisearch.com/docs/learn/configuration/instance_options#all-instance-options | */ 'meilisearch' => [ 'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'), 'key' => env('MEILISEARCH_KEY'), 'index-settings' => [ 'entities' => [ 'filterableAttributes' => ['id', 'campaign_id'], 'sortableAttributes' => ['name', 'entry'], ], ], ], ]; ================================================ FILE: config/sentry.php ================================================ env('SENTRY_LARAVEL_DSN', env('SENTRY_DSN')), // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) 'release' => env('APP_VERSION'), // When left empty or `null` the Laravel environment will be used 'environment' => env('SENTRY_ENVIRONMENT'), 'max_breadcrumbs' => 25, 'breadcrumbs' => [ // Capture Laravel logs in breadcrumbs 'logs' => true, // Capture SQL queries in breadcrumbs 'sql_queries' => true, // Capture bindings on SQL queries logged in breadcrumbs 'sql_bindings' => true, // Capture queue job information in breadcrumbs 'queue_info' => true, // Capture command information in breadcrumbs 'command_info' => true, ], 'tracing' => [ // Trace queue jobs as their own transactions 'queue_job_transactions' => env('SENTRY_TRACE_QUEUE_ENABLED', false), // Capture queue jobs as spans when executed on the sync driver 'queue_jobs' => true, // Capture SQL queries as spans 'sql_queries' => true, // Try to find out where the SQL query originated from and add it to the query spans 'sql_origin' => true, // Capture views as spans 'views' => true, // Capture HTTP client requests as spans 'http_client_requests' => true, // Indicates if the tracing integrations supplied by Sentry should be loaded 'default_integrations' => true, // Indicates that requests without a matching route should be traced 'missing_routes' => false, ], // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#send-default-pii 'send_default_pii' => env('SENTRY_SEND_DEFAULT_PII', false), // @see: https://docs.sentry.io/platforms/php/guides/laravel/configuration/options/#traces-sample-rate 'traces_sample_rate' => env('SENTRY_TRACES_SAMPLE_RATE') === null ? null : (float) env('SENTRY_TRACES_SAMPLE_RATE'), ]; ================================================ FILE: config/services.php ================================================ [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), 'endpoint' => env('MAILGUN_ENDPOINT', 'api.eu.mailgun.net'), 'scheme' => 'https', ], 'ses' => [ 'key' => env('SES_KEY'), 'secret' => env('SES_SECRET'), 'region' => env('SES_REGION', 'us-east-1'), ], 'sparkpost' => [ 'secret' => env('SPARKPOST_SECRET'), ], 'stripe' => [ 'enabled' => ! empty(env('STRIPE_KEY')) ? true : false, 'model' => User::class, 'key' => env('STRIPE_KEY'), 'secret' => env('STRIPE_SECRET'), ], 'facebook' => [ 'client_id' => env('FACEBOOK_CLIENT_ID'), 'client_secret' => env('FACEBOOK_CLIENT_SECRET'), 'redirect' => env('APP_URL') . '/auth/facebook/callback', ], 'google' => [ 'client_id' => env('GOOGLE_CLIENT_ID'), 'client_secret' => env('GOOGLE_CLIENT_SECRET'), 'redirect' => env('APP_URL') . '/auth/google/callback', ], 'twitter' => [ 'client_id' => env('TWITTER_CLIENT_ID'), 'client_secret' => env('TWITTER_CLIENT_SECRET'), 'redirect' => env('APP_URL') . '/auth/twitter/callback', ], ]; ================================================ FILE: config/session.php ================================================ env('SESSION_DRIVER', 'file'), /* |-------------------------------------------------------------------------- | Session Lifetime |-------------------------------------------------------------------------- | | Here you may specify the number of minutes that you wish the session | to be allowed to remain idle before it expires. If you want them | to immediately expire on the browser closing, set that option. | */ 'lifetime' => env('SESSION_LIFETIME', 120), 'expire_on_close' => false, /* |-------------------------------------------------------------------------- | Session Encryption |-------------------------------------------------------------------------- | | This option allows you to easily specify that all of your session data | should be encrypted before it is stored. All encryption will be run | automatically by Laravel and you can use the Session like normal. | */ 'encrypt' => false, /* |-------------------------------------------------------------------------- | Session File Location |-------------------------------------------------------------------------- | | When using the native session driver, we need a location where session | files may be stored. A default has been set for you but a different | location may be specified. This is only needed for file sessions. | */ 'files' => storage_path('framework/sessions'), /* |-------------------------------------------------------------------------- | Session Database Connection |-------------------------------------------------------------------------- | | When using the "database" or "redis" session drivers, you may specify a | connection that should be used to manage these sessions. This should | correspond to a connection in your database configuration options. | */ 'connection' => env('SESSION_CONNECTION'), /* |-------------------------------------------------------------------------- | Session Database Table |-------------------------------------------------------------------------- | | When using the "database" session driver, you may specify the table we | should use to manage the sessions. Of course, a sensible default is | provided for you; however, you are free to change this as needed. | */ 'table' => 'sessions', /* |-------------------------------------------------------------------------- | Session Cache Store |-------------------------------------------------------------------------- | | When using the "apc" or "memcached" session drivers, you may specify a | cache store that should be used for these sessions. This value must | correspond with one of the application's configured cache stores. | */ 'store' => env('SESSION_STORE'), /* |-------------------------------------------------------------------------- | Session Sweeping Lottery |-------------------------------------------------------------------------- | | Some session drivers must manually sweep their storage location to get | rid of old sessions from storage. Here are the chances that it will | happen on a given request. By default, the odds are 2 out of 100. | */ 'lottery' => [2, 100], /* |-------------------------------------------------------------------------- | Session Cookie Name |-------------------------------------------------------------------------- | | Here you may change the name of the cookie used to identify a session | instance by ID. The name specified here will get used every time a | new session cookie is created by the framework for every driver. | */ 'cookie' => env( 'SESSION_COOKIE', Str::slug(env('APP_NAME', 'laravel'), '_') . '_session' ), /* |-------------------------------------------------------------------------- | Session Cookie Path |-------------------------------------------------------------------------- | | The session cookie path determines the path for which the cookie will | be regarded as available. Typically, this will be the root path of | your application but you are free to change this when necessary. | */ 'path' => '/', /* |-------------------------------------------------------------------------- | Session Cookie Domain |-------------------------------------------------------------------------- | | Here you may change the domain of the cookie used to identify a session | in your application. This will determine which domains the cookie is | available to in your application. A sensible default has been set. | */ 'domain' => env('SESSION_DOMAIN'), /* |-------------------------------------------------------------------------- | HTTPS Only Cookies |-------------------------------------------------------------------------- | | By setting this option to true, session cookies will only be sent back | to the server if the browser has a HTTPS connection. This will keep | the cookie from being sent to you if it can not be done securely. | */ 'secure' => env('SESSION_SECURE_COOKIE'), /* |-------------------------------------------------------------------------- | HTTP Access Only |-------------------------------------------------------------------------- | | Setting this value to true will prevent JavaScript from accessing the | value of the cookie and the cookie will only be accessible through | the HTTP protocol. You are free to modify this option if needed. | */ 'http_only' => true, /* |-------------------------------------------------------------------------- | Same-Site Cookies |-------------------------------------------------------------------------- | | This option determines how your cookies behave when cross-site requests | take place, and can be used to mitigate CSRF attacks. By default, we | do not enable this as other CSRF protection services are in place. | | Supported: "lax", "strict", "none", null | */ 'same_site' => env('SESSION_SAME_SITE', 'lax'), ]; ================================================ FILE: config/subscription.php ================================================ env('APP_FRAUD_DETECTION', false), 'owlbear' => [ 'eur' => [ 'monthly' => env('STRIPE_OWLBEAR_EUR'), 'yearly' => env('STRIPE_OWLBEAR_EUR_YEARLY'), ], 'usd' => [ 'monthly' => env('STRIPE_OWLBEAR_USD'), 'yearly' => env('STRIPE_OWLBEAR_USD_YEARLY'), ], 'brl' => [ 'monthly' => env('STRIPE_OWLBEAR_BRL'), 'yearly' => env('STRIPE_OWLBEAR_BRL_YEARLY'), ], 'monthly' => [ env('STRIPE_OWLBEAR_EUR'), env('STRIPE_OWLBEAR_EUR_OLD'), env('STRIPE_OWLBEAR_USD'), env('STRIPE_OWLBEAR_USD_OLD'), env('STRIPE_OWLBEAR_BRL'), ], 'yearly' => [ env('STRIPE_OWLBEAR_EUR_YEARLY'), env('STRIPE_OWLBEAR_EUR_YEARLY_OLD'), env('STRIPE_OWLBEAR_USD_YEARLY'), env('STRIPE_OWLBEAR_USD_YEARLY_OLD'), env('STRIPE_OWLBEAR_BRL_YEARLY'), ], ], 'wyvern' => [ 'eur' => [ 'monthly' => env('STRIPE_WYVERN_EUR'), 'yearly' => env('STRIPE_WYVERN_EUR_YEARLY'), ], 'usd' => [ 'monthly' => env('STRIPE_WYVERN_USD'), 'yearly' => env('STRIPE_WYVERN_USD_YEARLY'), ], 'brl' => [ 'monthly' => env('STRIPE_WYVERN_BRL'), 'yearly' => env('STRIPE_WYVERN_BRL_YEARLY'), ], 'monthly' => [ env('STRIPE_WYVERN_EUR'), env('STRIPE_WYVERN_EUR_OLD'), env('STRIPE_WYVERN_USD'), env('STRIPE_WYVERN_USD_OLD'), env('STRIPE_WYVERN_BRL'), ], 'yearly' => [ env('STRIPE_WYVERN_EUR_YEARLY'), env('STRIPE_WYVERN_EUR_YEARLY_OLD'), env('STRIPE_WYVERN_USD_YEARLY'), env('STRIPE_WYVERN_USD_YEARLY_OLD'), env('STRIPE_WYVERN_BRL_YEARLY'), ], ], 'elemental' => [ 'eur' => [ 'monthly' => env('STRIPE_ELEMENTAL_EUR'), 'yearly' => env('STRIPE_ELEMENTAL_EUR_YEARLY'), ], 'usd' => [ 'monthly' => env('STRIPE_ELEMENTAL_USD'), 'yearly' => env('STRIPE_ELEMENTAL_USD_YEARLY'), ], 'brl' => [ 'monthly' => env('STRIPE_ELEMENTAL_BRL'), 'yearly' => env('STRIPE_ELEMENTAL_BRL_YEARLY'), ], 'monthly' => [ env('STRIPE_ELEMENTAL_EUR'), env('STRIPE_ELEMENTAL_EUR_OLD'), env('STRIPE_ELEMENTAL_USD'), env('STRIPE_ELEMENTAL_USD_OLD'), env('STRIPE_ELEMENTAL_BRL'), ], 'yearly' => [ env('STRIPE_ELEMENTAL_EUR_YEARLY'), env('STRIPE_ELEMENTAL_EUR_YEARLY_OLD'), env('STRIPE_ELEMENTAL_USD_YEARLY'), env('STRIPE_ELEMENTAL_USD_YEARLY_OLD'), env('STRIPE_ELEMENTAL_BRL_YEARLY'), ], ], 'old' => [ 'all' => [ env('STRIPE_OWLBEAR_EUR_OLD'), env('STRIPE_OWLBEAR_EUR_YEARLY_OLD'), env('STRIPE_OWLBEAR_USD_OLD'), env('STRIPE_OWLBEAR_USD_YEARLY_OLD'), env('STRIPE_WYVERN_EUR_OLD'), env('STRIPE_WYVERN_EUR_YEARLY_OLD'), env('STRIPE_WYVERN_USD_OLD'), env('STRIPE_WYVERN_USD_YEARLY_OLD'), env('STRIPE_ELEMENTAL_EUR_OLD'), env('STRIPE_ELEMENTAL_EUR_YEARLY_OLD'), env('STRIPE_ELEMENTAL_USD_OLD'), env('STRIPE_ELEMENTAL_USD_YEARLY_OLD'), ], 'oe' => env('STRIPE_OWLBEAR_EUR_OLD'), 'oey' => env('STRIPE_OWLBEAR_EUR_YEARLY_OLD'), 'ou' => env('STRIPE_OWLBEAR_USD_OLD'), 'ouy' => env('STRIPE_OWLBEAR_USD_YEARLY_OLD'), 'we' => env('STRIPE_WYVERN_EUR_OLD'), 'wey' => env('STRIPE_WYVERN_EUR_YEARLY_OLD'), 'wu' => env('STRIPE_WYVERN_USD_OLD'), 'wuy' => env('STRIPE_WYVERN_USD_YEARLY_OLD'), 'ee' => env('STRIPE_ELEMENTAL_EUR_OLD'), 'eey' => env('STRIPE_ELEMENTAL_EUR_YEARLY_OLD'), 'eu' => env('STRIPE_ELEMENTAL_USD_OLD'), 'euy' => env('STRIPE_ELEMENTAL_USD_YEARLY_OLD'), ], ]; ================================================ FILE: config/thumbor.php ================================================ env('THUMBOR_KEY'), 'bases' => [ 'user' => 'https://' . env('AWS_BUCKET') . '.s3.eu-central-1.amazonaws.com/', 'app' => 'https://' . env('AWS_BUCKET_APP') . '.s3.eu-central-1.amazonaws.com/', ], 'url' => env('THUMBOR_URL', 'http://localhost:8889/'), ]; ================================================ FILE: config/tracking.php ================================================ env('TRACKING_GTM'), /* * Google Analytics * Used to track visits to the application. * If empty, tracking will be disabled */ 'ga' => env('TRACKING_GA'), /* * Google Analytics conversation tracking * Used to track who converts to the app * If empty, tracking will be disabled */ 'ga_convo' => env('TRACKING_GA_CONVERSION'), /* * AdSense ID */ 'adsense' => env('TRACKING_ADSENSE'), 'adsense_sidebar' => env('TRACKING_ADSENSE_SIDEBAR'), 'adsense_dashboard' => env('TRACKING_ADSENSE_DASHBOARD'), 'adsense_entity' => env('TRACKING_ADSENSE_ENTITY'), 'adsense_footer' => env('TRACKING_ADSENSE_FOOTER'), /* * Venatus ad-manager */ 'venatus' => [ 'enabled' => ! empty(env('TRACKING_VENATUS')), 'id' => env('TRACKING_VENATUS'), 'sidebar' => env('TRACKING_VENATUS_DYNAMIC_MOBILE'), 'entity' => env('TRACKING_VENATUS_STATIC_BANNER'), 'hybrid' => env('TRACKING_VENATUS_DYNAMIC_BANNER'), 'profile' => env('TRACKING_VENATUS_DYNAMIC_MOBILE'), 'inline' => env('TRACKING_VENATUS_DYNAMIC_MOBILE'), 'rich' => env('TRACKING_VENATUS_RICH'), ], 'consent' => env('TRACKING_CONSENT') == 'True', 'ga_property_id' => env('GA4_PROPERTY_ID'), 'ga_credential_path' => env('GA4_CREDENTIAL_PATH'), ]; ================================================ FILE: config/trustedproxy.php ================================================ env('TRUSTED_PROXIES', false), 'proxies' => explode(',', env('TRUSTED_PROXIES', '')), ]; ================================================ FILE: config/view.php ================================================ [ resource_path('views'), ], /* |-------------------------------------------------------------------------- | Compiled View Path |-------------------------------------------------------------------------- | | This option determines where all the compiled Blade templates will be | stored for your application. Typically, this is within the storage | directory. However, as usual, you are free to change this value. | */ 'compiled' => realpath(storage_path('framework/views')), ]; ================================================ FILE: database/.gitignore ================================================ *.sqlite ================================================ FILE: database/factories/AbilityFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/AttributeFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), 'value' => rand(500, 5000), 'type_id' => 1, 'api_key' => '1', 'is_hidden' => 0, 'is_private' => 0, ]; } } ================================================ FILE: database/factories/BookmarkFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), 'type' => 'ability', ]; } } ================================================ FILE: database/factories/CalendarFactory.php ================================================ */ public function definition(): array { return [ 'name' => 'Gregorian', 'months' => '[{"name":"January","length":31,"type":"standard","alias":""},{"name":"February","length":28,"type":"standard","alias":""},{"name":"March","length":31,"type":"standard","alias":""},{"name":"April","length":30,"type":"standard","alias":""},{"name":"Mai","length":31,"type":"standard","alias":""},{"name":"June","length":30,"type":"standard","alias":""},{"name":"July","length":31,"type":"standard","alias":""},{"name":"August","length":31,"type":"standard","alias":""},{"name":"September","length":30,"type":"standard","alias":""},{"name":"October","length":31,"type":"standard","alias":""},{"name":"November","length":30,"type":"standard","alias":""},{"name":"December","length":31,"type":"standard","alias":""}]', 'weekdays' => '["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]', 'seasons' => '[{"name":"Spring","month":3,"day":21},{"name":"Summer","month":6,"day":21},{"name":"Autumn","month":9,"day":21},{"name":"Winter","month":12,"day":21}]', 'suffix' => 'AD', 'has_leap_year' => 1, 'leap_year_amount' => 1, 'leap_year_month' => 2, 'leap_year_offset' => 4, 'leap_year_start' => 4, 'skip_year_zero' => 1, 'start_offset' => 5, 'is_incrementing' => 1, ]; } } ================================================ FILE: database/factories/CampaignDashboardWidgetFactory.php ================================================ */ public function definition(): array { return [ 'widget' => 'header', ]; } } ================================================ FILE: database/factories/CampaignFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(20), 'entry' => '

    ' . fake()->text(500) . '

    ', 'excerpt' => fake()->text(100), 'ui_settings' => ['nested' => true], 'entity_visibility' => fake()->boolean(), 'entity_personality_visibility' => fake()->boolean(), 'visible_entity_count' => fake()->numberBetween(5, 100), 'is_featured' => false, 'boost_count' => 0, ]; } } ================================================ FILE: database/factories/CampaignStyleFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), 'content' => fake()->text(50), 'is_enabled' => false, 'is_theme' => false, ]; } } ================================================ FILE: database/factories/CharacterFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/ConversationFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), 'target_id' => 2, 'is_private' => false, ]; } } ================================================ FILE: database/factories/ConversationMessageFactory.php ================================================ */ public function definition(): array { return [ 'message' => fake()->text(20), ]; } } ================================================ FILE: database/factories/ConversationParticipantFactory.php ================================================ */ public function definition(): array { return [ 'character_id' => 1, ]; } } ================================================ FILE: database/factories/CreatureFactory.php ================================================ */ class CreatureFactory extends Factory { /** * Define the model's default state. * * @return array */ public function definition(): array { return [ 'name' => fake()->name(), 'is_private' => false, ]; } } ================================================ FILE: database/factories/DiceRollFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), 'parameters' => '2d2', 'is_private' => false, ]; } } ================================================ FILE: database/factories/EntityAbilityFactory.php ================================================ */ public function definition(): array { return [ 'charges' => rand(0, 100), ]; } } ================================================ FILE: database/factories/EntityAssetFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), 'type_id' => 1, 'is_pinned' => 0, 'visibility_id' => 1, ]; } } ================================================ FILE: database/factories/EntityTagFactory.php ================================================ */ public function definition(): array { return [ 'entity_id' => 1, 'tag_id' => 1, ]; } } ================================================ FILE: database/factories/EventFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/FamilyFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/ImageFactory.php ================================================ */ public function definition(): array { return [ 'id' => fake()->uuid(), 'name' => fake()->text(10), 'ext' => 'png', 'size' => 209, 'is_default' => 0, 'folder_id' => null, 'is_folder' => 0, 'visibility_id' => 1, ]; } } ================================================ FILE: database/factories/ItemFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/JournalFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/LocationFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), 'is_private' => false, ]; } } ================================================ FILE: database/factories/MapFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), ]; } } ================================================ FILE: database/factories/MapGroupFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), ]; } } ================================================ FILE: database/factories/MapLayerFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), ]; } } ================================================ FILE: database/factories/MapMarkerFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), 'longitude' => 1, 'latitude' => 1, 'icon' => 1, 'shape_id' => 1, ]; } } ================================================ FILE: database/factories/NoteFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/OrganisationFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/PostFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), 'entry' => '

    ' . fake()->text(500) . '

    ', 'position' => 1, ]; } } ================================================ FILE: database/factories/PostTagFactory.php ================================================ */ public function definition(): array { return [ 'post_id' => 1, 'tag_id' => 1, ]; } } ================================================ FILE: database/factories/QuestElementFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), 'entry' => '

    ' . fake()->text(50) . '

    ', ]; } } ================================================ FILE: database/factories/QuestFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/RaceFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/RelationFactory.php ================================================ */ public function definition(): array { return [ 'relation' => fake()->text(20), 'is_pinned' => 0, ]; } } ================================================ FILE: database/factories/ReminderFactory.php ================================================ */ public function definition(): array { return [ 'calendar_id' => 1, 'day' => 2, 'month' => 2, 'year' => 2, 'length' => 2, 'visibility_id' => 1, ]; } } ================================================ FILE: database/factories/TagFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->text(10), 'is_hidden' => 0, ]; } } ================================================ FILE: database/factories/TimelineElementFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), 'use_event_date' => false, ]; } } ================================================ FILE: database/factories/TimelineEraFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/TimelineFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), ]; } } ================================================ FILE: database/factories/UserFactory.php ================================================ */ public function definition(): array { return [ 'name' => fake()->name(), 'email' => fake()->unique()->safeEmail(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ]; } } ================================================ FILE: database/migrations/.gitkeep ================================================ ================================================ FILE: database/migrations/2014_04_02_193005_create_ltm_translations_table.php ================================================ increments('id'); $table->tinyInteger('status')->default(0); $table->string('locale', 32); $table->string('group', 128); $table->string('key', 128); $table->text('value')->nullable(); $table->text('saved_value')->nullable(); $table->text('source')->nullable(); $table->tinyInteger('is_deleted')->default(0); $table->tinyInteger('was_used')->default(0); $table->boolean('is_auto_added')->default(0); $table->timestamps(); $table->index(['group'], 'ix_ltm_translations_group'); $table->unique(['locale', 'group', 'key'], 'ixk_ltm_translations_locale_group_key'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('ltm_translations'); } } ================================================ FILE: database/migrations/2014_10_12_000000_create_users_table.php ================================================ increments('id'); $table->string('name'); $table->string('email')->unique(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('users'); } } ================================================ FILE: database/migrations/2014_10_12_100000_create_password_resets_table.php ================================================ string('email')->index(); $table->string('token'); $table->timestamp('created_at')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('password_resets'); } } ================================================ FILE: database/migrations/2016_01_01_000000_add_voyager_user_fields.php ================================================ string('avatar')->nullable()->after('email'); } if (! Schema::hasColumn('users', 'settings')) { $table->text('settings')->nullable()->after('avatar'); } // $table->integer('role_id')->nullable()->after('id'); }); } /** * Reverse the migrations. */ public function down() { if (Schema::hasColumn('users', 'avatar')) { Schema::table('users', function ($table) { $table->dropColumn('avatar'); }); } if (Schema::hasColumn('users', 'role_id')) { Schema::table('users', function ($table) { $table->dropColumn('role_id'); }); } } } ================================================ FILE: database/migrations/2016_04_11_00352701_create_user_locales_table.php ================================================ increments('id'); $table->integer('user_id', false, true); $table->text('locales')->nullable(); $table->text('tutorial')->nullable(); $table->index(['user_id'], 'ix_ltm_user_locales_user_id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('ltm_user_locales'); } } ================================================ FILE: database/migrations/2016_06_01_000001_create_oauth_auth_codes_table.php ================================================ string('id', 100)->primary(); $table->unsignedBigInteger('user_id')->index(); $table->unsignedBigInteger('client_id'); $table->text('scopes')->nullable(); $table->boolean('revoked'); $table->dateTime('expires_at')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('oauth_auth_codes'); } }; ================================================ FILE: database/migrations/2016_06_01_000002_create_oauth_access_tokens_table.php ================================================ string('id', 100)->primary(); $table->unsignedBigInteger('user_id')->nullable()->index(); $table->unsignedBigInteger('client_id'); $table->string('name')->nullable(); $table->text('scopes')->nullable(); $table->boolean('revoked'); $table->timestamps(); $table->dateTime('expires_at')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('oauth_access_tokens'); } }; ================================================ FILE: database/migrations/2016_06_01_000003_create_oauth_refresh_tokens_table.php ================================================ string('id', 100)->primary(); $table->string('access_token_id', 100)->index(); $table->boolean('revoked'); $table->dateTime('expires_at')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('oauth_refresh_tokens'); } }; ================================================ FILE: database/migrations/2016_06_01_000004_create_oauth_clients_table.php ================================================ bigIncrements('id'); $table->unsignedBigInteger('user_id')->nullable()->index(); $table->string('name'); $table->string('secret', 100)->nullable(); $table->string('provider')->nullable(); $table->text('redirect'); $table->boolean('personal_access_client'); $table->boolean('password_client'); $table->boolean('revoked'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('oauth_clients'); } }; ================================================ FILE: database/migrations/2016_06_01_000005_create_oauth_personal_access_clients_table.php ================================================ bigIncrements('id'); $table->unsignedBigInteger('client_id'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('oauth_personal_access_clients'); } }; ================================================ FILE: database/migrations/2016_10_21_190000_create_roles_table.php ================================================ bigIncrements('id'); $table->string('name')->unique(); $table->string('display_name'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('roles'); } } ================================================ FILE: database/migrations/2017_10_27_090105_create_campaign.php ================================================ increments('id'); $table->string('name'); $table->string('slug'); $table->string('locale', 5)->nullable(); $table->string('image', 191)->nullable(); $table->longText('entry')->nullable(); $table->text('excerpt')->nullable(); $table->string('header_image')->nullable(); $table->string('system', 45)->nullable(); $table->string('export_path')->nullable(); $table->date('export_date')->nullable(); $table->unsignedTinyInteger('visibility_id')->default(CampaignVisibility::private); $table->boolean('is_featured')->default(false); $table->boolean('entity_visibility')->default(false); $table->unsignedInteger('visible_entity_count')->default(0); $table->boolean('entity_personality_visibility')->default(true); $table->text('settings')->nullable(); $table->text('default_images')->nullable(); $table->text('ui_settings')->nullable(); $table->unsignedTinyInteger('boost_count')->nullable(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->dateTime('featured_until')->nullable(); $table->text('featured_reason')->nullable(); $table->unsignedInteger('follower')->default(0); $table->boolean('is_hidden')->default(0); $table->timestamps(); $table->index(['visibility_id', 'is_featured', 'visible_entity_count', 'featured_until', 'is_hidden'], 'campaigns_idx'); $table->index('follower'); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); }); Schema::create('campaign_user', function (Blueprint $table) { $table->increments('id'); $table->integer('user_id')->unsigned(); $table->integer('campaign_id')->unsigned(); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('campaign_user'); Schema::drop('campaigns'); } } ================================================ FILE: database/migrations/2017_10_27_091125_create_characters.php ================================================ increments('id'); $table->string('name'); $table->integer('campaign_id')->unsigned(); // Overview $table->longText('entry')->nullable(); $table->string('title')->nullable(); $table->string('type', 45)->nullable(); // Appearance $table->string('age', 20)->nullable(); $table->string('sex', 45)->nullable(); $table->string('pronouns', 45)->nullable(); $table->string('image')->nullable(); $table->timestamps(); $table->boolean('is_private')->default(false); $table->boolean('is_personality_pinned')->default(false); $table->boolean('is_appearance_pinned')->default(false); $table->index(['is_private']); $table->boolean('is_personality_visible')->default(true); $table->boolean('is_dead')->default(false); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); // Index $table->index(['name']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('characters'); } } ================================================ FILE: database/migrations/2017_10_27_091755_create_families.php ================================================ increments('id'); $table->timestamps(); $table->string('name'); $table->integer('campaign_id')->unsigned(); $table->integer('family_id')->unsigned()->nullable(); $table->string('type', 45)->nullable(); $table->longText('entry')->nullable(); $table->string('image')->nullable(); // Privacy $table->boolean('is_private')->default(false); $table->index(['is_private']); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('family_id')->references('id')->on('families')->onDelete('set null'); // Indexes $table->index(['name']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('families'); } } ================================================ FILE: database/migrations/2017_10_27_102246_create_locations.php ================================================ increments('id'); $table->string('name'); $table->string('type', 45)->nullable(); $table->string('image')->nullable(); $table->longText('entry')->nullable(); $table->integer('parent_location_id')->unsigned()->nullable(); $table->timestamps(); $table->integer('campaign_id')->unsigned(); // Privacy $table->boolean('is_private')->default(false); $table->boolean('is_map_private')->default(0); // Index $table->index(['name', 'type']); $table->index(['is_private']); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('parent_location_id')->references('id')->on('locations')->nullOnDelete(); }); Schema::create('location_attributes', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->string('value'); $table->integer('location_id')->unsigned(); $table->timestamps(); // Index $table->index(['name']); // Foreign $table->foreign('location_id')->references('id')->on('locations')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('location_attributes'); Schema::dropIfExists('locations'); } } ================================================ FILE: database/migrations/2017_10_28_091521_update_character_with_location.php ================================================ integer('location_id')->unsigned()->nullable(); $table->foreign('location_id')->references('id')->on('locations')->nullOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('characters', function (Blueprint $table) { $table->dropForeign('characters_location_id_foreign'); $table->dropColumn('location_id'); }); } } ================================================ FILE: database/migrations/2017_10_30_010000_create_real_visibilities_table.php ================================================ id(); $table->string('code', 10); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('visibilities'); } } ================================================ FILE: database/migrations/2017_10_30_010002_create_real_entities_table.php ================================================ increments('id'); $table->unsignedInteger('type_id')->nullable(); $table->string('name'); $table->boolean('is_private')->default(0); $table->integer('entity_id')->unsigned(); $table->integer('campaign_id')->unsigned(); $table->text('tooltip')->nullable(); $table->string('header_image')->nullable(); $table->boolean('is_template')->nullable(); $table->boolean('is_attributes_private')->default(false); $table->unsignedSmallInteger('focus_x')->nullable(); $table->unsignedSmallInteger('focus_y')->nullable(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->unsignedInteger('deleted_by')->nullable(); $table->timestamps(); // Foreign $table->foreign('campaign_id') ->references('id')->on('campaigns') ->cascadeOnDelete(); $table->foreign('created_by') ->references('id')->on('users') ->nullOnDelete(); $table->foreign('updated_by') ->references('id')->on('users') ->nullOnDelete(); $table->index(['name', 'is_private', 'is_template']); $table->index('updated_at'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entities'); } } ================================================ FILE: database/migrations/2017_10_30_160256_create_journal_table.php ================================================ increments('id'); $table->string('name'); $table->unsignedInteger('journal_id')->nullable(); $table->string('type')->nullable(); $table->string('image', 255)->nullable(); $table->date('date')->nullable(); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('author_id')->nullable(); $table->unsignedInteger('location_id')->nullable(); // Overview $table->longText('entry')->nullable(); $table->timestamps(); // Privacy $table->boolean('is_private')->default(false); $table->index(['is_private']); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('author_id')->references('id')->on('entities')->nullOnDelete(); $table->foreign('location_id')->references('id')->on('locations')->nullOnDelete(); $table->foreign('journal_id')->references('id')->on('journals')->nullOnDelete(); // Index $table->index(['name', 'type', 'date']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('journals'); } } ================================================ FILE: database/migrations/2017_10_30_160311_create_item_table.php ================================================ increments('id'); $table->string('name'); $table->string('type')->nullable(); $table->string('image', 255)->nullable(); $table->date('date')->nullable(); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('item_id')->nullable(); $table->unsignedInteger('character_id')->nullable(); $table->unsignedInteger('location_id')->nullable(); $table->longText('entry')->nullable(); $table->string('price')->nullable(); $table->string('size')->nullable(); $table->boolean('is_private')->default(false); $table->timestamps(); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('location_id')->references('id')->on('locations')->nullOnDelete(); $table->foreign('character_id')->references('id')->on('characters')->nullOnDelete(); $table->foreign('item_id')->references('id')->on('items')->nullOnDelete(); // Index $table->index(['name', 'type', 'is_private']); $table->index(['price', 'size'], 'items_price_idx'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('items'); } } ================================================ FILE: database/migrations/2017_10_30_162322_create_family_relations.php ================================================ integer('location_id')->unsigned()->nullable(); $table->foreign('location_id')->references('id')->on('locations')->nullOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() {} } ================================================ FILE: database/migrations/2017_10_30_164537_add_character_family.php ================================================ integer('family_id')->unsigned()->nullable(); $table->foreign('family_id')->references('id')->on('families')->nullOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() {} } ================================================ FILE: database/migrations/2017_10_31_124138_update_user_and_campaign_link.php ================================================ unsignedInteger('last_campaign_id')->nullable(); $table->string('password')->nullable()->change(); $table->string('provider')->nullable(); $table->string('provider_id')->nullable(); $table->string('locale', 5)->default('en'); $table->string('patreon_pledge', 10)->null(); $table->unsignedSmallInteger('default_pagination')->notNull()->default('15'); $table->string('date_format', 20)->notNull()->default('Y-m-d'); $table->char('currency', 3)->nullable(); $table->unsignedTinyInteger('booster_count')->nullable(); $table->dateTime('last_login_at')->nullable(); $table->boolean('has_last_login_sharing')->default(0); $table->string('theme', 20)->nullable(); $table->text('profile')->nullable(); $table->datetime('banned_until')->nullable(); $table->index(['provider', 'provider_id']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('last_campaign_id'); }); } } ================================================ FILE: database/migrations/2017_11_01_222903_create_organisation.php ================================================ increments('id'); $table->string('name'); $table->string('type')->nullable(); $table->string('image', 255)->nullable(); $table->integer('campaign_id')->unsigned(); $table->integer('location_id')->unsigned()->nullable(); $table->unsignedInteger('organisation_id')->nullable(); $table->longText('entry')->nullable(); $table->boolean('is_private')->default(false); $table->boolean('is_defunct')->default(0); $table->timestamps(); $table->index(['is_private']); $table->index('is_defunct'); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('location_id')->references('id')->on('locations')->nullOnDelete(); $table->foreign('organisation_id')->references('id')->on('organisations')->onDelete('set null'); // Index $table->index(['name', 'type']); }); Schema::create('organisation_member', function (Blueprint $table) { $table->increments('id'); $table->integer('organisation_id')->unsigned(); $table->integer('character_id')->unsigned(); $table->string('role', 45)->nullable(); $table->boolean('is_private')->default(false); $table->unsignedInteger('parent_id')->nullable(); $table->unsignedTinyInteger('pin_id')->nullable(); $table->unsignedTinyInteger('status_id')->default(0); $table->timestamps(); // Foreign $table->foreign('organisation_id')->references('id')->on('organisations')->cascadeOnDelete(); $table->foreign('character_id')->references('id')->on('characters')->cascadeOnDelete(); $table->foreign('parent_id')->references('id')->on('organisation_member')->nullOnDelete(); // Index $table->index(['is_private', 'role']); $table->index('pin_id'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('organisation_member'); Schema::dropIfExists('organisations'); } } ================================================ FILE: database/migrations/2017_11_03_181958_create_notes.php ================================================ increments('id'); $table->string('name'); $table->string('type')->nullable(); $table->string('image', 255)->nullable(); $table->integer('campaign_id')->unsigned(); $table->unsignedInteger('note_id')->nullable(); // Overview $table->longText('entry')->nullable(); $table->timestamps(); // Privacy $table->boolean('is_private')->default(false); $table->index(['is_private']); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('note_id')->references('id')->on('notes')->nullOnDelete(); // Index $table->index(['name', 'type']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('notes'); } } ================================================ FILE: database/migrations/2017_11_12_195702_create_invite_tokens.php ================================================ increments('id'); $table->integer('campaign_id')->unsigned(); $table->integer('created_by')->unsigned()->nullable(); $table->string('token', 128); $table->boolean('is_active')->default(true); $table->integer('validity')->unsigned()->nullable(); // $table->integer('role_id')->unsigned()->nullable(); $table->timestamps(); // Foreign $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); // $table->foreign('role_id')->references('id')->on('campaign_roles')->onDelete('set null'); $table->index(['token', 'is_active']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('campaign_invites'); } } ================================================ FILE: database/migrations/2017_11_16_145219_create_events.php ================================================ increments('id'); $table->string('name'); $table->string('type')->nullable(); $table->string('date')->nullable(); $table->string('image', 255)->nullable(); $table->boolean('is_private')->default(false); $table->integer('campaign_id')->unsigned(); $table->integer('location_id')->unsigned()->nullable(); $table->unsignedInteger('event_id')->nullable(); $table->longText('entry')->nullable(); $table->timestamps(); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('event_id')->references('id')->on('events')->nullOnDelete(); // Index $table->index(['name', 'type', 'date', 'is_private']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('events'); } } ================================================ FILE: database/migrations/2017_11_16_222319_create_campaign_settings.php ================================================ increments('id'); $table->integer('campaign_id')->unsigned(); $table->boolean('characters')->default(true); $table->boolean('events')->default(true); $table->boolean('families')->default(true); $table->boolean('items')->default(true); $table->boolean('journals')->default(true); $table->boolean('locations')->default(true); $table->boolean('notes')->default(true); $table->boolean('organisations')->default(true); $table->boolean('menu_links')->default(true); $table->boolean('maps')->default(true); $table->boolean('inventories')->default(true); $table->boolean('entity_attributes')->default(true); $table->timestamps(); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('campaign_settings'); } } ================================================ FILE: database/migrations/2017_11_22_111255_create_quests.php ================================================ increments('id'); $table->integer('campaign_id')->unsigned()->nullable(); $table->integer('quest_id')->unsigned()->nullable(); $table->unsignedInteger('instigator_id')->nullable(); $table->string('name'); $table->string('type', 45)->nullable(); $table->longText('entry')->nullable(); $table->string('image', 255)->nullable(); $table->boolean('is_private')->default(false); $table->date('date')->nullable(); $table->boolean('is_completed')->default(false); $table->timestamps(); // Indexes $table->index(['name', 'is_private']); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('quest_id')->references('id')->on('quests')->nullOnDelete(); $table->foreign('instigator_id')->references('id')->on('entities')->nullOnDelete(); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('quests')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('quests'); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('quests'); }); } } ================================================ FILE: database/migrations/2017_11_30_113216_create_relations_table.php ================================================ increments('id'); $table->boolean('is_private')->default(0); $table->integer('owner_id')->unsigned(); $table->integer('target_id')->unsigned(); $table->integer('campaign_id')->unsigned(); $table->string('relation', 255); $table->string('colour', 6)->nullable(); $table->tinyInteger('attitude')->default(0)->nullable(); $table->boolean('is_pinned')->default(false); $table->integer('created_by')->unsigned()->nullable(); $table->integer('updated_by')->unsigned()->nullable(); $table->unsignedInteger('mirror_id')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('owner_id')->references('id')->on('entities')->cascadeOnDelete(); $table->foreign('target_id')->references('id')->on('entities')->cascadeOnDelete(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('mirror_id')->references('id')->on('relations')->nullOnDelete(); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); $table->index(['is_private', 'attitude', 'relation', 'is_pinned']); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('relations'); } } ================================================ FILE: database/migrations/2018_01_25_112528_create_attributes_table.php ================================================ increments('id'); $table->string('name'); $table->text('value', 191)->nullable(); $table->string('type', 12)->nullable(); $table->boolean('is_private')->default(0); $table->integer('entity_id')->unsigned(); $table->unsignedSmallInteger('default_order')->null()->default('0'); $table->unsignedInteger('origin_attribute_id')->nullable(); $table->unsignedTinyInteger('type_id')->default(1); $table->boolean('is_pinned')->default(false); $table->string('api_key', 20)->null(); $table->boolean('is_hidden')->default(false); $table->timestamps(); // Foreign $table->foreign('origin_attribute_id')->references('id')->on('attributes')->onDelete('set null'); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->index(['name', 'is_private', 'is_hidden', 'is_pinned', 'default_order'], 'attributes_idx'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('attributes'); } } ================================================ FILE: database/migrations/2018_02_14_102646_create_attribute_template.php ================================================ increments('id'); $table->integer('campaign_id')->unsigned(); $table->unsignedInteger('attribute_template_id')->nullable(); $table->string('name'); $table->boolean('is_private')->default(false); $table->timestamps(); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('attribute_template_id')->references('id')->on('attribute_templates')->onDelete('set null'); // Indexes $table->index(['name', 'is_private']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('attribute_templates'); } } ================================================ FILE: database/migrations/2018_03_02_163640_create_new_acl.php ================================================ increments('id'); $table->string('name'); $table->integer('campaign_id')->unsigned(); $table->boolean('is_admin')->default(false); $table->boolean('is_public')->default(false); $table->timestamps(); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); // Indexes $table->index(['name']); }); // Each campaign member can have several roles Schema::dropIfExists('campaign_role_users'); Schema::create('campaign_role_users', function (Blueprint $table) { $table->increments('id'); $table->integer('campaign_role_id')->unsigned(); $table->integer('user_id')->unsigned(); $table->timestamps(); // Foreign $table->foreign('campaign_role_id')->references('id')->on('campaign_roles')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); // Each role can have default permissions for all the elements Schema::dropIfExists('campaign_permissions'); Schema::create('campaign_permissions', function (Blueprint $table) { $table->increments('id'); // A permission can either be related to a role $table->integer('campaign_role_id')->unsigned()->nullable(); // Or be related to a user $table->integer('user_id')->unsigned()->nullable(); // A key is a simple concept that allows us to easily get everything // browse_characters => Allow browsing characters // edit_locations_4 => Allow editing location id 4 // $table->string('key', 191); // The table name // $table->string('table_name', 191); $table->boolean('access')->default(true); $table->timestamps(); // Foreign $table->foreign('campaign_role_id')->references('id')->on('campaign_roles')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); // Indexes // $table->index(['key', 'table_name']); }); } /** * Reverse the migrations. * * @return void */ public function down() {} } ================================================ FILE: database/migrations/2018_03_14_152413_create_notifications_table.php ================================================ char('id', 36)->primary(); $table->string('type'); $table->morphs('notifiable'); $table->text('data'); $table->timestamp('read_at')->nullable(); $table->timestamps(); $table->index('read_at'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('notifications'); } } ================================================ FILE: database/migrations/2018_04_09_17054701_add_ui_settings_to_ltm_user_locales.php ================================================ dropIndex('ix_ltm_user_locales_user_id'); $table->unique('user_id', 'ixk_user_id_users_id'); $table->text('ui_settings')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { $prefix = Config::get('laravel-translation-manager::config.table_prefix', ''); Schema::table($prefix . 'ltm_user_locales', function (Blueprint $table) { $table->dropColumn('ui_settings'); $table->dropUnique('ixk_user_id_users_id'); $table->index(['user_id'], 'ix_ltm_user_locales_user_id'); }); } } ================================================ FILE: database/migrations/2018_04_16_090159_create_calendar_table.php ================================================ increments('id'); $table->integer('campaign_id')->unsigned()->nullable(); $table->string('name'); $table->unsignedInteger('calendar_id')->nullable(); $table->string('type', 45)->nullable(); $table->longText('entry')->nullable(); $table->string('image', 255)->nullable(); $table->boolean('is_private')->default(false); // Settings of the calendar $table->text('parameters')->nullable(); $table->text('months')->nullable(); $table->text('weekdays')->nullable(); $table->text('years')->nullable(); $table->text('seasons')->nullable(); $table->text('epochs')->nullable(); $table->text('moons')->nullable(); $table->string('date')->nullable(); $table->string('suffix')->nullable(); $table->text('month_aliases')->nullable(); $table->text('week_names')->nullable(); $table->string('reset', 5)->nullable(); $table->boolean('is_incrementing')->default(false); // Leap year stuff, single $table->boolean('has_leap_year')->default(false); $table->integer('leap_year_amount')->nullable(); $table->tinyInteger('leap_year_month')->unsigned()->nullable(); $table->tinyInteger('leap_year_offset')->unsigned()->nullable(); $table->tinyInteger('leap_year_start')->unsigned()->nullable(); $table->unsignedTinyInteger('start_offset')->nullable()->default(0); $table->boolean('skip_year_zero')->default(false); $table->timestamps(); // Indexes $table->index(['name', 'is_private']); $table->index(['is_incrementing']); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('calendar_id')->references('id')->on('calendars')->onDelete('set null'); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('calendars')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('calendars'); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('calendars'); }); $entities = Entity::where(['type' => 'calendar'])->get(); foreach ($entities as $entity) { $entity->delete(); } } } ================================================ FILE: database/migrations/2018_04_24_075544_create_entity_notes_table.php ================================================ increments('id'); $table->string('name'); $table->longText('entry')->nullable(); $table->boolean('is_private')->default(0); $table->unsignedInteger('entity_id'); $table->unsignedInteger('created_by')->nullable(); $table->integer('updated_by')->unsigned()->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->smallInteger('position'); $table->text('settings')->nullable(); $table->timestamps(); // Foreign $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); $table->index(['name', 'is_private']); $table->index(['position']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entity_notes'); } } ================================================ FILE: database/migrations/2018_04_24_124131_create_character_traits_table.php ================================================ increments('id'); $table->unsignedInteger('character_id'); $table->unsignedInteger('created_by')->nullable(); $table->string('name'); $table->text('entry')->nullable(); $table->boolean('is_private')->default(0); $table->unsignedTinyInteger('section_id')->default(CharacterTrait::SECTION_APPEARANCE); $table->unsignedSmallInteger('default_order')->nullable()->default('0'); $table->timestamps(); // Foreign $table->foreign('character_id')->references('id')->on('characters')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->index(['name', 'section_id', 'is_private']); $table->index(['default_order']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('character_traits'); } } ================================================ FILE: database/migrations/2018_04_24_193803_create_sections_table.php ================================================ increments('id'); $table->integer('campaign_id')->unsigned()->nullable(); $table->string('name'); $table->string('slug')->nullable(); $table->string('type', 45)->nullable(); $table->string('image', 255)->nullable(); $table->longText('entry')->nullable(); $table->boolean('is_private')->default(false); $table->string('colour', 20)->nullable(); $table->unsignedInteger('tag_id')->nullable(); $table->boolean('is_hidden')->default(false); $table->boolean('is_auto_applied')->default(0); $table->timestamps(); $table->index(['name', 'type', 'is_private', 'is_hidden', 'is_auto_applied']); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('set null'); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('tags')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('tags'); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('tags'); }); } } ================================================ FILE: database/migrations/2018_04_30_174331_create_entity_events.php ================================================ increments('id'); $table->integer('calendar_id')->unsigned(); $table->integer('entity_id')->unsigned(); $table->unsignedInteger('created_by')->nullable(); $table->smallInteger('length')->unsigned()->default(1); $table->boolean('is_recurring')->default(false); $table->string('comment')->nullable(); $table->string('colour', 12)->nullable(); $table->unsignedBigInteger('visibility_id'); $table->unsignedMediumInteger('day'); $table->unsignedMediumInteger('month'); $table->integer('year'); $table->string('recurring_until', 12)->nullable(); $table->string('recurring_periodicity', 5)->nullable(); $table->timestamps(); $table->index(['is_recurring']); $table->index(['day', 'month', 'year']); // Foreign $table->foreign('calendar_id')->references('id')->on('calendars')->cascadeOnDelete(); $table->foreign('entity_id')->references('id')->on('entities')->cascadeOnDelete(); $table->foreign('created_by')->on('users')->references('id')->nullOnDelete(); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entity_events'); } } ================================================ FILE: database/migrations/2018_05_19_091532_add_dice_rolls.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('character_id')->nullable(); $table->string('name', 191); $table->string('system', 20)->nullable(); $table->text('parameters')->nullable(); $table->boolean('is_private')->default(false); $table->timestamps(); $table->index(['name', 'system', 'is_private']); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('character_id')->references('id')->on('characters')->onDelete('cascade'); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('dice_rolls')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('dice_rolls'); DB::statement("delete from entities where type = 'dice_roll'"); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('dice_rolls'); }); } } ================================================ FILE: database/migrations/2018_05_25_102446_update_campaign_invites.php ================================================ integer('role_id')->unsigned()->nullable(); $table->foreign('role_id')->references('id')->on('campaign_roles')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('campaign_invites', function (Blueprint $table) { $table->dropForeign('campaign_invites_role_id_foreign'); $table->dropColumn('role_id'); }); } } ================================================ FILE: database/migrations/2018_05_25_113848_create_dice_roll_results.php ================================================ string('image', 255)->nullable(); }); Schema::dropIfExists('dice_roll_results'); Schema::create('dice_roll_results', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('dice_roll_id'); $table->unsignedInteger('created_by'); $table->text('results')->nullable(); $table->timestamps(); $table->foreign('dice_roll_id')->references('id')->on('dice_rolls')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('dice_roll_results'); Schema::table('dice_rolls', function (Blueprint $table) { $table->text('results')->nullable(); $table->unsignedInteger('created_by'); $table->foreign('created_by')->references('id')->on('users')->onDelete('cascade'); }); } } ================================================ FILE: database/migrations/2018_05_31_121110_create_custom_menu_table.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('entity_id')->nullable(); $table->string('name', 100); $table->string('icon', 45)->nullable(); $table->boolean('is_private')->default(false); $table->string('tab', 20)->nullable(); $table->string('filters', 255)->nullable(); $table->string('random_entity_type', 30)->nullable(); $table->string('menu', 20)->nullable(); $table->string('type', 30)->nullable(); $table->text('options')->nullable(); $table->string('parent', 25)->nullable(); $table->string('css', 40)->nullable(); $table->unsignedSmallInteger('position')->default(1); $table->boolean('is_active')->default('1'); $table->timestamps(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('entity_id')->references('id')->on('entities')->cascadeOnDelete(); $table->index(['name', 'position', 'is_active']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('menu_links'); } } ================================================ FILE: database/migrations/2018_07_17_131457_create_jobs_table.php ================================================ bigIncrements('id'); $table->string('queue')->index(); $table->longText('payload'); $table->unsignedTinyInteger('attempts'); $table->unsignedInteger('reserved_at')->nullable(); $table->unsignedInteger('available_at'); $table->unsignedInteger('created_at'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('jobs'); } } ================================================ FILE: database/migrations/2018_08_15_083901_create_failed_jobs_table.php ================================================ bigIncrements('id'); $table->text('connection'); $table->text('queue'); $table->longText('payload'); $table->longText('exception'); $table->timestamp('failed_at')->useCurrent(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('failed_jobs'); } } ================================================ FILE: database/migrations/2018_08_20_174638_create_conversations.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('created_by')->nullable(); $table->string('name', 191); $table->string('image', 255)->nullable(); $table->string('type', 45)->nullable(); $table->unsignedTinyInteger('target_id') ->default(ConversationTarget::users->value); $table->boolean('is_private')->default(false); $table->boolean('is_closed')->default(false); $table->timestamps(); $table->index(['name', 'is_private', 'is_closed']); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); }); Schema::create('conversation_participants', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('conversation_id'); $table->unsignedInteger('character_id')->nullable(); $table->unsignedInteger('user_id')->nullable(); $table->timestamps(); $table->foreign('conversation_id')->references('id')->on('conversations')->onDelete('cascade'); $table->foreign('character_id')->references('id')->on('characters')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); Schema::create('conversation_messages', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('conversation_id'); $table->unsignedInteger('character_id')->nullable(); $table->unsignedInteger('user_id')->nullable(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->longText('message')->nullable(); $table->timestamps(); $table->foreign('conversation_id')->references('id')->on('conversations')->onDelete('cascade'); $table->foreign('character_id')->references('id')->on('characters')->onDelete('set null'); $table->foreign('user_id')->references('id')->on('users')->onDelete('set null'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); $table->index('created_at'); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('conversations')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('conversation_messages'); Schema::dropIfExists('conversation_participants'); Schema::dropIfExists('conversations'); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('conversations'); }); } } ================================================ FILE: database/migrations/2018_09_19_121625_add_calendar_date_to_quest_and_journal.php ================================================ integer('calendar_id')->unsigned()->nullable(); $table->smallInteger('calendar_year')->nullable(); $table->smallInteger('calendar_month')->nullable(); $table->smallInteger('calendar_day')->nullable(); $table->index(['calendar_year', 'calendar_month', 'calendar_day'], 'quests_calendar_index'); $table->foreign('calendar_id')->references('id')->on('calendars')->onDelete('set null'); }); Schema::table('journals', function (Blueprint $table) { $table->integer('calendar_id')->unsigned()->nullable(); $table->smallInteger('calendar_year')->nullable(); $table->smallInteger('calendar_month')->nullable(); $table->smallInteger('calendar_day')->nullable(); $table->index(['calendar_year', 'calendar_month', 'calendar_day'], 'journals_calendar_index'); $table->foreign('calendar_id')->references('id')->on('calendars')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('quests', function (Blueprint $table) { $table->dropIndex('quests_calendar_index'); $table->dropColumn('calendar_id'); $table->dropColumn('calendar_year'); $table->dropColumn('calendar_month'); $table->dropColumn('calendar_day'); }); Schema::table('journals', function (Blueprint $table) { $table->dropIndex('journals_calendar_index'); $table->dropColumn('calendar_id'); $table->dropColumn('calendar_year'); $table->dropColumn('calendar_month'); $table->dropColumn('calendar_day'); }); } } ================================================ FILE: database/migrations/2018_09_25_134530_create_race.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->string('name', 191); $table->unsignedInteger('race_id')->nullable(); $table->string('image', 255)->nullable(); $table->string('type', 45)->nullable(); $table->longText('entry')->nullable(); $table->boolean('is_private')->default(false); $table->timestamps(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->index(['name', 'type', 'is_private']); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('race_id')->references('id')->on('races')->onDelete('set null'); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('races')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('races'); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('races'); }); } } ================================================ FILE: database/migrations/2018_10_17_130533_create_entity_categories.php ================================================ increments('id'); // $table->unsignedInteger('campaign_id'); $table->unsignedInteger('entity_id'); $table->unsignedInteger('tag_id'); $table->timestamps(); // $table->unsignedInteger('created_by')->nullable(); // $table->unsignedInteger('updated_by')->nullable(); // $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); // $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); // $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entity_tags'); } } ================================================ FILE: database/migrations/2018_10_23_124704_create_entity_files.php ================================================ increments('id'); $table->string('name', 45); $table->string('type', 20); $table->string('path', 191); $table->integer('size')->unsigned(); $table->boolean('is_private')->default(0); $table->unsignedInteger('entity_id'); $table->unsignedInteger('created_by')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->timestamps(); $table->foreign('entity_id')->references('id')->on('entities')->cascadeOnDelete(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); $table->index(['name', 'is_private', 'type']); }); } /** * Reverse the migrations. * * @return void */ public function down() {} } ================================================ FILE: database/migrations/2018_11_28_125839_add_campaign_dashboard_calendar.php ================================================ increments('id'); $table->integer('campaign_id')->unsigned(); $table->unsignedInteger('entity_id')->nullable(); $table->text('config')->nullable(); $table->unsignedTinyInteger('position'); $table->string('widget', 12); $table->unsignedTinyInteger('width')->default(0); $table->timestamps(); $table->index(['campaign_id', 'position']); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('campaign_dashboard_widgets'); } } ================================================ FILE: database/migrations/2018_12_06_222045_add_campaign_permission_entity_id.php ================================================ unsignedInteger('entity_id')->nullable()->default(null); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('campaign_permissions', function (Blueprint $table) { $table->dropForeign('campaign_permissions_entity_id_foreign'); $table->dropColumn('entity_id'); }); } } ================================================ FILE: database/migrations/2019_01_24_182847_create_entity_mentions.php ================================================ increments('id'); $table->unsignedInteger('entity_id')->nullable(); $table->integer('entity_note_id')->unsigned()->nullable(); $table->integer('campaign_id')->unsigned()->nullable(); $table->unsignedInteger('target_id'); $table->timestamps(); // If we delete the entity or target, remove mentions $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('target_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('entity_note_id')->references('id')->on('entity_notes')->onDelete('cascade'); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('entity_mentions'); } } ================================================ FILE: database/migrations/2019_02_22_083247_create_entity_log.php ================================================ increments('id'); $table->unsignedInteger('entity_id'); $table->unsignedInteger('impersonated_by')->nullable(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedTinyInteger('action')->default(1); $table->mediumText('changes')->nullable(); $table->timestamps(); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('impersonated_by')->references('id')->on('users')->onDelete('set null'); $table->index(['action', 'created_at']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entity_logs'); } } ================================================ FILE: database/migrations/2019_04_11_134145_create_entity_inventory.php ================================================ increments('id'); $table->unsignedInteger('entity_id'); $table->unsignedInteger('item_id')->nullable(); $table->string('name', 45)->nullable(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('amount')->nullable(); $table->string('position')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->text('description')->nullable(); $table->boolean('copy_item_entry')->nullable()->default(false); $table->boolean('is_equipped')->default(false); $table->timestamps(); // Index $table->index(['position']); // If we delete the entity or target, remove mentions $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('item_id')->references('id')->on('items')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('inventories'); } } ================================================ FILE: database/migrations/2019_05_03_000001_create_customer_columns.php ================================================ string('stripe_id')->nullable()->index(); $table->string('pm_type')->nullable(); $table->string('pm_last_four', 4)->nullable(); $table->timestamp('trial_ends_at')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn([ 'stripe_id', 'pm_type', 'pm_last_four', 'trial_ends_at', ]); }); } }; ================================================ FILE: database/migrations/2019_05_03_000002_create_subscriptions_table.php ================================================ id(); $table->foreignId('user_id'); $table->string('name'); $table->string('stripe_id')->unique(); $table->string('stripe_status'); $table->string('stripe_price')->nullable(); $table->integer('quantity')->nullable(); $table->timestamp('trial_ends_at')->nullable(); $table->timestamp('ends_at')->nullable(); $table->timestamps(); $table->index(['user_id', 'stripe_status']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('subscriptions'); } }; ================================================ FILE: database/migrations/2019_05_03_000003_create_subscription_items_table.php ================================================ id(); $table->foreignId('subscription_id'); $table->string('stripe_id')->unique(); $table->string('stripe_product'); $table->string('stripe_price'); $table->integer('quantity')->nullable(); $table->timestamps(); $table->index(['subscription_id', 'stripe_price']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('subscription_items'); } }; ================================================ FILE: database/migrations/2019_05_10_072500_create_entities.php ================================================ increments('id'); $table->string('code', 45); $table->boolean('is_special')->default(false); $table->boolean('is_enabled')->default(true); $table->unsignedTinyInteger('position')->default(0); $table->timestamps(); $table->index(['position', 'is_enabled', 'is_special']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('entity_types'); } } ================================================ FILE: database/migrations/2019_05_23_130252_update_attribute_templates_add_entity_type_id.php ================================================ unsignedInteger('entity_type_id')->nullable(); $table->foreign('entity_type_id')->references('id')->on('entity_types')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('attribute_templates', function (Blueprint $table) { $table->dropColumn('entity_type_id'); }); } } ================================================ FILE: database/migrations/2019_08_15_152154_create_campaign_follow.php ================================================ increments('id'); $table->unsignedInteger('user_id'); $table->unsignedInteger('campaign_id'); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('campaign_followers'); } } ================================================ FILE: database/migrations/2019_10_02_131224_create_campaign_boosts.php ================================================ increments('id'); $table->timestamps(); $table->softDeletes(); $table->unsignedInteger('user_id'); $table->unsignedInteger('campaign_id'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('campaign_boosts'); } } ================================================ FILE: database/migrations/2019_10_02_191220_custom_patreon.php ================================================ increments('id'); $table->string('name', 45); $table->timestamps(); $table->softDeletes(); }); Schema::table('campaigns', function (Blueprint $table) { $table->unsignedInteger('theme_id')->nullable(); $table->foreign('theme_id')->references('id')->on('themes')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('campaigns', function (Blueprint $table) { $table->dropColumn('theme_id'); }); Schema::drop('themes'); } } ================================================ FILE: database/migrations/2020_01_20_084615_create_calendar_weather.php ================================================ increments('id'); $table->unsignedInteger('calendar_id'); $table->unsignedInteger('created_by')->nullable(); $table->string('weather', 20); $table->string('temperature', 45)->nullable(); $table->string('precipitation', 45)->nullable(); $table->string('wind', 45)->nullable(); $table->string('effect', 45)->nullable(); $table->string('name', 40)->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->unsignedMediumInteger('day'); $table->unsignedMediumInteger('month'); $table->integer('year'); $table->timestamps(); $table->index(['day', 'month', 'year']); $table->foreign('calendar_id')->references('id')->on('calendars')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('calendar_weather'); } } ================================================ FILE: database/migrations/2020_03_05_174449_create_abilities_table.php ================================================ increments('id'); $table->string('name'); $table->string('type')->nullable(); $table->string('image', 255)->nullable(); $table->unsignedInteger('campaign_id'); // $table->unsignedInteger('character_id')->nullable(); // $table->unsignedInteger('location_id')->nullable(); $table->unsignedInteger('ability_id')->nullable(); $table->boolean('is_private')->default(false); // Overview $table->longText('entry')->nullable(); $table->string('charges', 120)->nullable(); $table->timestamps(); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('ability_id')->references('id')->on('abilities')->onDelete('cascade'); // Index $table->index(['name', 'type']); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('abilities')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('abilities'); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('abilities'); }); } } ================================================ FILE: database/migrations/2020_03_05_221303_create_entity_abilities.php ================================================ increments('id'); $table->unsignedInteger('entity_id'); $table->unsignedInteger('ability_id'); $table->unsignedInteger('created_by')->nullable(); $table->unsignedTinyInteger('position')->default(0); $table->unsignedBigInteger('visibility_id')->default(1); $table->text('note')->nullable(); $table->tinyInteger('charges')->nullable(); $table->timestamps(); // If we delete the entity or target, remove mentions $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('ability_id')->references('id')->on('abilities')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); $table->index(['position']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('entity_abilities'); } } ================================================ FILE: database/migrations/2020_03_25_192753_add_soft_deletes.php ================================================ softDeletes(); }); } } /** * Reverse the migrations. * * @return void */ public function down() {} } ================================================ FILE: database/migrations/2020_04_10_121227_create_discord.php ================================================ increments('id'); $table->string('app', 20); $table->integer('user_id')->unsigned(); $table->string('access_token'); $table->string('refresh_token'); $table->dateTime('expires_at'); $table->string('identifier', 45)->nullable(); $table->text('settings')->nullable(); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('user_apps'); } } ================================================ FILE: database/migrations/2020_04_29_175303_subscription_bills.php ================================================ increments('id'); $table->string('charge_id', 45)->nullable(); $table->string('source_id', 45); $table->string('tier', 12); $table->string('period', 10); $table->string('method', 10); $table->unsignedInteger('user_id'); $table->string('status', 12); // pending, charged, failed $table->timestamps(); $table->index(['source_id', 'charge_id', 'status']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('subscription_sources'); } } ================================================ FILE: database/migrations/2020_05_08_065550_create_images.php ================================================ char('id', 36); $table->unsignedInteger('campaign_id'); $table->string('name', 45)->nullable(); $table->string('ext', 4)->nullable(); $table->integer('size')->nullable(); $table->unsignedInteger('created_by')->nullable(); $table->boolean('is_default')->default(false); $table->unsignedBigInteger('visibility_id')->default(1); $table->timestamps(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); $table->unique('id'); $table->index(['is_default']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('images'); } } ================================================ FILE: database/migrations/2020_05_10_122725_update_dashboard_widgets_add_tags.php ================================================ unsignedInteger('widget_id'); $table->unsignedInteger('tag_id'); $table->foreign('widget_id')->references('id')->on('campaign_dashboard_widgets')->onDelete('cascade'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); $table->unique(['widget_id', 'tag_id']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('campaign_dashboard_widget_tags', function (Blueprint $table) {}); } } ================================================ FILE: database/migrations/2020_06_02_103227_create_sessions_table.php ================================================ string('id')->unique(); $table->unsignedBigInteger('user_id')->nullable(); $table->string('ip_address', 45)->nullable(); $table->text('user_agent')->nullable(); $table->text('payload'); $table->integer('last_activity'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('sessions'); } } ================================================ FILE: database/migrations/2020_06_06_084917_create_maps_table.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('location_id')->nullable(); $table->string('name'); $table->string('type')->nullable(); $table->string('image', 255)->nullable(); $table->unsignedSmallInteger('width')->nullable(); $table->unsignedSmallInteger('height')->nullable(); $table->unsignedInteger('map_id')->nullable(); $table->boolean('is_private')->default(false); $table->boolean('is_real')->default(false); // Overview $table->longText('entry')->nullable(); $table->text('config')->nullable(); $table->float('center_x')->nullable(); $table->float('center_y')->nullable(); $table->smallInteger('min_zoom')->nullable(); $table->smallInteger('max_zoom')->nullable(); $table->smallInteger('initial_zoom')->nullable(); $table->unsignedSmallInteger('grid')->nullable(); $table->timestamps(); $table->softDeletes(); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('map_id')->references('id')->on('maps')->onDelete('set null'); $table->foreign('location_id')->references('id')->on('locations')->onDelete('set null'); // Index $table->index(['name', 'type']); }); Schema::create('map_layers', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedInteger('map_id'); $table->unsignedInteger('created_by')->nullable(); $table->string('name'); $table->unsignedSmallInteger('position')->nullable(); $table->string('image', 255)->nullable(); $table->unsignedSmallInteger('width')->nullable(); $table->unsignedSmallInteger('height')->nullable(); // Overview $table->longText('entry')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->boolean('is_shown')->default(true); $table->unsignedTinyInteger('type_id')->nullable(); $table->timestamps(); // Foreign $table->foreign('map_id')->references('id')->on('maps')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); // Index $table->index(['name', 'position']); }); Schema::create('map_markers', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedInteger('map_id'); $table->unsignedInteger('entity_id')->nullable(); $table->unsignedSmallInteger('pin_size')->nullable(); $table->string('name')->nullable(); $table->longText('entry')->nullable(); $table->float('longitude'); $table->float('latitude'); $table->string('colour', 7)->nullable(); $table->unsignedTinyInteger('shape_id')->default(1); $table->unsignedTinyInteger('size_id')->default(1); $table->string('icon', 20)->nullable(); $table->text('custom_icon')->nullable(); $table->text('custom_shape')->nullable(); $table->boolean('is_draggable')->default(0); $table->unsignedInteger('created_by')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->string('font_colour', 7)->nullable(); $table->smallInteger('circle_radius')->unsigned()->nullable(); $table->text('polygon_style')->nullable(); $table->tinyInteger('opacity')->nullable(); $table->unsignedTinyInteger('chunking_status')->nullable(); $table->text('config')->nullable(); $table->timestamps(); // Foreign $table->foreign('map_id')->references('id')->on('maps')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); // Index $table->index(['name']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('map_markers'); Schema::drop('map_layers'); Schema::drop('maps'); } } ================================================ FILE: database/migrations/2020_07_24_160310_add_map_groups.php ================================================ bigIncrements('id'); $table->unsignedInteger('map_id'); $table->unsignedInteger('created_by')->nullable(); $table->string('name'); $table->unsignedSmallInteger('position')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->boolean('is_shown')->default(0); $table->timestamps(); // Foreign $table->foreign('map_id')->references('id')->on('maps')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); // Index $table->index(['name', 'position']); }); Schema::table('map_markers', function (Blueprint $table) { $table->unsignedBigInteger('group_id')->nullable(); $table->foreign('group_id')->references('id')->on('map_groups')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('map_groups'); } } ================================================ FILE: database/migrations/2020_08_02_125607_create_app_updates_table.php ================================================ increments('id'); $table->dateTime('published_at'); $table->dateTime('end_at')->nullable(); $table->string('name', 255); $table->string('excerpt', 255); $table->string('link', 255); $table->unsignedInteger('created_by')->nullable(); $table->unsignedTinyInteger('category_id')->default(1); $table->timestamps(); $table->foreign('created_by')->references('id')->on('users'); $table->index(['published_at', 'end_at']); $table->index(['category_id']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('releases'); } } ================================================ FILE: database/migrations/2020_08_03_155441_create_timelines_table.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('timeline_id')->nullable(); $table->string('name'); $table->string('type')->nullable(); $table->string('image', 255)->nullable(); $table->unsignedInteger('calendar_id')->nullable(); $table->boolean('is_private')->default(false); // Overview $table->longText('entry')->nullable(); $table->boolean('revert_order')->default(false); $table->timestamps(); $table->softDeletes(); // Foreign $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('calendar_id')->references('id')->on('calendars')->onDelete('set null'); $table->foreign('timeline_id')->references('id')->on('timelines')->onDelete('set null'); // Index $table->index(['name', 'type']); }); Schema::create('timeline_eras', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedInteger('timeline_id'); $table->unsignedInteger('created_by')->nullable(); $table->string('name'); $table->string('abbreviation')->nullable(); $table->integer('start_year')->nullable(); $table->integer('end_year')->nullable(); // Overview $table->longText('entry')->nullable(); $table->boolean('is_collapsed')->default(false); $table->unsignedTinyInteger('position')->nullable(); $table->timestamps(); // Foreign $table->foreign('timeline_id')->references('id')->on('timelines')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); // Index $table->index(['name', 'start_year', 'position']); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('timelines')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('timeline_eras'); Schema::drop('timelines'); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('timelines'); }); } } ================================================ FILE: database/migrations/2020_08_04_095517_create_timeline_elements.php ================================================ bigIncrements('id'); $table->unsignedInteger('timeline_id'); $table->unsignedBigInteger('era_id'); $table->unsignedInteger('entity_id')->nullable(); $table->unsignedInteger('position'); $table->string('name', 191)->nullable(); $table->string('date', 45)->nullable(); $table->mediumText('entry')->nullable(); $table->string('colour', 10)->default('grey'); $table->timestamps(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->text('icon')->nullable(); $table->boolean('use_event_date')->default(false); $table->boolean('is_collapsed')->default(false); $table->boolean('use_entity_entry')->default(false); $table->index('position'); $table->foreign('timeline_id')->references('id')->on('timelines')->onDelete('cascade'); $table->foreign('era_id')->references('id')->on('timeline_eras')->onDelete('cascade'); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('timeline_elements'); } } ================================================ FILE: database/migrations/2020_08_10_172320_update_entity_events_add_type.php ================================================ increments('id'); $table->string('name', 20); }); Schema::table('entity_events', function (Blueprint $table) { $table->unsignedInteger('type_id')->nullable(); $table->foreign('type_id')->references('id')->on('entity_event_types')->onDelete('cascade'); $table->unsignedInteger('elapsed')->nullable(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entity_events', function (Blueprint $table) { $table->dropColumn('type_id'); $table->dropColumn('elapsed'); }); Schema::drop('entity_event_types'); } } ================================================ FILE: database/migrations/2020_09_10_140424_add_entity_image_id.php ================================================ char('image_uuid', 36)->nullable(); $table->char('header_uuid', 36)->nullable(); $table->foreign('image_uuid')->references('id')->on('images')->nullOnDelete(); $table->foreign('header_uuid')->references('id')->on('images')->nullOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entities', function (Blueprint $table) { $table->dropForeign('entities_image_uuid_foreign'); $table->dropColumn('image_uuid'); }); } } ================================================ FILE: database/migrations/2020_09_22_115919_create_referral_table.php ================================================ bigIncrements('id'); $table->string('code', 45)->unique(); $table->boolean('is_valid'); $table->unsignedInteger('user_id')->nullable(); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users')->onDelete('set null'); }); Schema::table('users', function (Blueprint $table) { $table->unsignedBigInteger('referral_id')->nullable(); $table->foreign('referral_id')->references('id')->on('referrals')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropForeign('users_referral_id_foreign'); $table->dropColumn('referral_id'); }); Schema::dropIfExists('referrals'); } } ================================================ FILE: database/migrations/2020_11_07_083605_create_campaign_dashboards.php ================================================ bigIncrements('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('created_by')->nullable(); $table->string('name', 100); $table->index(['campaign_id', 'name']); $table->timestamps(); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); }); Schema::create('campaign_dashboard_roles', function (Blueprint $table) { $table->bigIncrements('id'); $table->unsignedBigInteger('campaign_dashboard_id'); $table->unsignedInteger('campaign_role_id'); $table->boolean('is_default')->default(false); $table->boolean('is_visible')->default(false); $table->index(['is_default']); $table->timestamps(); $table->foreign('campaign_dashboard_id')->references('id')->on('campaign_dashboards')->onDelete('cascade'); $table->foreign('campaign_role_id')->references('id')->on('campaign_roles')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('campaign_dashboard_roles'); Schema::dropIfExists('campaign_dashboards'); } } ================================================ FILE: database/migrations/2020_11_07_095122_update_campaign_widgets_add_dashboard_id.php ================================================ unsignedBigInteger('dashboard_id')->nullable(); $table->foreign('dashboard_id')->references('id')->on('campaign_dashboards')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('campaign_dashboard_widgets', function (Blueprint $table) { $table->dropForeign('campaign_dashboard_widgets_dashboard_id_foreign'); $table->dropColumn('dashboard_id'); }); } } ================================================ FILE: database/migrations/2020_12_12_191426_create_entity_note_permissions.php ================================================ bigIncrements('id'); $table->timestamps(); $table->unsignedInteger('entity_note_id'); $table->unsignedInteger('user_id')->nullable(); $table->unsignedInteger('role_id')->nullable(); $table->tinyInteger('permission'); $table->foreign('entity_note_id')->references('id')->on('entity_notes')->onDelete('cascade'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('role_id')->references('id')->on('campaign_roles')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entity_note_permissions'); } } ================================================ FILE: database/migrations/2021_01_03_231138_create_table_entity_links.php ================================================ bigIncrements('id'); $table->unsignedInteger('entity_id'); $table->string('name', 45); $table->string('icon', 45); $table->unsignedTinyInteger('position')->default(0); $table->text('url'); $table->unsignedInteger('created_by')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->timestamps(); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); $table->index(['position']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entity_links'); } } ================================================ FILE: database/migrations/2021_01_18_184615_create_campaign_gallery_folders.php ================================================ char('folder_id', 36)->nullable(); $table->boolean('is_folder')->default(false); $table->foreign('folder_id')->references('id')->on('images')->onDelete('cascade'); $table->index('is_folder'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('images', function (Blueprint $table) { $table->dropForeign('images_folder_id_foreign'); $table->dropColumn('folder_id'); $table->dropColumn('is_folder'); }); // Schema::dropIfExists('image_folders'); } } ================================================ FILE: database/migrations/2021_01_29_003755_update_menu_links_dashboard_id.php ================================================ unsignedBigInteger('dashboard_id')->nullable(); $table->foreign('dashboard_id')->references('id')->on('campaign_dashboards')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('menu_links', function (Blueprint $table) { $table->dropForeign('menu_links_dashboard_id_foreign'); $table->dropColumn('dashboard_id'); }); } } ================================================ FILE: database/migrations/2021_01_30_233554_create_campaign_submissions.php ================================================ boolean('is_open')->default(false); $table->index(['is_open']); }); Schema::create('campaign_submissions', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->unsignedInteger('user_id'); $table->unsignedInteger('campaign_id'); $table->text('text'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('campaign_submissions'); Schema::table('campaigns', function (Blueprint $table) { $table->dropIndex('campaigns_is_open_index'); $table->dropColumn('is_open'); }); } } ================================================ FILE: database/migrations/2021_04_07_230722_update_entities_add_marketplace_entity_uuid.php ================================================ char('marketplace_uuid', 36)->nullable(); $table->index('marketplace_uuid'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entities', function (Blueprint $table) { $table->dropColumn('marketplace_uuid'); }); } } ================================================ FILE: database/migrations/2021_04_11_202656_update_relations_add_marketplace_uuid.php ================================================ char('marketplace_uuid', 36)->nullable(); $table->index('marketplace_uuid'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('relations', function (Blueprint $table) { $table->dropColumn('marketplace_uuid'); }); } } ================================================ FILE: database/migrations/2021_04_20_201015_create_quest_elements_table.php ================================================ id(); $table->timestamps(); $table->integer('quest_id')->unsigned(); $table->integer('entity_id')->unsigned()->nullable(); $table->string('name', 100)->nullable(); $table->integer('created_by')->unsigned()->nullable(); $table->string('role', 191)->nullable(); $table->longText('description')->nullable(); $table->unsignedBigInteger('visibility_id')->default(1); $table->string('colour', 10)->nullable(); $table->foreign('quest_id')->references('id')->on('quests')->onDelete('cascade'); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('quest_elements'); } } ================================================ FILE: database/migrations/2021_05_25_125309_update_maps_center_marker.php ================================================ unsignedBigInteger('center_marker_id')->nullable(); $table->foreign('center_marker_id')->references('id')->on('map_markers')->nullable()->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('maps', function (Blueprint $table) { $table->dropForeign('maps_center_marker_id_foreign'); $table->dropColumn('center_marker_id'); }); } } ================================================ FILE: database/migrations/2021_07_19_095244_update_entity_notes_marketplace_uuid.php ================================================ char('marketplace_uuid', 36)->nullable(); $table->index('marketplace_uuid'); $table->unsignedInteger('location_id')->nullable(); $table->foreign('location_id')->references('id')->on('locations')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entity_notes', function (Blueprint $table) { $table->dropColumn('marketplace_uuid'); $table->dropColumn('location_id'); }); } } ================================================ FILE: database/migrations/2021_09_24_175239_create_entity_user_table.php ================================================ id(); $table->timestamps(); $table->tinyInteger('type_id'); $table->unsignedInteger('user_id'); $table->unsignedInteger('entity_id'); $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->index(['type_id', 'updated_at']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entity_user'); } } ================================================ FILE: database/migrations/2021_09_27_173618_create_campaign_styles_table.php ================================================ id(); $table->timestamps(); $table->unsignedInteger('campaign_id'); $table->string('name', 45); $table->text('content'); $table->boolean('is_enabled'); $table->softDeletes(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedTinyInteger('order')->nullable(); $table->index(['order', 'is_enabled']); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('campaign_styles'); } } ================================================ FILE: database/migrations/2021_10_19_151149_create_menu_link_tags_table.php ================================================ unsignedInteger('menu_link_id'); $table->unsignedInteger('tag_id'); $table->foreign('menu_link_id')->references('id')->on('menu_links')->onDelete('cascade'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); $table->unique(['menu_link_id', 'tag_id']); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('menu_link_tag'); } } ================================================ FILE: database/migrations/2021_11_19_031558_create_user_role_table.php ================================================ unsignedInteger('user_id'); $table->unsignedBigInteger('role_id'); $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); $table->foreign('role_id')->references('id')->on('roles')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('user_roles'); } } ================================================ FILE: database/migrations/2021_11_19_034012_update_entities_add_type_id.php ================================================ environment('testing') && ! Schema::hasColumn('entities', 'type_id')) { $table->unsignedInteger('type_id')->after('type')->nullable(); } $table->foreign('type_id')->references('id')->on('entity_types')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entities', function (Blueprint $table) { $table->dropColumn('type_id'); }); } } ================================================ FILE: database/migrations/2022_01_02_212212_add_character_race_table.php ================================================ increments('id'); $table->unsignedInteger('character_id'); $table->unsignedInteger('race_id'); $table->timestamps(); $table->foreign('character_id')->references('id')->on('characters')->cascadeOnDelete(); $table->foreign('race_id')->references('id')->on('races')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() {} } ================================================ FILE: database/migrations/2022_01_23_023952_create_entity_aliases_table.php ================================================ id(); $table->unsignedInteger('entity_id'); $table->unsignedInteger('created_by')->nullable(); $table->bigInteger('visibility_id')->unsigned(); $table->string('name', 191); $table->timestamps(); $table->index(['name']); $table->foreign('entity_id')->references('id')->on('entities')->cascadeOnDelete(); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('entity_aliases'); } } ================================================ FILE: database/migrations/2022_03_07_224142_create_character_families_table.php ================================================ id(); $table->unsignedInteger('character_id'); $table->unsignedInteger('family_id'); $table->timestamps(); $table->foreign('character_id')->references('id')->on('characters')->cascadeOnDelete(); $table->foreign('family_id')->references('id')->on('families')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('character_family'); } } ================================================ FILE: database/migrations/2022_04_27_194610_update_campaign_permissions_perf.php ================================================ unsignedInteger('campaign_id')->after('user_id')->nullable(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->unsignedTinyInteger('action')->after('campaign_id'); $table->unsignedInteger('misc_id')->nullable()->after('entity_id'); $table->unsignedInteger('entity_type_id')->after('action')->nullable(); $table->foreign('entity_type_id')->references('id')->on('entity_types')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('campaign_permissions', function (Blueprint $table) { $table->dropForeign('campaign_permissions_campaign_id_foreign'); $table->dropColumn('campaign_id'); $table->dropForeign('campaign_permissions_entity_type_id_foreign'); $table->dropColumn('entity_type_id'); $table->dropColumn('action'); $table->dropColumn('misc_id'); }); } } ================================================ FILE: database/migrations/2022_05_06_151813_update_users_add_card_expiration.php ================================================ dateTime('card_expires_at')->after($field)->nullable(); $table->index(['card_expires_at'], 'idx_card_expires_at'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropIndex('idx_card_expires_at'); $table->dropColumn('card_expires_at'); }); } } ================================================ FILE: database/migrations/2022_06_07_172445_update_maps_add_clustering_toggle.php ================================================ boolean('has_clustering')->default(true); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('maps', function (Blueprint $table) { $table->dropColumn('has_clustering'); }); } } ================================================ FILE: database/migrations/2022_06_23_154301_create_entity_assets_table.php ================================================ unsignedTinyInteger('type_id')->after('entity_id'); $table->text('metadata')->after('name')->nullable(); $table->unsignedSmallInteger('position')->nullable(); $table->boolean('is_pinned')->default(0); $table->index(['type_id', 'is_pinned']); }); DB::update('update entity_aliases set type_id = ' . EntityAssetType::alias->value); Schema::rename('entity_aliases', 'entity_assets'); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::rename('entity_assets', 'entity_aliases'); Schema::table('entity_aliases', function (Blueprint $table) { $table->dropIndex(['type_id']); $table->dropColumn('metadata'); $table->dropColumn('position'); $table->dropColumn('type_id'); }); } } ================================================ FILE: database/migrations/2022_08_23_005328_add_race_location_table.php ================================================ unsignedInteger('race_id'); $table->unsignedInteger('location_id'); $table->foreign('race_id')->references('id')->on('races')->cascadeOnDelete(); $table->foreign('location_id')->references('id')->on('locations')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('race_location'); } } ================================================ FILE: database/migrations/2022_09_02_191341_create_password_securities_table.php ================================================ increments('id'); $table->integer('user_id'); $table->boolean('google2fa_enable')->default(false); $table->string('google2fa_secret')->nullable(); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('password_securities'); } } ================================================ FILE: database/migrations/2022_09_21_231900_refactor_user_patreon.php ================================================ string('patreon_pledge', 10)->nullable()->change(); }); Schema::table('users', function (Blueprint $table) { $table->renameColumn('patreon_pledge', 'pledge'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->string('pledge', 10)->nullable(false)->change(); $table->renameColumn('pledge', 'patreon_pledge'); }); } } ================================================ FILE: database/migrations/2022_10_20_192238_create_creatures_table.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('creature_id')->nullable(); $table->string('name', 191); $table->string('image', 255)->nullable(); $table->string('type', 45)->nullable(); $table->longText('entry')->nullable(); $table->boolean('is_private')->default(false); $table->timestamps(); $table->softDeletes(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->index(['name', 'type', 'is_private']); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('creature_id')->references('id')->on('creatures')->onDelete('set null'); }); Schema::table('campaign_settings', function (Blueprint $table) { $table->boolean('creatures')->default(true); }); Schema::create('creature_location', function (Blueprint $table) { $table->unsignedInteger('creature_id'); $table->unsignedInteger('location_id'); $table->foreign('creature_id')->references('id')->on('creatures')->cascadeOnDelete(); $table->foreign('location_id')->references('id')->on('locations')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('creature_location'); Schema::dropIfExists('creatures'); Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('creatures'); }); } }; ================================================ FILE: database/migrations/2022_11_01_214611_update_entity_user_add_campaign_id_post_id_timeline_element_id_quest_element_id.php ================================================ unsignedInteger('campaign_id')->nullable(); $table->unsignedInteger('post_id')->nullable(); $table->unsignedBigInteger('timeline_element_id')->nullable(); $table->unsignedBigInteger('quest_element_id')->nullable(); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('post_id')->references('id')->on('entity_notes')->onDelete('cascade'); $table->foreign('timeline_element_id')->references('id')->on('timeline_elements')->onDelete('cascade'); $table->foreign('quest_element_id')->references('id')->on('quest_elements')->onDelete('cascade'); $table->unsignedInteger('entity_id')->nullable()->change(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entity_user', function (Blueprint $table) { $table->unsignedInteger('entity_id')->nullable(false)->change(); $table->dropForeign('campaign_id'); $table->dropForeign('post_id'); $table->dropForeign('quest_element_id'); $table->dropForeign('timeline_element_id'); $table->dropColumn('campaign_id'); $table->dropColumn('post_id'); $table->dropColumn('quest_element_id'); $table->dropColumn('timeline_element_id'); }); } }; ================================================ FILE: database/migrations/2022_11_07_192125_update_entity_mentions_add_quest_element_id_timeline_element_id.php ================================================ unsignedBigInteger('timeline_element_id')->nullable(); $table->unsignedBigInteger('quest_element_id')->nullable(); $table->foreign('timeline_element_id')->references('id')->on('timeline_elements')->onDelete('cascade'); $table->foreign('quest_element_id')->references('id')->on('quest_elements')->onDelete('cascade'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entity_mentions', function (Blueprint $table) { $table->dropForeign('quest_element_id'); $table->dropForeign('timeline_element_id'); $table->dropColumn('quest_element_id'); $table->dropColumn('timeline_element_id'); }); } }; ================================================ FILE: database/migrations/2022_11_08_161937_create_presets_table.php ================================================ id(); $table->timestamps(); $table->string('code', 20); }); Schema::create('presets', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->unsignedBigInteger('type_id'); $table->unsignedBigInteger('visibility_id'); $table->unsignedInteger('campaign_id'); $table->string('name', 191); $table->json('config'); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->foreign('visibility_id')->references('id')->on('visibilities')->cascadeOnDelete(); $table->foreign('type_id')->references('id')->on('preset_types')->cascadeOnDelete(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('presets'); Schema::dropIfExists('preset_types'); } }; ================================================ FILE: database/migrations/2022_12_08_155837_rename_entity_note_id_to_post_id.php ================================================ renameColumn('entity_note_id', 'post_id'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entity_note_permissions', function (Blueprint $table) { $table->renameColumn('post_id', 'entity_note_id'); }); } }; ================================================ FILE: database/migrations/2023_01_04_213154_create_bragi_logs_table.php ================================================ id(); $table->unsignedInteger('user_id')->nullable(); $table->unsignedInteger('campaign_id')->nullable(); $table->text('prompt'); $table->text('result')->nullable(); $table->json('data')->nullable(); $table->timestamps(); $table->index(['created_at']); $table->foreign('user_id')->references('id')->on('users')->nullOnDelete(); $table->foreign('campaign_id')->references('id')->on('campaigns')->nullOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('bragi_logs'); } }; ================================================ FILE: database/migrations/2023_01_16_021352_create_family_tree_table.php ================================================ id(); $table->timestamps(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); $table->mediumText('config')->nullable(); $table->unsignedInteger('family_id'); $table->foreign('family_id')->references('id')->on('families')->cascadeOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('family_trees'); } }; ================================================ FILE: database/migrations/2023_01_19_154910_cleanup_user_date_format.php ================================================ string('date_format', 5)->nullable()->default(null)->change(); $table->smallInteger('default_pagination')->nullable()->default(null)->change(); }); DB::statement('UPDATE users SET date_format = null WHERE date_format = \'Y-m-d\''); DB::statement('UPDATE users SET default_pagination = null WHERE default_pagination = 15'); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->string('date_format')->default('Y-m-d')->change(); $table->smallInteger('default_pagination')->default('15')->change(); }); } }; ================================================ FILE: database/migrations/2023_02_08_003255_update_entity_logs_add_post_id.php ================================================ integer('post_id')->unsigned()->nullable(); $table->foreign('post_id')->references('id')->on('entity_notes')->nullOnDelete(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('entity_logs', function (Blueprint $table) { $table->dropForeign(['post_id']); $table->dropColumn('post_id'); }); } }; ================================================ FILE: database/migrations/2023_03_14_152925_update_subscriptions_rename_stripe_plan.php ================================================ renameColumn('stripe_plan', 'stripe_price'); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('subscriptions', function (Blueprint $table) { $table->renameColumn('stripe_price', 'stripe_plan'); }); } }; ================================================ FILE: database/migrations/2023_03_14_153209_update_subscription_items_rename_stripe_plan.php ================================================ renameColumn('stripe_plan', 'stripe_price'); } if (! Schema::hasColumn('subscription_items', 'stripe_product')) { $table->string('stripe_product')->nullable()->after('stripe_id'); } }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('subscription_items', function (Blueprint $table) { $table->renameColumn('stripe_price', 'stripe_plan'); $table->dropColumn('stripe_product'); }); } }; ================================================ FILE: database/migrations/2023_03_14_154115_update_users_rename_card_brand_card_last_four.php ================================================ renameColumn('card_brand', 'pm_type'); } if (Schema::hasColumn('users', 'card_last_four')) { $table->renameColumn('card_last_four', 'pm_last_four'); } }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { if (Schema::hasColumn('users', 'pm_type')) { $table->renameColumn('pm_type', 'card_brand'); } if (Schema::hasColumn('users', 'pm_last_four')) { $table->renameColumn('pm_last_four', 'card_last_four'); } }); } }; ================================================ FILE: database/migrations/2023_05_04_185755_update_gallery_woff2.php ================================================ char('ext', 5)->nullable()->change(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('images', function (Blueprint $table) { $table->char('ext', 4)->nullable()->change(); }); } }; ================================================ FILE: database/migrations/2023_05_17_191929_update_images_add_focus_x_focus_y.php ================================================ integer('focus_x')->unsigned()->nullable(); $table->integer('focus_y')->unsigned()->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('images', function (Blueprint $table) { $table->dropColumn('focus_x'); $table->dropColumn('focus_y'); }); } }; ================================================ FILE: database/migrations/2023_06_02_175012_create_image_mentions_table.php ================================================ id(); $table->unsignedInteger('entity_id'); $table->char('image_id', 36); $table->unsignedInteger('post_id')->nullable(); $table->timestamps(); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); $table->foreign('image_id')->references('id')->on('images')->onDelete('cascade'); $table->foreign('post_id')->references('id')->on('entity_notes')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('image_mentions', function (Blueprint $table) { Schema::dropIfExists('image_mentions'); }); } }; ================================================ FILE: database/migrations/2023_06_09_215007_update_campaign_style_add_theme.php ================================================ boolean('is_theme')->default(0)->index(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_styles', function (Blueprint $table) { $table->dropColumn('is_theme'); }); } }; ================================================ FILE: database/migrations/2023_06_10_220139_create_api_logs.php ================================================ hasTable('api_logs')) { return; } Schema::connection('logs')->create('api_logs', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->unsignedInteger('user_id')->nullable(); $table->unsignedInteger('campaign_id')->nullable(); $table->text('uri')->nullable(); $table->json('params')->nullable(); $table->index(['created_at']); }); } /** * Reverse the migrations. */ public function down(): void { if (! config('logging.enabled')) { return; } Schema::connection('logs')->dropIfExists('api_logs'); } }; ================================================ FILE: database/migrations/2023_06_15_210220_create_user_logs_table.php ================================================ hasTable('user_logs')) { return; } Schema::connection('logs')->create('user_logs', function (Blueprint $table) { $table->id(); $table->integer('user_id')->unsigned(); $table->unsignedTinyInteger('type_id') ->default(UserAction::login->value); $table->string('ip', 255)->nullable(); $table->char('country', 6)->nullable(); $table->timestamps(); $table->index(['created_at']); }); } /** * Reverse the migrations. */ public function down(): void { if (! config('logging.enabled')) { return; } Schema::dropIfExists('user_logs'); } }; ================================================ FILE: database/migrations/2023_06_21_221002_update_attributes_rename_is_star.php ================================================ renameColumn('is_star', 'is_pinned'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('attributes', function (Blueprint $table) { $table->renameColumn('is_pinned', 'is_star'); }); } }; ================================================ FILE: database/migrations/2023_06_21_221208_update_relations_rename_is_star.php ================================================ renameColumn('is_star', 'is_pinned'); } }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('relations', function (Blueprint $table) { $table->renameColumn('is_pinned', 'is_star'); }); } }; ================================================ FILE: database/migrations/2023_06_27_194149_create_genres_table.php ================================================ increments('id'); $table->unsignedInteger('campaign_count')->default(0); $table->string('slug', 50); $table->timestamps(); $table->unique('slug'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('genres'); } }; ================================================ FILE: database/migrations/2023_06_27_195226_create_campaign_genre_table.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('genre_id'); $table->timestamps(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('genre_id')->references('id')->on('genres')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('campaign_genre'); } }; ================================================ FILE: database/migrations/2023_06_29_185027_create_user_flags_table.php ================================================ id(); $table->string('flag', 12); $table->timestamps(); $table->unsignedInteger('user_id'); $table->index('flag'); $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('user_flags'); } }; ================================================ FILE: database/migrations/2023_07_13_164526_create_post_layouts_table.php ================================================ increments('id'); $table->string('code', 25); $table->unsignedInteger('entity_type_id')->nullable(); $table->json('config')->nullable(); $table->timestamps(); $table->foreign('entity_type_id')->references('id')->on('entity_types')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('post_layouts'); } }; ================================================ FILE: database/migrations/2023_07_13_172639_update_entity_notes_add_layout_id.php ================================================ unsignedInteger('layout_id')->nullable(); $table->foreign('layout_id')->references('id')->on('post_layouts')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_notes', function (Blueprint $table) { $table->dropColumn('layout_id'); }); } }; ================================================ FILE: database/migrations/2023_07_28_181821_update_calendars_add_format.php ================================================ string('format', 20)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('calendars', function (Blueprint $table) { $table->dropColumn('format'); }); } }; ================================================ FILE: database/migrations/2023_08_11_012819_reset_campaign_slug.php ================================================ dropColumn('slug'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaigns', function (Blueprint $table) { $table->dropColumn('slug'); }); } }; ================================================ FILE: database/migrations/2023_08_14_225219_add_new_slug.php ================================================ string('slug', 45)->nullable()->unique()->after('name'); }); DB::statement('UPDATE campaigns SET slug = id'); } /** * Reverse the migrations. */ public function down(): void {} }; ================================================ FILE: database/migrations/2023_08_16_170220_remove_campaign_export_path.php ================================================ dropColumn('export_path'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaigns', function (Blueprint $table) {}); } }; ================================================ FILE: database/migrations/2023_08_21_233952_create_tutorials_table.php ================================================ id(); $table->unsignedInteger('user_id'); $table->string('code', 45); $table->timestamps(); $table->unique(['user_id', 'code']); $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('user_tutorials'); } }; ================================================ FILE: database/migrations/2023_08_30_170905_migrate_to_bookmarks.php ================================================ renameColumn('menu_links', 'bookmarks'); }); Schema::table('bookmark_tag', function (Blueprint $table) { $table->renameColumn('menu_link_id', 'bookmark_id'); }); DB::update("UPDATE bookmarks SET parent = 'bookmarks' WHERE parent = 'menu_links'"); DB::update("UPDATE bookmarks SET type = 'bookmark' WHERE type = 'menu_link'"); DB::update("UPDATE bookmarks SET menu = 'bookmark' WHERE menu = 'menu_link'"); DB::update("UPDATE bookmarks SET random_entity_type = 'bookmark' WHERE random_entity_type = 'menu_link'"); } /** * Reverse the migrations. */ public function down(): void { Schema::rename('bookmarks', 'menu_links'); Schema::rename('bookmark_tag', 'menu_link_tag'); Schema::table('campaign_settings', function (Blueprint $table) { $table->renameColumn('bookmarks', 'menu_links'); }); Schema::table('menu_link_tag', function (Blueprint $table) { $table->renameColumn('bookmark_id', 'menu_link_id'); }); } }; ================================================ FILE: database/migrations/2023_09_12_200523_migrate_to_posts.php ================================================ foreign('post_id')->references('id')->on('posts')->onDelete('cascade'); }); Schema::table('entity_mentions', function (Blueprint $table) { $table->renameColumn('entity_note_id', 'post_id'); $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::rename('posts', 'entity_notes'); Schema::rename('post_permissions', 'entity_note_permissions'); Schema::table('post_permissions', function (Blueprint $table) { $table->foreign('post_id')->references('id')->on('entity_notes')->onDelete('cascade'); }); Schema::table('entity_mentions', function (Blueprint $table) { $table->renameColumn('entity_note_id', 'post_id'); $table->foreign('entity_note_id')->references('id')->on('entity_notes')->onDelete('cascade'); }); } }; ================================================ FILE: database/migrations/2023_10_15_183420_add_image_to_entities.php ================================================ string('image_path', 255)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->dropColumn('image_path'); }); } }; ================================================ FILE: database/migrations/2023_10_16_202303_update_locations_rename_parent_location_id_location_id.php ================================================ renameColumn('parent_location_id', 'location_id'); $table->foreign('location_id')->references('id')->on('locations')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('locations', function (Blueprint $table) { $table->renameColumn('location_id', 'parent_location_id'); $table->foreign('parent_location_id')->references('id')->on('locations')->nullOnDelete(); }); } }; ================================================ FILE: database/migrations/2023_10_21_172645_update_entity_type_bookmark.php ================================================ code = 'bookmark'; $type->save(); } /** * Reverse the migrations. */ public function down(): void {} }; ================================================ FILE: database/migrations/2023_10_25_173253_create_campaign_exports_table.php ================================================ increments('id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('created_by')->nullable(); $table->integer('size'); $table->tinyInteger('type'); $table->tinyInteger('status'); $table->string('path')->nullable(); $table->timestamps(); $table->index(['status']); $table->foreign('campaign_id')->references('id')->on('campaigns')->onDelete('cascade'); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('campaign_exports'); } }; ================================================ FILE: database/migrations/2023_10_28_210131_add_is_deleted_index.php ================================================ tables as $tablename) { Schema::table($tablename, function (Blueprint $table) { $table->index('deleted_at'); }); } } /** * Reverse the migrations. */ public function down(): void { foreach ($this->tables as $tablename) { Schema::table($tablename, function (Blueprint $table) { $table->dropIndex('deleted_at'); }); } } }; ================================================ FILE: database/migrations/2023_11_05_174423_create_campaign_import_table.php ================================================ id(); $table->timestamps(); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('user_id')->nullable(); $table->text('config')->nullable(); $table->unsignedTinyInteger('status_id')->default(1); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('user_id')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('campaign_import'); } }; ================================================ FILE: database/migrations/2023_11_07_034812_rollback_entities_deleted_index.php ================================================ dropIndex('entities_deleted_at_index'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) {}); } }; ================================================ FILE: database/migrations/2023_11_19_121322_update_campaign_exports_add_progress.php ================================================ float('progress')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_exports', function (Blueprint $table) { $table->dropColumn('progress'); }); } }; ================================================ FILE: database/migrations/2023_11_21_215234_update_calendars_add_show_birthdays.php ================================================ boolean('show_birthdays')->default(false); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('calendars', function (Blueprint $table) { $table->dropColumn('show_birthdays'); }); } }; ================================================ FILE: database/migrations/2023_11_28_121013_cleanup_entity_images.php ================================================ dropColumn('image'); }); } } /** * Reverse the migrations. */ public function down(): void {} }; ================================================ FILE: database/migrations/2023_11_28_215313_update_map_markers_add_is_popupless.php ================================================ boolean('is_popupless')->default(0); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('map_markers', function (Blueprint $table) { $table->dropColumn('is_popupless'); }); } }; ================================================ FILE: database/migrations/2023_12_02_154727_remove_old_trees.php ================================================ dropColumn('_lft'); $table->dropColumn('_rgt'); }); } } /** * Reverse the migrations. */ public function down(): void {} }; ================================================ FILE: database/migrations/2023_12_05_125348_create_tiers_table.php ================================================ id(); $table->string('code', 30)->unique(); $table->string('name', 30); $table->float('monthly'); $table->float('yearly'); $table->tinyInteger('position'); $table->index(['position', 'deleted_at']); $table->timestamps(); $table->softDeletes(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('tiers'); } }; ================================================ FILE: database/migrations/2024_01_08_144309_create_features_table.php ================================================ id(); $table->timestamps(); $table->string('name', 45)->unique(); }); Schema::dropIfExists('feature_statuses'); Schema::create('feature_statuses', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->string('name', 45)->unique(); }); Schema::dropIfExists('features'); Schema::create('features', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->unsignedInteger('created_by')->nullable(); $table->string('name', 90); $table->text('description')->nullable(); $table->unsignedBigInteger('category_id')->nullable(); $table->unsignedBigInteger('status_id'); $table->unsignedInteger('upvote_count')->default(0); $table->foreign('status_id')->references('id')->on('feature_statuses')->cascadeOnDelete(); $table->foreign('category_id')->references('id')->on('feature_categories')->cascadeOnDelete(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->index(['name']); }); Schema::create('feature_upvotes', function (Blueprint $table) { $table->id(); $table->timestamps(); $table->unsignedInteger('user_id'); $table->unsignedBigInteger('feature_id'); $table->unique(['user_id', 'feature_id']); $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); $table->foreign('feature_id')->references('id')->on('features')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('feature_upvotes'); Schema::dropIfExists('features'); Schema::dropIfExists('feature_categories'); Schema::dropIfExists('feature_statuses'); } }; ================================================ FILE: database/migrations/2024_01_10_191244_create_game_systems_table.php ================================================ id(); $table->string('name', 70); $table->timestamps(); $table->unique('name'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('game_systems'); } }; ================================================ FILE: database/migrations/2024_01_10_191740_create_campaign_system_table.php ================================================ id(); $table->unsignedInteger('campaign_id'); $table->unsignedBigInteger('system_id'); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('system_id')->references('id')->on('game_systems')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('campaign_system'); } }; ================================================ FILE: database/migrations/2024_01_11_150113_update_attributes.php ================================================ string('api_key', 20)->nullable()->change(); // $table->tinyInteger('is_hidden')->default(0)->change(); }); DB::raw('ALTER TABLE `attributes` MODIFY `is_hidden` tinyint(1) DEFAULT 0 NOT NULL;'); } /** * Reverse the migrations. */ public function down(): void { Schema::table('attributes', function (Blueprint $table) {}); } }; ================================================ FILE: database/migrations/2024_01_19_024046_cleanup_reminders.php ================================================ dropColumn('date'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_events', function (Blueprint $table) {}); } }; ================================================ FILE: database/migrations/2024_01_22_203110_create_user_validations_table.php ================================================ id(); $table->char('token', 36); $table->unsignedInteger('user_id'); $table->boolean('is_valid')->default(false); $table->timestamps(); $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); $table->index(['is_valid']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('user_validations'); } }; ================================================ FILE: database/migrations/2024_01_25_192113_update_creatures_add_is_extinct.php ================================================ boolean('is_extinct')->default(0); $table->index('is_extinct'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('creatures', function (Blueprint $table) { $table->dropIndex(['is_extinct']); $table->dropColumn('is_extinct'); }); } }; ================================================ FILE: database/migrations/2024_01_26_194006_update_campaigns_add_is_discreet.php ================================================ boolean('is_discreet')->default(0); $table->index('is_discreet'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaigns', function (Blueprint $table) { $table->dropIndex(['is_discreet']); $table->dropColumn('is_discreet'); }); } }; ================================================ FILE: database/migrations/2024_02_06_162941_create_feature_files.php ================================================ id(); $table->unsignedBigInteger('feature_id'); $table->string('path')->nullable(); $table->timestamps(); $table->foreign('feature_id')->references('id')->on('features')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('feature_files'); } }; ================================================ FILE: database/migrations/2024_02_27_095646_update_features_add_original.php ================================================ text('original')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('features', function (Blueprint $table) { $table->dropColumn('original'); }); } }; ================================================ FILE: database/migrations/2024_03_07_010249_update_features_add_rejection.php ================================================ text('rejection')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('features', function (Blueprint $table) { $table->dropColumn('rejection'); }); } }; ================================================ FILE: database/migrations/2024_03_27_173140_create_webhooks.php ================================================ id(); $table->unsignedInteger('campaign_id'); $table->unsignedTinyInteger('action'); $table->string('url', 191); $table->unsignedTinyInteger('type'); $table->text('message')->nullable(); $table->timestamps(); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->unsignedTinyInteger('status'); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('webhooks'); } }; ================================================ FILE: database/migrations/2024_04_03_224729_create_webhook_tags_table.php ================================================ id(); $table->unsignedBigInteger('webhook_id'); $table->unsignedInteger('tag_id'); $table->foreign('webhook_id')->references('id')->on('webhooks')->onDelete('cascade'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('webhook_tags'); } }; ================================================ FILE: database/migrations/2024_04_15_220412_create_organisation_location_table.php ================================================ unsignedInteger('organisation_id'); $table->unsignedInteger('location_id'); $table->foreign('organisation_id')->references('id')->on('organisations')->cascadeOnDelete(); $table->foreign('location_id')->references('id')->on('locations')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('organisation_location'); } }; ================================================ FILE: database/migrations/2024_04_23_062512_update_subscriptions_change_name_type.php ================================================ renameColumn('name', 'type'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('subscriptions', function (Blueprint $table) { $table->renameColumn('type', 'name'); }); } }; ================================================ FILE: database/migrations/2024_04_24_193659_update_campaign_settings_add_assets.php ================================================ boolean('assets')->default(true); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('assets'); }); } }; ================================================ FILE: database/migrations/2024_05_02_162659_update_inventories_add_image_uuid.php ================================================ char('image_uuid', 36)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('inventories', function (Blueprint $table) { $table->dropColumn('image_uuid'); }); } }; ================================================ FILE: database/migrations/2024_05_28_202511_create_tier_pricing_table.php ================================================ id(); $table->timestamps(); $table->unsignedBigInteger('tier_id'); $table->string('currency', 3); $table->double('cost', 10, 3)->unsigned(); $table->unsignedTinyInteger('period')->default(1); $table->string('stripe_id', 191); $table->index(['currency', 'period']); $table->foreign('tier_id')->references('id')->on('tiers')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('tier_prices'); } }; ================================================ FILE: database/migrations/2024_06_01_000001_create_oauth_device_codes_table.php ================================================ char('id', 80)->primary(); $table->foreignId('user_id')->nullable()->index(); $table->foreignUuid('client_id')->index(); $table->char('user_code', 8)->unique(); $table->text('scopes'); $table->boolean('revoked'); $table->dateTime('user_approved_at')->nullable(); $table->dateTime('last_polled_at')->nullable(); $table->dateTime('expires_at')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('oauth_device_codes'); } /** * Get the migration connection name. */ public function getConnection(): ?string { return $this->connection ?? config('passport.connection'); } }; ================================================ FILE: database/migrations/2024_06_04_202056_update_posts_add_deleted_at_deleted_by.php ================================================ softDeletes(); $table->index('deleted_at'); $table->unsignedInteger('deleted_by')->nullable(); $table->foreign('deleted_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('posts', function (Blueprint $table) { $table->dropForeign('deleted_by'); $table->dropIndex('deleted_at'); $table->dropColumn('deleted_by'); $table->dropColumn('deleted_at'); }); } }; ================================================ FILE: database/migrations/2024_06_14_201056_update_campaigns_add_deleted_by_deleted_at.php ================================================ softDeletes(); $table->index('deleted_at'); $table->unsignedInteger('deleted_by')->nullable(); $table->foreign('deleted_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaigns', function (Blueprint $table) { $table->dropForeign('deleted_by'); $table->dropIndex('deleted_at'); $table->dropColumn('deleted_by'); $table->dropColumn('deleted_at'); }); } }; ================================================ FILE: database/migrations/2024_06_22_004252_update_character_race_add_is_private.php ================================================ boolean('is_private')->default(false); $table->index('is_private'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('character_race', function (Blueprint $table) { $table->dropIndex('is_private'); $table->dropColumn('is_private'); }); } }; ================================================ FILE: database/migrations/2024_07_02_172619_update_posts_add_is_template.php ================================================ boolean('is_template')->default(false); $table->index('is_template'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('posts', function (Blueprint $table) { $table->dropIndex('is_template'); $table->dropColumn('is_template'); }); } }; ================================================ FILE: database/migrations/2024_07_08_051702_update_webhooks_add_settings.php ================================================ text('settings')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('webhooks', function (Blueprint $table) { $table->dropColumn('settings'); }); } }; ================================================ FILE: database/migrations/2024_07_09_062122_update_character_family_add_is_private.php ================================================ boolean('is_private')->default(false); $table->index('is_private'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('character_family', function (Blueprint $table) { $table->dropIndex('is_private'); $table->dropColumn('is_private'); }); } }; ================================================ FILE: database/migrations/2024_07_25_205325_update_map_layers_add_image_uuid.php ================================================ char('image_uuid', 36)->after('image')->nullable(); $table->foreign('image_uuid')->references('id')->on('images')->nullOnDelete(); }); Schema::table('map_layers', function (Blueprint $table) { $table->renameColumn('image', 'image_path'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('map_layers', function (Blueprint $table) { $table->dropForeign('map_layers_image_uuid_foreign'); $table->dropColumn('image_uuid'); }); Schema::table('map_layers', function (Blueprint $table) { $table->renameColumn('image_path', 'image'); }); } }; ================================================ FILE: database/migrations/2024_07_26_160208_update_entity_assets_add_image_uuid.php ================================================ char('image_uuid', 36)->nullable(); $table->foreign('image_uuid')->references('id')->on('images')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_assets', function (Blueprint $table) { $table->dropForeign('entity_assets_image_uuid_foreign'); $table->dropColumn('image_uuid'); }); } }; ================================================ FILE: database/migrations/2024_07_31_215400_remove_unused_slug.php ================================================ dropColumn('slug'); }); } } /** * Reverse the migrations. */ public function down(): void {} }; ================================================ FILE: database/migrations/2024_08_12_231117_update_locations_add_is_destroyed.php ================================================ boolean('is_destroyed')->default(0); $table->index('is_destroyed'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('locations', function (Blueprint $table) { $table->dropIndex('is_destroyed'); $table->dropColumn('is_destroyed'); }); } }; ================================================ FILE: database/migrations/2024_08_13_202759_update_creatures_add_is_dead.php ================================================ boolean('is_dead')->default(0); $table->index('is_dead'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('creatures', function (Blueprint $table) { $table->dropIndex('is_dead'); $table->dropColumn('is_dead'); }); } }; ================================================ FILE: database/migrations/2024_08_14_145655_update_races_add_is_extinct.php ================================================ boolean('is_extinct')->default(0); $table->index('is_extinct'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('races', function (Blueprint $table) { $table->dropIndex('is_extinct'); $table->dropColumn('is_extinct'); }); } }; ================================================ FILE: database/migrations/2024_08_14_164041_update_families_add_is_extinct.php ================================================ boolean('is_extinct')->default(0); $table->index('is_extinct'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('families', function (Blueprint $table) { $table->dropIndex('is_extinct'); $table->dropColumn('is_extinct'); }); } }; ================================================ FILE: database/migrations/2024_08_27_175447_update_notifications_migrate_user_model.php ================================================ where('notifiable_type', 'App\\User') ->update(['notifiable_type' => 'App\\Models\\User']); } /** * Reverse the migrations. */ public function down(): void {} }; ================================================ FILE: database/migrations/2025_01_09_180214_update_objects_add_weight.php ================================================ string('weight')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('items', function (Blueprint $table) { $table->dropColumn('weight'); }); } }; ================================================ FILE: database/migrations/2025_01_10_175606_update_quests_add_location_id.php ================================================ unsignedInteger('location_id')->nullable(); $table->foreign('location_id')->references('id')->on('locations')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('quests', function (Blueprint $table) { $table->dropForeign('quests_location_id_foreign'); $table->dropColumn('location_id'); }); } }; ================================================ FILE: database/migrations/2025_01_11_022946_update_modules_add_campaign_id.php ================================================ unsignedInteger('campaign_id')->nullable(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->string('singular', 50)->nullable(); $table->string('plural', 50)->nullable(); $table->string('icon', 30)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_types', function (Blueprint $table) { $table->dropForeign('entity_types_campaign_id_foreign'); $table->dropColumn('campaign_id'); $table->dropColumn('singular'); $table->dropColumn('plural'); $table->dropColumn('icon'); }); } }; ================================================ FILE: database/migrations/2025_01_13_064804_create_post_tag_table.php ================================================ increments('id'); $table->unsignedInteger('post_id'); $table->unsignedInteger('tag_id'); $table->timestamps(); $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('post_tag'); } }; ================================================ FILE: database/migrations/2025_01_13_230814_update_entities_add_boilerplate.php ================================================ longText('entry')->nullable(); $table->string('type', 45)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->dropColumn('entry'); $table->dropColumn('type'); }); } }; ================================================ FILE: database/migrations/2025_01_14_144924_update_bookmarks_add_entity_type_id.php ================================================ unsignedInteger('entity_type_id')->nullable(); $table->foreign('entity_type_id')->references('id')->on('entity_types')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('bookmarks', function (Blueprint $table) { $table->dropForeign('bookmarks_entity_type_id_foreign'); $table->dropColumn('entity_type_id'); }); } }; ================================================ FILE: database/migrations/2025_01_15_160953_migrate_entry_to_entities.php ================================================ exclude($exclude)->get() as $entityType) { DB::statement('UPDATE entities JOIN ' . $entityType->pluralCode() . ' as s ON entities.entity_id = s.id SET entities.entry = s.entry, entities.type = s.type WHERE entities.type_id = ' . $entityType->id); } } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) {}); } }; ================================================ FILE: database/migrations/2025_01_16_164318_add_dashboard_entity_type_id.php ================================================ unsignedInteger('entity_type_id')->nullable(); $table->foreign('entity_type_id')->references('id')->on('entity_types')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_dashboard_widgets', function (Blueprint $table) { $table->dropForeign('campaign_dashboard_widgets_entity_type_id_foreign'); $table->dropColumn('entity_type_id'); }); } }; ================================================ FILE: database/migrations/2025_01_16_175851_migrate_widgets_to_entity_types.php ================================================ get() as $entityType) { DB::statement('UPDATE campaign_dashboard_widgets SET entity_type_id = ' . $entityType->id . " WHERE config like '%\"entity\":\"" . $entityType->code . "\"%'"); } } /** * Reverse the migrations. */ public function down(): void {} }; ================================================ FILE: database/migrations/2025_01_17_161652_add_parent_to_entities.php ================================================ unsignedInteger('parent_id')->nullable(); $table->foreign('parent_id')->references('id')->on('entities')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->dropForeign('entities_parent_id_foreign'); $table->dropColumn('parent_id'); }); } }; ================================================ FILE: database/migrations/2025_01_27_142523_add_word_count.php ================================================ tableNames as $tableName) { Schema::table($tableName, function (Blueprint $table) { $table->unsignedInteger('words')->nullable(); }); } } /** * Reverse the migrations. */ public function down(): void { foreach ($this->tableNames as $tableName) { Schema::table($tableName, function (Blueprint $table) { $table->dropColumn('words'); }); } } }; ================================================ FILE: database/migrations/2025_02_21_204219_update_module_icon.php ================================================ string('icon', 100)->change(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_types', function (Blueprint $table) { $table->string('icon', 30)->change(); }); } }; ================================================ FILE: database/migrations/2025_03_18_055651_update_features_add_message_id.php ================================================ string('icon', 100)->nullable(); $table->unsignedBigInteger('message_id')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('features', function (Blueprint $table) { $table->dropColumn('message_id'); }); } }; ================================================ FILE: database/migrations/2025_03_21_150239_rename_entity_events_to_reminders.php ================================================ unsignedBigInteger('remindable_id')->nullable(); $table->string('remindable_type')->nullable(); $table->index(['remindable_id', 'remindable_type']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('reminders', function (Blueprint $table) { $table->dropIndex(['remindable_id', 'remindable_type']); $table->dropColumn('remindable_type'); $table->dropColumn('remindable_id'); }); } }; ================================================ FILE: database/migrations/2025_03_21_151008_fill_entity_events.php ================================================ unsignedInteger('entity_id')->nullable()->change(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('reminders', function (Blueprint $table) { // }); } }; ================================================ FILE: database/migrations/2025_04_10_064831_update_attribute_templates_add_is_enabled.php ================================================ boolean('is_enabled')->default(true); $table->index('is_enabled'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('attribute_templates', function (Blueprint $table) { $table->dropIndex('attribute_templates_is_enabled_index'); $table->dropColumn('is_enabled'); }); } }; ================================================ FILE: database/migrations/2025_04_21_170958_update_user_logs_add_campaign_id.php ================================================ hasTable('user_logs')) { return; } Schema::connection('logs')->table('user_logs', function (Blueprint $table) { $table->unsignedBigInteger('campaign_id')->nullable(); $table->json('data')->nullable(); $table->unsignedInteger('impersonated_by')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { if (! config('logging.enabled')) { return; } if (! Schema::connection('logs')->hasTable('user_logs')) { return; } Schema::connection('logs')->table('user_logs', function (Blueprint $table) { $table->dropColumn('data'); $table->dropColumn('campaign_id'); $table->dropColumn('impersonated_by'); }); } }; ================================================ FILE: database/migrations/2025_05_29_015926_update_bookmarks_add_created_by_updated_by.php ================================================ unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('bookmarks', function (Blueprint $table) { $table->dropColumn('created_by'); $table->dropColumn('updated_by'); }); } }; ================================================ FILE: database/migrations/2025_05_29_161824_update_campaign_dashboard_widgets_add_created_by_updated_by.php ================================================ unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_dashboard_widgets', function (Blueprint $table) { $table->dropColumn('created_by'); $table->dropColumn('updated_by'); }); } }; ================================================ FILE: database/migrations/2025_05_29_162746_update_attributes_add_created_by_updated_by.php ================================================ unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('attributes', function (Blueprint $table) { $table->dropColumn('created_by'); $table->dropColumn('updated_by'); }); } }; ================================================ FILE: database/migrations/2025_06_06_053052_update_reminders_remove_entity_id.php ================================================ getDriverName() !== 'sqlite') { $table->dropForeign('entity_events_entity_id_foreign'); $table->dropColumn('entity_id'); } }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('reminders', function (Blueprint $table) { $table->unsignedInteger('entity_id')->nullable(); $table->foreign('entity_id')->references('id')->on('entities')->onDelete('cascade'); }); } }; ================================================ FILE: database/migrations/2025_06_09_161939_cleanup_old_entry_and_type.php ================================================ dropIndex(['name_type']); // drops index that contains "type" $table->dropColumn('type'); $table->dropColumn('entry'); }); } elseif (in_array($tablename, ['items', 'races', 'creatures'])) { Schema::table($tablename, function (Blueprint $table) { $table->dropIndex(['name_type_is_private']); // drops index that contains "type" $table->dropColumn('type'); $table->dropColumn('entry'); }); } elseif ($tablename == 'events') { Schema::table($tablename, function (Blueprint $table) { $table->dropIndex(['name_type_date_is_private']); // drops index that contains "type" $table->dropColumn('type'); $table->dropColumn('entry'); }); } elseif ($tablename == 'journals') { Schema::table($tablename, function (Blueprint $table) { $table->dropIndex(['name_type_date']); // drops index that contains "type" $table->dropColumn('type'); $table->dropColumn('entry'); }); } elseif ($tablename == 'tags') { Schema::table($tablename, function (Blueprint $table) { $table->dropIndex(['name_type_is_private_is_hidden_is_auto_applied']); // drops index that contains "type" $table->dropColumn('type'); $table->dropColumn('entry'); }); } else { Schema::table($tablename, function (Blueprint $table) { $table->dropColumn('type'); $table->dropColumn('entry'); }); } } } /** * Reverse the migrations. */ public function down(): void { // } }; ================================================ FILE: database/migrations/2025_06_09_162836_cleanup_old_fields.php ================================================ ['family_id'], 'locations' => ['is_map_private'], 'organisations' => ['location_id'], 'quests' => ['calendar_id', 'calendar_year', 'calendar_month', 'calendar_day'], 'journals' => ['calendar_id', 'calendar_year', 'calendar_month', 'calendar_day'], ]; foreach ($tables as $tablename => $extra) { Schema::table($tablename, function (Blueprint $table) use ($extra, $tablename) { if (Schema::getConnection()->getDriverName() !== 'sqlite') { foreach ($extra as $column) { if (Str::endsWith($column, '_id')) { $table->dropForeign($tablename . '_' . $column . '_foreign'); } $table->dropColumn($column); } } }); } } /** * Reverse the migrations. */ public function down(): void { // } }; ================================================ FILE: database/migrations/2025_06_09_181415_rename_campaign_submissions_to_applications.php ================================================ table('api_logs', function (Blueprint $table) { $table->string('response', 3)->nullable(); $table->decimal('duration', 8, 3)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { if (! config('logging.enabled')) { return; } Schema::connection('logs')->table('api_logs', function (Blueprint $table) { $table->dropColumn('response'); $table->dropColumn('duration'); }); } }; ================================================ FILE: database/migrations/2025_06_16_211604_drop_oauth_personal_access_clients.php ================================================ unsignedBigInteger('parent_id'); $table->string('parent_type'); $table->index(['parent_id', 'parent_type']); // }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_logs', function (Blueprint $table) { $table->dropIndex(['parent_id', 'parent_type']); $table->dropColumn('parent_type'); $table->dropColumn('parent_id'); }); } }; ================================================ FILE: database/migrations/2025_06_24_124354_migrate_entity_logs.php ================================================ getDriverName() !== 'sqlite') { $table->dropForeign('entity_logs_entity_id_foreign'); $table->dropColumn('entity_id'); $table->dropForeign('entity_logs_post_id_foreign'); $table->dropColumn('post_id'); } else { $table->dropConstrainedForeignId('entity_id'); $table->dropConstrainedForeignId('post_id'); } }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_logs', function (Blueprint $table) { // }); } }; ================================================ FILE: database/migrations/2025_06_25_161814_create_campaign_flags_table.php ================================================ id(); $table->string('flag', 12); $table->timestamps(); $table->unsignedInteger('campaign_id'); $table->index('flag'); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('campaign_flags'); } }; ================================================ FILE: database/migrations/2025_07_03_215618_update_objects_add_creator_id.php ================================================ unsignedInteger('creator_id')->nullable(); $table->foreign('creator_id')->references('id')->on('entities')->onDelete('cascade'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('items', function (Blueprint $table) { $table->dropForeign('entity_events_creator_id_foreign'); $table->dropColumn('creator_id'); }); } }; ================================================ FILE: database/migrations/2025_07_03_222904_update_objects_migrate_character_id_to_creator_id.php ================================================ getDriverName() == 'sqlite') { DB::statement(' UPDATE items SET creator_id = ( SELECT entities.id FROM entities WHERE entities.entity_id = items.character_id AND entities.type_id = 1 ) WHERE character_id IS NOT NULL '); } else { DB::statement(' UPDATE items JOIN entities ON entities.entity_id = items.character_id AND entities.type_id = 1 SET items.creator_id = entities.id WHERE items.character_id IS NOT NULL '); } } public function down(): void { // Safely undo: only null creator_id if it matches the expected entity DB::statement(' UPDATE items JOIN entities ON entities.entity_id = items.character_id AND entities.type_id = 1 SET items.creator_id = NULL WHERE items.creator_id = entities.id '); } }; ================================================ FILE: database/migrations/2025_08_14_185936_update_posts_remove_is_private.php ================================================ dropColumn('is_private'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('posts', function (Blueprint $table) { $table->boolean('is_private')->default(0); }); } }; ================================================ FILE: database/migrations/2025_09_04_194330_update_campaign_flags_add_value.php ================================================ string('value', 64)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_flags', function (Blueprint $table) { $table->dropColumn('value'); }); } }; ================================================ FILE: database/migrations/2025_09_12_160608_create_whiteboards_table.php ================================================ id(); $table->foreignId('campaign_id')->cascadeOnDelete(); $table->string('name', 191)->index(); $table->string('type', 45)->nullable(); $table->json('data')->nullable(); $table->boolean('is_private')->default(false); $table->timestamps(); $table->softDeletes(); $table->foreignId('created_by')->nullable()->nullOnDelete(); $table->foreignId('updated_by')->nullable()->nullOnDelete(); $table->foreignId('deleted_by')->nullable()->nullOnDelete(); $table->index(['name', 'type', 'is_private']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('whiteboards'); } }; ================================================ FILE: database/migrations/2025_09_17_034429_update_user_flags_add_amount.php ================================================ string('amount', 20)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('user_flags', function (Blueprint $table) { $table->dropColumn('amount'); }); } }; ================================================ FILE: database/migrations/2025_09_22_230759_update_campaign_imports_add_logs_errors.php ================================================ text('logs')->nullable(); $table->text('errors')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_imports', function (Blueprint $table) { $table->dropColumn('logs'); $table->dropColumn('errors'); }); } }; ================================================ FILE: database/migrations/2025_09_29_173336_update_map_markers_add_css.php ================================================ string('css', 45)->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('map_markers', function (Blueprint $table) { $table->dropColumn('css'); }); } }; ================================================ FILE: database/migrations/2025_09_29_204049_update_entities_add_archived_at.php ================================================ timestamp('archived_at')->nullable(); $table->index('archived_at'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->dropColumn('archived_at'); }); } }; ================================================ FILE: database/migrations/2025_10_01_200028_update_map_groups_add_parent_id.php ================================================ unsignedBigInteger('parent_id')->nullable(); $table->foreign('parent_id')->references('id')->on('map_groups')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('map_groups', function (Blueprint $table) { $table->dropForeign('map_groups_parent_id_foreign'); $table->dropColumn('parent_id'); }); } }; ================================================ FILE: database/migrations/2025_10_02_170514_update_campaign_settings_add_whiteboards.php ================================================ boolean('whiteboards')->default(false); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('whiteboards'); }); } }; ================================================ FILE: database/migrations/2025_11_04_205450_create_entity_locations2_table.php ================================================ id(); $table->timestamps(); $table->integer('entity_id')->unsigned(); $table->integer('location_id')->unsigned(); $table->unsignedInteger('created_by')->nullable(); $table->foreign('entity_id')->references('id')->on('entities')->cascadeOnDelete(); $table->foreign('location_id')->references('id')->on('locations')->cascadeOnDelete(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('entity_locations'); } }; ================================================ FILE: database/migrations/2025_11_06_154720_update_entity_assets_add_updated_by.php ================================================ unsignedInteger('updated_by')->nullable(); $table->foreign('updated_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_assets', function (Blueprint $table) { $table->dropColumn('updated_by'); }); } }; ================================================ FILE: database/migrations/2025_11_19_155526_migrate_campaigns_discreet.php ================================================ value . "' WHERE visibility_id = '" . CampaignVisibility::public->value . "' and is_discreet = 1"; DB::statement($statement); } /** * Reverse the migrations. */ public function down(): void { // } }; ================================================ FILE: database/migrations/2025_11_19_160105_remove_campaigns_is_discreet.php ================================================ index( [ 'campaign_id', 'type_id', 'archived_at', 'deleted_at', 'updated_at', ], 'dashboard_entity_list_idx'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->dropIndex('dashboard_entity_list_idx'); }); } }; ================================================ FILE: database/migrations/2025_12_22_212249_migrate_character_locations_to_entity_locations.php ================================================ join('entities', function ($join) { $join->on('entities.entity_id', '=', 'characters.id') ->where('entities.type_id', '=', config('entities.ids.character')); }) ->whereNotNull('characters.location_id') ->select('entities.id as entity_id', 'characters.location_id', 'entities.created_by') ->get(); foreach ($characters as $character) { DB::table('entity_locations')->insert([ 'entity_id' => $character->entity_id, 'location_id' => $character->location_id, 'created_by' => $character->created_by, 'created_at' => now(), 'updated_at' => now(), ]); } Schema::table('characters', function (Blueprint $table) { $table->dropForeign(['location_id']); $table->dropColumn('location_id'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('characters', function (Blueprint $table) { $table->integer('location_id')->unsigned()->nullable(); $table->foreign('location_id')->references('id')->on('locations')->nullOnDelete(); }); } }; ================================================ FILE: database/migrations/2026_01_14_161511_cleanup_old_referrals.php ================================================ dropForeign('users_referral_id_foreign'); $table->dropColumn('referral_id'); }); } } /** * Reverse the migrations. */ public function down(): void { Schema::table('users', function (Blueprint $table) { // }); } }; ================================================ FILE: database/migrations/2026_01_14_170634_create_referral_codes_table.php ================================================ bigIncrements('id'); $table->unsignedInteger('user_id')->nullable(); $table->string('code', 45)->unique(); $table->boolean('is_valid'); $table->timestamps(); $table->dateTime('revoked_at')->nullable(); $table->foreign('user_id')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('referrals'); } }; ================================================ FILE: database/migrations/2026_01_14_174144_create_referral_events_table.php ================================================ bigIncrements('id'); $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('referred_by')->nullable(); $table->tinyInteger('type')->default(1); $table->timestamps(); $table->foreign('created_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('referred_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('referral_events'); } }; ================================================ FILE: database/migrations/2026_01_14_174215_update_users_referred_by.php ================================================ unsignedBigInteger('referred_by')->nullable(); $table->foreign('referred_by')->references('id')->on('referrals')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropForeign(['referred_by']); $table->dropColumn('referred_by'); }); } }; ================================================ FILE: database/migrations/2026_01_15_224615_create_whiteboard_shapes_table.php ================================================ id(); $table->unsignedBigInteger('whiteboard_id'); $table->unsignedBigInteger('group_id')->nullable(); $table->string('type'); $table->bigInteger('x'); $table->bigInteger('y'); $table->bigInteger('scale_x'); $table->bigInteger('scale_y'); $table->bigInteger('width'); $table->bigInteger('height'); $table->bigInteger('rotation'); $table->bigInteger('z_index'); $table->boolean('is_locked')->default(false); $table->json('shape'); $table->foreignId('created_by')->nullable()->nullOnDelete(); $table->foreignId('updated_by')->nullable()->nullOnDelete(); $table->foreignId('deleted_by')->nullable()->nullOnDelete(); $table->timestamps(); $table->softDeletes(); $table->foreign('whiteboard_id')->references('id')->on('whiteboards')->cascadeOnDelete(); $table->foreign('group_id')->references('id')->on('whiteboard_shapes')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('whiteboard_shapes'); } }; ================================================ FILE: database/migrations/2026_01_15_232603_create_whiteboard_strokes_table.php ================================================ id(); $table->foreignId('shape_id')->cascadeOnDelete(); $table->binary('points', 4294967295); $table->string('color'); $table->unsignedBigInteger('width'); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('whiteboard_strokes'); } }; ================================================ FILE: database/migrations/2026_01_23_204425_create_spotlights_table.php ================================================ id(); $table->unsignedInteger('campaign_id'); $table->string('url'); $table->dateTime('featured_at'); $table->unsignedInteger('featured_by')->nullable(); $table->tinyInteger('status')->default(1); $table->foreign('featured_by')->references('id')->on('users')->nullOnDelete(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('spotlights'); } }; ================================================ FILE: database/migrations/2026_01_23_205038_cleanup_old_spotlights.php ================================================ dropIndex('campaigns_idx'); } $table->index(['visibility_id', 'visible_entity_count', 'is_hidden'], 'campaigns_idx'); $table->dropColumn('is_featured'); $table->dropColumn('featured_until'); $table->dropColumn('featured_reason'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaigns', function (Blueprint $table) { // }); } }; ================================================ FILE: database/migrations/2026_01_23_211003_create_spotlight_survey_table.php ================================================ id(); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('created_by')->nullable(); $table->json('content_json')->nullable(); $table->unsignedTinyInteger('status')->default(1); $table->timestamps(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('created_by')->references('id')->on('users')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('spotlight_survey'); } }; ================================================ FILE: database/migrations/2026_01_30_100000_rename_description_to_entry_in_quest_elements_table.php ================================================ renameColumn('description', 'entry'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('quest_elements', function (Blueprint $table) { $table->renameColumn('entry', 'description'); }); } }; ================================================ FILE: database/migrations/2026_02_03_221238_create_playstyles_table.php ================================================ id(); $table->string('slug')->unique(); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('playstyles'); } }; ================================================ FILE: database/migrations/2026_02_04_170050_migrate_organisation_locations_to_entity_locations.php ================================================ join('entities', function ($join) { $join->on('entities.entity_id', '=', 'organisations.id') ->where('entities.type_id', '=', config('entities.ids.organisation')); }) ->join('organisation_location', 'organisation_location.organisation_id', '=', 'organisations.id') ->select('entities.id as entity_id', 'organisation_location.location_id', 'entities.created_by') ->get(); foreach ($organisations as $org) { DB::table('entity_locations')->insert([ 'entity_id' => $org->entity_id, 'location_id' => $org->location_id, 'created_by' => $org->created_by, 'created_at' => now(), 'updated_at' => now(), ]); } } /** * Reverse the migrations. */ public function down(): void { // Note: This is a data migration. The down() method doesn't restore data // from entity_locations back to the old pivot tables because that data // is still preserved in the original tables. } }; ================================================ FILE: database/migrations/2026_02_04_170051_migrate_creature_locations_to_entity_locations.php ================================================ join('entities', function ($join) { $join->on('entities.entity_id', '=', 'creatures.id') ->where('entities.type_id', '=', config('entities.ids.creature')); }) ->join('creature_location', 'creature_location.creature_id', '=', 'creatures.id') ->select('entities.id as entity_id', 'creature_location.location_id', 'entities.created_by') ->get(); foreach ($creatures as $creature) { DB::table('entity_locations')->insert([ 'entity_id' => $creature->entity_id, 'location_id' => $creature->location_id, 'created_by' => $creature->created_by, 'created_at' => now(), 'updated_at' => now(), ]); } // Note: Old pivot tables are intentionally NOT dropped for safety. // They can be removed in a later cleanup migration after verification. } /** * Reverse the migrations. */ public function down(): void { // Note: This is a data migration. The down() method doesn't restore data // from entity_locations back to the old pivot tables because that data // is still preserved in the original tables. } }; ================================================ FILE: database/migrations/2026_02_04_170052_migrate_race_locations_to_entity_locations.php ================================================ join('entities', function ($join) { $join->on('entities.entity_id', '=', 'races.id') ->where('entities.type_id', '=', config('entities.ids.race')); }) ->join('race_location', 'race_location.race_id', '=', 'races.id') ->select('entities.id as entity_id', 'race_location.location_id', 'entities.created_by') ->get(); foreach ($races as $race) { DB::table('entity_locations')->insert([ 'entity_id' => $race->entity_id, 'location_id' => $race->location_id, 'created_by' => $race->created_by, 'created_at' => now(), 'updated_at' => now(), ]); } } /** * Reverse the migrations. */ public function down(): void { // Note: This is a data migration. The down() method doesn't restore data // from entity_locations back to the old pivot tables because that data // is still preserved in the original tables. } }; ================================================ FILE: database/migrations/2026_02_04_213616_create_campaign_filters_table.php ================================================ id(); $table->unsignedInteger('campaign_id'); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->unsignedTinyInteger('type'); $table->text('entry')->nullable(); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('campaign_filters'); } }; ================================================ FILE: database/migrations/2026_02_05_074106_create_campaign_playstyle_table.php ================================================ id(); $table->unsignedInteger('campaign_id'); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->unsignedBigInteger('playstyle_id'); $table->foreign('playstyle_id')->references('id')->on('playstyles')->cascadeOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('campaign_playstyle'); } }; ================================================ FILE: database/migrations/2026_02_06_185348_update_entities_force_name.php ================================================ string('name')->nullable(false)->change(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->string('name')->nullable()->change(); }); } }; ================================================ FILE: database/migrations/2026_02_06_190751_add_entity_id_index_to_entities.php ================================================ index('entity_id'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->dropIndex(['entity_id']); }); } }; ================================================ FILE: database/migrations/2026_02_06_190754_add_composite_index_to_entity_tags.php ================================================ index(['entity_id', 'tag_id']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_tags', function (Blueprint $table) { $table->dropIndex(['entity_id', 'tag_id']); }); } }; ================================================ FILE: database/migrations/2026_02_06_190757_add_deleted_at_index_to_soft_delete_tables.php ================================================ */ protected array $tables = [ 'attribute_templates', 'calendars', 'campaign_boosts', 'campaign_styles', 'characters', 'conversations', 'creatures', 'dice_rolls', 'events', 'families', 'items', 'journals', 'locations', 'maps', 'notes', 'organisations', 'plugins', 'quests', 'races', 'tags', 'themes', 'timelines', 'whiteboards', 'whiteboard_shapes', ]; /** * Run the migrations. */ public function up(): void { foreach ($this->tables as $tableName) { if (! Schema::hasTable($tableName)) { continue; } Schema::table($tableName, function (Blueprint $table) { $table->index('deleted_at'); }); } } /** * Reverse the migrations. */ public function down(): void { foreach ($this->tables as $tableName) { if (! Schema::hasTable($tableName)) { continue; } Schema::table($tableName, function (Blueprint $table) { $table->dropIndex(['deleted_at']); }); } } }; ================================================ FILE: database/migrations/2026_02_07_072211_update_applications_add_new_fields.php ================================================ text('character_concept')->nullable(); $table->tinyInteger('experience')->default(0); $table->json('availability_days')->nullable(); $table->time('time_start')->nullable(); $table->time('time_end')->nullable(); $table->string('timezone')->nullable(); $table->tinyInteger('pref_rp_combat')->default(1); $table->tinyInteger('pref_tone')->default(1); $table->string('external_link')->nullable(); $table->string('additional_notes')->nullable(); $table->tinyInteger('status')->default(0); $table->index(['status']); }); } public function down(): void { Schema::table('applications', function (Blueprint $table) { $table->dropIndex(['status']); $table->dropColumn([ 'character_concept', 'experience', 'availability_days', 'time_start', 'time_end', 'timezone', 'pref_rp_combat', 'pref_tone', 'external_link', 'additional_notes', 'status', ]); }); } }; ================================================ FILE: database/migrations/2026_02_11_162437_add_alias_config.php ================================================ boolean('aliases')->default(true); $table->renameColumn('assets', 'media'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaign_settings', function (Blueprint $table) { $table->dropColumn('aliases'); $table->renameColumn('media', 'assets'); }); } }; ================================================ FILE: database/migrations/2026_02_16_155432_create_campaign_events_table.php ================================================ id(); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('created_by')->nullable(); $table->string('event'); $table->json('metadata')->nullable(); $table->timestamps(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('created_by')->references('id')->on('users')->cascadeOnDelete(); $table->index(['campaign_id', 'event']); $table->index(['campaign_id', 'created_at']); $table->index('event'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('campaign_events'); } }; ================================================ FILE: database/migrations/2026_02_16_215608_update_entities_add_source.php ================================================ string('source', 20)->default('user')->index(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->dropColumn('source'); }); } }; ================================================ FILE: database/migrations/2026_02_16_232044_fix_users_referred_by_foreign_key.php ================================================ dropForeign(['referred_by']); $table->dropColumn('referred_by'); $table->unsignedInteger('referred_by')->nullable(); $table->foreign('referred_by')->references('id')->on('users')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropForeign(['referred_by']); $table->dropColumn('referred_by'); $table->unsignedBigInteger('referred_by')->nullable(); $table->foreign('referred_by')->references('id')->on('referrals')->nullOnDelete(); }); } }; ================================================ FILE: database/migrations/2026_02_23_193432_add_prioritised_to_campaigns.php ================================================ boolean('is_prioritised')->default(false)->after('is_open'); }); } public function down(): void { Schema::table('campaigns', function (Blueprint $table) { $table->dropColumn('is_prioritised'); }); } }; ================================================ FILE: database/migrations/2026_03_03_225300_update_characters_is_dead_to_tinyinteger.php ================================================ tinyInteger('is_dead')->unsigned()->default(0)->change(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('characters', function (Blueprint $table) { $table->boolean('is_dead')->default(false)->change(); }); } }; ================================================ FILE: database/migrations/2026_03_04_044125_update_quests_is_completed_to_tinyinteger.php ================================================ where('is_completed', 1)->update(['is_completed' => 2]); Schema::table('quests', function (Blueprint $table) { $table->tinyInteger('is_completed')->unsigned()->default(0)->change(); }); } /** * Reverse the migrations. */ public function down(): void { DB::table('quests')->where('is_completed', 2)->update(['is_completed' => 1]); DB::table('quests')->where('is_completed', '>', 1)->update(['is_completed' => 0]); Schema::table('quests', function (Blueprint $table) { $table->boolean('is_completed')->default(false)->change(); }); } }; ================================================ FILE: database/migrations/2026_03_05_184417_migrate_event_locations_to_entity_locations.php ================================================ whereIn('entity_id', function ($query) { $query->select('entities.id') ->from('entities') ->where('entities.type_id', '=', config('entities.ids.event')); }) ->delete(); $events = DB::table('events') ->join('entities', function ($join) { $join->on('entities.entity_id', '=', 'events.id') ->where('entities.type_id', '=', config('entities.ids.event')); }) ->join('locations', 'locations.id', '=', 'events.location_id') ->whereNotNull('events.location_id') ->select('entities.id as entity_id', 'events.location_id', 'entities.created_by') ->get(); foreach ($events as $event) { DB::table('entity_locations')->insert([ 'entity_id' => $event->entity_id, 'location_id' => $event->location_id, 'created_by' => $event->created_by, 'created_at' => now(), 'updated_at' => now(), ]); } Schema::table('events', function (Blueprint $table) { $table->dropColumn('location_id'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('events', function (Blueprint $table) { $table->integer('location_id')->unsigned()->nullable(); }); } }; ================================================ FILE: database/migrations/2026_03_05_214251_update_characters_pinned_defaults.php ================================================ boolean('is_appearance_pinned')->default(1)->change(); $table->boolean('is_personality_pinned')->default(1)->change(); }); } public function down(): void { Schema::table('characters', function (Blueprint $table) { $table->boolean('is_appearance_pinned')->default(0)->change(); $table->boolean('is_personality_pinned')->default(0)->change(); }); } }; ================================================ FILE: database/migrations/2026_03_06_140442_rename_characters_is_dead_to_status.php ================================================ renameColumn('is_dead', 'status_id'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('characters', function (Blueprint $table) { $table->renameColumn('status_id', 'is_dead'); }); } }; ================================================ FILE: database/migrations/2026_03_06_140443_rename_quests_is_completed_to_status.php ================================================ renameColumn('is_completed', 'status_id'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('quests', function (Blueprint $table) { $table->renameColumn('status_id', 'is_completed'); }); } }; ================================================ FILE: database/migrations/2026_03_09_224121_add_copy_entity_entry_to_quest_elements_table.php ================================================ boolean('copy_entity_entry')->nullable()->default(false); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('quest_elements', function (Blueprint $table) { $table->dropColumn('copy_entity_entry'); }); } }; ================================================ FILE: database/migrations/2026_03_11_224335_add_icon_to_tags_table.php ================================================ string('icon', 100)->nullable()->after('colour'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('tags', function (Blueprint $table) { $table->dropColumn('icon'); }); } }; ================================================ FILE: database/migrations/2026_03_12_172704_add_title_to_locations_table.php ================================================ string('title')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('locations', function (Blueprint $table) { $table->dropColumn('title'); }); } }; ================================================ FILE: database/migrations/2026_03_16_170743_update_cancellations_add_secondary.php ================================================ string('secondary')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('subscription_cancellations', function (Blueprint $table) { $table->dropColumn('secondary'); }); } }; ================================================ FILE: database/migrations/2026_03_16_211952_create_entity_listing_preferences_table.php ================================================ id(); $table->unsignedInteger('user_id'); $table->unsignedInteger('campaign_id'); $table->unsignedInteger('type_id'); $table->json('visible_columns')->nullable(); $table->string('layout', 10)->nullable(); $table->boolean('nested')->nullable(); $table->timestamps(); $table->unique(['user_id', 'campaign_id', 'type_id'], 'listing_pref_unique'); $table->foreign('user_id')->references('id')->on('users')->cascadeOnDelete(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); $table->foreign('type_id')->references('id')->on('entity_types')->cascadeOnDelete(); }); } public function down(): void { Schema::dropIfExists('entity_listing_preferences'); } }; ================================================ FILE: database/migrations/2026_03_17_000001_migrate_parent_ids_to_entities.php ================================================ ['key' => 'ability_id', 'type_id' => config('entities.ids.ability')], 'attribute_templates' => ['key' => 'attribute_template_id', 'type_id' => config('entities.ids.attribute_template')], 'calendars' => ['key' => 'calendar_id', 'type_id' => config('entities.ids.calendar')], 'creatures' => ['key' => 'creature_id', 'type_id' => config('entities.ids.creature')], 'events' => ['key' => 'event_id', 'type_id' => config('entities.ids.event')], 'families' => ['key' => 'family_id', 'type_id' => config('entities.ids.family')], 'items' => ['key' => 'item_id', 'type_id' => config('entities.ids.item')], 'journals' => ['key' => 'journal_id', 'type_id' => config('entities.ids.journal')], 'locations' => ['key' => 'location_id', 'type_id' => config('entities.ids.location')], 'maps' => ['key' => 'map_id', 'type_id' => config('entities.ids.map')], 'notes' => ['key' => 'note_id', 'type_id' => config('entities.ids.note')], 'organisations' => ['key' => 'organisation_id', 'type_id' => config('entities.ids.organisation')], 'quests' => ['key' => 'quest_id', 'type_id' => config('entities.ids.quest')], 'races' => ['key' => 'race_id', 'type_id' => config('entities.ids.race')], 'tags' => ['key' => 'tag_id', 'type_id' => config('entities.ids.tag')], 'timelines' => ['key' => 'timeline_id', 'type_id' => config('entities.ids.timeline')], ]; foreach ($models as $table => $config) { DB::statement(" UPDATE entities e JOIN {$table} c ON e.entity_id = c.id AND e.type_id = {$config['type_id']} JOIN {$table} parent_child ON c.{$config['key']} = parent_child.id JOIN entities parent_entity ON parent_entity.entity_id = parent_child.id AND parent_entity.type_id = {$config['type_id']} SET e.parent_id = parent_entity.id WHERE c.{$config['key']} IS NOT NULL AND e.parent_id IS NULL "); } } public function down(): void { // The old child table columns still exist, so no data is lost. // Nullify entities.parent_id for standard types only. $standardTypeIds = [ config('entities.ids.ability'), config('entities.ids.attribute_template'), config('entities.ids.calendar'), config('entities.ids.creature'), config('entities.ids.event'), config('entities.ids.family'), config('entities.ids.item'), config('entities.ids.journal'), config('entities.ids.location'), config('entities.ids.map'), config('entities.ids.note'), config('entities.ids.organisation'), config('entities.ids.quest'), config('entities.ids.race'), config('entities.ids.tag'), config('entities.ids.timeline'), ]; DB::table('entities') ->whereIn('type_id', $standardTypeIds) ->update(['parent_id' => null]); } }; ================================================ FILE: database/migrations/2026_03_17_000002_drop_parent_columns_from_child_tables.php ================================================ 'ability_id', 'attribute_templates' => 'attribute_template_id', 'calendars' => 'calendar_id', 'creatures' => 'creature_id', 'events' => 'event_id', 'families' => 'family_id', 'items' => 'item_id', 'journals' => 'journal_id', 'maps' => 'map_id', 'notes' => 'note_id', 'organisations' => 'organisation_id', 'quests' => 'quest_id', 'races' => 'race_id', 'tags' => 'tag_id', 'timelines' => 'timeline_id', ]; foreach ($columns as $table => $column) { if (Schema::hasColumn($table, $column)) { Schema::table($table, function (Blueprint $table) use ($column) { $table->dropForeign($table->getTable() . '_' . $column . '_foreign'); $table->dropColumn($column); }); } } Schema::table('locations', function (Blueprint $table) { $table->dropForeign('locations_location_id_foreign'); $table->dropForeign('locations_parent_location_id_foreign'); $table->dropColumn('location_id'); }); } public function down(): void { $columns = [ 'abilities' => 'ability_id', 'attribute_templates' => 'attribute_template_id', 'calendars' => 'calendar_id', 'creatures' => 'creature_id', 'events' => 'event_id', 'families' => 'family_id', 'items' => 'item_id', 'journals' => 'journal_id', 'locations' => 'location_id', 'maps' => 'map_id', 'notes' => 'note_id', 'organisations' => 'organisation_id', 'quests' => 'quest_id', 'races' => 'race_id', 'tags' => 'tag_id', 'timelines' => 'timeline_id', ]; foreach ($columns as $table => $column) { Schema::table($table, function (Blueprint $table) use ($column) { $table->unsignedInteger($column)->nullable(); }); } } }; ================================================ FILE: database/migrations/2026_03_17_092044_create_item_creator_table.php ================================================ increments('id'); $table->unsignedInteger('item_id'); $table->unsignedInteger('creator_id'); $table->timestamps(); $table->foreign('item_id')->references('id')->on('items')->cascadeOnDelete(); $table->foreign('creator_id')->references('id')->on('entities')->cascadeOnDelete(); }); // Migrate existing creator_id data to the pivot table DB::statement(' INSERT INTO item_creator (item_id, creator_id, created_at, updated_at) SELECT id, creator_id, NOW(), NOW() FROM items WHERE creator_id IS NOT NULL '); Schema::table('items', function (Blueprint $table) { $table->dropForeign(['creator_id']); $table->dropColumn('creator_id'); }); } public function down(): void { Schema::table('items', function (Blueprint $table) { $table->unsignedInteger('creator_id')->nullable(); $table->foreign('creator_id')->references('id')->on('entities')->cascadeOnDelete(); }); // Migrate first creator back to the items table DB::statement(' UPDATE items INNER JOIN ( SELECT item_id, MIN(creator_id) as creator_id FROM item_creator GROUP BY item_id ) ic ON items.id = ic.item_id SET items.creator_id = ic.creator_id '); Schema::dropIfExists('item_creator'); } }; ================================================ FILE: database/migrations/2026_03_18_152531_add_per_page_to_entity_listing_preferences_table.php ================================================ unsignedSmallInteger('per_page')->nullable()->after('nested'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entity_listing_preferences', function (Blueprint $table) { $table->dropColumn('per_page'); }); } }; ================================================ FILE: database/migrations/2026_03_23_193613_convert_tag_colours_to_hex.php ================================================ whereIn('colour', [ 'red', 'yellow', 'brown', 'aqua', 'light-blue', 'green', 'navy', 'teal', 'orange', 'purple', 'maroon', 'grey', 'gray', 'pink', 'black', ]) ->update([ 'colour' => DB::raw("CASE colour WHEN 'red' THEN 'D93D33' WHEN 'yellow' THEN 'f39c12' WHEN 'brown' THEN 'a35831' WHEN 'aqua' THEN '00829B' WHEN 'light-blue' THEN '3A7CAD' WHEN 'green' THEN '058943' WHEN 'navy' THEN '001F3F' WHEN 'teal' THEN '2D8289' WHEN 'orange' THEN 'C85208' WHEN 'purple' THEN '605ca8' WHEN 'maroon' THEN 'D81B60' WHEN 'grey' THEN '797676' WHEN 'gray' THEN '797676' WHEN 'pink' THEN 'C822D7' WHEN 'black' THEN '111111' ELSE colour END"), ]); } /** * Reverse the migrations. */ public function down(): void { DB::table('tags') ->whereIn('colour', [ 'D93D33', 'f39c12', 'a35831', '00829B', '3A7CAD', '058943', '001F3F', '2D8289', 'C85208', '605ca8', 'D81B60', '797676', 'C822D7', '111111', ]) ->update([ 'colour' => DB::raw("CASE colour WHEN 'D93D33' THEN 'red' WHEN 'f39c12' THEN 'yellow' WHEN 'a35831' THEN 'brown' WHEN '00829B' THEN 'aqua' WHEN '3A7CAD' THEN 'light-blue' WHEN '058943' THEN 'green' WHEN '001F3F' THEN 'navy' WHEN '2D8289' THEN 'teal' WHEN 'C85208' THEN 'orange' WHEN '605ca8' THEN 'purple' WHEN 'D81B60' THEN 'maroon' WHEN '797676' THEN 'grey' WHEN 'C822D7' THEN 'pink' WHEN '111111' THEN 'black' ELSE colour END"), ]); } }; ================================================ FILE: database/migrations/2026_03_26_000001_create_category_statuses_table.php ================================================ increments('id'); $table->unsignedInteger('category_id'); $table->string('key', 45); $table->string('icon', 45)->nullable(); $table->unsignedTinyInteger('sort_order')->default(0); $table->boolean('is_default')->default(false); $table->foreign('category_id')->references('id')->on('entity_types')->cascadeOnDelete(); $table->unique(['category_id', 'key']); $table->index('sort_order'); $table->index('is_default'); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('category_statuses'); } }; ================================================ FILE: database/migrations/2026_03_26_000002_add_status_id_to_entities_table.php ================================================ unsignedInteger('status_id')->nullable(); $table->foreign('status_id')->references('id')->on('category_statuses')->nullOnDelete(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('entities', function (Blueprint $table) { $table->dropForeign(['status_id']); $table->dropColumn('status_id'); }); } }; ================================================ FILE: database/migrations/2026_04_03_164253_update_features_add_meta.php ================================================ json('meta')->nullable(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('features', function (Blueprint $table) { $table->dropColumn('meta'); }); } }; ================================================ FILE: database/migrations/2026_04_03_215445_create_campaign_descriptions_table.php ================================================ id(); $table->unsignedInteger('campaign_id')->unique(); $table->longText('description')->nullable(); $table->text('excerpt')->nullable(); $table->timestamps(); $table->foreign('campaign_id')->references('id')->on('campaigns')->cascadeOnDelete(); }); // Copy existing entry and excerpt data into the new table DB::statement(' INSERT INTO campaign_descriptions (campaign_id, description, excerpt, created_at, updated_at) SELECT id, entry, excerpt, NOW(), NOW() FROM campaigns WHERE entry IS NOT NULL OR excerpt IS NOT NULL '); Schema::table('campaigns', function (Blueprint $table) { $table->dropColumn(['entry', 'excerpt']); }); } /** * Reverse the migrations. */ public function down(): void { Schema::table('campaigns', function (Blueprint $table) { $table->longText('entry')->nullable(); $table->text('excerpt')->nullable(); }); DB::statement(' UPDATE campaigns c INNER JOIN campaign_descriptions cd ON cd.campaign_id = c.id SET c.entry = cd.description, c.excerpt = cd.excerpt '); Schema::dropIfExists('campaign_descriptions'); } }; ================================================ FILE: database/migrations/2026_05_08_181031_drop_old_status_columns.php ================================================ dropColumn('status_id'); }); Schema::table('quests', function (Blueprint $table) { $table->dropColumn('status_id'); }); Schema::table('creatures', function (Blueprint $table) { $table->dropColumn('is_dead'); $table->dropColumn('is_extinct'); }); Schema::table('locations', function (Blueprint $table) { $table->dropColumn('is_destroyed'); }); Schema::table('organisations', function (Blueprint $table) { $table->dropColumn('is_defunct'); }); Schema::table('races', function (Blueprint $table) { $table->dropColumn('is_extinct'); }); Schema::table('families', function (Blueprint $table) { $table->dropColumn('is_extinct'); }); } public function down(): void { Schema::table('characters', function (Blueprint $table) { $table->unsignedTinyInteger('status_id')->default(0); }); Schema::table('quests', function (Blueprint $table) { $table->unsignedTinyInteger('status_id')->default(0); }); Schema::table('creatures', function (Blueprint $table) { $table->boolean('is_dead')->default(false); $table->boolean('is_extinct')->default(false); }); Schema::table('locations', function (Blueprint $table) { $table->boolean('is_destroyed')->default(false); }); Schema::table('organisations', function (Blueprint $table) { $table->boolean('is_defunct')->default(false); }); Schema::table('races', function (Blueprint $table) { $table->boolean('is_extinct')->default(false); }); Schema::table('families', function (Blueprint $table) { $table->boolean('is_extinct')->default(false); }); } }; ================================================ FILE: database/migrations/2026_05_08_181951_drop_audit_columns_from_creatures_races_whiteboards.php ================================================ dropForeign('creatures_created_by_foreign'); $table->dropForeign('creatures_updated_by_foreign'); $table->dropColumn(['created_by', 'updated_by']); }); Schema::table('races', function (Blueprint $table) { $table->dropForeign('races_created_by_foreign'); $table->dropForeign('races_updated_by_foreign'); $table->dropColumn(['created_by', 'updated_by']); }); Schema::table('whiteboards', function (Blueprint $table) { $table->dropColumn(['created_by', 'updated_by', 'deleted_by']); }); } public function down(): void { Schema::table('creatures', function (Blueprint $table) { $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); }); Schema::table('races', function (Blueprint $table) { $table->unsignedInteger('created_by')->nullable(); $table->unsignedInteger('updated_by')->nullable(); $table->foreign('created_by')->references('id')->on('users')->onDelete('set null'); $table->foreign('updated_by')->references('id')->on('users')->onDelete('set null'); }); Schema::table('whiteboards', function (Blueprint $table) { $table->foreignId('created_by')->nullable()->nullOnDelete(); $table->foreignId('updated_by')->nullable()->nullOnDelete(); $table->foreignId('deleted_by')->nullable()->nullOnDelete(); }); } }; ================================================ FILE: database/migrations/2026_05_08_183610_drop_system_and_is_discreet_from_campaigns.php ================================================ dropColumn(['system', 'is_discreet']); }); } public function down(): void { Schema::table('campaigns', function (Blueprint $table) { $table->string('system')->nullable(); $table->boolean('is_discreet')->default(false); }); } }; ================================================ FILE: database/migrations/2026_05_08_185251_drop_follower_from_campaigns.php ================================================ dropColumn('follower'); }); } public function down(): void { Schema::table('campaigns', function (Blueprint $table) { $table->unsignedInteger('follower')->default(0); }); } }; ================================================ FILE: database/migrations/2026_05_08_190847_drop_export_date_from_campaigns.php ================================================ dropColumn('export_date'); }); } public function down(): void { Schema::table('campaigns', function (Blueprint $table) { $table->date('export_date')->nullable(); }); } }; ================================================ FILE: database/seeders/CategoryStatusSeeder.php ================================================ [ ['key' => 'alive', 'icon' => null, 'sort_order' => 1, 'is_default' => true], ['key' => 'missing', 'icon' => 'fa-question', 'sort_order' => 2, 'is_default' => false], ['key' => 'dead', 'icon' => 'fa-skull', 'sort_order' => 3, 'is_default' => false], ], 'creature' => [ ['key' => 'dead', 'icon' => 'fa-skull', 'sort_order' => 1, 'is_default' => false], ['key' => 'extinct', 'icon' => 'fa-skull-crossbones', 'sort_order' => 2, 'is_default' => false], ], 'location' => [ ['key' => 'destroyed', 'icon' => 'fa-house-crack', 'sort_order' => 1, 'is_default' => false], ], 'race' => [ ['key' => 'extinct', 'icon' => 'fa-skull-crossbones', 'sort_order' => 1, 'is_default' => false], ], 'quest' => [ ['key' => 'not_started', 'icon' => null, 'sort_order' => 1, 'is_default' => true], ['key' => 'ongoing', 'icon' => 'fa-spinner', 'sort_order' => 2, 'is_default' => false], ['key' => 'completed', 'icon' => 'fa-check', 'sort_order' => 3, 'is_default' => false], ['key' => 'abandoned', 'icon' => 'fa-ban', 'sort_order' => 4, 'is_default' => false], ], 'family' => [ ['key' => 'extinct', 'icon' => 'fa-skull-crossbones', 'sort_order' => 1, 'is_default' => false], ], 'organisation' => [ ['key' => 'defunct', 'icon' => 'fa-ban', 'sort_order' => 1, 'is_default' => false], ], 'item' => [ ['key' => 'owned', 'icon' => null, 'sort_order' => 1, 'is_default' => false], ['key' => 'lost', 'icon' => 'fa-question', 'sort_order' => 2, 'is_default' => false], ['key' => 'destroyed', 'icon' => 'fa-house-crack', 'sort_order' => 3, 'is_default' => false], ], ]; foreach ($statuses as $code => $categoryStatuses) { $entityType = EntityType::where('code', $code)->first(); if (! $entityType) { continue; } foreach ($categoryStatuses as $status) { DB::table('category_statuses')->updateOrInsert( [ 'category_id' => $entityType->id, 'key' => $status['key'], ], [ 'icon' => $status['icon'], 'sort_order' => $status['sort_order'], 'is_default' => $status['is_default'], ] ); } } } } ================================================ FILE: database/seeders/DatabaseSeeder.php ================================================ call(EntityEventTypeSeeder::class); $this->call(EntityTypesTableSeeder::class); $this->call(CategoryStatusSeeder::class); $this->call(PresetTypeTableSeeder::class); $this->call(GameSystemSeeder::class); $this->call(ThemesTableSeeder::class); $this->call(RolesTableSeeder::class); $this->call(VisibilitiesTableSeeder::class); $this->call(GenreTableSeeder::class); $this->call(PostLayoutTableSeeder::class); $this->call(FeatureCategorySeeder::class); $this->call(FeatureStatusSeeder::class); $this->call(TierSeeder::class); $this->call(PlaystyleSeeder::class); } } ================================================ FILE: database/seeders/EntityEventTypeSeeder.php ================================================ where('id', $type->value)->exists(); if (! $exists) { EntityEventType::query()->insert(['id' => $type->value, 'name' => $type->name]); $created++; } } } } ================================================ FILE: database/seeders/EntityTypesTableSeeder.php ================================================ 'fa-user', 'family' => 'fa-family', 'location' => 'fa-circle-location-arrow', 'organisation' => 'fa-screen-users', 'item' => 'fa-gem', 'note' => 'fa-book-open', 'event' => 'fa-cake-candles', 'calendar' => 'fa-calendar', 'race' => 'fa-person-fairy', 'quest' => 'fa-sign-hanging', 'journal' => 'fa-books', 'tag' => 'fa-tags', 'dice_roll' => 'fa-dice', 'conversation' => 'fa-comments', 'attribute_template' => 'fa-file-export', 'ability' => 'fa-fire', 'map' => 'fa-map', 'timeline' => 'fa-list-timeline', 'bookmark' => 'fa-bookmark', 'creature' => 'fa-deer', 'whiteboard' => 'fa-chalkboard', ]; $position = 1; $created = 0; foreach ($types as $code => $icon) { $type = EntityType::default()->firstOrNew([ 'code' => $code, ]); // if (! $type->exists) { // continue; // } $type->fill([ 'code' => $code, 'is_enabled' => true, 'is_special' => false, 'position' => $position, 'icon' => $icon, ])->save(); $created++; $position++; } } } ================================================ FILE: database/seeders/FeatureCategorySeeder.php ================================================ $cat, ]); $c->save(); } } } ================================================ FILE: database/seeders/FeatureStatusSeeder.php ================================================ $stat, ]); $s->save(); } } } ================================================ FILE: database/seeders/GameSystemSeeder.php ================================================ $name, ]); if ($genre->exists) { continue; } $genre->fill([ 'name' => $name, ])->save(); } } } ================================================ FILE: database/seeders/GenreTableSeeder.php ================================================ $name, ]); if ($genre->exists) { continue; } $genre->fill([ 'slug' => $name, ])->save(); } } } ================================================ FILE: database/seeders/PlaystyleSeeder.php ================================================ $slug], ); } } } ================================================ FILE: database/seeders/PostLayoutTableSeeder.php ================================================ $code, ]); if (! $layout->exists) { $layout->fill([ 'code' => $code, 'entity_type_id' => $entity_type, ])->save(); } } } } ================================================ FILE: database/seeders/PresetTypeTableSeeder.php ================================================ $name, ]); if (! $type->exists) { $type->fill([ 'code' => $name, ])->save(); $created++; } } } } ================================================ FILE: database/seeders/RolesTableSeeder.php ================================================ 'admin']); if (! $role->exists) { $role->fill([ 'display_name' => 'Administrator', ])->save(); } $role = Role::firstOrNew(['name' => 'user']); if (! $role->exists) { $role->fill([ 'display_name' => 'Normal User', ])->save(); } $role = Role::firstOrNew(['name' => 'translator']); if (! $role->exists) { $role->fill([ 'display_name' => 'Translator', ])->save(); } $role = Role::firstOrNew(['name' => 'api']); if (! $role->exists) { $role->fill([ 'display_name' => 'Api', ])->save(); } $role = Role::firstOrNew(['name' => Pledge::ROLE]); if (! $role->exists) { $role->fill([ 'display_name' => 'Patreon', ])->save(); } $role = Role::firstOrNew(['name' => 'partner']); if (! $role->exists) { $role->fill([ 'display_name' => 'Partner', ])->save(); } $role = Role::firstOrNew(['name' => 'wordsmith']); if (! $role->exists) { $role->fill([ 'display_name' => 'Wordsmith', ])->save(); } } } ================================================ FILE: database/seeders/ThemesTableSeeder.php ================================================ $theme, ]); if (! $type->exists) { $type->fill([ 'name' => $theme, ])->save(); $created++; } } } } ================================================ FILE: database/seeders/TierPricingSeeder.php ================================================ owlbear()->wyvern()->elemental(); } protected function owlbear(): self { /** @var Tier $tier */ $tier = Tier::where('name', 'Owlbear')->first(); foreach (['eur', 'usd'] as $currency) { TierPrice::create([ 'currency' => $currency, 'period' => PricingPeriod::Monthly, 'cost' => 4.99, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.owlbear.' . $currency . '.monthly'), ]); TierPrice::create([ 'currency' => $currency, 'period' => PricingPeriod::Yearly, 'cost' => 49.90, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.owlbear.' . $currency . '.yearly'), ]); } TierPrice::create([ 'currency' => 'brl', 'period' => PricingPeriod::Monthly, 'cost' => 19.99, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.owlbear.brl.monthly'), ]); TierPrice::create([ 'currency' => 'brl', 'period' => PricingPeriod::Yearly, 'cost' => 199.90, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.owlbear.brl.yearly'), ]); return $this; } protected function wyvern(): self { /** @var Tier $tier */ $tier = Tier::where('name', 'Wyvern')->first(); foreach (['eur', 'usd'] as $currency) { TierPrice::create([ 'currency' => $currency, 'period' => PricingPeriod::Monthly, 'cost' => 9.99, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.wyvern.' . $currency . '.monthly'), ]); TierPrice::create([ 'currency' => $currency, 'period' => PricingPeriod::Yearly, 'cost' => 99.90, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.wyvern.' . $currency . '.yearly'), ]); } TierPrice::create([ 'currency' => 'brl', 'period' => PricingPeriod::Monthly, 'cost' => 39.99, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.wyvern.brl.monthly'), ]); TierPrice::create([ 'currency' => 'brl', 'period' => PricingPeriod::Yearly, 'cost' => 399.90, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.wyvern.brl.yearly'), ]); return $this; } protected function elemental(): self { /** @var Tier $tier */ $tier = Tier::where('name', 'Elemental')->first(); foreach (['eur', 'usd'] as $currency) { TierPrice::create([ 'currency' => $currency, 'period' => PricingPeriod::Monthly, 'cost' => 24.99, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.elemental.' . $currency . '.monthly'), ]); TierPrice::create([ 'currency' => $currency, 'period' => PricingPeriod::Yearly, 'cost' => 249.90, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.elemental.' . $currency . '.yearly'), ]); } TierPrice::create([ 'currency' => 'brl', 'period' => PricingPeriod::Monthly, 'cost' => 99.99, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.elemental.brl.monthly'), ]); TierPrice::create([ 'currency' => 'brl', 'period' => PricingPeriod::Yearly, 'cost' => 999.90, 'tier_id' => $tier->id, 'stripe_id' => config('subscription.elemental.brl.yearly'), ]); return $this; } } ================================================ FILE: database/seeders/TierSeeder.php ================================================ Pledge::KOBOLD]); if (! $tier->exists) { $tier->fill([ 'code' => 'kobold', 'name' => Pledge::KOBOLD, 'monthly' => 0, 'yearly' => 0, 'position' => 1, ])->save(); } $tier = Tier::firstOrNew(['name' => Pledge::OWLBEAR]); if (! $tier->exists) { $tier->fill([ 'code' => 'owlbear', 'name' => Pledge::OWLBEAR, 'monthly' => 4.99, 'yearly' => 49.90, 'position' => 2, ])->save(); } $tier = Tier::firstOrNew(['name' => Pledge::WYVERN]); if (! $tier->exists) { $tier->fill([ 'code' => 'wyvern', 'name' => Pledge::WYVERN, 'monthly' => 9.99, 'yearly' => 99.90, 'position' => 3, ])->save(); } $tier = Tier::firstOrNew(['name' => Pledge::ELEMENTAL]); if (! $tier->exists) { $tier->fill([ 'code' => 'elemental', 'name' => Pledge::ELEMENTAL, 'monthly' => 24.99, 'yearly' => 249.90, 'position' => 4, ])->save(); } } } ================================================ FILE: database/seeders/VisibilitiesTableSeeder.php ================================================ $theme, ]); if (! $type->exists) { $type->fill([ 'code' => $theme, ])->save(); $created++; } } } } ================================================ FILE: docker/mysql/create-testing-database.sh ================================================ #!/usr/bin/env bash mysql --user=root --password="$MYSQL_ROOT_PASSWORD" <<-EOSQL CREATE DATABASE IF NOT EXISTS testing; GRANT ALL PRIVILEGES ON \`testing%\`.* TO '$MYSQL_USER'@'%'; CREATE DATABASE IF NOT EXISTS logs; GRANT ALL PRIVILEGES ON \`logs%\`.* TO '$MYSQL_USER'@'%'; EOSQL ================================================ FILE: docker-compose.yml ================================================ # For more information: https://laravel.com/docs/sail services: laravel.test: build: context: ./vendor/laravel/sail/runtimes/8.4 dockerfile: Dockerfile args: WWWGROUP: '${WWWGROUP}' image: sail-8.4/app extra_hosts: - 'host.docker.internal:host-gateway' ports: - '${APP_PORT:-80}:80' - '${HMR_PORT:-8080}:8080' - '${VITE_PORT:-5173}:${VITE_PORT:-5173}' environment: WWWUSER: '${WWWUSER}' LARAVEL_SAIL: 1 XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' volumes: - '.:/var/www/html' networks: - sail depends_on: - mariadb - redis - minio - meilisearch - mailpit # - reverb mariadb: image: 'mariadb:11' ports: - '${FORWARD_DB_PORT:-3306}:3306' environment: MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' MYSQL_ROOT_HOST: '%' MYSQL_DATABASE: '${DB_DATABASE}' MYSQL_USER: '${DB_USERNAME}' MYSQL_PASSWORD: '${DB_PASSWORD}' MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' volumes: - 'sail-mariadb:/var/lib/mysql' - './.mariadb/conf.d:/etc/mysql/conf.d' - './.mariadb/10-create-logs-database.sh:/docker-entrypoint-initdb.d/10-create-logs-database.sh' - './docker/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh' networks: - sail healthcheck: test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] retries: 3 timeout: 5s redis: image: 'redis:alpine' ports: - '${FORWARD_REDIS_PORT:-6379}:6379' volumes: - 'sail-redis:/data' networks: - sail healthcheck: test: - CMD - redis-cli - ping retries: 3 timeout: 5s minio: image: 'minio/minio:RELEASE.2024-07-15T19-02-30Z' ports: - '${FORWARD_MINIO_PORT:-9000}:9000' - '${FORWARD_MINIO_CONSOLE_PORT:-8900}:8900' environment: MINIO_ROOT_USER: sail MINIO_ROOT_PASSWORD: password volumes: - 'sail-minio:/data/minio' networks: - sail command: 'minio server /data/minio --console-address ":8900"' healthcheck: test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"] retries: 3 timeout: 5s thumbor: image: 'beeyev/thumbor-s3:7.1-slim-alpine' platform: linux/amd64 tty: true ports: - '${FORWARD_THUMBOR_PORT:-8888}:8888' volumes: - 'sail-thumbor:/data' networks: - sail environment: - LOG_LEVEL=info - LOADER=thumbor_aws.loader - AWS_LOADER_REGION_NAME=local - AWS_LOADER_BUCKET_NAME=kanka - AWS_LOADER_S3_ACCESS_KEY_ID=sail - AWS_LOADER_S3_SECRET_ACCESS_KEY=password - 'AWS_LOADER_S3_ENDPOINT_URL=http://minio:9000/' - RESULT_STORAGE=thumbor_aws.result_storage - AWS_RESULT_STORAGE_BUCKET_NAME=thumbnails - AWS_RESULT_STORAGE_S3_ACCESS_KEY_ID=sail - AWS_RESULT_STORAGE_S3_SECRET_ACCESS_KEY=password - 'AWS_RESULT_STORAGE_S3_ENDPOINT_URL=http://minio:9000/' - AWS_RESULT_STORAGE_ROOT_PATH=rs - RESULT_STORAGE_STORES_UNSAFE=True - ALLOW_UNSAFE_URL=True - RESULT_STORAGE_EXPIRATION_SECONDS=2629746 - QUALITY=80 - AUTO_WEBP=True - RESPECT_ORIENTATION=True - MAX_AGE=86400 - HTTP_LOADER_VALIDATE_CERTS=False depends_on: - minio thumbor-nginx: image: 'nginx:1.23' tty: true volumes: - './.nginx:/etc/nginx/conf.d/' ports: - '8889:80' environment: - NGINX_PORT=8889 networks: - sail depends_on: - thumbor mailpit: image: axllent/mailpit ports: - '8025:8025' # Web UI - '${MAIL_PORT:-1025}:1025' # SMTP server networks: - sail meilisearch: image: 'getmeili/meilisearch:latest' ports: - '${FORWARD_MEILISEARCH_PORT:-7700}:7700' environment: MEILI_NO_ANALYTICS: '${MEILISEARCH_NO_ANALYTICS:-false}' MEILI_MASTER_KEY: '${MEILISEARCH_KEY}' volumes: - 'sail-meilisearch:/meili_data' networks: - sail healthcheck: test: ["CMD", "wget", "--no-verbose", "--spider", "http://127.0.0.1:7700/health"] retries: 3 timeout: 5s # reverb: # build: # context: ./vendor/laravel/sail/runtimes/8.4 # dockerfile: Dockerfile # args: # WWWGROUP: "${WWWGROUP}" # image: sail-8.4/app # extra_hosts: # - "host.docker.internal:host-gateway" # ports: # - "${REVERB_PORT:-8080}:8080" # command: "php artisan reverb:start" # environment: # WWWUSER: "${WWWUSER}" # LARAVEL_SAIL: 1 # XDEBUG_MODE: "${SAIL_XDEBUG_MODE:-off}" # XDEBUG_CONFIG: "${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}" # IGNITION_LOCAL_SITES_PATH: "${PWD}" # volumes: # - ".:/var/www/html" # networks: # - sail # depends_on: # - mariadb # - redis networks: sail: name: sail driver: bridge volumes: sail-mariadb: driver: local sail-redis: driver: local sail-minio: driver: local sail-thumbor: driver: local sail-thumbor-nginx: driver: local sail-meilisearch: driver: local ================================================ FILE: docs/api/readme.md ================================================ # 🤖 APIs Read our [documentation](https://docs.kanka.io/en/latest/advanced/api.html) to get a full overview and access to the API documentation. ================================================ FILE: docs/assets.md ================================================ # 🎨 Compiling assets During development, when changes to vue, js or css code are made its necessary to run `sail yarn dev` to re-compile your assets in the background and reload the page whenever something changes in the css, vue or js code. Once you're ready for prod, stop `dev` mode and run `sail yarn build`, this takes around 5-10 seconds, builds all compiled assets and deletes old asset files that are replaced, commit those changes to git. ## Deploying assets to cloudfront To deploy assets to prod, set the `ASSET_URL` env to the cloudfront URL. Now rebuild the assets > sail yarn build Next, sync the build folder to s3 > aws s3 sync public/build/ s3://kanka-user-assets/build/ --include "*" --acl public-read --delete If doing an update, first do sync without --delete. For the api docs to work, the same needs to be done for the `public/vendor/binarytorch` folder. > aws s3 sync public/vendor/binarytorch/ s3://kanka-user-assets/vendor/binarytorch/ --include "*" --acl public-read --delete For leaflet, summernote etc, the same needs to be done for the `public/js/` folder. > aws s3 sync public/vendor/leaflet/ s3://kanka-user-assets/vendor/leaflet/ --include "*" --acl public-read --delete ## Images Stuff like default thumbnails, subscriber thumbnails > aws s3 sync public/images/ s3://kanka-user-assets/images/ --include "*" --acl public-read --delete ================================================ FILE: docs/contributing.md ================================================ # ❤️ Thanks for wanting to contribute First off, thank you for your interest in helping improve Kanka! While we're a small team and can't review external code contributions or manage pull requests at this time, there are still plenty of valuable ways you can make a difference. ## 🤝 How you can help We're always looking for passionate community members to lend a hand in ways that truly matter: * **Help with user documentation**: If you're good at explaining things or spot missing information in our [documentation](https://docs.kanka.io), jump into the [Kanka Discord](http://kanka.io/go/discord) and let us know! * **Onboard new users**: Welcome new members in the [Discord](http://kanka.io/go/discord) and help answer questions. Your experience can make a big difference in someone’s journey. * **Report translation errors**: If you see typos or weird phrasing in translated content, we’d love to hear about it on [Discord](http://kanka.io/go/discord) so we can fix it. ## ⛔ No external code contributions As much as we appreciate the spirit of open-source collaboration, our team doesn't currently have the bandwidth to manage pull requests or integrate external code contributions. We're focused on building and maintaining features internally to ensure a consistent experience for all users. ================================================ FILE: docs/debugging.md ================================================ # 🔎 Debugging If you're having issues with your local instance, these two methods are your best bet. ## 🚩 Enable debug mode If you're having 500 errors any of the app's URLs, try the following. Enable debug mode by editing the `.env` file and replacing `APP_DEBUG=false` with `APP_DEBUG=true`. This will now show the error in the browser, which can give you a hint as to what went wrong. ## ❌ Check your error logs If you want access to the detailed error logs, execute the following commands. ```bash tail -f storage/logs/laravel-{date}.log ``` ================================================ FILE: docs/meilisearch.md ================================================ # ✨ Meilisearch setup Before importing entities to meilisearch is important to do some setup, first of all there are some .ENV parameters to be set. `SCOUT_DRIVER` tells scout which search engine to use, it should be set to `meilisearch`. `SCOUT_QUEUE` tells scout if it should use the jobs queue if set to true or not if set to false/not set. `MEILISEARCH_HOST` is the url of the meilisearch server, where the requests will be sent to, by default its set to: `http://localhost:7700` which is usually the route for a local test setup. `MEILISEARCH_KEY` is the key/password which authorizes read/write to the meilisearch database, information on how to set it up can be found on Meilisearch's docs: `https://www.meilisearch.com/docs/learn/security/master_api_keys`. Now we can start importing the entities. ## Importing entities To import all entities that are setup to work with meilisearch, run: > sail artisan setup:meilisearch ### Individual models To import entities from an individual model, run: > sail artisan scout:import "App\Models\ModelName" This has to be run for each model type we wish to import to the Meilisearch database, for example if we wish to import TimelineElements and Characters we would do: > sail artisan scout:import "App\Models\TimelineElements" > sail artisan scout:import "App\Models\Characters" ## Config change It's also important to run this following command the first time meilisearch is set up and whenever any of the index settings on `config/scout.php` are modified: > sail artisan scout:sync-index-settings ## Testing Call the following api endpoint with a valid token > http://api.kanka.test:8081/1.0/campaigns/xxx/fulltext-search?term=adam ================================================ FILE: docs/running.md ================================================ # 💻 Running Kanka Kanka is a dockerized self-hosted PHP application. > :warning: **Warning** > > This docker setup is meant for developers working on Kanka. **Do not use** this docker setup to host Kanka on the web! It comes with 0 security (no root password and all ports open). > > This setup works as is for our team running with Docker on Linux and macOS. We only provide limited support for helping people host Kanka locally on Discord from Monday to Friday 9am-3pm (GMT-5). We currently do not provide any paid support for helping people self-host Kanka or enabling premium features. ## Differences compared to kanka.io These developer docker instances are quite different from [kanka.io](https://kanka.io/) that we've listed below. * No security, no automatic backups. * No support for premium campaigns and subscriber bonuses. * No advanced caching, meaning that as the data grows, the app will become much slower. * No third-party setup like discord, google/meta/x (formerly twitter) logins, stripe, or analytics. * No emails or notifications. * No access to the third party tools Kanka pays for like FontAwesome PRO, meaning some icons in the app won't work. * And lastly, **limited support from the Kanka team to debug your local setup**. ## Docker Kanka is set up to run with Docker and [Laravel Sail](https://laravel.com/docs/11.x/sail). It comes with several machines. * Laravel Sail for running the Kanka PHP application * [Mariadb](https://mariadb.org/) for the database * [Redis](https://redis.com/) for the cache * [minIO](https://min.io/) for file storage * [Thumbor](https://www.thumbor.org/) for image thumbnails * [Mailpit](https://mailpit.axllent.org/) for email testing ### Prerequisite Kanka has minimal hardware requirements and can run adequately on a €4/month Hetzner virtual machine. The machine will need the following software to run Kanka: * [Docker](https://www.docker.com/) * [Github CLI](https://cli.github.com/) This GitHub repository needs to be Cloned (`git clone`) on your local machine. All commands are to be executed in the Kanka folder. When on Linux, Docker needs to run with your user and not with sudo! This is important for file permissions to properly work. To set up docker to run with your user, [follow these instructions](https://docs.docker.com/engine/install/linux-postinstall/). ## Installation ### 1. Checkout the project Checkout Kanka on your local machine ```bash gh repo clone owlchester/kanka ``` or ```bash git clone https://github.com/owlchester/kanka.git ``` ### 2. Configure the database Once the project has finished downloading, copy the `.env.example` file and save it under `.env` at the root of your new Kanka installation. This file contains all the configuration settings to run Kanka, like the database details, the project's name, options for max file upload sizes, etc. > **Hidden by default** > > Most operating systems hide files starting with a dot by default. You can either change this in your operating system's file explorer, or by accessing the files in the terminal. Optionally, if you want to change some configs, edit the new `.env` file. In most cases, you don't need to touch anything. ### 3. Installing dependencies Run the following command to install all the dependencies needed by Kanka. This command will start up a small docker to install everything through [composer](https://getcomposer.org). ```bash docker run --rm \ -u "$(id -u):$(id -g)" \ -v $(pwd):/var/www/html \ -w /var/www/html \ laravelsail/php84-composer:latest \ composer install --ignore-platform-reqs ``` #### Easier life To make your life easier, we recommend setting an alias to the `sail` command. ```bash alias sail='[ -f sail ] && bash sail || bash vendor/bin/sail' ``` Otherwise, whenever this setup mentions a sail command, replace it with `./vendor/bin/sail`. ### 4. Run docker Everything should now be ready to run all those docker images. Passing the `-d` parameter starts it in the background. ```bash sail up -d ``` ### 5. Bucket setup > Warning, we use an older version of minIO, despite minIO being abandoned and having multiple vulnerabilities. RustFS will eventually replace minIO, but it's still in Alpha and doesn't have a migration tool. Image uploading in the app is stored on a *minio* service. This mimics the amazon S3 storage, and makes it easier to handle images rather than hosting them directly in the docker responsible for PHP. First, go to [localhost:9000](http://localhost:9000). This is where your files will be stored. Login with `sail` as the user and `password` as the password. Create a new bucket called `kanka`. This will redirect you to the new `kanka` bucket. Click on the top right cogwheel icon to access the bucket's config interface. On this page, change the `Access Policy` from `private` to `public`. Without this, uploaded file won't be visible in the browser. Next up, create a second bucket called `thumbnails`. Same as before, go back and set it's `Access Policy` from `private` to `public`. This bucket will contain your thumbnails. ### 6. Run the installer Now that the bucket is set up, go back to your console run the following commands. This will take care of setting up Kanka's database with all the boilerplate content to make it work. ```bash sail artisan kanka:install ``` Lastly, set up the full-text search engine with the following line. ```bash sail artisan setup:meilisearch ``` ## Next up Your local development instance of Kanka is now ready to go! Navigate to [localhost:8081](http://localhost:8081), and you should see the Kanka application and be able to create an account. We recommend making an alias in your `/etc/hosts` file to point `kanka.test` to your localhost, so what (kanka.test:8081)[http://kanka.test:8081] also works. Here are a few extra resources: * Our [documentation](https://docs.kanka.io) covers how to use Kanka. * Having trouble? Check out the [debugging](/docs/debugging.md). * Learn how to [update](/docs/updating.md) your version of Kanka. ### Shutting down To stop the docker images, run the following command from your Kanka folder. ```bash sail down ``` ### Sharing your local Kanka Do not make your Kanka instance accessible to the web! To share your Kanka instance with your friends, use the `sail share` command. Follow the [official documentation](https://laravel.com/docs/11.x/sail#sharing-your-site), or set up a reverse proxy in front of it. ================================================ FILE: docs/specs/2026-05-07-family-tree-cytoscape-redesign.md ================================================ # Family Tree Cytoscape Redesign **Date:** 2026-05-07 **Status:** Approved ## Problem The current family tree is implemented as 9 hand-rolled Vue components that manually calculate pixel coordinates (`drawX`, `drawY`, `column`, `row`) for every node. The data structure is a deeply nested JSON tree that can only represent a single founder with partners and shared children. It cannot represent complex family graphs — e.g. a character's partner who has their own ex-spouse and children from a previous relationship. The code is full of bugs and very difficult to extend. ## Solution Replace the entire frontend with a Cytoscape.js-based graph renderer, following the same patterns established in `Web.vue` (the entity relations web). Replace the nested JSON config with a flat `{ nodes, edges }` graph format that Cytoscape consumes directly. --- ## Data Model The `family_trees.config` column changes from a nested array to a flat object: ```json { "nodes": [ { "id": "uuid-1", "entity_id": 10 }, { "id": "uuid-2", "entity_id": 20 }, { "id": "uuid-3", "entity_id": null, "isUnknown": true } ], "edges": [ { "id": "uuid-4", "source": "uuid-1", "target": "uuid-2", "type": "partner", "role": "Wife", "colour": "#cc0000", "cssClass": "", "visibility": 1 }, { "id": "uuid-5", "source": "uuid-1", "target": "uuid-6", "type": "child", "role": "", "colour": "", "cssClass": "", "visibility": 1 } ] } ``` **Edge types:** - `child` — directed, drives the dagre top-down hierarchy (parent → child) - `partner` — undirected, encouraged to stay at the same rank via `weight: 0` **Unknown people** — a node with `entity_id: null` and `isUnknown: true`, rendered as a question-mark avatar. **Node fields:** `id` (UUID), `entity_id` (int or null), `isUnknown` (bool), `cssClass` (string), `colour` (string), `visibility` (int). **Edge fields:** `id` (UUID), `source` (node UUID), `target` (node UUID), `type` (`partner|child`), `role` (string), `colour` (string), `cssClass` (string), `visibility` (int). --- ## Component Architecture **Replaces:** `FamilyTree.vue`, `FamilyNode.vue`, `FamilyEntity.vue`, `FamilyRelation.vue`, `FamilyRelations.vue`, `FamilyChildren.vue`, `ChildrenLine.vue`, `RelationLine.vue`, `FamilyParentChildrenLine.vue` (9 files) **New files:** - `resources/js/components/families/FamilyTree.vue` — main component (replaces in-place) - `resources/js/components/families/FamilyTreeModal.vue` — single modal for all edit actions The 8 supporting components are deleted entirely. All layout logic moves to Cytoscape/dagre. ### FamilyTree.vue Follows the composition API + lazy import pattern from `Web.vue`. Responsibilities: - Init Cytoscape with `cytoscape-dagre` (new dependency) and `cytoscape-panzoom` (already installed) - On mount: fetch from the API, build Cytoscape elements, run layout - **View mode:** nodes as circle avatars + name labels; hover shows tippy tooltip with full entity card; child edges as solid downward arrows; partner edges as dashed lines; edge role shown on hover - **Edit mode:** activated by toolbar "Edit" button; tap node → action panel (Add Partner / Add Child / Edit / Delete); tap edge → edit/delete modal; edge-drawing mode triggered by choosing "Add partner" or "Add child" (tap source, then tap target) - Save: POST flat `{ nodes, edges }` to existing save endpoint ### FamilyTreeModal.vue Single `

    ` handling all edit interactions: - Add/edit a node: character picker (tomselect), unknown toggle, colour, CSS class, visibility - Add/edit an edge: role label, colour, visibility, type selector (partner/child) - Reuses existing dialog patterns (`window.openDialog`, `window.closeDialog`, tomselect) --- ## Backend Changes ### FamilyTreeService **`api()` / load:** - Load **all** family members (not just top 10) and return them as staging nodes - Also load any characters referenced by `entity_id` in the saved config that are not family members (e.g. a partner from another family) — same as the current `prepareEntities()` logic - Merge both sets: nodes in the saved config are returned with their saved metadata; family members not yet in the config appear as unconnected staging nodes - Entity data format unchanged: `id, name, url, thumb, birth, death, status, tags` - Visibility filtering moves from nested relation traversal to flat edge iteration **`save()`:** - Accepts `{ nodes: [...], edges: [...] }` instead of the old nested array - UUID assignment via `prepareForSave()` — same logic, just applied to both arrays - Visibility and missing-entity cleanup operate on `edges[]` instead of recursive `relations[]` **No new routes or controller changes** — same endpoints, same auth. --- ## Layout - Library: `cytoscape-dagre` (new dependency, ~15kb gzipped) - `rankDir: 'TB'` (top-to-bottom) - `child` edges drive rank assignment - `partner` edges use `weight: 0` to encourage same-rank placement - Unconnected staging nodes (family members with no edges) shown as a row at the top of the canvas --- ## Out of Scope - Migration of existing saved trees — separate task / migration command - Drag-repositioning of nodes (auto-layout only, positions not persisted) - Non-character entities in the tree (same restriction as today) ================================================ FILE: docs/translating.md ================================================ # 💬 Translating kanka To help translate Kanka into one of the supporter languages, contact us in `#general` on the official [Kanka Discord](http://kanka.io/go/discord) server. ================================================ FILE: docs/updating.md ================================================ # 🔄 Updating We recommend always running from a release tag rather than the `main` branch. You can check which branch you are on by going `git branch` in the Kanka root folder on your machine (not in docker). When a new version of Kanka is released, from your host machine you want to do `git pull` to get the newest updates. Updates usually include changes to the database, so run the following to run the migrations: When updating your local installation, we recommend checkout out each tag chronologically in order to safely update your data. > :warning: **Warning** > Never ever checkout the `@develop` branch as it is unstable and will break your installation. ## ⚡ Backup We **strongly** recommend backing up your database data before running any upgrade. You can create a backup of your data by running the following command. Note that this backup command is only available from version 1.44 and onward. ```bash sail artisan backup:run ``` This will create a gzip file in `storage/app/{app_name}/{date}.zip` ## 🏷️ Checkout out a specific tag In your project's root folder, run the following command to checkout a specific tag, in this example version 1.42. ```bash git checkout tags/2.0 -b 2.0 ``` Then run the update instructions of version 1.42. These updates are found in the project's "Releases" on GitHub. Once that's done, checkout version 1.43 by running: ```bash git checkout tags/2.0 -b 2.0 ``` And run the update instructions of version 1.43. Repeat until you are running the latest version. ================================================ FILE: docs/websockets.md ================================================ # Websockets Some interfaces like whiteboards support collaborative real-time updates, powered by websockets. ## Running To activate websockets, simply run ``` sail artisan reverb:start ``` ================================================ FILE: lang/base/attributes/.gitkeep ================================================ ================================================ FILE: lang/base/calendars/.gitkeep ================================================ ================================================ FILE: lang/base/campaigns/.gitkeep ================================================ ================================================ FILE: lang/base/emails/.gitkeep ================================================ ================================================ FILE: lang/base/entities/.gitkeep ================================================ ================================================ FILE: lang/base/front/.gitkeep ================================================ ================================================ FILE: lang/base/maps/.gitkeep ================================================ ================================================ FILE: lang/base/readme.md ================================================ # Laravel translations https://github.com/Laravel-Lang/lang/tree/main/locales ================================================ FILE: lang/base/settings/.gitkeep ================================================ ================================================ FILE: lang/base/subscriptions/.gitkeep ================================================ ================================================ FILE: lang/base/timelines/.gitkeep ================================================ ================================================ FILE: lang/base/users/.gitkeep ================================================ ================================================ FILE: lang/de/abilities.php ================================================ [], 'children' => [ 'actions' => [ 'attach' => 'An Objekt anhängen', ], 'create' => [ 'attach_success' => '{1} Die Fähigkeit :name wurde an :count Objekt angehängt.|[2,*] Die Fähigkeit :name wurde an :count Objekte angehängt.', 'helper' => 'Anhängen von :name an ein oder mehrere Objekten.', 'title' => 'Objekte anhängen', ], 'description' => 'Objekte mit dieser Fähigkeit', 'title' => 'Fähigkeit :name Objekt', ], 'create' => [ 'title' => 'Neue Fähigkeit', ], 'destroy' => [], 'edit' => [], 'entities' => [], 'fields' => [ 'charges' => 'Ladungen', ], 'helpers' => [], 'index' => [], 'lists' => [ 'empty' => 'Füge Kräfte, Zaubersprüche oder Talente hinzu. Viele Entwickler nutzen dies, um D&D-Klassen zu modellieren.', ], 'placeholders' => [ 'charges' => 'Anzahl der Verwendungen. Attribute können mit mit {Level} * {CHA} referenziert werden.', 'name' => 'Feuerball, Alarm, listiger Schlag', 'type' => 'Zauber, Kraftakt, Attacke', ], 'reorder' => [ 'parentless' => 'kein übergepordnetes Objekt', 'success' => 'Fähigkeiten erfolgreich neu geordnet.', 'title' => 'Ordne die Fähigkeiten neu', ], 'show' => [ 'tabs' => [ 'reorder' => 'Fähigkeiten neu anordnen', ], ], ]; ================================================ FILE: lang/de/account/social.php ================================================ 'Anmeldung durch :provider', 'subtitle' => 'Wechsel von einem von :provider verwalteten Login zu einem von Kanka verwalteten, bei dem du dich mit deiner E-Mail und deinem Passwort anmeldest.', 'title' => 'Zu einem Kanka-Login wechseln', ]; ================================================ FILE: lang/de/attribute_templates.php ================================================ [], 'create' => [ 'title' => 'Erstelle eine neue Attributvorlage', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'auto_apply' => 'automatisch übernehmen', 'is_enabled' => 'Aktiviert', ], 'hints' => [ 'automatic' => 'Attribute wurden automatisch aus der Attribut-Vorlage ":link" erstellt.', 'automatic_apply' => '{1} Das folgende :count-Attribut wurde automatisch von :link übernommen | [2,] Die folgenden :count-Attribute wurden automatisch von :link übernommen.', 'entity_type' => 'Wenn diese Option aktiviert ist, wird beim Erstellen eines neuen Objekts dieses Typs diese Attributvorlage automatisch angewendet.', 'is_disabled' => 'Diese Vorlage ist deaktiviert.', 'is_enabled' => 'Aktiviere diese Vorlage zur Verwendung in der Kampagne.', 'parent_attribute_template' => 'Diese Attributvorlage kann eine übergeordnete Attributvorlage haben. Wenn man diese Vorlage anwendet, werden sie und alle übergeordneten Vorlagen angewendet.', ], 'index' => [], 'lists' => [ 'empty' => 'Erstelle Vorlagen, um gemeinsame Attribute für mehrere Objekte wiederzuverwenden.', ], 'placeholders' => [ 'name' => 'Name der Attributvorlage', ], 'show' => [], ]; ================================================ FILE: lang/de/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Fehler', 'rendering' => 'Beim Rendern des Marktplatz-Plugins ist ein Fehler aufgetreten. Bitte wenden Sie sich an den Plugin-Ersteller.', ], ], 'helpers' => [], 'list' => [ 'sheets' => 'Charakterbögen', ], 'pitch' => 'Suche und füge Charakterbögen auf dem :marketplace einer :boosted-campaign hinzu.', ]; ================================================ FILE: lang/de/auth.php ================================================ [ 'permanent' => 'Du wurdest dauerhaft gesperrt.', 'temporary' => '{1} Du wurdest gesperrt für :days day|[2,*] Du wurdest gesperrt für :days days', ], 'confirm' => [ 'confirm' => 'Bestätigung', 'error' => 'Falsche Kennwort. Bitte versuchen Sie es erneut.', 'helper' => 'Bitte bestätigen Sie Ihr Passwort, bevor Sie fortfahren können.', 'title' => 'Passwort Bestätigung', ], 'continue' => [ 'facebook' => 'Mit facebook fortfahren', 'google' => 'Mit google fortfahren', 'x' => 'Mit X fortfahren', ], 'failed' => 'Wir kennen diese Logindaten nicht.', 'helpers' => [ 'password' => 'Passwort anzeigen / verbergen', ], 'login' => [ 'fields' => [ '2fa' => 'Einmaliges Passwort', 'email' => 'Email', 'password' => 'Passwort', ], 'no-account' => 'Du hast noch kein Konto?', 'or' => 'ODER', 'password_forgotten' => 'Passwort vergessen?', 'sign-up' => 'registriere dich', 'submit' => 'Login', 'title' => 'Login', ], 'register' => [ 'already' => 'Hast du schon ein Konto? :login', 'errors' => [ 'email_already_taken' => 'Ein Account mit dieser Email ist bereits registriert.', 'general_error' => 'Beim erstellen des Accounts ist ein Fehler aufgetreten. Bitte erneut versuchen.', ], 'fields' => [ 'email' => 'Email', 'name' => 'Nutzername', 'password' => 'Passwort', ], 'log-in' => 'Log in', 'submit' => 'Registrieren', 'title' => 'Registrieren', 'tos' => 'Durch die Registrierung eines Kontos stimmst du unseren :terms und :privacy zu.', ], 'reset' => [ 'fields' => [ 'email' => 'Email Adresse', 'password' => 'Passwort', 'password_confirmation' => 'Passwort bestätigen', ], 'send' => 'Link zum zurücksetzen des Passwords verschickt', 'submit' => 'Passwort zurücksetzen', 'title' => 'Passwort zurücksetzen', ], 'tfa' => [ 'helper' => 'Die Zwei-Faktor-Authentifizierung ist aktiviert. Bitte gib das von dir Authentifizierungs-App bereitgestellte One Time Password (OTP) an.', 'title' => 'Zwei-Faktor-Authentifizierung', ], 'throttle' => 'Zu viele Anmeldeversuche. Bitte versuche es in :seconds Sekunden noch einmal.', 'x-twitter' => 'X früher bekannt als Twitter', ]; ================================================ FILE: lang/de/banners.php ================================================ 'Um 20% Ermäßigung auf Ihr erstes Jahresabonnement zu bekommen, nutzen Sie den Code :code!', ]; ================================================ FILE: lang/de/billing/information.php ================================================ [ 'update' => 'Aktualisierungsinformationen', ], 'helper' => 'Deine Geschäftsadresse, Umsatzsteuer-Identifikationsnummer usw. können auf allen deinen Belegen angegeben werden.', 'title' => 'Aktualisieren deine Rechnungsdaten', ]; ================================================ FILE: lang/de/billing/invoices.php ================================================ [ 'download' => 'Download PDF', ], 'description' => 'Rechnungen der letzten 24 Monate anzeigen.', 'empty' => 'Keine Rechnungen gefunden', 'fields' => [ 'amount' => 'Menge', 'date' => 'Datum', 'invoice' => 'Rechnung', 'status' => 'Status', ], 'paypal' => 'Bitte beachte , dass nur Zahlungen, die über Stripe und nicht über PayPal getätigt wurden, hier sichtbar sind.', 'status' => [ 'paid' => 'bezahlt', 'pending' => 'Ausstehend', ], 'title' => 'Rechnungsverlauf', ]; ================================================ FILE: lang/de/billing/menu.php ================================================ 'Rechnungsverlauf', 'overview' => 'Übersicht', 'payment-method' => 'Zahlungsmethode', ]; ================================================ FILE: lang/de/billing/payment_methods.php ================================================ 'Bezahlverfahren', 'types' => [ 'card' => 'Karte', ], ]; ================================================ FILE: lang/de/bookmarks.php ================================================ [ 'customise' => 'Passe die Seitenleiste an', ], 'create' => [ 'title' => 'Neuer Menü Link', ], 'destroy' => [], 'edit' => [ 'title' => 'Menü Link :name', ], 'fields' => [ 'active' => 'Aktiv', 'dashboard' => 'Dashboard', 'default_dashboard' => 'Standard-Dashboard', 'filters' => 'Filter', 'menu' => 'Menü', 'position' => 'Position', 'random_type' => 'zufälliger Objekttyp', 'selector' => 'Quick Link-Konfiguration', 'target' => 'Ziel', ], 'helpers' => [ 'active' => 'Inaktive Quicklinks werden nicht in der Seitenleiste angezeigt.', 'css' => 'Füge eine CSS-Klasse hinzu, die zum Link des Lesezeichens in der Seitenleiste hinzugefügt wird.', 'dashboard' => 'Lassen Sie den Quick Link auf eines der benutzerdefinierten Dashboards der Kampagne abzielen. Diese Funktion ist nur verfügbar für :boosted.', 'default_dashboard' => 'Verknüpfen Sie stattdessen mit dem Standard-Dashboard der Kampagne. Es muss noch ein benutzerdefiniertes Dashboard ausgewählt werden.', 'entity' => 'Richten Sie diesen Menülink ein, um direkt zu einem Objekt zu gelangen. Das Feld :tab steuert, welche der Registerkarten fokussiert ist. Das :menu Feld steuert, welche Unterseite des Objekts geöffnet wird.', 'position' => 'Dieses Feld kann genutzt werden um die Linkreihenfolge im Menü festzulegen.', 'random' => 'Verwenden Sie dieses Feld, um einen Schnelllink zu einem zufälligen Objekt zu erhalten. Sie können den Link filtern, um nur zu einem bestimmten Objekttyp zu gelangen.', 'selector' => 'Konfigurieren Sie, wohin dieser Quicklink führt, wenn ein Benutzer in der Seitenleiste darauf klickt.', 'type' => 'Richten Sie diesen Menülink ein, um direkt zu einer Liste von Objekten zu gelangen. Kopieren Sie zum Filtern der Ergebnisse Teile der URL in die Liste der gefilterten Objekte nach :? Melden Sie sich im Feld :filter an', ], 'index' => [], 'lists' => [ 'empty' => 'Speicher Lesezeichen für Ihre am häufigsten verwendeten Objekte oder gefilterten Listen, um schneller darauf zugreifen zu können.', ], 'placeholders' => [ 'filters' => 'location_id=15&type=city', 'menu' => 'Menü Unterseite (Nutze den letzten Text der URL)', 'tab' => 'Geschichte, Beziehungen, Notizen', ], 'random_no_entity' => 'Keine zufälligen Objekte gefunden.', 'random_types' => [ 'any' => 'jedes Objekt', ], 'reorder' => [ 'success' => 'Menü Links neu geordnet.', 'title' => 'Menü Links neu anordnen', ], 'show' => [], 'targets' => [ 'dashboard' => 'Eines der Dashboards der Kampagne', 'entity' => 'ein einzelnes Objekt', 'random' => 'ein zufälliges Objekt', 'select' => 'wähle eine Option', 'type' => 'Liste der Objekte eines bestimmten Objekttyps/Moduls', ], 'visibilities' => [ 'is_active' => <<<'TEXT' Zeige den Quicklink in der Seitenleiste an TEXT , ], ]; ================================================ FILE: lang/de/bragi.php ================================================ [ 'generate' => 'generieren', 'insert' => 'benützen', ], 'errors' => [ 'invalid-sub' => 'Um auf diese Funktion zugreifen zu können, benötigst du ein Wyvern- oder Elemental-Abonnement.', 'out-of-tokens' => 'Du hast keine Tokens mehr! Auf :date bekommst du automatisch mehr.', ], 'here' => 'hier', 'intro' => 'Hallo! Ich bin :name, eine KI, die dir dabei hilft, Hintergrundgeschichten für deine Charaktere in Kanka zu generieren. Mehr über mich erfährst du: hier.', 'kankappy' => 'Du bist heimlich ein Schüler von Kankappy.', 'loading' => 'Bitte halte durch, ich denke angestrengt nach und es kann bis zu einer Minute dauern!', 'placeholders' => [ 'prompt' => 'Gib eine Anforderung an, die in eine Hintergrundgeschichte umgewandelt wird.', ], 'token-limit' => 'Du hast derzeit :amount Tokens. Jedes Mal, wenn ich eine Hintergrundgeschichte erstelle, kostet dies einen Token, verwende sie also mit Bedacht!', ]; ================================================ FILE: lang/de/calendars/weather.php ================================================ [], 'create' => [ 'helper' => 'Füge Wetterinformationen hinzu, die im Kalender angezeigt werden sollen.', 'success' => 'Wetter hinzugefügt.', 'title' => 'Neuer Wettereffekt', ], 'destroy' => [ 'success' => 'Wetter entfernt', ], 'edit' => [ 'success' => 'Wetter aktualisiert', 'title' => 'Wetter aktualisieren', ], 'fields' => [ 'effect' => 'Effekt', 'name' => 'Name', 'precipitation' => 'Niederschlag', 'temperature' => 'Temperatur', 'weather' => 'Wetter', 'wind' => 'Wind', ], 'options' => [ 'weather' => [ 'bolt' => 'Donner', 'cloud' => 'Wolkig', 'cloud-rain' => 'Regnerisch', 'cloud-showers-heavy' => 'Starkregen', 'cloud-sun' => 'Bewölkt und sonnig', 'cloud-sun-rain' => 'Wolke, Sonne und Regen', 'meteor' => 'Meteor', 'smog' => 'Smog', 'snowflake' => 'Schnee', 'sun' => 'Sonnig', 'wind' => 'Windig', ], ], 'placeholders' => [ 'effect' => 'Magische oder natürliche Wirkung', 'name' => 'Optionaler benutzerdefinierter Wettertext', 'precipitation' => 'Wassermenge', 'temperature' => 'Täglich hoch und niedrig', 'wind' => 'Windgeschwindigkeit', ], ]; ================================================ FILE: lang/de/calendars.php ================================================ [ 'add_epoch' => 'Epoche hinzufügen', 'add_intercalary' => 'Schalttage hinzufügen', 'add_month' => 'Monat hinzufügen', 'add_moon' => 'Mond hinzufügen', 'add_reminder' => 'Ereignis hinzufügen', 'add_season' => 'Jahreszeit hinzufügen', 'add_weather' => 'Wettereffekt einstellen', 'add_week' => 'Wochennamen hinzufügen', 'add_weekday' => 'Wochentag hinzufügen', 'add_year' => 'Jahr hinzufügen', 'set_today' => 'Als aktuellen Tag festlegen.', 'today' => 'Heute', 'update_weather' => 'Wetter aktualisieren', ], 'checkboxes' => [ 'is_recurring' => 'Wiederholt sich jedes Jahr', ], 'create' => [ 'title' => 'Neuer Kalender', ], 'destroy' => [], 'edit' => [ 'today' => 'Kalenderdatum aktualisiert.', ], 'event' => [ 'create' => [ 'success' => 'Kalender Ereignis wurde erstellt', 'title' => 'Kalender Ereignis zu :name hinzufügen', ], 'destroy' => 'Ereignis aus Kalender :name entfernt', 'edit' => [ 'success' => 'Kalender Ereignis wurde aktualisiert', 'title' => 'Aktualisiere das Kalender Ereignis in :name', ], 'errors' => [ 'invalid_entity' => 'Ungültige Objektauswahl', ], 'helpers' => [ 'other_calendar' => 'Sie bearbeiten eine Erinnerung, die sich im :calender befindet.', ], 'success' => 'Event \':event\' zum Kalender hinzugefügt.', ], 'events' => [ 'bulks' => [ 'delete' => '{1} Gelöschte :count reminder.|[2,*] Gelöschte :count reminders.', 'patch' => '{1} Aktualisierte :count reminder.|[2,*] Aktualisierte :count reminders.', ], 'end' => '(ende)', 'filters' => [ 'show_after' => 'Zeige heute und die Zukunft', 'show_all' => 'Zeige alles', 'show_before' => 'Zeige die Vergangenheit', ], 'start' => '(start)', ], 'fields' => [ 'comment' => 'Kommentar', 'current_day' => 'Aktueller Tag', 'current_month' => 'Aktueller Monat', 'current_year' => 'Aktuelles Jahr', 'date' => 'Aktuelles Datum', 'day' => 'Tag', 'default_layout' => 'Standardlayout', 'format' => 'Format', 'is_incrementing' => 'Vorausdatierung', 'is_recurring' => 'Wiederkehrend', 'leap_year' => 'Schaltjahre', 'leap_year_amount' => 'Tage hinzufügen', 'leap_year_month' => 'Monat', 'leap_year_offset' => 'Jedes', 'leap_year_start' => 'Schaltjahr', 'length' => 'Länge des Ereignisses', 'length_days' => ':count Tag|:count Tage', 'month' => 'Monat', 'months' => 'Monate', 'moons' => 'Monde', 'parameters' => 'Parameter', 'recurring_until' => 'Wiederholt sich bis zum Jahr', 'reset' => 'Wöchentliches zurücksetzen', 'seasons' => 'Jahreszeiten', 'show_birthdays' => 'Zeige Geburtstage', 'skip_year_zero' => 'Jahr Null überspringen', 'start_offset' => 'Startdatum', 'suffix' => 'Suffix', 'week_names' => 'Wochennamen', 'weekdays' => 'Wochentage', 'year' => 'Jahr', ], 'helpers' => [ 'default_layout' => 'Wählen Sie aus, welches Layout der Kalender standardmäßig verwenden soll, wenn er angezeigt wird.', 'format' => 'Füge benutzerdefinierte Datumsformatierungen für Kalenderelemente hinzu.', 'month_type' => 'Schaltmonate benutzen keine Wochentage, aber beeinflussen trotzdem Monde und Jahreszeiten.', 'moon_offset' => 'Standardmäßig erscheint der erste Vollmond am ersten Tag des Jahres 0. Eine Verschiebung des Offsets , wird nach dem ersten Vollmond angezeigt. Dieser Wert kann negativ (bis zur Länge des ersten Monats) oder positiv (bis zur Länge des ersten Monats) sein.', 'start_offset' => 'Standardmäßig startet der Kalender am ersten Wochentag des Jahres 0. Das Ändern dieses Felds beeinflusst, wo der erste Tag des Kalenders platziert wird.', ], 'hints' => [ 'event_length' => 'Wie lange ein Ereignis dauern soll. Ein Ereignis kann nicht länger als zwei Monate dauern.', 'is_incrementing' => 'Wenn aktiviert, wird das aktuelle Datum des Kalenders automatisch um 00:00 UTC erhöht', 'leap_year' => 'Richte Schaltjahre für den Kalender ein.', 'months' => 'Dein Kalender sollte mindestens 2 Monate haben.', 'moons' => 'Hinzugefügte Monde werden bei jedem Vollmond im Kalender angezeigt.', 'parent_calendar' => 'Wenn Sie dem Kalender einen übergeordneten Kalender geben, werden die Erinnerungen und Wettereffekte des übergeordneten Kalenders angezeigt.', 'reset' => 'Beginnen Sie den Anfang des Monats oder Jahres immer am ersten Wochentag.', 'seasons' => 'Erstelle Jahreszeiten in dem du den jeweiligen Start festlegst. Kanka übernimmt den Rest.', 'show_birthdays' => 'Zeige die jährlichen Geburtstage von Charakteren an, für die in diesem Kalender eine Geburtstagserinnerung bis zu ihrem Sterbedatum vorhanden ist.', 'skip_year_zero' => 'Standardmäßig ist das erste Jahr des Kalenders das Jahr Null. Aktiviere diese Option, um das Jahr Null zu überspringen.', 'weekdays' => 'Lege die Namen deiner Wochentage fest. Es werden mindestens 2 Wochentage benötigt.', 'weeks' => 'Definieren Sie einige Namen für die wichtigeren Wochen Ihres Kalenders.', 'years' => 'Manche Jahre sind so wichtig, dass sie ihren eigenen Namen haben.', ], 'index' => [], 'layouts' => [ 'month' => 'Monat', 'monthly' => 'standardmäßig monatlich', 'year' => 'Jahr', 'yearly' => 'standardmäßig jährlich', ], 'lists' => [ 'empty' => 'Erstelle einen Kalender, um Termine, Festivals oder Ereignisse im Spiel im Laufe der Zeit zu verfolgen.', ], 'modals' => [ 'switcher' => [ 'title' => 'Jahreswechsel', ], ], 'month_types' => [ 'intercalary' => 'Schaltmonat', 'standard' => 'Standard', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Vollmond', 'fullmoon_name' => ':moon Vollmond', 'month' => 'Monatlich', 'newmoon' => 'Neumond', 'newmoon_name' => ':moon Neumond', 'none' => 'Keiner', 'unnamed_moon' => 'Mond :number', 'year' => 'Jährlich', ], ], 'resets' => [ '' => 'Keiner', 'month' => 'Monatlich', 'year' => 'Jährlich', ], ], 'panels' => [ 'intercalary' => 'Schalttage', 'leap_year' => 'Schaltjahr', 'months' => 'Monatlich', 'weeks' => 'Wöchentlich', 'years' => 'Benamte Jahre', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Dauer in Tagen', 'month' => 'Am Ende welchen Monats', 'name' => 'Name des Schaltmonats', ], 'month' => [ 'alias' => 'Monat Alias', 'length'=> 'Anzahl der Tage', 'name' => 'Monatsname', 'type' => 'Typ', ], 'moon' => [ 'fullmoon' => 'Vollmond alle (Tage)', 'name' => 'Mond Name', 'offset' => 'Tag des ersten Vollmonds', ], 'seasons' => [ 'day' => 'Starttag', 'month' => 'Startmonat', 'name' => 'Jahreszeitname', ], 'weeks' => [ 'name' => 'Wochenname', 'number' => 'Nummer', ], 'year' => [ 'name' => 'Name', 'number' => 'Jahr', ], ], 'placeholders' => [ 'colour' => 'Farbe', 'comment' => 'Geburtstag, Volksfest, Sonnenwende', 'date' => 'Das aktuelle Datum', 'leap_year_amount' => 'Anzahl der Tage, die bei einem Schaltjahr hinzugefügt werden', 'leap_year_month' => 'Monat, in dem die Tage hinzugefügt werden', 'leap_year_offset' => 'Alle wieviele Jahre ist Schaltjahr', 'leap_year_start' => 'Erstes Jahr, das ein Schaltjahr ist', 'length' => 'Ereignislänge in Tagen', 'months' => 'Anzahl der Monate in einem Jahr', 'recurring_until' => 'Letztes Jahr der Wiederholung (leer lassen für immer wiederholend)', 'seasons' => 'Anzahl der Jahrezeiten', 'suffix' => 'Aktueller Suffix der Ära (v. Chr., n. Chr.)', 'type' => 'Art des Kalenders', 'weekdays' => 'Anzahl der Tage in einer Woche', ], 'show' => [ 'missing_details' => 'Dieser Kalender konnte nicht angezeigt werden. Kalender brauchen mindestens 2 Monate und 2 Wochentage um korrekt generiert zu werden.', 'moon_1first_quarter' => ':moon erstes Viertel', 'moon_full' => ':moon Vollmond', 'moon_last_quarter' => ':moon abnehmend', 'moon_new' => ':moon Neumond', 'tabs' => [ 'events' => 'Kalender Events', 'weather' => 'Wetter', ], ], 'sorters' => [ 'after' => 'Heute & danach', 'before'=> 'Heute & davor', ], 'validators' => [ 'format' => 'Das Datumsformat ist ungültig.', 'moon_offset' => 'Der Versatz zum ersten Vollmond des Mondes kann nicht größer sein als die Länge des ersten Monats des Kalenders.', ], 'warnings' => [ 'event_length' => 'Erinnerungen, die sich über mehrere Jahre erstrecken, sind nur in den ersten beiden Jahren sichtbar. Erfahre mehr in unserer :documentation.', ], ]; ================================================ FILE: lang/de/callouts.php ================================================ [ 'subscription' => 'Erfahre mehr über Abonnements', ], 'booster' => [ 'actions' => [ 'boost' => 'Boost :campaign', 'superboost' => 'Superboost :campaign', ], 'learn-more' => 'Was sind Booster?', 'limitation' => 'Um auf diese Funktion zugreifen zu können, muss die Kampagne geboostet werden.', 'limitations' => [ 'boosted' => 'Um auf diese Funktion zugreifen zu können, muss die Kampagne geboostet werden.', 'superboosted' => 'Um auf diese Funktion zugreifen zu können, muss die Kampagne supergeboostet werden.', ], 'multiple' => 'Um auf diese Funktionen zugreifen zu können, muss die Kampagne geboostet werden.', 'pitches' => [ 'element-class' => 'Gib diesem Element eine benutzerdefinierte CSS-Klasse mit einer :boosted-campaign.', 'icon' => 'Schalte mit einer :boosted-campaign Millionen von benutzerdefinierten Symbolen von FontAwesome frei.', ], 'titles' => [ 'boosted' => 'geboostete Funktion', 'superboosted' => 'supergeboostete Funktion', ], ], 'premium' => [ 'learn-more' => 'Was sind Premium Kampangen?', 'limitation' => 'Um auf diese Funktion zugreifen zu können, müssen Premiumfunktionen aktiviert sein.', 'multiple' => 'Um auf diese Funktionen zugreifen zu können, müssen die Premiumfunktionen für :campaign aktiviert werden.', 'title' => 'Premium-Kampagnenfunktion', 'unlock' => 'Schalte Premium-Funktionen für :campaign frei', ], 'subscribe' => [ 'pitch-image' => 'Abonniere, um bis zu :max MB Datei-Upload-Größen freizuschalten.', 'share-booster' => 'Boost :campaign, um die Größe des Datei-Uploads für alle Mitglieder der Kampagne zu erhöhen.', 'share-premium' => 'Erhöhe die Datei-Upload-Größe für alle Mitglieder der Kampagne mit einer Premium-Kampagne.', ], ]; ================================================ FILE: lang/de/campaigns/achievements.php ================================================ 'Herzlichen Glückwunsch!', 'connections' => '{0} Keine Verbindungen erstellt|{1} Eine Verbindung erstellt|[2,*] :amount der erstellten Verbindungen', 'created' => '{0} Kein :plural erstellt|{1} Ein :singular erstellt|[2,*] :amount:Plural erstellt', 'dead' => '{0} Keine Krimis|{1} Ein Mörderkrimi|[2,*] :amountMörderkrimis', 'goal' => 'Ziel:number', 'goal_reached' => 'In der Kampagne wurde die folgende Leistung freigeschaltet:', 'level' => 'Stufe :number', 'markers' => '{0} Keine Kartenmarkierungen erstellt|{1} Eine Kartenmarkierung erstellt|[2,*] :amountder erstellten Kartenmarkierungen', 'painter' => '{0} Keine Themen erstellt|{1} Ein Thema erstellt|[2,*] :amount der erstellten Themen', 'pitch' => 'Kampagnenerfolge sind eine unterhaltsame Art und Weise, Meilensteine auf deiner Reise durch die Welt zu feiern. Verfolge den Fortschritt, binde deine Spieler ein und präsentiere deine Errungenschaften mit einer Premium-Kampagne.', 'plugins' => '{0} Keine Plugins installiert|{1} Ein Plugin installiert|[2,*] :amountder installierten Plugins', 'remaining' => [ 'generic' => 'Mehr und das nächste Level wird freigeschaltet.', ], 'tagged' => '{0} Keine Objekte getaggt|{1} Ein getaggtes Objekt|[2,*] :amount getaggte Objekte', 'titles' => [ 'calendars' => 'Zeitwächter', 'characters' => 'Namensgeber', 'connections' => 'Amor', 'creatures' => 'Züchter', 'dead' => 'Mörder', 'events' => 'Lehrmeister', 'families' => 'Familienplanung', 'locations' => 'Ersteller', 'markers' => 'Kartograph', 'organisations' => 'Fusionen und Übernahmen', 'plugins' => 'Plugin Kenner', 'quests' => 'Vordenker', 'tags' => 'Unter Kontrolle', 'themes' => 'Maler', ], ]; ================================================ FILE: lang/de/campaigns/applications.php ================================================ [ 'accept' => 'akzeptieren', 'reject' => 'ablehnen', ], 'apply' => [ 'apply' => 'anwenden', 'help' => 'Diese Kampagne steht neuen Mitgliedern offen. Um sich anzumelden, füllen Sie das Formular aus. Sie werden benachrichtigt, wenn die Kampagnenadministratoren Ihre Bewerbung überprüfen.', 'remove_text' => 'Ihre Einreichung', 'success' => [ 'apply' => 'Ihre Bewerbung wurde gespeichert. Sie können sie jederzeit ändern oder abbrechen. Sie werden benachrichtigt, wenn die Kampagnenadministratoren dies überprüfen.', 'remove'=> 'Ihre Bewerbung wurde entfernt.', 'update'=> 'Ihre Bewerbung wurde aktualisiert. Sie können sie jederzeit ändern oder abbrechen. Sie werden benachrichtigt, wenn die Kampagnenadministratoren dies überprüfen.', ], 'title' => 'beitreten :name', ], 'errors' => [], 'fields' => [ 'application' => 'Bewerbung', 'reason' => 'Grund der Genehmigung / Ablehnung', ], 'helpers' => [ 'modal' => 'Bei einer Kampagne, die für Bewerbungen offen und öffentlich ist, können sich Benutzer für die Teilnahme an der Kampagne bewerben.', 'no_applications_title' => 'Keine Anwendungen gefunden', 'reason' => 'Wenn dies der Fall ist, wird der Bewerber mit dieser Begründung benachrichtigt.', 'role' => 'Bei Genehmigung die Rolle, welcher der Bewerber hinzugefügt wird.', ], 'open' => [ 'closed' => 'Kamagpene ist geschlossen', 'open' => 'Kampagne ist offen', 'title' => 'Offene Kampagne', ], 'placeholders' => [ 'note' => 'Notieren Sie Ihre Bewerbung für die Teilnahme an der Kampagne', 'reason' => 'dein Grund', ], 'public' => [ 'private' => 'Kampagne ist privat', 'public' => 'Kampagne ist öffentlich', 'title' => 'öffentliche Kampagne', ], 'statuses' => [], 'title' => 'Beitrittsanfragen', 'toggle' => [ 'closed' => 'Anwendung geschlossen', 'label' => 'Status', 'open' => 'Anwendung offen', 'success' => 'Anwendungsstatus der Kampagne aktualisiert.', 'title' => 'Anwendungsstatus', ], 'tutorial' => 'Über Kampagnenanmeldungen können Personen Zugang zu dieser Kampagne beantragen. Die Antragsteller füllen ein kurzes Formular aus, und die Kampagnenadministratoren können jede Anfrage prüfen, annehmen oder ablehnen. Genehmigte Benutzer werden der Kampagne mit der Rolle hinzugefügt, die du ihnen bei der Prüfung zuweist.', 'update' => [ 'approve' => 'Wählen Sie die Rolle aus, die dem Benutzer in Ihrer Kampagne hinzugefügt werden soll.', 'approved' => 'Bewerbung genehmigt', 'reject' => 'Schreiben Sie dem Benutzer eine optionale Nachricht, warum Sie seine Bewerbung ablehnen.', 'rejected' => 'Bewerbung abgelehnt', ], ]; ================================================ FILE: lang/de/campaigns/builder.php ================================================ 'Erstelle mit dieser Schnittstelle visuell ein Thema für die Kampagne. Scrolle nach unten, um zu sehen, wie sich die Änderungen auf verschiedene Elemente der Kampagne auswirken würden. Wenn eine Farbe ausgewählt wird, wird automatisch eine „Kontrastfarbe“ zum Einfärben von Text ausgewählt. Erfahre mehr über Theming in unseren :docs.', 'pitch' => 'Psst, wir haben einen Theme-Builder, wenn du nur einige der Farben der Kampagne ändern möchtest 😉', 'pitch-go' => 'Mach mich zum Theme-Builder', 'reset' => 'Theme-Builder-Stil zurückgesetzt.', 'success' => 'Theme-Builder-Stil gespeichert.', 'title' => 'Theme-Builder', ]; ================================================ FILE: lang/de/campaigns/dashboard-header.php ================================================ [ 'success' => 'Kampagnen-Dashboard-Header aktualisiert.', 'title' => 'Aktualisierung des Kampagnen-Dashboard-Headers', ], ]; ================================================ FILE: lang/de/campaigns/default-images.php ================================================ [ 'add' => 'Fügen Sie ein neues Standardobjektbild hinzu', ], 'call-to-action' => 'Lade ein benutzerdefiniertes Thumbnail für alle Charaktere, Orte oder andere Elemente der Kampagne hoch. Diese Bilder werden dann in verschiedenen Listen angezeigt.', 'create' => [ 'error' => 'Fehler beim Speichern des neuen Standardobjektbild. :type bereits gesetzt?', 'success' => 'Standardobjektbild für :type erstellt', 'title' => 'neues Standardobjektbild', ], 'destroy' => [ 'success' => 'Standardobjektbild für :type entfernt', ], 'index' => [], ]; ================================================ FILE: lang/de/campaigns/delete.php ================================================ 'backup', 'confirm' => 'Wenn du sicher bist, dass du :campaign endgültig löschen willst, schreibe :code in das Feld unten.', 'confirm-button' => 'Dauerhaft löschen :name', 'helper' => 'Das Löschen einer Kampagne ist eine dauerhafte Aktion, die nicht rückgängig gemacht werden kann. Dabei werden alle mit der Kampagne verbundenen Daten von unseren Servern entfernt, einschließlich der Bilder und Assets. Wir empfehlen, ein :backup zu erstellen, bevor du fortfährst.', 'issue' => 'Das folgende Problem muss behoben werden, bevor die Kampagne gelöscht werden kann.', 'members' => 'Alle anderen Mitglieder müssen aus der Kampagne entfernt werden.', 'success' => ':name wurde endgültig gelöscht.', 'title' => 'Löschung', ]; ================================================ FILE: lang/de/campaigns/export.php ================================================ [ 'download' => 'Herunterladen', 'export' => 'Exportieren Sie die Kampagnendaten', ], 'confirm' => [ 'notification' => 'Mitglieder mit der Rolle :admin werden benachrichtigt, wenn der Export zum Download bereitsteht.', 'title' => 'Exportbestätigung', 'warning' => 'Du bist dabei, die Daten der Kampagne zu exportieren. Dieser Vorgang kann je nach Größe der Kampagne lange dauern. Du kannst Kanka weiterhin verwenden, während unsere Server den Export generieren.', ], 'errors' => [ 'limit' => 'Die Kampagne wurde heute schon einmal exportiert. Bitte versuchen Sie es morgen erneut.', ], 'expired' => 'Link abgelaufen', 'helpers' => [], 'progress' => 'Fortschritt', 'size' => 'Größe', 'status' => [ 'failed' => 'fehlgeschlagen', 'finished' => 'fertig', 'running' => 'läuft', 'scheduled' => 'geplant', ], 'success' => 'Der Kampagnenexport wird vorbereitet. Sie werden in Kanka benachrichtigt, sobald es zum Herunterladen bereit ist.', 'title' => 'Kampagnen-Export', ]; ================================================ FILE: lang/de/campaigns/gallery.php ================================================ [ 'close' => 'Schließen', 'file-link' => 'Dateilink', 'focus_point' => 'Fokuspunkt festlegen', 'image-link' => 'Bildlink', 'reset_focus' => 'Fokuspunkt zurücksetzen', 'save' => 'Speichern', 'upgrade' => 'Speicherplatz erweitern', ], 'breadcrumb' => 'Gallerie', 'bulk' => [ 'destroy' => [ 'confirm' => 'Bist du sicher, dass du die ausgewählten Elemente dauerhaft entfernen möchtest? Diese Aktion kann nicht rückgängig gemacht werden.', 'success' => '{0}Keine Dateien entfernt.|{1}Eine Datei entfernt.|{2,*} :Anzahl der entfernten Dateien.', ], ], 'cta' => 'Verwalte und verwende Bilder während der gesamten Kampagne wieder.', 'destroy' => [ 'folder' => 'Ordner :name gelöscht.', 'success' => 'Bild :name gelöscht', ], 'errors' => [ 'max' => 'Bitte wähle nur bis zu :count Dateien gleichzeitig aus.', 'permissions' => 'Deinen Kampagnenrollen fehlen die Berechtigungen :permission, um Bilder in die Kampagnengalerie hochladen zu dürfen.', 'storage' => 'Es ist nicht genügend Speicherplatz zum Hochladen der ausgewählten Bilder vorhanden. Verfügbarer Speicherplatz: :available.', ], 'fields' => [ 'created_by' => 'hochgeladen von', 'details' => 'Details', 'ext' => 'äußerlich', 'file_type' => 'Dateityp', 'folder' => 'Ordner', 'image_mentioned_in' => '{0} Dieses Bild wird in keinem Objekt der Kampagne erwähnt.|{1} In einem Eintrag/Beitrag erwähnt.|[2,*] erwähnt in :count Einträgen/Beiträgen.', 'image_used_in' => '{0} Wird als Bild in keines Object verwendet.|{1} Wird als Bild eines Objekts verwendet.|[2,*] Wird als Bild von :count entities verwendet.', 'link' => 'Link', 'name' => 'Name', 'size' => 'Größe', 'unused' => 'Nirgendwo verwendet', 'used_in' => 'Verwendet in', ], 'focus' => [ 'locked' => 'Eine Premium-Kampagne ist erforderlich, um den Fokuspunkt eines Bildes zu setzen.', 'removed' => 'Bildfokus entfernt.', 'updated' => 'Bildfokus aktualisiert.', ], 'new_folder' => [ 'title' => 'neuer Ordner', ], 'no_folder' => 'kein Ordner', 'pitch' => 'Lade Bilder direkt aus dem Texteditor in die Galerie der Kampagne hoch.', 'placeholders' => [ 'search' => 'Bildname suchen ...', ], 'storage' => [ 'of' => 'von', 'title' => 'Speicher', ], 'title' => 'Kampagne :campaign Gallerie', 'update' => [ 'folder' => 'Ordner geändert.', 'success' => 'Bild geändert', ], 'uploader' => [ 'add' => 'neue hinzufügen', 'new_folder' => 'neuer Ordner', 'or' => 'oder', 'select_file' => 'Wählen Sie eine Datei aus', 'well' => 'Datei fallen lassen um sie hochzuladen', ], ]; ================================================ FILE: lang/de/campaigns/import.php ================================================ [ 'import' => 'Lade den Export hoch', ], 'fields' => [ 'updated' => 'Letztes Update', ], 'form' => 'Formular hochladen', 'progress' => [ 'uploading' => 'Hochladen', ], 'status' => [ 'failed' => 'Fehlgeschlagen', 'finished' => 'Beendet', 'queued' => 'In Warteschlange', 'running' => 'Laufend', ], 'title' => 'Importieren', ]; ================================================ FILE: lang/de/campaigns/invites.php ================================================ [ 'helper' => 'Erstelle einen Einladungslink, den du an deine Spieler senden kannst, damit diese der Kampagne beitreten können.', ], ]; ================================================ FILE: lang/de/campaigns/limits.php ================================================ 'Limit erreicht', ]; ================================================ FILE: lang/de/campaigns/members.php ================================================ [ 'limited' => ':amount der :total members.', 'title' => 'Verfügbare Mitglieder', 'unlimited' => ':amount der unbegrenzten Mitglieder.', ], 'roles' => [ 'admin' => 'Du kannst hier keine Benutzer direkt zur Rolle „:admin“ hinzufügen. Dies geschieht in der Benutzeroberfläche der Rolle „:admin“.', 'helper' => 'Hinzufügen oder Entfernen von Rollen des Mitglieds :user.', 'success' => 'Rollen erfolgreich aktualisiert für :user.', 'title' => 'Mitgliederrollen bearbeiten', ], ]; ================================================ FILE: lang/de/campaigns/modules.php ================================================ [ 'create' => 'Modul erstellen', 'customise' => 'Anpassen', ], 'create' => [ 'helper' => 'Erstelle ein neues benutzerdefiniertes Modul, um Objekte zu speichern, die nicht in andere Module passen.', 'success' => 'Neues Modul erstellt.', 'title' => 'Neues Modul', ], 'delete' => [ 'confirm' => 'Schreibe :code, wenn du sicher sind, dass du das benutzerdefinierte Modul :name dauerhaft löschen willst.', 'helper' => 'Bist du sicher, dass du das benutzerdefinierte Modul :name entfernen möchtest? Dadurch werden auch alle Objekte, Lesezeichen und Widgets, die mit diesem Modul verknüpft sind, dauerhaft gelöscht.', 'success' => 'Modul :name gelöscht', 'title' => 'Modul löschen', ], 'errors' => [ 'disabled' => 'Das Modul :name ist deaktiviert. :fix', 'limit' => 'Kampagnen sind derzeit nur auf :max benutzerdefinierte Module beschränkt, während wir diese neue Funktion ausbauen.', ], 'fields' => [ 'icon' => 'Modulsymbol', 'plural' => 'Modul Mehrzahl Name', 'singular' => 'Modul Einzal Name', ], 'helpers' => [ 'custom' => 'Dies ist ein benutzerdefiniertes Modul.', 'icon' => 'Das Symbol :fontawesome, zum Beispiel :example.', 'plural' => 'Der Pluralname für Objekte des neuen Moduls. Zum Beispiel, Tränke', 'roles' => 'Wähle die Rollen aus, die die Berechtigung haben sollen, Objekte dieses neuen Moduls anzuzeigen. Dies kann später in den Rollenberechtigungen geändert werden.', 'singular' => 'Der Einzahlname für ein Objekt des neuen Moduls. Zum Beispiel, Trank', ], 'pitch' => 'Benenne das diesem Modul zugeordnete Symbol für die gesamte Kampagne um und ändere es.', 'pitch-custom' => 'Erstelle benutzerdefinierte Module, um einzigartige Objekte zu speichern.', 'rename' => [ 'helper' => 'Ändere den Namen und das Symbol des Moduls im Laufe der Kampagne. Lasse das Feld leer, um die Standardeinstellung von Kanka zu verwenden.', 'success' => 'Modul angepasst', 'title' => 'Passe das Modul :module an', ], 'reset' => [ 'default' => 'Dadurch werden nur die Standardmodule zurückgesetzt, nicht aber die benutzerdefinierten Module.', 'success' => 'Die Kampagnenmodule wurden zurückgesetzt.', 'title' => 'Benutzerdefinierte Modulnamen und -symbole zurücksetzen', 'warning' => 'Bist du sicher, dass du die Kampagnenmodule auf ihre ursprünglichen Namen und Symbole zurücksetzen möchtest?', ], 'sections' => [ 'custom' => 'Benutzerdefinierte Module', 'default' => 'Standard-Module', 'features' => 'Funktionen', ], 'states' => [ 'disable' => 'Deaktivieren', 'enable' => 'Aktivieren', ], ]; ================================================ FILE: lang/de/campaigns/overview.php ================================================ [ 'title' => 'Anhänger', ], 'member' => [ 'title' => 'Mitgliedschaft', ], 'premium' => [ 'enable' => 'Aktivieren von Premiumfunktionen', ], 'status' => [ 'title' => 'Sichtbarkeit', ], ]; ================================================ FILE: lang/de/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'deaktiviere plugins', 'enable' => 'aktiviere plugins', 'update' => 'aktualisiere plugins', ], 'changelog' => 'Änderungsprotokoll', 'disable' => 'Plugin deaktivieren', 'enable' => 'Plugin aktivieren', 'import' => 'Importieren', 'update' => 'Plugin aktualisieren', 'update_available' => 'Update verfügbar!', ], 'bulks' => [ 'delete' => '{1} entferne :count plugin.|[2,*] entferte :count plugins', 'disable' => '{1} deaktiviere :count plugin.|[2,*] deaktivierte :count plugins.', 'enable' => '{1} aktiviere :count plugin.|[2,*] aktivierte :count plugins.', 'update' => '{1} aktualisiere :count plugin.|[2,*] aktualisierte :count plugins.', ], 'destroy' => [ 'success' => 'Plugin :plugin entfernt.', ], 'disabled' => [ 'success' => 'Plugin :plugin deaktiviert', ], 'empty_list' => 'Die Kampagne hat derzeit keine Plugins. Gehen Sie zum Marktplatz, um einige zu installieren, und kehren Sie zurück, um sie zu aktivieren.', 'enabled' => [ 'success' => 'Plugin :plugin aktiviert', ], 'errors' => [ 'invalid_plugin' => 'Ungültiges Plugin.', ], 'fields' => [ 'name' => 'Plugin Name', 'obsolete' => 'Dieses Plugin wurde vom Kanka-Team als veraltet markiert, was bedeutet, dass es nicht mehr so funktioniert, wie es ursprünglich von seinem Ersteller beabsichtigt war.', 'status' => 'Status', 'type' => 'Plugin Typ', ], 'import' => [ 'button' => 'Importieren', 'created' => 'Folgende Objekte wurden erstellt:', 'helper' => 'Sie sind im Begriff, :count Objekte aus dem :plugin -Plugin zu importieren. Wenn dieses Plugin zuvor importiert wurde, können Änderungen, die Sie an den importierten Objekten vorgenommen haben, verloren gehen.', 'no_new_entities' => 'Es müssen keine neuen Objekte importiert werden.', 'option_only_import' => 'Importieren Sie nur neue Objekte und überspringen Sie zuvor importierte Objekte.', 'option_private' => 'Importieren Sie alle Objekte als privat.', 'success' => '{1} Importiert :count Objekt aus dem Plugin :plugin. |[2,*] Importiert :count Objekte aus dem Plugin zählen :plugin.', 'title' => 'Importieren :plugin', 'updated' => 'Folgende Objekte wurden aktualisiert:', ], 'info' => [ 'helper' => 'Wenn eine neue Version eines Plugins veröffentlicht wird, können Sie es auf die neueste Version für Ihre Kampagne aktualisieren.', 'title' => 'Plugin :plugin Aktualisierungen', 'updates' => 'Aktualisierungen', ], 'pitch' => 'Installiere und verwalte Plug-ins vom :marketplace.', 'status' => [ 'always' => 'Dieser Plugin-Typ ist immer aktiv, es sei denn, er wird entfernt.', 'disabled' => 'Deaktiviert', 'enabled' => 'Aktviert', ], 'templates' => [ 'name' => ':name von :user', ], 'title' => 'Kampagne :name Plugin', 'types' => [ 'attribute' => 'Attributvorlage', 'pack' => 'Inhaltspaket', 'theme' => 'Thema', ], 'update' => [ 'success' => 'Plugin :plugin aktualisiert.', ], ]; ================================================ FILE: lang/de/campaigns/public.php ================================================ [ 'new' => 'Veröffentliche es für die Community oder behalte es privat für eingeladene Mitglieder.', 'permissions' => 'Wenn du deine Kampagne öffentlich machst, werden Inhalte nicht automatisch geteilt. Lege in den Einstellungen für die Rolle „öffentlich“ fest, was öffentliche Betrachter sehen können.', ], 'title' => 'Ändere die Sichtbarkeit der Kampagne', 'update' => [ 'private' => 'Die Kampagne ist jetzt privat und nur für Mitglieder sichtbar.', 'public' => 'Die Kampagne ist nun öffentlich. Es kann einige Zeit dauern, bis sie auf der Seite :public-campaigns angezeigt wird.', 'unlisted' => 'Die Kampagne ist jetzt nicht mehr gelistet. Jeder, der über einen Link verfügt, kann darauf zugreifen, aber sie wird nicht auf der Seite „Öffentliche Kampagnen” angezeigt.', ], ]; ================================================ FILE: lang/de/campaigns/recovery.php ================================================ [ 'recover' => 'Wiederherstellen', 'recover_selected' => 'Ausgewählte wiederherstellen', ], 'error' => 'Beim Versuch Objekte wiederherzustellen, ist ein Fehler aufgetreten.', 'fields' => [ 'deleted' => 'Gelöscht', 'deleted_at' => 'Gelöscht :date von :user', ], 'name_link' => ':name wurde erfolgreich wiederhergestellt', 'order' => [ 'newest' => 'Ordnen nach :newest', 'newest_first' => 'Neuestes zuerst', 'oldest' => 'Ordnen nach :oldest', 'oldest_first' => 'Älteste zuerst', 'type' => 'Ordnen nach :type', 'type_order' => 'Typ', ], 'posts' => [], 'premium' => 'Die Wiederherstellung von Elementen ist eine Premium-Kampagnenfunktion.', 'success_v2'=> '{1} :count Element wurde wiedergefunden.|[2,*] :count Elmente wurden wiedergefunden.', 'title' => 'Objektwiederherstellung für :campaign', 'toggle' => [], 'tutorial' => 'Zeige kürzlich gelöschte Elemente aus der Kampagne an und stelle sie wieder her. Objekte, Beiträge und andere unterstützende Daten können für eine bestimmte Anzahl von Tagen wiederhergestellt werden, bevor sie endgültig gelöscht werden. Bei der Wiederherstellung eines Elements werden alle Daten vollständig wiederhergestellt.', ]; ================================================ FILE: lang/de/campaigns/roles.php ================================================ [ 'status' => 'Status: :status', ], 'create' => [ 'helper' => 'Erstelle eine neue Rolle für die Kampagne.', ], 'overview' => [ 'limited' => ':amount der :total erstellter Rollen.', 'title' => 'verfügbare Rollen', 'unlimited' => ':amount der erstellten unbegrenzten Rollen.', ], 'public' => [], 'show' => [ 'title' => ':role Berechtigungen - :campaign', ], 'toggle' => [ 'disabled' => 'Mitglieder der Rolle :role können keine :action :entities mehr ausführen', 'enabled' => 'Mitglieder der Rolle :role können jetzt :action :entities', ], 'warnings' => [ 'adding-to-admin' => 'Mitglieder der Rolle :name haben Zugriff auf alles in der Kampagne und können nicht von anderen Mitgliedern der Rolle entfernt werden . Nach :amount Minuten können nur sie sich aus der Rolle entfernen.', ], ]; ================================================ FILE: lang/de/campaigns/sidebar.php ================================================ [ 'reset' => 'Zurücksetzen', ], 'call-to-action' => 'Passe die Reihenfolge, Symbole und Namen der Elemente in der Seitenleiste der Kampagne an.', 'helpers' => [ 'bookmarks' => 'Lesezeichen werden hier nicht aufgeführt, da jedes Lesezeichen seine eigene :position setting hat, die bestimmt, wo es in der Seitenleiste erscheint.', 'image' => 'Füge ein Bild hinzu, das die Kampagne darstellt. Dieses Bild wird in der Seitenleiste und in der Oberfläche des Kampagnenwechslers verwendet. Du kannst es jederzeit ändern, indem du die Kampagne bearbeitest.', 'reordering'=> 'Ordne die Seitenleiste neu an, indem du die Symbole auf der linken Seite ziehst und ablegst.', ], 'image-success' => 'Das neue Kampagnenbild wurde gespeichert. Dieses Bild kann durch Bearbeiten der Kampagne wieder geändert werden.', 'reset' => [ 'success' => 'Die Einrichtung der Kampagnen-Seitenleiste wurde zurückgesetzt.', 'title' => 'Seitenleisten-Setup zurücksetzen', 'warning' => 'Möchten Sie die Seitenleiste Ihrer Kampagne wirklich auf die Standardwerte zurücksetzen?', ], 'success' => 'Einrichtung der Kampagnenseitenleiste gespeichert.', 'title' => 'Kampagne :campagin Seitenleiste einrichten', 'tooltips' => [ 'image' => 'Ändere das Hintergrundbild', ], ]; ================================================ FILE: lang/de/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Kalender', 'title' => 'Zeitnehmer', ], 'murderer' => [ 'goal' => 'tote Charaktere', 'title' => 'Mörder', ], ], 'fields' => [ 'created' => 'Erstellt am', 'creator' => 'Erstellt von', 'general' => 'Allgemein', ], 'targets' => [], 'title2' => 'Statistiken', 'titles' => [ 'calendars' => 'Zeitnehmer level :level', 'characters'=> 'Namensgeber level :level', 'dead' => 'Mörder level :level', 'families' => 'Familienplanung level :level', 'locations' => 'Baumeister level :level', 'quests' => 'Superhirn level :level', 'races' => 'Züchter level :level', ], ]; ================================================ FILE: lang/de/campaigns/styles.php ================================================ [ 'current' => 'Aktuelles Thema :theme', 'disable' => 'deaktiviert', 'enable' => 'aktiviert', 'new' => 'Neues Design', ], 'bulks' => [ 'delete' => '{1} entfernt :count style.|[2,*] entfernt :count styles.', 'disable' => '{1} deaktiviert :count style.|[2,*] deaktiviert :count styles.', 'enable' => '{1} aktiviert :count style.|[2,*] aktiviert :count styles.', ], 'create' => [ 'success' => 'Neues Design angelegt.', 'title' => 'Neuer Stil', ], 'delete' => [ 'success' => 'Design :name gelöscht.', ], 'errors' => [ 'max_content' => 'Die CSS-Regel darf nicht länger als :amount Zeichen sein.', 'max_reached' => 'Maximale Anzahl an Stilen (:max) erreicht.', ], 'fields' => [ 'content' => 'CSS Regel', 'is_enabled' => 'Aktiviert', 'length' => 'Länge', 'modified' => 'Geändert', 'name' => 'Name', 'order' => 'Reihenfolge', ], 'helpers' => [ 'here' => 'auf unserem Blog', 'is_enabled' => 'Aktiviere dieses Thema auf jeder Seite.', 'main' => 'Sie können das CSS Design für Ihre geboostete Kampagne anpassen. Falls Sie Themes aus dem Marktplatz in Ihrer Kampagne verwenden, dann wird Ihr angepasstes Design nach diesen Themes geladen. Mehr über das Erstellen eigener Designs für Ihre Kampagne finden Sie :here.', 'tutorial' => 'Steuer den visuellen Stil der Kampagne. Wähle Farben, Layout-Einstellungen und andere Darstellungsoptionen. Diese Änderungen wirken sich nur auf diese Kampagne aus und können jederzeit aktualisiert werden.', ], 'pitch' => 'Erstelle ein benutzerdefiniertes CSS-Design, um das Erscheinungsbild der Kampagne vollständig anzupassen.', 'placeholders' => [ 'name' => 'Name des Stils', ], 'reorder' => [ 'save' => 'neue Reihenfolge speichern', 'success' => '{1} neu angeordnet :count style.|[2,*] neu angeordnet :count styles.', 'title' => 'Stile neu anordnen', ], 'theme' => [ 'none' => 'Benutzerpräferenz verwenden', 'override' => 'Thema überschreiben', 'success' => 'Kampagnenthema aktualisiert.', 'title' => 'Aktualisieren Sie das Kampagnenthema', ], 'title' => 'Kampagnen Theming', 'toggle' => [ 'disable' => 'Stil erfolgreich deaktiviert.', 'enable' => 'Stil erfolgreich aktiviert.', ], 'update' => [ 'success' => 'Design :name aktualisiert.', 'title' => 'Aktualisiere Design', ], ]; ================================================ FILE: lang/de/campaigns/vanity.php ================================================ 'Der Name :vanity ist verfügbar!', 'forever' => 'Einmal festgelegt, kann dies nicht mehr geändert werden. :docs', 'helper-v2' => 'Gib der Kampagne eine einprägsame, benutzerdefinierte Webadresse. Anstelle von :default könnte die Kampagne beispielsweise wie folgt erscheinen: :example', 'rule' => 'Das Feld :field benötigt mindestens ein alphabetisches Zeichen.', 'rule2' => 'Das Feld :field lässt das folgende Zeichen nicht zu: /.', 'set' => 'Die Vanity-URL der Kampagne ist dauerhaft auf :vanity eingestellt.', ]; ================================================ FILE: lang/de/campaigns/webhooks.php ================================================ [ 'action' => 'Status ändern', 'add' => 'Webhook erstellen', 'bulks' => [ 'delete_success' => '{1} Gelöscht :count webhook.|[2,*] Gelöscht :count webhooks.', 'disable' => 'Deaktivieren', 'disable_success' => '{1} Deaktiviert :count webhook.|[2,*] Deaktiviert :count webhooks.', 'enable' => 'Aktivieren', 'enable_success' => '{1} Freigegeben :count webhook.|[2,*] Freigegeben :count webhooks.', ], 'test' => 'Test Webhook', 'update' => 'Webhook aktualisieren', ], 'create' => [ 'success' => 'Webhook erfolgreich erstellt', 'title' => 'Neuen Webhook hinzufügen', ], 'destroy' => [ 'success' => 'Webhook erfolgreich gelöscht', ], 'edit' => [ 'success' => 'Webhook erfolgreich aktualisiert', 'title' => 'Webhook aktualisieren', ], 'fields' => [ 'enabled' => 'Aktiviert', 'event' => 'Event', 'events' => [ 'deleted' => 'Gelöschtes Objekt', 'edited' => 'Bearbeitetes Objekt', 'new' => 'Neues Objekt', ], 'message' => 'Nachricht', 'private_entities' => [ 'helper' => 'Löse den Webhook nicht aus, wenn du private Objekte aktualisierst.', 'skip' => <<<'TEXT' Private Objekte überspringen TEXT , ], 'type' => 'Typ', 'types' => [ 'custom' => 'Nachricht', 'payload' => 'Nutzlast', ], 'url' => 'Url', ], 'helper' => [ 'active' => 'Falls der Webhook gerade aktiv ist', 'message' => 'Hinzufügen einer benutzerdefinierten Nachricht mit Unterstützung für Mappings', 'status' => 'Umschalten des aktiven Status des Webhooks', ], 'placeholders' => [ 'message' => '{who} hat Änderungen an {name}, vorgenommen, sieh sie dir hier {url} an.', 'url' => 'Url des Ziel-Webhooks', ], 'test' => [ 'success' => 'Testanfrage gesendet', ], 'title' => 'Webhooks', ]; ================================================ FILE: lang/de/campaigns.php ================================================ [], 'create' => [ 'success' => 'Kampagne erstellt.', 'title' => 'Neue Kampagne erstellen', ], 'destroy' => [], 'edit' => [ 'success' => 'Kampagne aktualisiert', ], 'entity_note_visibility' => [], 'entity_personality_visibilities' => [ 'private' => 'Die Persönlichkeit neuer Charaktere ist im Standard auf privat eingestellt.', ], 'entity_visibilities' => [ 'private' => 'Neue Objekte sind privat', ], 'errors' => [ 'access' => 'Du hast keinen Zugang zu dieser Kampagne.', 'premium' => 'Diese Funktion ist nur für Premium-Kampagnen verfügbar.', 'unknown_id' => 'Unbekannte Kampagne.', ], 'export' => [], 'fields' => [ 'boosted' => 'geboosted durch', 'entity_count' => 'Objekt Zähler', 'entry' => 'Kampagnenbeschreibung', 'followers' => 'Abonnenten', 'genre' => 'Genre(s)', 'header_image' => 'Header Bild', 'image' => 'Bild', 'locale' => 'Sprache', 'name' => 'Name', 'open' => 'Offen für Bewerbungen', 'premium' => 'Premium freigeschaltet von :name', 'public' => 'Sichtbarkeit der Kampagne', 'public_campaign_filters' => 'Öffentliche Kampagnenfilter', 'superboosted' => 'supergeboosted von', 'system' => 'System', 'theme' => 'Thema', 'vanity' => 'Vanity-URL', ], 'following' => 'Abonniert', 'helpers' => [ 'boosted' => 'Einige Funktionen sind freigeschaltet, da diese Kampagne geboosted wird. Weitere Informationen finden Sie auf der :settings page.', 'css' => 'Schreibe dein eigenes CSS, das du auf die Seiten deiner Kampagne laden kannst. Bitte beachte, dass jeder Missbrauch dieser Funktion dazu führen kann, dass dein benutzerdefiniertes CSS entfernt wird. Wiederholungen oder schwerwiegende Verstöße können dazu führen, dass deine Kampagne entfernt wird.', 'dashboard' => 'Passen Sie die Anzeige des Kampagnen-Dashboard-Widgets an, indem Sie die folgenden Felder ausfüllen.', 'excerpt' => 'Die Kampagnenzusammenfassung wird im Dashboard angezeigt. Schreib daher ein paar Sätze, die deine Welt vorstellen. Idealerweise hältst du es kurz und informativ.', 'header_image' => 'Das Bild wird im Widget des Kampagnen-Header-Dashboards als Hintergrund angezeigt.', 'hide_history' => 'Aktivieren Sie diese Option, um den Verlauf von Objekten vor Nichtmitglieder der Kampagne zu verbergen.', 'hide_members' => 'Aktivieren Sie diese Option, um die Liste der Kampagnenmitglieder der Kampagne für Nicht-Administratoren auszublenden.', 'locale' => 'Die Sprache, in der deine Kampagne geschrieben ist. Dies wird genutzt, um Inhalte zu erstellen und öffentliche Kampagnen zu gruppieren.', 'name' => 'Deine Kampagne/Welt kann einen beliebigen Namen haben, solange dieser mindestens 4 Buchstaben oder Zahlen enthält.', 'no_entry' => 'Anscheinend hat die Kampagne noch keine Beschreibung! Lasst uns das beheben.', 'premium' => 'Einige Funktionen sind verfügbar, da die Premiumfunktionen dieser Kampagne freigeschaltet sind. Weitere Informationen findest du auf der Seite :settings.', 'public_campaign_filters' => 'Helfen Sie anderen, die Kampagne unter anderen öffentlichen Kampagnen zu finden, indem Sie die folgenden Informationen bereitstellen.', 'public_no_visibility' => 'Kopf hoch! Ihre Kampagne ist öffentlich, aber die öffentliche Rolle der Kampagne kann auf nichts zugreifen. :fix.', 'system' => 'Wenn deine Kampagne öffentlich einsehbar ist, dann wird das System in der :link Seite angezeigt.', 'systems' => 'Um die Benutzer nicht mit Optionen zu überfordern, sind einige Funktionen von Kanka nur mit bestimmten Rollenspielsystemen (z.B. dem D&D 5e-Monster-Werteblock) verfügbar. Wenn wir hier unterstützte Systeme hinzufügen, werden diese Funktionen aktiviert.', 'theme' => 'Legen Sie das Thema für die Kampagne fest und überschreiben Sie die Benutzereinstellungen.', 'view_public' => 'Um Ihre Kampagne als öffentlichen Betrachter anzuzeigen, öffnen Sie :link in einem Inkognito-Fenster.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Kopieren Sie den Link in Ihre Zwischenablage', 'link' => 'Neuer Link', ], 'create' => [ 'buttons' => [ 'create' => 'Einladung erstellen', ], 'success_link' => 'Link :link erstellt', 'title' => 'Lade jemanden zu deiner Kampagne ein', ], 'destroy' => [ 'success' => 'Einladung entfernt.', ], 'error' => [ 'inactive_token' => 'Dieses Token wurde bereits genutzt oder die Kampagne existiert nicht mehr.', 'invalid_token' => 'Dieser Token ist nicht mehr gültig.', 'join' => 'Bitte melde dich an oder registriere ein neues Konto, um an :campaign teilzunehmen.', ], 'fields' => [ 'created' => 'Senden', 'role' => 'Rolle', 'token' => 'Token', 'type' => 'Typ', 'usage' => 'Maximale Anzahl von Verwendungen', ], 'helpers' => [ 'role' => 'Benutzer müssen erst beitreten, bevor sie zur Administratorrolle befördert werden können.', 'usage' => 'Wie oft der Einladungslink verwendet werden kann, bevor er inaktiv wird.', ], 'unlimited_validity' => 'Unbegrenzt', 'usages' => [ 'five' => '5 Verwendungen', 'no_limit' => 'kein Limit', 'once' => '1 Verwendung', 'ten' => '10 Verwendungen', ], ], 'leave' => [ 'action' => 'Kampagne verlassen', 'confirm' => 'Bist du sicher, dass du die Kampagne :name verlassen möchtest? Du hast danach keinen Zugang mehr, außer ein Besitzer der Kampagne lädt dich erneut ein.', 'confirm-button' => 'Ja, Kampagne verlassen', 'error' => 'Kann die Kampagne nicht verlassen.', 'fix' => 'Gehe zu den Kampagnenmitgliedern', 'no-admin-left' => 'Das Verlassen der Kampagne ist nicht möglich, da du sonst ohne Administratoren wärst. Füge zuerst ein anderes Mitglied zur Administratorrolle hinzu.', 'success' => 'Du hast die Kampagne verlassen.', 'title' => 'Kampagne verlassen', ], 'members' => [ 'actions' => [ 'remove' => 'aus Kampagne entfernen', 'switch' => 'Wechseln', 'switch-back' => 'Zurück zu meinem User', 'switch-entity' => 'Anzeigen als', ], 'fields' => [ 'banned' => 'Benutzer ist gesperrt', 'joined' => 'Beigetreten', 'last_login' => 'Letzter Login', 'name' => 'Nutzer', 'role' => 'Rolle', 'roles' => 'Rollen', ], 'helpers' => [ 'switch' => 'Zu diesem User wechseln', ], 'impersonating' => [ 'message' => <<<'TEXT' Du siehst die Kampagne jetzt als ein anderer User. Einige Funktionen wurden deaktiviert, aber ansonsten sieht es genau so aus wie es der User sehen würde. Um zurück zu deinem User zu wechseln, benutze den "Zurück zu meinem User" Button, wo sonst der Logout Button zu finden ist. TEXT , 'title' => 'Ansicht von :name', ], 'invite' => [ 'description' => 'Du kannst deine Freunde zu deiner Kampagne einladen, in dem du ihre Email Adresse eingibst. Wenn sie die Einladung annehmen, werden sie als \'Zuschauer\' hinzugefügt. Du kannst die Einladung auch jederzeit abbrechen.', 'more' => 'Du kannst neue Rollen unter :link hinzufügen.', 'title' => 'Einladen', ], 'removal' => 'Du entfernst ":member" aus der Kampagne.', 'roles' => [ 'member' => 'Mitglied', 'owner' => 'Besitzer (privat Option sichtbar)', 'player' => 'Spieler', 'public' => 'Öffentlich', 'viewer' => 'Zuschauer', ], 'switch_back_success' => 'Du bist nun zurück in deinem eigentlichen User.', ], 'mentions' => [], 'modules' => [], 'open_campaign' => [], 'options' => [], 'overview' => [ 'entity-count' => '{0} Keine Objekte|{1} :amount Objekt|[2,*] :amount Objekte', 'follower-count' => '{0} Keine Follower|{1} :amount Follower|[2,*] :amount Follower', ], 'panels' => [ 'dashboard' => 'Dashboard', 'privacy' => 'Datenschutz-Standardeinstellungen', 'setup' => 'erstellen', 'sharing' => 'Teilen', 'systems' => 'Systeme', 'ui' => 'Schnittstelle', ], 'placeholders' => [ 'locale' => 'Sprachcode', 'name' => 'Dein Kampagnenname', 'system' => 'D&D 5e, 3.5, Pathfinder, Gurps, DSA', ], 'privacy' => [ 'hidden' => 'Versteckt', 'private' => 'privat', 'visible' => 'sichtbar', ], 'public' => [ 'helpers' => [ 'introduction' => 'Kampagnen sind standardmäßig privat und können öffentlich gemacht werden. Dadurch kann jeder auf sie zugreifen und sie auf der Seite :public-campaigns verfügbar machen, wenn sie über Objekte verfügen, die für die Rolle :public-role sichtbar sind. Eine öffentliche Kampagne ist für alle sichtbar, aber damit ihr Inhalt sichtbar ist, benötigt die Rolle :public-role angemessene Berechtigungen.', ], ], 'roles' => [ 'actions' => [ 'add' => 'Rolle hinzufügen', 'duplicate' => 'Rolle dupliziert', 'permissions' => 'Berechtigungen verwalten', 'rename' => 'Rolle umbenennen', 'save' => 'Rolle speichern', ], 'admin_role' => 'Administratorenrolle', 'bulks' => [ 'delete' => '{1} entfernt :count role.|[2,*] entfernt :count roles.', 'edit' => '{1} aktualisiert :count role.|[2,*] aktualisiert :count roles.', ], 'create' => [ 'success' => 'Rolle erstellt.', 'title' => 'Erstelle eine neue Rolle für :name', ], 'destroy' => [ 'success' => 'Rolle entfernt.', ], 'edit' => [ 'success' => 'Rolle aktualisiert.', 'title' => 'Rolle :name bearbeiten', ], 'fields' => [ 'copy_permissions' => 'Kopierberechtigungen', 'name' => 'Name', 'permissions' => 'Berechtigungen', 'type' => 'Typ', 'users' => 'Nutzer', ], 'helper' => [ '1' => 'Eine Kampagne kann so viele Rollen haben, wie du willst. Die "Admin" Rolle hat automatisch Zugriff auf alles in einer Kampagne, aber jede andere Rolle kann spezielle Berechtigungen auf unterschiedliche Typen von Objekten (Charaktere, Orte, etc.) haben.', '2' => 'Objekte können feiner abgestimmte Berechtigungen haben, die du im "Berechtigungen" Tab des Objekts einstellen kannst. Dieser Tab erscheint, wenn du mehrere Rollen in deiner Kampagne hast.', '3' => 'Man kann entweder ein "opt-out" System verwenden, in dem Rollen lesenden Zugriff auf alle Objekte bekommen und mit der "Privat" Checkbox bestimmte Objekte ausgeblendet werden. Oder man gibt Rollen wenige Berechtigungen und setzt jedes Objekt explizit auf sichtbar.', '4' => 'Boosted-Kampagnen können eine unbegrenzte Anzahl von Rollen haben.', 'permissions_helper' => 'Dupliziere alle Berechtigungen der Rolle, sowohl für Module als auch für Objekte.', ], 'hints' => [ 'campaign_not_public' => 'Die öffentliche Rolle hat Berechtigungen, aber die Kampagne ist privat. Sie können diese Einstellung auf der Registerkarte Freigabe ändern, wenn Sie die Kampagne bearbeiten.', 'empty_role' => 'Die Rolle hat noch keine Mitglieder.', 'role_admin' => 'Die Rolle :name gewährt deinen Mitgliedern automatisch Zugriff vollen Kampagnen Zugriff.', 'role_permissions' => 'Erlaube der Rolle \':name\' die folgenden Aktionen auf allen Objekten.', ], 'members' => 'Mitglieder', 'modals' => [ 'details' => [ 'campaign' => 'Kampagnenberechtigungen erlauben Folgendes.', 'entities' => 'Hier ist eine kurze Zusammenfassung, was Mitglieder dieser Rolle erhalten, wenn eine Berechtigung festgelegt wird.', 'more' => 'Weitere Informationen finden Sie in unserem Tutorial-Video auf Youtube', 'title' => 'Berechtigungsdetails', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Erstellen', 'dashboard' => 'Dashboard', 'delete' => 'Entfernen', 'edit' => 'Bearbeiten', 'gallery' => [ 'browse' => 'Durchsuche', 'manage' => 'Volle Kontrolle', 'upload' => 'Hochladen', ], 'manage' => 'Verwalten', 'members' => 'Mitglieder', 'permission'=> 'Verwalte Berechtigungen', 'read' => 'Anschauen', 'toggle' => 'Alles ändern', ], 'helpers' => [ 'add' => 'Erstellen von Objekten dieses Typs zulassen. Sie werden automatisch berechtigt, von ihnen erstellte Objekte anzuzeigen und zu bearbeiten, wenn sie nicht über die Berechtigung zum Anzeigen oder Bearbeiten verfügen.', 'dashboard' => 'Erlauben Sie die Bearbeitung der Dashboards und Dashboard-Widgets.', 'delete' => 'Erlauben Sie das Entfernen aller Objekte dieses Typs.', 'edit' => 'Bearbeitung aller Objekte dieses Typs zulassen.', 'gallery' => [ 'browse' => 'Ermögliche das Anzeigen der Galerie und das Festlegen des Bilds eines Objekts aus der Galerie.', 'manage' => 'Erlaube alles in der Galerie so, wie es ein Administrator kann, einschließlich der Bearbeitung und Löschung von Bildern.', 'upload' => 'Ermöglicht das Hochladen von Bildern in die Galerie. Die von dir hochgeladenen Bilder werden nur angezeigt, wenn sie nicht mit der Durchsuchungsberechtigung kombiniert sind.', ], 'manage' => 'Erlauben Sie die Bearbeitung der Kampagne wie ein Kampagnenadministrator, ohne dass die Mitglieder die Kampagne löschen können.', 'members' => 'Erlauben Sie, neue Mitglieder zur Kampagne einzuladen.', 'not_public'=> 'Die Kampagne ist nicht öffentlich. Berechtigungen für die öffentliche Rolle können festgelegt werden, werden aber ignoriert. Bearbeite die Kampagne, um sie öffentlich zu machen.', 'permission'=> 'Erlaube Einstellungsberechtigungen für Objekte dieses Typs, die sie bearbeiten können.', 'read' => 'Erlauben Sie die Anzeige aller Objekte dieses Typs, die nicht privat sind.', ], ], 'placeholders' => [ 'name' => 'Name der Rolle', ], 'title' => 'Kampagne :name Rollen', 'types' => [ 'owner' => 'Besitzer', 'public' => 'Öffentlich', 'standard' => 'Standard', ], 'users' => [ 'actions' => [ 'add' => 'Hinzufügen', 'remove' => ':user von der :role Rolle', 'remove_user' => 'Benutzer aus der Rolle entfernen', ], 'create' => [ 'success' => 'Benutzer zu Rolle hinzugefügt.', 'title' => 'Füge ein Mitglied zur Rolle :name hinzu', ], 'destroy' => [ 'success' => 'Benutzer aus Rolle entfernt', ], 'errors' => [ 'cant_kick_admins' => 'Um Missbrauch zu vermeiden, ist es nicht möglich, andere Mitglieder aus der :admin-Rolle der Kampagne zu entfernen. Bei Problemen kontaktiere uns unter :discord oder unter :email.', 'needs_more_roles' => 'Du musst dich zu einer anderen Rolle in der Kampagne hinzufügen, bevor du dich selbst aus der :admin-Rolle entfernen kannst.', ], 'fields' => [ 'name' => 'Name', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'aktivieren', ], 'boosted' => 'Diese Funktion befindet sich in der Beta-Phase und ist derzeit nur verfügbar für :boosted.', 'deprecated' => [ 'help' => 'Dieses Modul ist veraltet, was bedeutet, dass es nicht mehr gepflegt wird und dass Fehler nicht bei jedem neuen Update getestet werden. Verwende dieses Modul mit dem Wissen, dass es irgendwann aus Kanka entfernt wird.', 'title' => 'Veraltet', ], 'disabled' => 'Das Modul :module ist deaktiviert.', 'enabled' => 'Das Modul :module ist aktiviert.', 'errors' => [ 'module-disabled' => 'Das angeforderte Modul ist derzeit in den Kampagneneinstellungen deaktiviert. :fix.', ], 'helpers' => [ 'abilities' => 'Erstellen Sie Fähigkeiten, seien es Talente, Zauber oder Kräfte, die Objekten zugewiesen werden können.', 'assets' => 'Lade Dateien hoch, setze Links und definiere Aliasnamen für einzelne Objekte.', 'bookmarks' => 'Erstelle Lesezeichen für Objekte oder gefilterte Listen, die in der Seitenleiste angezeigt werden.', 'calendars' => 'Der Ort, um die Kalender deiner Welt zu erstellen.', 'characters' => 'Die Leute, die deine Welt bevölkern.', 'conversations' => 'Fiktive Gespräche zwischen Charakteren oder zwischen Kampagnennutzern.', 'creatures' => 'Erschaffe mit dem Kreaturenmodul die Kreaturen, Tiere und Monster deiner Welt.', 'dice_rolls' => 'Für die, die Kanka für RPG Kampagnen benutzen, eine Möglichkeit Würfelwürfe zu verwalten.', 'entity_attributes' => 'Verfolge Attribute zu Objekten der Kampagne, zum Beispiel Lebenspunkte oder Geschwindigkeit.', 'events' => 'Feiertage, Festlichkeiten, Katastrophen, Geburtstage, Kriege.', 'families' => 'Klans oder Familien, deren Beziehungen und deren Mitglieder.', 'inventories' => 'verwalten sie die Inventare ihrer Objekte', 'items' => 'Waffen, Fahrzeuge, Reliquien, Tränke.', 'journals' => 'Beobachtungen von Spielern oder Spielvorbereitungen vom Spielleiter.', 'locations' => 'Planeten, Ebenen, Kontinente, Flüsse, Staaten, Siedlungen, Tempel, Tavernen.', 'maps' => 'Laden Sie Karten mit Ebenen und Markierungen hoch, die auf andere Objekte in der Kampagne verweisen.', 'notes' => 'Sagen, Religionen, Geschichte, Magie, Spezies.', 'organisations' => 'Kulte, Militäreinheiten, Fraktionen, Gilden.', 'quests' => 'Um Aufgaben mit Charakteren und Ort zu verfolgen.', 'races' => 'Wenn deine Kampagne mehr als eine Spezies hat, hier kannst du den Überblick behalten.', 'tags' => 'Jedes Objekt kann eine Kategorie habe. Kategorien können zu anderen Kategorien gehören und Objekte können nach Kategorie gefiltert werden.', 'timelines' => 'Stellen Sie die Geschichte Ihrer Welt mit Zeitstrahlen dar.', 'whiteboards' => 'Zeichne und schreibe auf Whiteboards, um deine Welt und deine Ziele visuell zu planen.', ], ], 'sharing' => [ 'filters' => 'Öffentliche Kampagnen sind auf der Seite :public-campaigns sichtbar. Das Ausfüllen dieser Felder erleichtert es den Nutzern, die Kampagne zu entdecken.', 'language' => 'Die Sprache, in der der Inhalt der Kampagne verfasst ist.', 'system' => 'Beim Spielen eines TTRPG wird das System verwendet, mit dem in der Kampagne gespielt wurde.', ], 'show' => [ 'actions' => [ 'edit' => 'Kampagne editieren', ], 'tabs' => [ 'achievements' => 'Erfolge', 'customisation' => 'Anpassung', 'danger' => 'Gefahr', 'data' => 'Daten', 'default-images' => 'Standardbilder', 'defaults' => 'Standardwerte', 'deletion' => 'Löschung', 'export' => 'Export', 'import' => 'Importieren', 'logs' => 'Logs', 'management' => 'Management', 'members' => 'Mitglieder', 'plugins' => 'Plugins', 'recovery' => 'Wiederherstellen', 'roles' => 'Rollen', 'sidebar' => 'Einrichtung der Seitenleiste', 'stats' => 'Statistik', 'styles' => 'Thematisierung', 'webhooks' => 'Webhaken', ], 'title' => 'Kampagne :name', ], 'status' => [ 'free' => 'Premium-Funktionen deaktiviert.', 'legacy' => [ 'title' => 'Erhöhte Funktionen (Legacy)', ], 'premium' => 'Premium-Funktionen werden durch :name freigeschaltet.', 'title' => 'Premium Funktionen', ], 'superboosted' => [], 'themes' => [ 'none' => 'Keine (standardmäßig Benutzereinstellungen)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Nur für Kampagnenadministratoren sichtbar', 'visible' => 'sichtbar für Mitglieder', ], 'fields' => [ 'entity_history' => 'Verlaufsprotokolle des Objekts', 'member_list' => 'Mitgliederliste der Kampagne', ], 'helpers' => [ 'entity-history' => 'Steuere, wer die letzten Änderungen sehen kann, die an einzelnen Objekten der Kampagne vorgenommen wurden.', 'member-list' => 'Kontrolliere, wer sehen kann, wer an der Kampagne teilnimmt.', 'theme' => 'Zeige die Kampagne im Thema des Benutzers an oder erzwinge die Darstellung in einem der folgenden Themen.', ], 'members' => [ 'hidden' => 'Nur für Kampagnenadministratoren sichtbar', 'visible' => 'für Mitglieder sichtbar', ], ], 'visibilities' => [ 'private' => 'Privat', 'public' => 'Öffentlich', 'unlisted' => 'öffentlich (nicht gelistet)', ], 'warning' => [], ]; ================================================ FILE: lang/de/characters.php ================================================ [ 'add_appearance' => 'Füge ein Aussehen hinzu', 'add_personality' => 'Füge eine Persönlichkeit hinzu', ], 'conversations' => [], 'create' => [ 'title' => 'Erstelle einen neuen Charakter', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'families' => [ 'reorder' => [ 'success' => 'Charakterfamilien erfolgreich aktualisiert.', ], ], 'fields' => [ 'age' => 'Alter', 'is_appearance_pinned' => 'Angeheftetes Aussehen', 'is_dead' => 'Tot', 'is_personality_pinned' => 'Angeheftete Persönlichkeit', 'is_personality_visible' => 'Persönlichkeit sichtbar?', 'life' => 'Leben', 'physical' => 'Körper', 'pronouns' => 'Pronomen', 'sex' => 'Geschlecht', 'title' => 'Titel', 'traits' => 'Eigenschaften', ], 'helpers' => [ 'age' => 'Sie können dieses Objektes mit einem Kalender Ihrer Kampagne verknüpfen, um stattdessen automatisch dessen Alter zu berechnen. :more.', ], 'hints' => [ 'is_appearance_pinned' => 'Wenn diese Option aktiviert ist, werden die Aussehensmerkmale des Charakters unter dem Eintrag auf der Übersichtsseite angezeigt.', 'is_dead' => 'Dieser Charakter ist tot', 'is_personality_visible' => 'Du kannst den kompletten Persönlichkeitsbereich vor deinen Zuschauern verstecken.', 'personality_not_visible' => 'Persönlichkeitsmerkmale dieses Charakters sind derzeit nur für Administratoren sichtbar.', 'personality_visible' => 'Persönlichkeitsmerkmale dieses Charakters sind für alle sichtbar.', ], 'index' => [], 'items' => [], 'journals' => [], 'labels' => [ 'appearance' => [ 'entry' => 'Erscheinungsbeschreibung', 'name' => 'Erscheinungsname', ], 'personality' => [ 'entry' => 'Beschreibung von Persönlichkeitsmerkmalen', 'name' => 'Name des Persönlichkeitsmerkmals', ], ], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => 'Charakter wurde der Organisation hinzugefügt.', 'title' => 'Neue Organisation für :name', ], 'destroy' => [ 'success' => 'Character aus Organisation entfernt.', ], 'edit' => [ 'success' => 'Organisation des Charakters aktualisiert', 'title' => 'Aktualisiere Organisation für :name', ], 'fields' => [ 'role' => 'Rolle', ], ], 'placeholders' => [ 'age' => 'Alter', 'appearance_entry' => 'Beschreibung', 'appearance_name' => 'Haare, Augen, Haut, Größe', 'name' => 'Name des Charakters', 'personality_entry' => 'Details', 'personality_name' => 'Persönlichkeitsmerkmal: Ziele, Gewohnheiten, Ängste, Bindungen', 'physical' => 'Körper', 'pronouns' => 'Er / Ihn, Sie / Sie, Sie / Ihre', 'sex' => 'Geschlecht', 'title' => 'Titel', 'traits' => 'Eigenschaften', 'type' => 'NSC, Spieler Charakter, Gottheit', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Quests bei denen der Charakter Auftraggeber war.', 'quest_member' => 'Quests an denen der Charakter teilgenommen hat.', ], ], 'races' => [ 'reorder' => [ 'success' => 'Die Charakterrassen wurden erfolgreich aktualisiert.', ], ], 'sections' => [ 'appearance' => 'Aussehen', 'personality' => 'Persönlichkeit', ], 'show' => [], 'warnings' => [ 'personality_hidden' => 'Es ist dir nicht erlaubt, die Persönlichkeit dieses Charakters zu bearbeiten.', ], ]; ================================================ FILE: lang/de/colours.php ================================================ 'Eisblau', 'black' => 'Schwarz', 'blue' => 'Blau', 'brown' => 'Braun', 'green' => 'Grün', 'grey' => 'Grau', 'light-blue' => 'Hellblau', 'maroon' => 'Magenta', 'navy' => 'Marineblau', 'none' => 'Keine', 'orange' => 'Orange', 'pink' => 'Rosa', 'purple' => 'Violett', 'red' => 'Rot', 'teal' => 'Türkis', 'white' => 'Weiss', 'yellow' => 'Gelb', ]; ================================================ FILE: lang/de/concept.php ================================================ 'geboostete Kampagne', 'premium-campaign' => 'Premium Kampagne', 'premium-campaign-count' => '{0} Keine Premium-Kampagnen |{1} 1 Premium-Kampagne |[2,*] :count Premium-Kampagnen', 'premium-campaigns' => 'Premium Kampagnen', 'premium-feature' => 'Premium-Funktion', 'superboosted-campaign' => 'supergeboostete Kampagne', ]; ================================================ FILE: lang/de/confirm/editing.php ================================================ 'Zurück', 'description' => 'Offenbar bearbeitet gerade jemand anderes diese Seite! Möchtest du zurückgehen oder diese Warnung ignorieren, auf die Gefahr hin, Daten zu verlieren?', 'ignore' => 'Bearbeite es trotzdem', 'members' => 'Mitglieder, die diese Seite bearbeiten:', 'title' => 'Warnung', 'user' => ':user seit :since', ]; ================================================ FILE: lang/de/confirm.php ================================================ [ 'bulk' => 'Möchtest du die ausgewählten Elemente wirklich löschen?', 'helper' => 'Möchtest du :name wirklich löschen?', 'recover' => 'Diese Aktion kann bis zu :day Tage lang auf der Seite :recover rückgängig gemacht werden.', 'recoverable' => 'Diese Aktion kann bis zu :day Tage lang mit einer :premium-Kampagne rückgängig gemacht werden.', 'title' => 'Element entfernen', ], ]; ================================================ FILE: lang/de/conversations.php ================================================ [ 'title' => 'Neue Unterhaltung', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_closed' => 'geschlossen', 'messages' => 'Nachrichten', 'participants' => 'Teilnehmer', ], 'hints' => [ 'empty' => 'An diesem Gespräch sind keine Teilnehmer beteiligt.', 'participants' => 'Bitte füge Teilnehmer zu deiner Unterhaltung hinzu, indem du das :icon Symbol oben rechts drückst.', ], 'index' => [], 'lists' => [ 'empty' => 'Zeichne Dialoge, Briefe oder den Austausch zwischen Charakteren und Fraktionen auf.', ], 'messages' => [ 'destroy' => [ 'success' => 'Nachricht gelöscht.', ], 'is_updated' => 'Aktualisiert', 'load_previous' => 'Lade vorherige Nachrichten', 'placeholders' => [ 'message' => 'Deine Nachricht', ], ], 'participants' => [ 'create' => [ 'success' => 'Teilnehmer :entity zu Unterhaltung hinzugefügt.', ], 'destroy' => [ 'success' => 'Teilnehmer :entity von Unterhaltung entfernt.', ], 'helper' => 'Hinzufügen und Entfernen von Teilnehmern aus :name.', 'modal' => 'Teilnehmer', 'title' => 'Teilnehmer von :name', ], 'placeholders' => [ 'name' => 'Name der Unterhaltung', 'type' => 'Im Spiel, Vorbereitung, Handlung', ], 'show' => [ 'is_closed' => 'Unterhaltung geschlossen', ], 'tabs' => [ 'participants' => 'Teilnehmer', ], 'targets' => [ 'characters' => 'Charaktere', 'members' => 'Mitglieder', ], ]; ================================================ FILE: lang/de/cookieconsent.php ================================================ 'Cookies zulassen', 'dismiss' => 'Zurückweisen', 'header' => 'Cookie-Zustimmung', 'link' => 'Erfahre mehr', 'message' => 'Kanka verwendet Cookies, um sicherzustellen, dass du das beste Erlebnis auf unserer Website hast.', 'policy' => 'Cookie-Richtlinie', 'reject' => 'Verfall', ]; ================================================ FILE: lang/de/creatures.php ================================================ [ 'title' => 'Neue Kreatur', ], 'creatures' => [], 'fields' => [ 'is_dead' => 'Tod', 'is_extinct' => 'Ausgestorben', ], 'helpers' => [], 'hints' => [ 'is_dead' => 'Diese Kreatur ist tot.', 'is_extinct' => 'Diese Kreatur ist ausgestorben.', ], 'lists' => [ 'empty' => 'Füge Tiere, Monster oder Fabelwesen hinzu, denen deine Helden begegnen oder mit denen sie sich anfreunden können.', ], 'placeholders' => [ 'type' => 'Pflanzenfresser, aquatisch, mythisch', ], 'show' => [], ]; ================================================ FILE: lang/de/crud.php ================================================ [ 'actions' => 'Aktionen', 'apply' => 'Übernehmen', 'back' => 'Zurück', 'change' => 'Ändern', 'close' => 'schließen', 'confirm' => 'bestätigen', 'copy' => 'Kopieren', 'copy_mention' => 'Kopie [] erwähnen', 'copy_to_campaign' => 'Kopiere zu Kampagne', 'disable' => 'deaktivieren', 'enable' => 'aktivieren', 'explore_view' => 'Verschachtelte Ansicht', 'export' => 'Exportieren', 'find_out_more' => 'Mehr erfahren', 'go_to' => 'Gehe zu :name', 'help' => 'Hilfe', 'json-export' => 'Export (json)', 'markdown-export' => 'Exportieren (Markdown)', 'move' => 'Verschieben', 'new' => 'Neu', 'new_child' => 'neues Unterobjekt', 'new_post' => 'Neue Objektnotiz', 'next' => 'Weiter', 'open' => 'Offen', 'print' => 'Drucken', 'reorder' => 'Neu anordnen', 'reset' => 'Zurücksetzen', 'transform' => 'Umwandeln', ], 'add' => 'Hinzufügen', 'alerts' => [ 'copy_attribute' => 'Die Erwähnung des Attributs wurde in Ihre Zwischenablage kopiert.', 'copy_invite' => 'Der Einladungslink zur Kampagne wurde in deine Zwischenablage kopiert.', 'copy_mention' => 'Die erweiterte Erwähnung dieses Objekts wurde in Ihre Zwischenablage kopiert.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Bearbeitung vieler Objekte', 'permissions' => 'Berechtigungen ändern', ], 'age' => [ 'helper' => 'Sie können + und - vor der Nummer verwenden, um das Alter dynamisch zu aktualisieren.', ], 'buttons' => [ 'label' => 'Für ausgewählt', ], 'edit' => [ 'locations' => 'Aktion für Standorte', 'tagging' => 'Aktion für Tags', 'tags' => [ 'add' => 'Hinzufügen', 'remove' => 'Entfernen', ], 'title' => 'Mehrere Objekte bearbeiten', ], 'errors' => [ 'admin' => 'Nur Kampagnenadmins können den "Privat" Status eines Objektes ändern.', 'general' => 'Bei der Verarbeitung Ihrer Aktion ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut und kontaktieren Sie uns, wenn das Problem weiterhin besteht. Fehlermeldung: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Überschreiben', ], 'helpers' => [ 'override' => 'Wenn ausgewählt, werden die Berechtigungen der ausgewählten Objekte mit diesen überschrieben. Wenn das Kontrollkästchen deaktiviert ist, werden die ausgewählten Berechtigungen zu den vorhandenen Berechtigungen hinzugefügt.', ], 'title' => 'Ändert die Berechtigungen für mehrere Objekte', ], ], 'bulk_templates' => [ 'bulk_title' => 'Wenden Sie eine Vorlage auf mehrere Objekte an', ], 'cancel' => 'Abbrechen', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Kopieren Sie Objekte in eine andere Kampagne', 'panel' => 'Kopieren', 'title' => 'Kopiere :name in eine andere Kampagne', ], 'create' => 'Erstellen', 'datagrid' => [ 'empty' => 'Nichts zu sehen bisher.', ], 'delete_modal' => [ 'callout' => 'Psst!', 'confirm' => 'Bestätige das Entfernen', 'permanent' => 'Diese Aktion ist dauerhaft.', 'recoverable' => 'Objekte können mit einer :boosted-campaign bis zu :day Tage lang wiederhergestellt werden.', 'title' => 'Löschen bestätigen', ], 'destroy_many' => [], 'dynamic' => [ 'permission' => 'Du hast nicht die nötige Berechtigungen, um ein Objekt des Moduls :module zu erstellen.', 'unknown' => 'Ungültiges Objekt des :module Moduls .', ], 'edit' => 'Bearbeiten', 'errors' => [ 'boosted_campaigns' => 'Diese Funktion ist nur für :boosted verfügbar', 'unavailable_feature' => 'nicht verfügbare Eigenschaft', ], 'events' => [], 'fields' => [ 'calendar_date' => 'Kalenderdatum', 'child' => 'Kind', 'closed' => 'geschlossen', 'colour' => 'Farbe', 'copy_abilities' => 'Kopiere Fähigkeiten', 'copy_inventory' => 'Kopiere Inventar', 'copy_links' => 'Kopiere Objekt Links', 'copy_permissions' => 'Berechtigungen kopieren (dies überschreibt die auf der Registerkarte Berechtigungen festgelegten Werte)', 'copy_posts' => 'Beiträge kopieren (dies umfasst die Berechtigungen für Beiträge)', 'copy_reminders' => 'Erinnerungen kopieren', 'creator' => 'Ersteller', 'date_range' => 'Datumsbereich', 'excerpt' => 'Auszug', 'has_entity_files' => 'Hat Objektdateien', 'has_image' => 'hat ein Bild', 'has_posts' => 'hat gepostet', 'header_image' => 'Kopfzeilenbild', 'image' => 'Bild', 'is_closed' => 'Die Konversation wird geschlossen und es werden keine neuen Nachrichten mehr akzeptiert.', 'is_private' => 'Privat', 'is_private_v3' => 'Zeigen Sie dies nur Mitgliedern der Rolle :admin-role an. Dies überschreibt alle anderen Berechtigungen.', 'is_star' => 'Angepinnt', 'locations' => ':first in :second', 'name' => 'Name', 'parent' => 'übergeordnetes Element', 'position' => 'Position', 'replace_mentions' => 'Ersetze die Attributerwähnungen im Eintrag durch die des neuen Objekts', 'template' => 'Vorlage', 'tooltip' => 'Kurzinfo', 'type' => 'Typ', 'visibility' => 'Sichtbarkeit', 'word-count' => 'Wort count: :number', ], 'files' => [ 'errors' => [ 'max' => 'Du hast die maximale Anzahl (:max) von Dateien in diesem Objekt erreicht.', 'max_size' => 'Die Kampagne hat die maximale Dateispeicherkapazität erreicht.', 'no_files' => 'Keine Dateien.', ], 'hints' => [ 'limit' => 'In jedem Objekt kann eine maximale Anzahl von :max Dateien hochgeladen werden.', 'limitations' => 'Unterstütze Formate: :formats. Max. Dateigröße: :size', ], ], 'filter' => 'Filter', 'filters' => [ 'all' => 'Filter um alle Unterobjekte zu sehen', 'clear' => 'Filter zurücksetzen', 'copy_helper' => 'Verwenden Sie die kopierten Filter in Ihrer Zwischenablage als Werte für Filter in Dashboard-Widgets und Quicklinks.', 'copy_to_clipboard' => 'Kopiere Filter in die Zwischenablage', 'direct' => 'Filter um nur direkte Unterobjekte zu sehen', 'filtered' => 'Zeige :count von :total :entity.', 'lists' => [ 'desktop' => [ 'all' => 'Zeige alle Nachkommen (:count)', 'filtered' => 'Zeige direkte Nachkommen (:count)', ], ], 'mobile' => [ 'clear' => 'Löschen', 'copy' => 'Clipboard', ], 'options' => [ 'children' => 'Mit untergeordneten Objekten', 'exclude' => 'Ausschließen', 'hide' => 'verbergen', 'include' => 'Einschließen', 'none' => 'keine', 'show' => 'anzeigen', ], 'show' => 'Zeigen', 'sorting' => [ 'asc' => ':field Aufsteigend', 'desc' => ':field absteigend', 'helper' => 'Steuern Sie, in welcher Reihenfolge die Ergebnisse angezeigt werden.', ], 'title' => 'Filter', ], 'fix-this-issue' => 'Beheben Sie dieses Problem', 'forms' => [ 'actions' => [ 'calendar' => 'Füge Datum hinzu', ], 'copy_options' => 'Kopiere Optionen', ], 'helpers' => [ 'copy_options' => 'Kopieren Sie die folgenden verwandten Elemente aus der Quelle in das neue Objekt.', 'linking' => 'Verknüpfung mit anderen Objekten', ], 'hidden' => 'Versteckt', 'hints' => [ 'calendar_date' => 'Ein Datum erlaubt es, Listen einfach zu filtern und pflegt ein Ereignis im ausgewählten Kalender.', 'image_dimension' => 'Empfohlene Abmessungen: :dimension Pixel.', 'image_limitations' => 'Unterstützte Formate: :formats. Maximale Dateigröße: :size.', 'image_recommendation' => 'empfohlene Abmaße: :width by :height px.', 'is_star' => 'Angepinnte Objekte erscheinen im Objektmenü.', 'tooltip' => 'Ersetzen Sie die automatisch generierte Kurzinfo durch den folgenden Inhalt.', ], 'history' => [ 'created_clean' => 'Erstellt von :name :date', 'created_date_clean' => 'Erstellt :date', 'unknown' => 'Unbekannt', 'updated_clean' => 'Zuletzt bearbeitet von :name :date', 'updated_date_clean' => 'Zuletzt bearbeitet :date', 'view' => 'Zeige Objektprotokoll', ], 'image' => [ 'error' => 'Wir konnten das von dir angeforderte Bild nicht laden. Es könnte sein, dass die Website nicht erlaubt, Bilder herunterzuladen (typisch für Squarespace und DeviantArt) oder dass der Link nicht mehr gültig ist.', ], 'is_private' => 'Dieses Objekt ist privat und nicht von Zuschauern einsehbar.', 'keyboard-shortcut' => 'Tastaturkürzel: Code', 'move' => [], 'navigation' => [ 'cancel' => 'Abbrechen', 'or_cancel' => 'oder :cancel', 'skip_to_content' => 'Navigation überspringen', ], 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Hinzufügen', 'deny' => 'verweigern', 'ignore' => 'Ignorieren', 'remove' => 'Entfernen', ], 'bulk_entity' => [ 'allow' => 'erlauben', 'deny' => 'verweigern', 'inherit' => 'Erben', ], 'delete' => 'Löschen', 'edit' => 'Bearbeiten', 'toggle' => 'Umschalten', 'view' => 'Ansicht', ], 'fields' => [ 'member' => 'Mitglied', 'role' => 'Rolle', ], 'helpers' => [ 'setup' => 'Verwenden Sie diese Schnittstelle, um zu optimieren, wie Rollen und Benutzer mit diesem Objekt interagieren können. :allow ermöglicht dem Benutzer oder der Rolle, diese Aktion auszuführen. :deny wird ihnen diese Handlung verweigern. :inherit verwendet die Berechtigung des Benutzers oder der Hauptrolle. Ein Benutzer, der auf :allow eingestellt ist, kann die Aktion ausführen, auch wenn seine Rolle auf :deny eingestellt ist.', ], 'success' => 'Berechtigungen gespeichert.', 'title' => 'Berechtigungen', 'too_many_members' => 'Diese Kampagne hat zu viele Mitglieder (> 10), um in dieser Benutzeroberfläche angezeigt zu werden. Verwenden Sie die Schaltfläche Berechtigung in der Objektansicht, um die Berechtigungen im Detail zu steuern.', ], 'placeholders' => [ 'calendar' => 'Wähle einen Kalender', 'entry' => 'Verwende @ gefolgt von drei Buchstaben, um andere Objekte der Kampagne zu erwähnen.', 'fallback' => 'Wähle :module', 'gallery_image' => 'Wählen Sie ein Bild aus der Kampagnengalerie', 'image_url' => 'Du kannst ein Bild auch von einer URL hochladen', 'journal' => 'Wähle ein Logbuch', 'location' => 'Wähle einen Ort', 'multiple' => 'Wähle eine oder mehrere aus', 'name' => 'Name des Objekts', 'organisation' => 'Wähle eine Organisation', 'parent' => 'übergeordnetes Element wählen', 'tag' => 'Wähle ein Tag', 'timeline' => 'Wähle einen Zeitstrahl', 'type' => 'Art des Objekts', 'user' => 'wähle einen Benutzer', ], 'relations' => [], 'remove' => 'Löschen', 'reorder' => [ 'empty' => 'Keine Elemente zum Neuordnen.', ], 'save' => 'Speichern', 'save_and_close' => 'Speichern und schließen', 'save_and_copy' => 'Speichern und kopieren', 'save_and_new' => 'Speichern und neu', 'save_and_update' => 'Speichern und aktualisieren', 'save_and_view' => 'Speichern und ansehen', 'search' => 'Suchen', 'select' => 'Auswählen', 'tabs' => [ 'abilities' => 'Fähigkeiten', 'inventory' => 'Inventar', 'mentions' => 'Erwähnungen', 'overview' => 'Übersicht', 'permissions' => 'Berechtigungen', 'premium' => 'Premium', 'profile' => 'Profil', 'reminders' => 'Erinnerungen', ], 'titles' => [ 'editing' => ':name editieren', 'new' => 'Neu :module', ], 'tooltips' => [], 'update' => 'Bearbeiten', 'users' => [ 'unknown' => 'Unbekannt', ], 'view' => 'Ansehen', 'visibilities' => [ 'admin' => 'Admin', 'admin-self' => 'Selbst & Admin', 'all' => 'Jeder', 'members' => 'Mitglieder', 'self' => 'Selbst', ], ]; ================================================ FILE: lang/de/dashboard.php ================================================ [ 'customise' => 'Dashboard anpassen', 'follow' => 'Folgen', 'join' => 'beitreten', 'unfollow' => 'Nicht mehr folgen', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Editieren', 'new' => 'neues Dashboard', ], 'create' => [ 'helper' => 'Erstelle ein neues Dashboard für :name und weise es zu, welche Rollen es sehen oder als Standard-Dashboard verwenden können.', 'success' => 'Neues Kampagnen Dashboard :name erstellt', 'title' => 'Neues Kampagnen Dashboard', ], 'custom' => [ 'text' => 'Sie bearbeiten derzeit das Dashboard :name der Kampagne.', ], 'default' => [ 'text' => 'Sie bearbeiten derzeit das Standard Dashboard der Kampagne.', 'title' => 'Standard Dashboard', ], 'delete' => [ 'success' => 'Dashboard :name entfernt', ], 'fields' => [ 'copy_widgets' => 'Widgets kopieren', 'name' => 'Dashboard Name', 'visibility' => 'Sichtbarkeit', ], 'helpers' => [ 'copy_widgets' => 'Duplizieren Sie die Widgets aus dem :name -Dashboard in dieses neue.', ], 'pitch' => 'Erstelle mehrere Dashboards mit benutzerdefinierten Berechtigungen für jede Rolle der Kampagne.', 'placeholders' => [ 'name' => 'Name des Dashboards', ], 'update' => [ 'success' => 'Kampagnen Dashboard :name aktualisiert', 'title' => 'Kampagnen Dashboard :name aktualisieren', ], 'visibility' => [ 'default' => 'Standard', 'none' => 'keiner', 'visible' => 'Sichtbar', ], ], 'helpers' => [ 'follow' => 'Wenn du einer Kampagne folgst, wird sie im Kampagnenwähler (oben rechts) unter deinen Kampagnen angezeigt.', 'join' => 'Diese Kampagne steht neuen Mitgliedern offen. Klicken Sie, um sich zu bewerben, um daran teilzunehmen.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Widget hinzufügen', 'back_to_dashboard' => 'Zurück zum Dashboard.', 'edit' => 'Widget bearbeiten.', 'new' => 'Neues :type widget', ], 'reorder' => [ 'helper' => 'Zieh mich, um mich zu bewegen', 'success' => 'Widgets neu geordnet.', ], 'title' => 'Kampagnen Dashboard Einrichtung', 'tutorial' => [ 'blog' => 'unser Tutorial', 'text' => 'Benötigen Sie Hilfe beim Einrichten Ihres Kampagnen-Dashboards? Lesen Sie :blog für Hilfe und Inspiration.', ], ], 'title' => 'Dashboard', 'welcome' => [], 'widgets' => [ 'advanced_options_boosted' => 'Aktiviere weitere Optionen wie das Anzeigen von Pins mit einer :boosted_campaign.', 'calendar' => [ 'actions' => [ 'next' => 'Datum auf nächsten Tag ändern', 'previous' => 'Datum auf vorigen Tag ändern', ], 'previous_events' => 'Vorige', 'upcoming_events' => 'Bevorstehende', ], 'campaign' => [ 'helper' => 'Dieses Widget zeigte den Kampagnenkopf an. Dieses Widget wird immer im Standard-Dashboard angezeigt.', ], 'create' => [ 'helper' => 'Wähle einen Widget-Typ aus, den du zum Dashboard :name hinzufügen möchtest.', 'helper-default' => 'Wähle einen Widget-Typ aus, den du zum Standard-Dashboard hinzufügen möchtest.', 'success' => 'Widget zum Dashboard hinzugefügt.', 'title' => 'Neues Widget', ], 'delete' => [ 'success' => 'Widget vom Dashboard entfernt.', ], 'fields' => [ 'class' => 'CSS-Klasse', 'dashboard' => 'Dashboard', 'name' => 'Benutzerdefinierter Widget-Name', 'optional-entity' => 'Link zum Objekt', 'order' => 'Sortierung', 'size' => 'Größe', 'width' => 'Breite', ], 'helpers' => [ 'class' => 'Definieren Sie eine benutzerdefinierte CSS-Klasse, die dem Widget hinzugefügt wird.', 'filters' => 'Klicke hier, um mehr über die verfügbaren Filteroptionen zu erfahren.', ], 'orders' => [ 'name_asc' => 'Name aufsteigend', 'name_desc' => 'Name absteigend', 'oldest' => 'Älteste geänderte', 'recent' => 'Kürzlich modifiziert', ], 'preview' => [ 'displays' => [ 'expand' => 'Erweiterbarer Eintrag', 'full' => 'Vollständiger Eintrag', ], 'fields' => [ 'display' => 'Anzeige', ], ], 'random' => [ 'helpers' => [ 'name' => 'Sie können den Namen der zufälligen Objekte mit {name} referenzieren', ], 'type' => [ 'all' => 'Alle', ], ], 'recent' => [ 'advanced_filter' => 'Erweiterter Filter', 'advanced_filters' => [ 'mentionless' => 'Erwähnungslos (Objekte, die andere Objekte nicht erwähnen)', 'unmentioned' => 'Nicht erwähnt (Objekte, die von anderen Objekten nicht erwähnt werden)', ], 'all-entities' => 'Alle Objekte', 'entity-header' => 'Verwenden Sie den Objekt-Header als Bild', 'filters' => 'Filter', 'help' => 'Nur das zuletzt aktualisierte Objekt anzeigen, aber eine vollständige Vorschau des Objektes anzeigen', 'helpers' => [ 'entity-header' => 'Wenn Ihr Objekt über einen Objekt-Header verfügt (erweiterte Kampagnenfunktion), stellen Sie dieses Widget so ein, dass dieses Bild anstelle des Bilds des Objektes verwendet wird.', 'show_attributes' => 'Zeigen Sie die Attribute der Objekte unter dem Eintrag an.', 'show_members' => 'Wenn es sich bei dem Objekt um eine Familie oder Organisation handelt, zeigen Sie ihre Mitglieder unter dem Eintrag an.', 'show_relations' => 'Zeigen Sie die angehefteten Beziehungen des Objekts unter dem Eintrag an.', ], 'show_attributes' => 'Zeige Attribute', 'show_members' => 'Zeige Mitglieder', 'show_relations' => 'Zeige angepinnte Beziehungen', 'singular' => 'Einzelnes Objekt', 'tags' => 'Filtern Sie die Liste der zuletzt geänderten Objekte nach bestimmten Tags.', 'title' => 'Vor kurzem aktualisiert', ], 'tabs' => [ 'advanced' => 'Erweitert', 'setup' => 'Setup', ], 'unmentioned' => [ 'title' => 'Unerwähnte Objekte', ], 'update' => [ 'success' => 'Widget angepasst.', ], 'widths' => [ '0' => 'automatisch', '12'=> 'Komplett (100%)', '3' => 'winzig (25%)', '4' => 'Klein (33%)', '6' => 'Halb (50%)', '8' => 'Weit (66%)', '9' => 'Groß (75%)', ], ], ]; ================================================ FILE: lang/de/dashboards/setup.php ================================================ [ 'add' => 'Widget hinzufügen', ], 'sections' => [ 'switch' => 'Wechseln zu...', ], 'tooltips' => [ 'add' => 'Klicke hier, um ein neues Widget zum Dashboard hinzuzufügen.', 'switch' => 'Klicke hier, um zu einem anderen Dashboard zu wechseln.', ], ]; ================================================ FILE: lang/de/dashboards/widgets/calendar.php ================================================ 'Zeigt anstehende Erinnerungen an.', 'name' => 'Kalender', ]; ================================================ FILE: lang/de/dashboards/widgets/campaign.php ================================================ 'Zeigt eine Vorschau der Kampagne.', 'name' => 'Kampagne', ]; ================================================ FILE: lang/de/dashboards/widgets/header.php ================================================ 'Zeigt eine Textüberschrift an.', 'name' => 'Kopfzeile', ]; ================================================ FILE: lang/de/dashboards/widgets/help.php ================================================ 'Zeigt Hilfe und Community-Links an.', 'name' => 'Hilfe & Community', 'title' => 'Hilfe & Community', ]; ================================================ FILE: lang/de/dashboards/widgets/preview.php ================================================ 'Zeigt ein bestimmtes Objekt an.', 'name' => 'ausgewähltes Objekt', ]; ================================================ FILE: lang/de/dashboards/widgets/random.php ================================================ 'Zeigt eine zufälliges Objekt aus der Kampagne an.', 'name' => 'zufälliges Objekt', ]; ================================================ FILE: lang/de/dashboards/widgets/recent.php ================================================ 'Zeigt eine Liste der zuletzt geänderten Objekte an.', 'name' => 'Kürzlich geänderte Objekte', ]; ================================================ FILE: lang/de/dashboards/widgets/welcome.php ================================================ 'Zeigt eine Willkommensnachricht mit Tipps an.', 'endings' => [], 'focus' => [ 'text' => 'Hier, das bin ich!', 'title' => 'Hey', ], 'intros' => [ '1' => 'Begrüße dein neues Worldbuilding-Zuhause, :user! Wir haben Ihre erste Kampagne eingerichtet und zwei Beispiele für :Charaktere und :Orte beigefügt. Diese sind auch hier auf dem Dashboard der Kampagne zu sehen.', '2' => 'Um loszulegen, klicke auf den großen :new-entity-Button (oder drücke :letter auf deiner Tastatur), und klicke auf :characters, um deinen ersten Charakter zu erstellen. So einfach ist das! Du kannst alle deine Charaktere, Orte und andere :entities in der Seitenleiste links auf der Seite finden.', '3' => 'Hier sind unsere 5 besten Tricks für die Verwendung von Kanka', ], 'name' => 'Willkommen', 'title' => 'Willkommen bei :kanka! 🎉', 'tricks' => [ '1' => 'Schreibe beim Verfassen von Beschreibungen die Namen von Elementen der Kampagne nicht neu. Gebe stattdessen :code und drei Buchstaben ein, um :andere Elemente der Kampagne zu erwähnen. Diese Erwähnungen werden automatisch aktualisiert, wenn du die Namen änderst.', '2' => 'Um den Namen, das Thema oder das Bild der Kampagne zu bearbeiten, klicke in der Seitenleiste auf :world und dann auf die Schaltfläche :edit.', '3' => 'Schreibe geheime Informationen über Objekte als :posts statt in das Haupttextfeld.', '4' => 'Lade deine Freunde zur Kampagne ein, indem du auf :world und :members klickst. Von dort aus kannst du Einladungslinks erstellen.', '5' => 'Du kannst diese Willkommensnachricht entfernen und andere Informationen auf dieser Seite (genannt Dashboard) anzeigen. Scrolle nach unten und klicke auf die Schaltfläche :.', 'mention' => 'erwähnen', ], ]; ================================================ FILE: lang/de/datagrids.php ================================================ [ 'back_to' => 'zurück zu :name', ], 'modes' => [ 'flatten' => 'Umschalten auf ein flacheres Layout', 'grid' => 'Wechsel in die Rasteransicht', 'nested' => 'Zu einem verschachtelten Layout wechseln', 'table' => 'Wechsel in die Tischansicht', ], 'tooltips' => [ 'nested' => 'Dieses Objekt hat untergeordnete Objekte. Klicke auf das Bild, um es anzuzeigen.', ], ]; ================================================ FILE: lang/de/datetime.php ================================================ 'Tag', 'days' => 'Tage', 'elapsed_ago' => 'vor :duration', 'hour' => 'Stunde', 'hours' => 'Stunden', 'just_now' => 'gerade eben', 'minute' => 'Minute', 'minutes' => 'Minuten', 'month' => 'Monat', 'months' => 'Monate', 'second' => 'Sekunde', 'seconds' => 'Sekunden', 'week' => 'Woche', 'weeks' => 'Wochen', 'year' => 'Jahr', 'years' => 'Jahre', ]; ================================================ FILE: lang/de/default.php ================================================ 'Seitentitel', ]; ================================================ FILE: lang/de/dice_roll_results.php ================================================ [ 'title' => 'Ergebnisse des Würfelwurfs', ], ]; ================================================ FILE: lang/de/dice_rolls.php ================================================ [ 'title' => 'Neuer Würfelwurf', ], 'destroy' => [ 'dice_roll' => 'Würfelwurf entfernt.', ], 'edit' => [], 'fields' => [ 'created_at' => 'Gewürfelt am', 'parameters' => 'Parameter', 'results' => 'Ergebnisse', 'rolls' => 'Würfe', ], 'hints' => [ 'parameters' => 'Was sind meine Würfeloptionen?', ], 'index' => [ 'actions' => [ 'results' => 'Ergebnisse', ], ], 'lists' => [ 'empty' => 'Erstelle und speicher Würfelwürfe für die Kampagne, um die Ergebnisse direkt in Kanka zu verfolgen.', ], 'placeholders' => [ 'name' => 'Name des Würfelwurfs', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Wurf', ], 'error' => 'Würfelwurf nicht erfolgreich. Kann die Parameter nicht parsen.', 'fields' => [ 'creator' => 'Ersteller', 'date' => 'Datum', 'result' => 'Ergebnis', ], 'hint' => 'Alle Würfe, die für dieses Würfelwurf-Template gewürfelt wurden.', 'success' => 'Würfel gewürfelt.', ], 'show' => [ 'tabs' => [ 'results' => 'Ergebnisse', ], ], ]; ================================================ FILE: lang/de/emails/activity/email.php ================================================ 'Die E-Mail-Adresse Ihres Kanka-Kontos wurde geändert in :email.', 'title' => 'E-Mail geändert', ]; ================================================ FILE: lang/de/emails/activity/password.php ================================================ 'Das Passwort für dein Kanka-Konto wurde geändert.', 'help' => 'Wenn du das nicht warst, kontaktiere uns bitte unter :email.', 'title' => 'Passwort geändert', ]; ================================================ FILE: lang/de/emails/purge/first.php ================================================ 'Wenn du dein Konto regelmäßig nutzt, mache dir keine Sorgen, wir löschen nur Konten und Kampagnen, die nicht aktiv genutzt werden.', 'help' => 'Brauchst du Hilfe bei der Benutzung von Kanka? Trete unserem :discord bei oder kontaktiere uns unter :email', 'intro_account' => 'Wir möchten dir mitteilen, dass dein Konto in :Tagen gelöscht wird, da du es in den letzten :Monaten nicht genutzt hast.', 'intro_campaigns' => 'Wir möchten dich darüber informieren, dass dein Konto und die folgenden Kampagnen in :Tagen gelöscht werden, da du es in den letzten :Monaten nicht genutzt hast.', 'keep' => 'Wenn du dein Konto aktiv halten möchtest, melde dich bitte innerhalb der nächsten :Tage an.', 'title' => 'dein Kanka-Konto wird gelöscht in: Anzahl Tage', 'warning' => [ 'account' => 'Sobald dies geschehen ist, werden alle Daten, die mit deinem Konto unter :email verbunden ist, endgültig gelöscht.', 'campaigns' => 'Sobald dies geschehen ist, werden alle Daten, die mit deinem Konto verbunden sind, unter email :email, sowie die folgenden Kampagnen endgültig gelöscht.', ], ]; ================================================ FILE: lang/de/emails/purge/second.php ================================================ 'Dies ist eine letzte Erinnerung daran, dass das Kanka-Konto unter der E-Mail-Adresse :email in :amount Tagen gelöscht wird, da du es in den letzten :duration Monaten nicht verwendet hast.', 'title' => 'Letzte Warnung: Ihr Kanka-Konto wird in :amount Tagen gelöscht', ]; ================================================ FILE: lang/de/emails/subscriptions/expiring.php ================================================ 'Aktualisiere deine Kartendetails', 'primary' => 'Dies ist eine automatische Warnung, dass Ihr :brand **** :last bald abläuft.', 'title' => 'Ablaufende Karte für Ihr Abonnement', 'valid' => 'Wenn Sie Ihr Abonnement behalten möchten, bitte :action.', ]; ================================================ FILE: lang/de/emails/subscriptions/upcoming.php ================================================ 'Wenn Sie Ihr Abonnement nicht erneuern möchten, melden Sie sich bitte bei Ihrem Kanka-Konto an und :link.', 'closing' => 'Mit freundlichen Grüßen,', 'dear' => 'Lieber: Name', 'link' => 'Kündigen Sie Ihr Abonnement', 'notice' => 'Wichtiger Hinweis zur Verlängerung:', 'primary' => 'Dies ist eine automatische Erinnerung, dass wir Ihr :brand **** :last on :date automatisch für Ihr Kanka-Abonnement belasten werden.', 'title' => 'Jährliche Zahlung für Ihr Kanka-Abonnement', 'valid' => 'Bitte stellen Sie sicher, dass Ihre Kreditkarte am Tag der Zahlung gültig ist.', ]; ================================================ FILE: lang/de/emails/subscriptions/validation.php ================================================ 'E-Mail-Überprüfung des Kanka-Kontos.', ]; ================================================ FILE: lang/de/emails/validation.php ================================================ 'Validierung fehlgeschlagen, bitte versuche es erneut.', 'modal' => 'Für ein Abonnement ist eine bestätigte E-Mail erforderlich. Bitte prüfe deinen Posteingang auf einen Link zur Bestätigung deiner E-Mail, bevor du den Anmeldevorgang fortsetzt.', 'success' => 'E-Mail erfolgreich validiert.', ]; ================================================ FILE: lang/de/emails/welcome/2024.php ================================================ 'Viel Spaß beim Weltenbau und vielen Dank, dass du dich mit uns auf diese Reise begeben hast,', 'header' => 'Willkommen beim besten Ort, um deine Kampagne zu erstellen, :name!', 'lead_1' => 'Kanka wurde von passionierten Rollenspielern entwickelt, die einen einfachen und gemeinschaftlichen Ansatz für die Welterstellung wollten, ohne dabei Kompromisse bei den Funktionen einzugehen.', 'lead_2' => 'Daraus ist Kanka entstanden. Wir sind hier, um dir bei der Organisation deiner Kampagne zu helfen, damit du dich darauf konzentrieren kannst, deine Welt zum Leben zu erwecken.', 'ps' => 'PS: Wenn du mit uns in Kontakt treten willst , findest du uns auf :discord, oder unter :email.', 'what_1' => 'Um dir den Einstieg zu erleichtern, haben wir deine erste Kampagne erstellt und zwei Beispielcharaktere und -orte hinzugefügt. Du kannst :starten, wenn du möchtest.', 'what_2' => 'Du solltest dir auch diese Quellen ansehen:', 'what_3' => 'Unser :kb, wenn du grundlegende Fragen zu den Funktionen von Kanka und deinem Konto hast.', 'what_4' => 'Im :doc wird alles ausführlicher behandelt.', 'what_5' => 'Und schließlich: Kampagnen, die zeigen, was andere mit Kanka gemacht haben.', 'what_new' => 'ein neues beginnen', 'what_now' => 'Was nun?', 'why' => 'Du hast diese E-Mail erhalten, weil du dich auf unserer Website angemeldet hast.', ]; ================================================ FILE: lang/de/emails/welcome.php ================================================ [ 'basics' => [ 'text_1' => 'Bei einem so umfangreichen Werkzeug wie Kanka kann es schwierig sein, zu wissen, wo man anfangen soll oder was zu tun ist. Unser :kb behandelt die grundlegendsten Fragen, die du haben könntest, und für weitere Hilfe kannst du unser :doc besuchen.', 'title' => 'Die Grundlagen', ], 'chat' => [ 'text_1' => 'Wir hören gerne von unseren Nutzern. Am aktivsten sind wir auf :discord, wo du viele unserer engagierten Nutzer, ein Onboarding-Team sowie die Gründer von Kanka findest, die dir alle Fragen beantworten können. Du kannst uns auch eine E-Mail an :email schreiben.', 'title' => 'Möchtest du dich unterhalten?', ], 'intro' => [ 'header' => 'Willkommen in der besten Worldbuilding-Community, :name!', 'link' => 'Geh in deine Welt!', 'text_1' => 'Begrüße dein neues Worldbuilding-Zuhause, :name! Die Gemeinschaft liegt in unserer DNA, und wir freuen uns, dass du dich uns anschließt. Kanka ist die Idee leidenschaftlicher Rollenspieler, die an eine vereinfachte und gemeinschaftliche Herangehensweise an den Weltenbau glauben, ohne Kompromisse bei den Funktionen einzugehen.', 'text_2' => 'Wir haben deine erste Kampagne eingerichtet und zwei Beispielcharaktere und -orte beigefügt, um dir den Einstieg zu erleichtern.', ], 'preview' => 'Sei Teil der besten Worldbuilding-Community, :name!', ], 'header' => 'Willkommen bei Kanka :name', 'header_sub' => 'Herzlichen Glückwunsch, Sie haben den ersten Schritt in der Erschaffung Ihrer Welt getan :kanka!', 'pricing' => 'Preisgestaltung', 'section_1' => 'Wohin jetzt?', 'section_11' => 'Erschaffe deine Welt,', 'section_2' => 'Die wichtigste Ressource ist: :discord, wo Sie viele unserer engagierten Benutzer, ein Onboarding-Team sowie den Gründer von Kanka finden, die alle Ihre Fragen beantworten können.', 'section_4' => 'Unser :youtube bietet Videos zu den Grundlagen von Kanka. Obwohl noch nicht alle Themen behandelt werden, fügen wir regelmäßig neue Videos hinzu.', 'section_4_v2' => 'Unsere :knowledge-base deckt die grundlegendsten Fragen ab, die du möglicherweise hast, und für eine umfassendere Hilfe kannst du zu unserer :documentation wechseln!', 'section_6' => 'Kontaktiere uns', 'section_7' => 'Wenn Sie keine Antwort auf Ihre Fragen gefunden haben oder einfach nur Kontakt aufnehmen möchten, finden Sie uns auf: :facebook oder per E-Mail unter :email. Wir sind ein kleines Team von 2 Freunden, aber wir beantworten jede E-Mail, die wir erhalten. Bitte zögern Sie nicht!', 'section_8' => 'Eine letzte Sache', 'section_9_v2' => 'Wir haben dafür gesorgt, dass alle Kernfunktionen von Kanka kostenlos sind, und wir werden es immer so halten. Wenn Sie uns jedoch bei diesem Projekt unterstützen möchten, können Sie Abonnent werden und Zugang zu zusätzlichen Funktionen sowie zu unserer ewigen Dankbarkeit erhalten. Weitere Informationen finden Sie auf der :pricing page.', 'social_account' => 'Wenn Sie Probleme beim Anmelden in Ihrem Konto haben, eine kleine Erinnerung daran, dass Sie ein :provider login verwenden. Sie können dies in Ihren Kontoeinstellungen ändern.', 'title' => 'Erste Schritte mit Kanka', ]; ================================================ FILE: lang/de/entities/abilities.php ================================================ [ 'add' => 'Füge eine Fähigkeit hinzu', 'reset' => 'Fähigkeit zurücksetzen', 'sync' => 'Hinzufügen von Rassen', ], 'charges' => [ 'left' => ':amount verbleibend', ], 'create' => [ 'helper' => 'Verknüpfe eine von mehreren Fähigkeiten mit :name.', 'success' => 'Fähigkeit :ability hinzugefügt zu :entity', 'success_multiple' => 'Fähigkeiten :abilities hinzugefügt zu :entity', 'title' => 'Fügen Sie eine Fähigkeit hinzu zu :name', ], 'fields' => [ 'note' => 'Notiz', 'position' => 'Position', ], 'groups' => [ 'unorganised' => 'Unorganisiert', ], 'helpers' => [ 'note' => 'Sie können Objekte mit erweiterten Erwähnungen (z. B. :code) und Attributen der Objekte (z. B. :attr) in diesem Feld referenzieren.', 'recharge' => 'Setze alle Ladungen für bereits genutzte Fähigkeiten zurück.', 'sync' => 'Importiere Fähigkeiten, die in der Rasse des Charakters definiert sind.', ], 'import' => [ 'errors' => [ 'no_race' => 'Der Charakter hat keine Spezies', 'not_character' => 'Das Objekt ist kein Charakter', ], 'helper' => 'Fähigkeiten der folgenden Rassen hinzufügen :name gehört zu:', 'no_abilities' => 'Derzeit gibt es keine Möglichkeiten, aus den Rassen zu importieren :name gehört zu.', 'race_abilities' => '{1} :name (:count ability)|[2,*] :name (:count abilities)', 'success' => '{1} :count Fähigkeit importiert. | [2, *] :count Fähigkeiten importieren.', ], 'recharge' => [ 'success' => 'Alle Ladungen wurden zurückgesetzt.', ], 'reorder' => [ 'parentless' => 'kein übergepordnetes Objekt', 'success' => 'Fähigkeiten erfolgreich neu geordnet.', ], 'show' => [ 'helper' => 'Fügen Sie diesem Objekt Fähigkeiten hinzu. Sie können die Sichtbarkeit jederzeit bearbeiten oder eine Fähigkeit entfernen. Fähigkeiten, die zu derselben übergeordneten Fähigkeit gehören, werden als Filterfelder angezeigt.', 'reorder' => 'Fähigkeiten neu anordnen', 'title' => 'Objektfähigkeiten für :name', ], 'types' => [ 'unorganised' => 'Die Fähigkeiten sind nach ihrem übergeordneten Feld gruppiert und werden hier wieder angezeigt.', ], 'update' => [ 'success' => 'Objektfähigkeit: Fähigkeit aktualisiert.', 'title' => 'Objektfähigkeit für :name', ], ]; ================================================ FILE: lang/de/entities/actions.php ================================================ [ 'set' => 'Vorlage erstellen', 'success' => [ 'set' => 'Objekt :name als Vorlage gesetzt', 'unset' => 'Objekt :name nicht länger als Vorlage gesetzt', ], 'toggle' => 'Umgeschalteter Vorlagenstatus.', 'unset' => 'Vorlage entfernen', ], ]; ================================================ FILE: lang/de/entities/aliases.php ================================================ [ 'add' => 'Alias hinzufügen', ], 'create' => [ 'helper' => 'Erstelle einen Alias für :name, damit er in der globalen Suche und durch :code-Erwähnungen gefunden werden kann.', 'success' => 'Alias ​​:name zu :entity hinzugefügt.', 'title' => 'Add an alias to :name', ], 'destroy' => [ 'success' => 'Aliasname: Name entfernt.', ], 'fields' => [ 'name' => 'Name', ], 'helpers' => [ 'primary' => 'Das Festlegen eines oder mehrerer Aliase für das Objekt macht sie in der globalen Suche (obere Leiste) und durch Erwähnungen auffindbar.', ], 'limit' => 'Die Kampagne hat die Grenze der verfügbaren Aliase erreicht. Um unbegrenzt Aliase zu erhalten, schalte Premium-Funktionen frei.', 'pitch' => 'Erstelle Aliase für dieses Objekt, um es einfach über die Suche und Erwähnungen zu finden.', 'placeholders' => [ 'name' => 'Neuer Alias', ], 'unboosted' => [], 'update' => [ 'success' => 'Alias ​​:name für :entity aktualisiert.', 'title' => 'Alias ​​für :name aktualisieren', ], ]; ================================================ FILE: lang/de/entities/assets.php ================================================ [ 'alias' => 'Alias', 'file' => 'Datei', 'link' => 'Link', ], 'copy_alias' => [ 'success' => 'Alias-Erwähnung in die Zwischenablage kopiert.', ], 'show' => [ 'title' => 'Anhänge von :name', ], ]; ================================================ FILE: lang/de/entities/attributes.php ================================================ [ 'load' => 'Laden', 'manage' => 'Verwalten', 'more' => 'Mehr Optionen', 'remove_all' => 'Alles löschen', 'save_and_edit' => 'Bestätigen und Bearbeiten', 'save_and_story'=> 'Bestätigen und Ansehen', 'show_hidden' => 'Ausgeblendete Attribute anzeigen', 'toggle_privacy'=> 'Privat/öffentlich', ], 'create' => [], 'destroy' => [], 'edit' => [], 'errors' => [ 'api' => 'Ungültige Daten', 'loop' => 'Diese Attributberechnung enthält eine Endlosschleife!', 'no_attribute_selected' => 'Wähle zunächst ein oder mehrere Eigenschaften aus.', 'too_many_v2' => 'Maximale Felder erreicht (:count/:max). Lösche zuerst einige Eigenschaften, bevor du weitere hinzufügen kannst.', ], 'fields' => [ 'community_templates' => 'Community Vorlagen', 'is_private' => 'Private Attribute', 'is_star' => 'Angepinnt', 'preferences' => 'Einstellungen', 'value' => 'Wert', ], 'filters' => [ 'name' => 'Attributname', 'value' => 'Attributwert', ], 'helpers' => [ 'delete_all' => 'Möchten Sie wirklich alle Attribute dieses Objekts löschen?', 'is_private' => 'Erlaube nur Mitgliedern der Rolle :admin-role, die Attribute dieses Objekts zu sehen.', 'setup' => 'Sie können Elemente wie TP oder Intelligenz eines Objekts mittels Attributen darstellen. Attribute können Sie manuell hinzufügen, indem sie auf den :manage Button klicken oder Sie wenden eine Attributsvorlage an.', ], 'hints' => [], 'index' => [ 'success' => 'Attribute für :entity aktualisiert', 'title' => 'Attribute für :name', ], 'labels' => [ 'checkbox' => 'Checkbox Name', 'name' => 'Attributsname', 'section' => 'Abteilungsname', 'value' => 'Attributswert', ], 'live' => [ 'success' => 'Attribute :attribute aktualisiert', 'title' => ':attribute aktualisieren', ], 'placeholders' => [ 'attribute' => 'Anzahl der Eroberungen, Challenge Rating, Initiative, Bevölkerung', 'block' => 'Blockname', 'checkbox' => 'Checkbox Name', 'icon' => [ 'class' => 'FontAwesome oder RPG Awesome class: fas fa-users', 'name' => 'Symbolname', ], 'number' => 'Nummernname', 'random' => [ 'name' => 'Attributname', 'value' => '1-100 oder Liste von Werten, die durch ein Komma getrennt sind', ], 'section' => 'Abteilungsname', 'value' => 'Wert des Attributs', ], 'ranges' => [ 'text' => 'Verfügbare Optionen: :options', ], 'sections' => [ 'unorganised' => 'Unorganisiert', ], 'show' => [ 'hidden' => 'Ausgeblendete Attribute', 'title' => ':name Attribut', ], 'template' => [ 'load' => [ 'success' => 'Vorlage geladen', 'title' => 'Aus Vorlage laden', ], 'pitch' => 'Lade Attribute aus einer Attributvorlage oder installierte Plugins aus dem :plugin. Gib diesem Lesezeichen ein spezielles :fontawesome Symbol, wie :example, mit einem :premium.', 'success' => 'Attributvorlage :name wird auf :entity angewendet', 'title' => 'Wende eine Attributvorlage auf :name an', ], 'title' => 'Eigenschaften', 'toasts' => [ 'bulk_deleted' => 'Eigenschaften gelöscht', 'bulk_privacy' => 'Eigenschaften Privatsphäre umgeschaltet', 'lock' => 'Attribut gesperrt', 'pin' => 'Attribut angepinnt', 'unlock' => 'Attribut freigeschaltet', 'unpin' => 'Attribut nicht angepinnt', ], 'tutorials' => [], 'types' => [ 'attribute' => 'Attribute', 'block' => 'Block', 'checkbox' => 'Checkbox', 'icon' => 'Symbol', 'number' => 'Nummer', 'random' => 'Zufällig', 'section' => 'Abteilung', 'text' => 'Mehrzeiliger Text', ], 'update' => [ 'success' => 'Attribut von :entity aktualisiert', ], 'visibility' => [ 'entry' => 'Das Attribut wird im Objektmenü angezeigt.', 'private' => 'Attribut nur für Mitglieder der Rolle "Admin" sichtbar.', 'public' => 'Attribut für alle Mitglieder sichtbar.', 'tab' => 'Das Attribut wird nur im Attribute-Reiter angezeigt.', ], ]; ================================================ FILE: lang/de/entities/children.php ================================================ 'Unterobjekte', ]; ================================================ FILE: lang/de/entities/events.php ================================================ [ 'helper' => 'Erstelle eine Erinnerung, um :name mit einem Kalender zu verknüpfen.', ], 'fields' => [ 'type' => 'Ereignistyp', ], 'helpers' => [ 'characters' => 'Wenn Sie den Typ entweder als Geburts- oder als Todesdatum für diesen Charakter festlegen, wird automatisch dessen Alter berechnet. :more.', 'founding' => 'Wenn su den Typ als :type festlegst, wird das Alter des Objekts seit der Gründung automatisch berechnet.', ], 'show' => [ 'actions' => [ 'add' => 'Erinnerung hinzufügen', ], 'title' => ':name Erinnerung', ], 'types' => [ 'birth' => 'Geburt', 'birthday' => 'Geburtstag', 'death' => 'Tod', 'founded' => 'Gegründet', 'primary' => 'Primär', ], 'years-ago' => '{1} :Jahr vor zählen|[2,*] :Jahr vor zählen', ]; ================================================ FILE: lang/de/entities/files.php ================================================ [ 'max' => [ 'helper' => 'Du kannst keine weiteren Dateien anhängen, es sei denn, du entfernst eine vorhandene Datei.', 'limit' => 'Dieses Objekt hat sein Dateilimit erreicht.', ], 'upgrade' => [ 'limit' => 'Du hast das Limit von :limit Dateien für dieses Objekt erreicht.', 'premium' => 'Upgrade auf eine Premium-Kampagne, um unbegrenzt Dateien anzuhängen und noch mehr kreative Flexibilität zu erhalten.', 'upgrade' => 'Upgrade auf eine Premium-Kampagne, um bis zu :limit Dateien anzuhängen und noch mehr kreative Flexibilität zu erhalten.', ], ], 'create' => [ 'helper' => 'Füge eine Datei zu :name hinzu. Die Datei wird auf das Speicherlimit der Galerie angerechnet.', 'success_plural' => '{1} Datei :name hinzugefügt.|[2,*] :count der hinzugefügten Dateien.', 'title' => 'Neue Datei für :entity', ], 'destroy' => [ 'success' => 'Datei :file entfernt', ], 'fields' => [ 'file' => 'Datei', 'files' => 'Dateien', 'name' => 'Dateiname', ], 'max' => [ 'title' => 'Limit erreicht', ], 'update' => [ 'success' => 'Datei :file aktualisiert', 'title' => 'Objektdatei aktualisiert', ], ]; ================================================ FILE: lang/de/entities/image.php ================================================ [ 'change_focus' => 'Fokuspunkt ändern', 'change_visibility' => 'Sichtbarkeit ändern', 'replace_image' => 'Bild ersetzen', 'save-replace' => 'Bild ersetzen', 'save_focus' => 'Fokus speichern', 'view' => 'Bild zeigen', ], 'call-to-action' => 'Klicke auf das Bild des Objekts, um seinen Fokuspunkt festzulegen, anstatt die automatische Vermutung zu verwenden.', 'focus' => [ 'breadcrumb' => 'Bildfokus', 'helper' => 'Klicken Sie auf das Bild, um den Fokuspunkt festzulegen. Klicken Sie auf den Fokuspunkt, um ihn zu entfernen.', 'panel_title' => 'Bildfokus', 'success' => 'Bildfokus aktualisiert', 'title' => 'Objekt :Name des Bildfokus', 'unboosted' => 'Das Setzen eines Bildfokuspunktes ist :boosted-campaigns vorbehalten.', 'warning' => 'Der Fokuspunkt für :gallery -Bilder wird von allen Objekten, die dasselbe Bild verwenden, gemeinsam genutzt.', ], 'gallery_permissions' => [ 'admin' => 'Dieses Galeriebild ist nur für die Mitglieder der :admin -Rolle der Kampagne sichtbar.', 'adminself' => 'Dieses Galeriebild ist nur für den :creator und die Mitglieder der :admin -Rolle der Kampagne sichtbar.', 'member' => 'Dieses Galeriebild ist nur für die Mitglieder der Kampagne sichtbar.', 'self' => 'Dieses Bild ist nur für dich sichtbar.', ], 'replace' => [ 'breadcrumb' => 'Bildaustausch', 'panel_title' => 'Objektbild ersetzt', 'success' => 'Bild ersetzt', 'title' => 'Objekt :name Bild ersetzt', ], 'visibility' => [ 'helper' => 'Ändere die Sichtbarkeit des Galeriebildes und bestimme, wer es sehen kann.', 'updated' => 'Sichtbarkeit des Bildes aktualisiert.', ], ]; ================================================ FILE: lang/de/entities/inventories.php ================================================ [ 'copy_from_entity' => 'Von einem anderen Objekt kopieren', 'copy_inventory' => 'Inventar kopieren', 'generate' => 'Generieren', 'multiple' => 'Elemente hinzufügen', ], 'copy' => [ 'helper' => 'Kopiere das gesamte Inventar eines Objekts nach :name.', ], 'create' => [ 'helper' => 'Füge einen Gegenstand zum Inventar von :name hinzu. Er kann optional mit einem vorhandenen Objekt aus der Kampagne verknüpft werden.', 'success' => 'Gegenstand :item zu :entity hinzugefügt', 'success_bulk' => '{0} Kein Element zu :entity.|{1} hinzugefügt Element :count wurde zu :entity.|[2,*] hinzugefügt Elemente :count wurden zu :entity.', 'title' => 'Füge einen Gegenstand zu :name hinzu', ], 'default_position' => 'Unorganisiert', 'destroy' => [ 'success' => 'Gegenstand :item wurde von :entity entfernt', 'success_position' => 'Elemente in :position werden aus :entity entfernt.', ], 'fields' => [ 'amount' => 'Menge', 'copy_entity_entry_v2' => 'Objekteintrag verwenden', 'description' => 'Beschreibung', 'is_equipped' => 'Ausgestattet', 'item_amount' => 'Anzahl der Artikel', 'match_all' => 'Alle Tags abgleichen', 'name' => 'Name', 'position' => 'Position', 'qty' => 'ANZ', 'replace' => 'Inventar ersetzen', ], 'generate' => [ 'helper' => 'Erstelle eine Inventarliste für :name basierend auf den vorhandenen Elementen in der Kampagne.', 'title' => 'Inventar generieren', ], 'helpers' => [ 'amount' => 'Anzahl der Artikel', 'copy_entity_entry_v2' => 'Zeigt den Eintrag des Objekts anstelle der benutzerdefinierten Beschreibung an.', 'description' => 'Hinzufügen einer benutzerdefinierten Beschreibung für den Artikel', 'is_equipped' => 'Markiere diese Gegenstände als ausgerüstet.', 'name' => 'Gebe dem Objekt einen Namen. Ein Name ist erforderlich, wenn kein Objekt ausgewählt ist.', 'replace' => 'Ersetzt das aktuelle Inventar durch deas generierte Inventar.', ], 'placeholders' => [ 'amount' => 'Die Menge', 'description' => 'Benutzt, Beschädigt, usw.', 'name' => 'Erforderlich, wenn kein Element ausgewählt ist', 'position' => 'Ausgerüstet, Rucksack, Lager, Bank', ], 'show' => [ 'helper' => 'Objekten können Gegenstände zugeordnet werden um ein Inventar zu generieren.', 'title' => 'Objekt :name: Inventar', 'unsorted' => 'Unsortiert', ], 'togglers' => [ 'hide' => [ 'price' => 'Preis ausblenden', 'quantity' => 'Menge ausblenden', 'size' => 'Größe ausblenden', 'weight' => 'Gewicht ausblenden', ], 'show' => [ 'price' => 'zeige Preis', 'quantity' => 'zeige Menge', 'size' => 'zeige Größe', 'weight' => 'zeige Gewicht', ], ], 'tooltips' => [ 'equipped' => 'Dieser Artikel ist ausgestattet mit', ], 'tutorials' => [ 'all' => 'Verfolge, was :name besitzt, lagert oder anbietet, indem du diesem Inventar Artikel hinzufügst.', ], 'update' => [ 'success' => 'Gegenstand \':name\' für :entity aktualisiert', 'title' => 'Aktualisiere Gegenstand von :name', ], ]; ================================================ FILE: lang/de/entities/links.php ================================================ [ 'add' => 'Link hinzufügen', ], 'call-to-action' => 'Füge Links zu externen Ressourcen auf diesem Objekt hinzu, wie z. B. zu DnDBeyond, und sie werden direkt in der Übersicht des Objekts angezeigt.', 'create' => [ 'helper' => 'Füge einen externen Link zu :name hinzu, zum Beispiel zu deiner DnDBeyond-Seite.', 'success' => 'Link :name hinzugefügt zu :entity.', 'title' => 'Fügen Sie einen Link hinzu zu :name', ], 'destroy' => [ 'success' => 'Link :name entfernt von :entity.', ], 'fields' => [ 'icon' => 'Symbol', 'name' => 'Name', 'position' => 'Position', 'url' => 'URL', ], 'go' => [ 'actions' => [ 'confirm' => 'Ich bin mir sicher', 'trust' => 'Frage nicht noch einmal', ], 'description' => 'Dieser Link führt dich zu :link. Bist du sicher, dass du dorthin gehen möchtest?', 'title' => 'verlasse Kanka', ], 'helpers' => [ 'icon' => 'Sie können das für den Link angezeigte Symbol anpassen. Verwenden Sie eines der kostenlosen Symbole von :fontawesome oder lassen Sie dieses Feld für die Standardeinstellung leer.', 'parent' => 'Zeige diesen Quick-Link nach einem Element der Seitenleiste an, anstatt im Quick-Links-Bereich der Seitenleiste.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Durch geboostete Kampagnen können Links zu Objekten hinzufügen, die auf externe Websites verweisen.', 'title' => 'Links für :name', ], 'unboosted' => [], 'update' => [ 'success' => 'Link :name aktualisiert für :entity.', 'title' => 'Link aktualisieren für :name', ], ]; ================================================ FILE: lang/de/entities/logs.php ================================================ [ 'create' => 'Erstellen', 'create_post' => 'erstelle Beitrag ":post"', 'delete' => 'Löschen', 'delete_post' => 'Beitrag gelöscht', 'reorder_post' => 'Beiträge neu geordnet', 'restore' => 'wiederherstellen', 'reveal' => 'Details anzeigen', 'update' => 'Aktualisieren', 'update_post' => 'aktualisiere Beitrag ":post"', 'view' => 'Änderungen anzeigen', ], 'call-to-action' => 'Vollständige Änderungsprotokolle für bis zu :amount Tage sind für Superboosted-Kampagnen verfügbar.', 'fields' => [ 'action' => 'Aktion', 'date' => 'Datum', ], 'filters' => [ 'keywords' => 'Schlüsselwörter', ], 'impersonated' => 'Dargestellt von :name', 'none' => 'Keine', 'show' => [ 'title' => 'Objektprotokoll: :name', ], 'tooltips' => [ 'post' => 'Zum Beitrag gehen', ], ]; ================================================ FILE: lang/de/entities/map-points.php ================================================ 'Dieses Objekt ist auf den folgenden Karten angepinnt.', 'title' => ':name Kartenpunkte', ]; ================================================ FILE: lang/de/entities/mentions.php ================================================ [ 'element' => 'Element', 'type' => 'Typ', ], 'helper' => 'Das Folgende ist eine Liste von Objekten, die dieses Objekt in ihrem Feld "Eintrag" erwähnen.', 'mentioned_in_v2' => 'Dieses Objekt wird in :count Objekte,, Objektnotizen oder Kampagnen erwähnt. :more.', 'see_more' => 'Details zeigen', 'show' => [ 'title' => 'Erwähnungen von Objekt: :name', ], 'title' => 'Erwähntes Objekt', ]; ================================================ FILE: lang/de/entities/move.php ================================================ [ 'copy' => 'Kopiere', 'transfer' => 'Übertragung', ], 'errors' => [ 'permission' => 'Sie dürfen keine Objekte dieses Typs in der Zielkampagne erstellen.', 'permission_update' => 'Sie dürfen dieses Objekt nicht verschieben.', 'same_campaign' => 'Sie müssen eine andere Kampagne auswählen, in die das Objekt verschoben werden soll.', 'unknown_campaign' => 'Unbekennt Kamapgne', ], 'fields' => [ 'campaign' => 'Zielkampagne', 'copy' => 'Kopiere', 'select_one' => 'Kampagne auswählen', ], 'helpers' => [ 'copy' => 'Erstelle eine Kopie des Objekts in der Zielkampagne.', ], 'panel' => [ 'description' => 'Wählen Sie eine Kampagne aus, in die Sie verschieben möchten, oder erstellen Sie eine Kopie dieses Objekts.', 'description_bulk_copy' => 'Wählen Sie eine Kampagne aus, in die Sie die ausgewählten Objekte kopieren möchten.', 'title' => 'Verschieben oder kopieren Sie ein Objekt in eine andere Kampagne', ], 'success' => 'Objekt :name verschoben', 'success_copy' => 'Objekt :name kopiert', 'title' => 'Bewege :name', 'warnings' => [ 'custom' => 'Dieses Objekt gehört nicht zu einem Standardmodul, sondern zu einem benutzerdefinierten „:module“-Objekttyp. Sie wird als Notiz-Objekt in der Zielkampagne erstellt.', ], ]; ================================================ FILE: lang/de/entities/notes.php ================================================ [ 'add' => 'Notiz hinzufügen', 'add_role' => 'Rolle hinzufügen', 'add_user' => 'Benutzer hinzufügen', ], 'collapsed' => [ 'closed' => 'Der Beitrag wird auf die Kopfzeile reduziert', 'open' => 'Beitrag wird erweitert', ], 'copy_mention' => [ 'copy' => 'Erweiterte Erwähnung kopieren', 'copy_with_name' => 'Kopiere die erweiterte Erwähnung mit dem Beitragsnamen', 'success' => 'Erweiterte Erwähnung zum Posten in die Zwischenablage kopiert.', ], 'create' => [ 'success' => 'Notiz \':name\' zu :entity hinzugefügt.', ], 'destroy' => [ 'success' => 'Notiz \':name\' von :entity entfernt.', ], 'edit' => [ 'success' => 'Notiz \':name\' für :entity aktualisiert.', ], 'fields' => [ 'creator' => 'Ersteller', 'display' => 'Anzeige', 'name' => 'Name', 'position' => 'Position', ], 'footer' => [ 'created' => 'Erstellt von :user am :date', 'updated' => 'Aktualisiert von :user am :date', ], 'hint' => 'Informationen, die nicht ganz in die Standardfelder eines Objektes passen oder privat bleiben sollen, können als Notiz hinzugefügt werden.', 'hints' => [ 'reorder' => 'Sie können Objektnotizen eines Objekts neu anordnen, indem Sie im Menü des Objekts auf das :icon-Symbol neben der Story klicken.', ], 'index' => [], 'move' => [ 'copy' => 'Erstelle eine Kopie auf Grundlage des Zielobjektes', 'copy_success' => 'Beitrag :name erfolgreich nach :entity kopiert.', 'copy_title' => 'Behalte eine Kopie', 'description' => 'Wähle ein Objekt aus, das du verschieben möchtest, oder erstelle eine Kopie dieses Beitrags.', 'entity' => 'Zielobjekt', 'move_success' => 'Beitrag :name erfolgreich nach :entity verschoben.', ], 'placeholders' => [ 'name' => 'Name der Notiz, Beobachtung oder Anmerkung', ], 'show' => [ 'advanced' => 'Erweiterte Berechtigungen', 'title' => 'Objektnotiz :name für :entity', ], 'states' => [ 'collapsed' => 'verkleinern', 'expanded' => 'vergrößern', ], 'warning' => [], ]; ================================================ FILE: lang/de/entities/permissions.php ================================================ [ 'text' => 'Dieses Objekt ist auf privat gesetzt. Benutzerdefinierte Berechtigungen können weiterhin definiert werden, aber solange das Objekt privat ist, werden diese ignoriert, und nur Mitglieder der :admin-Rolle der Kampagne können das Objekt sehen.', 'warning' => 'Warning', ], 'quick' => [ 'empty-permissions' => 'Keine Rolle oder Benutzer außerhalb der Kampagnenadministratoren haben Zugriff auf dieses Objekt.', 'manage' => 'Berechtigungen verwalten', 'screen-reader' => 'Datenschutzeinstellungen öffnen', 'success' => [ 'private' => ':entity ist jetzt versteckt.', 'public' => ':entity ist jetzt sichtbar.', ], 'title' => 'Berechtigungen', 'viewable-by' => 'Sichtbar für', ], ]; ================================================ FILE: lang/de/entities/pins.php ================================================ 'Links', 'title' => 'Pins', ]; ================================================ FILE: lang/de/entities/profile.php ================================================ [ 'edit_profile' => 'Profil editiert', ], 'history' => 'Verlauf', 'show' => [ 'tab_name' => 'Profile', 'title' => ':name Profil', ], ]; ================================================ FILE: lang/de/entities/quests.php ================================================ 'Dieses Objekt ist Teil der folgenden Quests.', 'title' => ':name Quests', ]; ================================================ FILE: lang/de/entities/relations.php ================================================ [ 'mode-map' => 'Tool zur Beziehungsdarstellung', 'mode-table' => 'Tabelle der Beziehungen und Verbindungen', ], 'bulk' => [ 'delete' => '{1} :count Beziehung gelöscht.|[2,*] :count Beziehungen gelöscht.', 'fields' => [ 'delete_mirrored' => 'Gespiegelte löschen', 'unmirror' => 'Unlink gespiegelt', 'update_mirrored' => 'Aktualisierung gespiegelt', ], 'helpers' => [ 'delete_mirrored' => 'Lösche auch die gespiegelte Verbindungen.', 'unmirror' => 'Entkopple gespiegelte Verbindungen.', 'update_mirrored' => 'Aktualisiere gespiegelte Verbindungen.', ], 'success' => [ 'editing' => '{1} :count Beziehung wurde aktualisiert.|[2,*] :count Beziehungen wurden aktualisiert.', 'editing_partial' => '{1} :count/:total Beziehung wurde aktualisiert.|[2,*] :count/:total Beziehungen wurden aktualisiert.', ], ], 'call-to-action' => 'Untersuche visuell die Beziehungen dieses Objekts und wie es mit dem Rest der Kampagne verbunden ist.', 'connections' => [ 'map_point' => 'Kartenpunkt', 'mention' => 'Erwähnung', 'quest_element' => 'Quest Elemente', 'timeline_element' => 'Zeitstrahlelement', ], 'create' => [ 'helper' => 'Erstelle eine Verbindung zwischen :name und einer oder mehreren Objekten.', 'new_title' => 'Neue Beziehungen', 'success_bulk' => '{1} :count-Verbindung zu :entity.|[2,*] hinzugefügt :count-Verbindungen zu :entity.', ], 'delete_mirrored' => [ 'helper' => 'Diese Beziehung wird auf dem Zielobjekt gespiegelt. Wähle diese Option, um auch die gespiegelte Beziehung zu entfernen.', 'option' => 'Gespiegelte Beziehung löschen', ], 'destroy' => [ 'mirrored' => 'Dadurch wird auch die gespiegelte Beziehung gelöscht und ist dauerhaft.', 'success' => 'Beziehung für :name entfernt', ], 'fields' => [ 'attitude' => 'Einstellung', 'is_pinned' => 'Angeheftet', 'owner' => 'Quelle', 'target' => 'Ziel', 'targets' => 'Zielobjekt', 'two_way' => 'Gespiegelte Beziehung erstellen', 'unmirror' => 'Hebe die Spiegelung dieser Beziehung auf.', ], 'filters' => [ 'connection' => 'Verbindung Beziehung', 'name' => 'Ziel der Verbindung', ], 'helper' => 'Richten Sie Beziehungen zwischen Objekten mit Einstellungen und Sichtbarkeit ein. Beziehungen können auch an das Menü der Berechtigung angeheftet werden.', 'helpers' => [ 'description' => 'Gib die Art der Verbindung zwischen den beiden Objekte an.', 'no_relations' => 'Dieses Objekt hat derzeit keine Beziehungen zu anderen Objekten der Kampagne.', ], 'hints' => [ 'attitude' => 'In diesem optionalen Feld können Sie die Standardordnungsbeziehungen definieren, sie wird in absteigender Reihenfolge angezeigt.', 'two_way' => 'Wenn du eine gespiegelte Beziehung erstellst, wird die gleiche Beziehung auch auf dem Ziel erstellt. Wenn du diese später editierst, wird die gespiegelte Beziehung nicht aktualisiert.', ], 'index' => [ 'title' => 'Beziehungen', ], 'options' => [ 'mentions' => 'Beziehungen + verbundene + erwähnt', 'only_relations' => 'Nur direkte Beziehungen', 'related' => 'Beziehungen + verbundene', 'relations' => 'Beziehungen', 'show' => 'zeige', ], 'panels' => [ 'related' => 'verbunden', ], 'placeholders' => [ 'attitude' => '-100 bis 100, 100 ist maximal positiv.', ], 'show' => [ 'title' => 'Beziehungen für :name', ], 'types' => [ 'family_member' => 'Familienmitglied', 'organisation_member' => 'Organisationsmitlgied', ], 'update' => [ 'success' => 'Beziehung für :name aktualisiert', 'title' => 'Beziehungen aktualisieren', ], ]; ================================================ FILE: lang/de/entities/reminders.php ================================================ [ 'add' => 'Link zu einem Kalender', 'remove' => 'Kalenderdatum entfernen', ], 'helpers' => [ 'pitch' => 'Lasse den realen Kalender hinter dir und verknüpfe diese Einheit mit einem Kalender aus deiner Welt, um in deine fiktive Zeitlinie einzutauchen.', ], ]; ================================================ FILE: lang/de/entities/story.php ================================================ [ 'collapse_all' => 'Alle ausblenden', 'expand_all' => 'Alle einblenden', 'load_more' => 'mehr laden', 'login_for_more' => 'Melde dich an, um weitere Beiträge anzuzeigen', ], 'reorder' => [ 'helper' => 'Ziehe Beiträge per Drag & Drop, um sie auf der Übersichtsseite des Objekts neu anzuordnen.', 'icon_tooltip' => 'Beiträge neu anordnen', 'panel_title' => 'Beiträge neu anordnen', 'save' => 'Speichere neue Reihenfolge', 'success' => 'Beiträge neu geordnet.', ], 'update' => [ 'title' => 'Aktualisieren :entity Eintrag', ], 'warning' => [], ]; ================================================ FILE: lang/de/entities/tags.php ================================================ [ 'helper' => 'tag hinzufügen oder entfernen zu :name', 'title' => 'tag hinzufügen oder entfernen', ], ]; ================================================ FILE: lang/de/entities/timelines.php ================================================ 'Zeitleisten mit Elementen, die mit diesem Objekt verknüpft sind, werden unten angezeigt.', 'show' => [ 'title' => 'Zeitstrahl von :name', ], ]; ================================================ FILE: lang/de/entities/transform.php ================================================ [], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Unbekannter oder unzulässige Objekttyp.', ], 'success' => '{1} :count Objekt zu neuem Typ umgewandelt: :type.|[2,*] :count Objekte zu neuem Typ umgewandelt: :type.', ], 'fields' => [ 'select_one' => 'Eines auswählen', 'target' => 'Neuer Objekttyp', ], 'panel' => [ 'bulk_description' => 'Verändere den Objekttyp mehrerer Objekte. Seien Sie sich bewusst, dass dadurch Daten verloren gehen könnten aufgrund unterschiedlicher Felder zwischen den Objekttypen.', 'bulk_title' => 'Massenumwandlung von Objekten', 'title' => 'wandle ein Objekt um', ], 'success' => 'Objekt :name umgewandelt', 'title' => ':name umwandeln', ]; ================================================ FILE: lang/de/entities.php ================================================ 'Fähigkeiten', 'ability' => 'Fähigkeit', 'attribute_template' => 'Attributvorlage', 'attribute_templates' => 'Attributsvorlagen', 'bookmark' => 'Lesezeichen', 'bookmarks' => 'Lesezeichen', 'calendar' => 'Kalender', 'calendars' => 'Kalender', 'campaign' => 'Kampagne', 'campaigns' => 'Kampagnen', 'character' => 'Charakter', 'characters' => 'Charaktere', 'conversation' => 'Unterhaltung', 'conversations' => 'Unterhaltungen', 'creator' => [ 'actions' => [ 'create' => 'Erstellen :type', 'full' => 'Gehe zum vollständigen Formular', 'more' => 'Füge weitere Details hinzu', ], 'back' => 'Zurück zur Auswahl', 'bulk_names' => 'Füge einen Namen pro Zeile hinzu', 'duplicate' => 'Es gibt andere Objekte dieses Typs mit demselben Namen.', 'helper_v2' => 'Erstelle schnell die Grundlage für ein neues Objekt, ohne deinen aktuellen Fluss zu unterbrechen.', 'missing_v2' => 'In dieser Oberfläche sind nur Module verfügbar, die aktiviert sind und für die du die Berechtigung zum Erstellen hast. :learn-more.', 'modes' => [ 'bulk' => 'Bulk hinzufügen', 'default' => 'Schnell hinzufügen', ], 'name' => [ 'new' => 'Neuer Name', 'remove' => 'entfernen', ], 'success_multiple' => '{1} Neues Objekt :link erstellt.|[2,*] Neues Objekt :link erstellt.', 'success_multiple_posts' => '{1} Neuer Beitrag :link erstellt.|[2,*] Neue Beiträge :link erstellt.', 'title' => 'Neues Objekt', 'titles' => [ 'everything' => 'Alles', 'quick-access' => 'Schnellzugriff', ], 'tooltip' => 'Erstellen Sie ein neues Objekt, ohne die aktuelle Seite zu verlassen', 'tooltips' => [ 'create' => 'Erstelle das Objekt und gehe zurück zum Objektauswahlbildschirm', 'create_more' => 'Erstelle das Objekt und beginne mit der Erstellung eines anderen des gleichen Typs', 'edit' => 'Erstelle das Objekt und beginne mit der Bearbeitung', ], ], 'creature' => 'Kreatur', 'creatures' => 'Kreaturen', 'dice_roll' => 'Würfelwurf', 'dice_rolls' => 'Würfelwürfe', 'event' => 'Ereignis', 'events' => 'Ereignisse', 'families' => 'Familien', 'family' => 'Familie', 'inventories' => 'Inventare', 'item' => 'Gegenstand', 'items' => 'Gegenstände', 'journal' => 'Logbuch', 'journals' => 'Logbücher', 'location' => 'Ort', 'locations' => 'Orte', 'map' => 'Karte', 'maps' => 'Karten', 'new' => [], 'note' => 'Notiz', 'notes' => 'Notizen', 'organisation' => 'Organisation', 'organisations' => 'Organisationen', 'quest' => 'Quest', 'quest_element' => 'Quest Element', 'quests' => 'Quests', 'race' => 'Spezies', 'races' => 'Spezies', 'relation' => 'Beziehung', 'relations' => 'Beziehungen', 'reminders' => 'Erinnerungen', 'tag' => 'Tag', 'tags' => 'Tags', 'templates' => 'Vorlagen', 'timeline' => 'Zeitstrahl', 'timeline_element' => 'Zeitstrahl Element', 'timelines' => 'Zeitstrahlen', 'whiteboard' => 'Whiteboard', 'whiteboards' => 'Whiteboards', ]; ================================================ FILE: lang/de/errors.php ================================================ [ 'body' => 'Es sieht so aus, als hättest du keine Berechtigung, diese Seite anzuzeigen!', 'title' => 'Zugang verweigert', ], '403-form' => [ 'help' => 'Dies kann an der Zeitüberschreitung Ihrer Sitzung liegen. Bitte versuchen Sie erneut, sich in einem anderen Fenster anzumelden, bevor Sie speichern.', ], '404' => [ 'body' => 'Entschuldige, diese Seite haben wir leider nicht finden können.', 'title' => 'Seite nicht gefunden', ], '500' => [ 'body' => [ '1' => 'Ups, irgendetwas ist hier schief gegangen.', '2' => 'Ein Bericht über den aufgetretenen Fehler wurde an uns geschickt, aber manchmal hilft es wenn wir etwas mehr darüber wissen.', ], 'title' => 'Fehler', ], '503' => [ 'body' => [ '1' => 'Kanka ist aktuell in Wartung, was normalerweise bedeutet, dass ein Update eingespielt wird!', '2' => 'Entschuldige die Unannehmlichkeiten. Alles wird bald wieder normal funktionieren.', ], 'json' => 'Kanka wird derzeit gewartet, bitte versuchen Sie es in ein paar Minuten erneut.', 'title' => 'Wartung', ], '503-form' => [], 'back-to-campaigns' => 'Gehe zurück zu einer deiner Kampagnen', 'footer' => 'Wenn du weitere Hilfe brauchst, bitte kontaktiere uns über hello@kanka.io oder über :discord.', 'log-in' => 'Wenn du dich in dein Konto einloggst, erfährst du vielleicht, wonach du suchst.', 'post_layout' => 'Ungültiges Beitragslayout.', 'private-campaign' => [ 'auth' => [ 'helper' => 'Du hast keinen Zugang zu dieser Kampagne.', ], 'guest' => [ 'helper' => 'Die Kampagne, auf die du zuzugreifen versuchst, ist privat und du bist nicht angemeldet.', 'login' => 'Wenn du dich anmeldest, kannst du auf die Inhalte zugreifen.', ], 'title' => 'private Kamapgne', ], ]; ================================================ FILE: lang/de/events.php ================================================ [ 'title' => 'Neues Ereignis erstellen', ], 'destroy' => [], 'edit' => [], 'events' => [ 'helper' => 'Ereignisse, die dieses Objekt als übergeordnetes Ereignis haben, werden hier angezeigt.', ], 'fields' => [ 'date' => 'Datum', ], 'helpers' => [ 'date' => 'Dieses Feld kann alles enthalten und ist nicht mit den Kalendern der Kampagne verknüpft. Um dieses Ereignis mit einem Kalender zu verknüpfen, fügen Sie es im Kalender oder auf der Registerkarte Erinnerungen dieses Ereignisses hinzu.', ], 'index' => [], 'lists' => [ 'empty' => 'Füge bedeutende Momente wie Schlachten, Krönungen oder Entdeckungen zur Geschichte deiner Welt hinzu.', ], 'placeholders' => [ 'date' => 'Ein Datum für dein Ereginis', 'type' => 'Zeremonie, Fest, Katastrophe, Schlacht, Geburt', ], 'show' => [], 'tabs' => [ 'calendars' => 'Kalendereinträge', ], ]; ================================================ FILE: lang/de/export.php ================================================ 'Inhalt', 'hidden_campaign' => 'Versteckte Kampagne', 'index' => 'Objektindex', ]; ================================================ FILE: lang/de/families/trees.php ================================================ [ 'clear' => 'Alles löschen', 'first' => 'Füge einen Gründer hinzu', 'founder' => 'Einen neuen Gründer hinzufügen', 'rename-relation' => 'Beziehung umbenennen', 'reset' => 'Änderungen verwerfen', 'save' => 'Speichern', ], 'modal' => [ 'first-title' => 'Objekt auswählen', 'helper' => 'Ersetze das Objekt durch ein anderes aus der Kampagne', 'relation' => 'Beziehung', 'title' => 'Objekt ersetzen', ], 'modals' => [ 'clear' => [ 'confirm' => 'Bist du dir sicher, dass du alle Daten aus dem Stammbaum neu initialisieren möchtest?', ], 'entity' => [ 'add' => [ 'founder' => 'Gründer', 'member' => 'Mitglied', 'success' => 'Objekt hinzufügt', 'title' => 'Objekt hinzufügen', ], 'child' => [ 'success' => 'Untergeordnetes Objekt hinzugefügt.', 'title' => 'Untergeordnetes Objekt', ], 'edit' => [ 'helper' => 'Aktiviere diese Option, wenn die Beziehung unbekannt ist. Ein Charakter kann später hinzugefügt werden.', 'success' => 'Objekt aktualisiert', 'title' => 'Objekt aktualisieren', ], 'founder' => [ 'title' => 'Einen neuen Gründer hinzufügen', ], 'remove' => [ 'confirm' => 'Möchtest du dieses Objekt wirklich aus dem Stammbaum entfernen?', 'success' => 'Objekt entfernt', ], ], 'relations' => [ 'add' => [ 'success' => 'Beziehung hinzugefügt', 'title' => 'Beziehung hinzufügen', ], 'edit' => [ 'success' => 'Beziehung aktualisiert.', 'title' => 'Beziehung aktualisiert', ], 'unknown' => 'Unbekannt', ], 'reset' => [ 'confirm' => 'Bist du sicher, dass du alle am Stammbaum vorgenommenen Änderungen verwerfen möchtest?', ], ], 'pitch' => 'Erstelle einen detaillierten Stammbaum für die Familien der Kampagne.', 'success' => [ 'cleared' => 'Stammbaum gelöscht', 'reseted' => 'Stammbaum zurückgesetzt', 'saved' => 'Stammbaum gespeichert', ], 'title' => ':name Stammbaum', 'unknown' => 'nicht etabliert', ]; ================================================ FILE: lang/de/families.php ================================================ [ 'title' => 'Erstelle eine neue Familie', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Diese Familie ist ausgestorben.', 'members' => 'Mitglieder einer Familie werden hier gelistet. Ein Charakter kann einer Familie hinzugefügt werden, in dem bei dem gewünschten Charakter das Familiendropdown genutzt wird.', ], 'index' => [], 'lists' => [ 'empty' => 'Verfolge Abstammungslinien, Clans oder Adelshäuser, die deine Charaktere miteinander verbinden.', ], 'members' => [ 'create' => [ 'helper' => 'Füge ein oder mehrere Mitglieder zu :name hinzu.', 'success' => '{0} Kein Mitglied wurde hinzugefügt.|{1} 1 Mitglied wurde hinzugefügt.|[2,*] :count Mitglieder wurden hinzugefügt.', 'title' => 'Neue Mitglieder', ], ], 'placeholders' => [ 'name' => 'Name der Familie', 'type' => 'königlich, edel, ausgestorben', ], 'show' => [ 'tabs' => [ 'tree' => 'Stammbaum', ], ], ]; ================================================ FILE: lang/de/faq.php ================================================ [ 'account_settings' => 'Account Einstellungen', 'answer' => 'Um Ihr Konto zu löschen, gehen Sie zu Ihrer :account Seite und scrollen Sie nach unten zum Abschnitt zum Löschen des Kontos. Dadurch werden Ihr Konto und alle Kampagnen gelöscht, bei denen Sie das einzige Mitglied sind.', 'question' => 'Wie kann ich meinen Account löschen?', ], 'app_backup' => [ 'answer' => 'Wir führen täglich zwei Sicherungen durch, um Datenverlust zu vermeiden. Unsere eigenen Kampagnen befinden sich ebenfalls auf dem Server, daher möchten wir kein Risiko eingehen!', 'question' => 'Wie oft werden die Daten von Kanka gesichert?', ], 'attribute-templates' => [ 'answer' => <<<'TEXT' Die beste Art Attributvorlagen zu erklären, ist mit einem Beispiel. Stellen wir uns vor, dass deine Welt eine Menge Orte hat. Für viele dieser Orte möchtest du eigene Attribute wie "Bewohnerzahl", "Klima" und "Kriminalitätsrate" haben. Nun kannst du diese Attribute in jedem Ort einzeln hinzufügen, das wird aber schnell mühsam und manchmal vergisst man das Attribut "Kriminalitätsrate" zu hinterlegen. Hier kommen die Attributvorlagen zum Einsatz. Du kannst eine Attributvorlage mit deinen Attributen "Bewohnerzahl", "Klima" und "Kriminalitätsrate" füllen und später diese Vorlage bei deinen Orten benutzen. Die Attributvorlage wird automatisch in jedem Ort hinterlegt und du musst nur noch die Werte der Attribute füllen! TEXT , 'question' => 'Was sind Attributvorlagen?', ], 'backup' => [ 'answer' => 'Einmal am Tag können Sie alle Daten Ihrer Kampagne als ZIP-Datei exportieren. Klicken Sie in der App im linken Menü auf "Kampagne" und dann auf "Exportieren". Dadurch wird ein Export erstellt, der 30 Minuten lang verfügbar ist. Sie können diesen Export nicht auf Kanka hochladen. Er dient nur Ihrer eigenen Sicherheit oder wenn Sie die Anwendung nicht mehr verwenden möchten.', 'question' => 'Wie kann ich meine Kampagne sichern oder exportieren?', ], 'bugs' => [ 'answer' => 'Treten Sie einfach unserem :discord server bei und melden Sie Ihren Fehler im Kanal #error-and-bugs.', 'question' => 'Wie kann ich einen Fehler melden?', ], 'campaign-sync' => [ 'answer' => 'Kanka hat diese Funktion nicht. Wenn Sie jedoch versuchen, mehrere Spielgruppen in derselben Welt zu haben, sollten Sie dieselbe Kampagne verwenden und Ihre Gruppen durch eine Kombination aus Quests, Tags und Berechtigungen trennen', 'question' => 'Kann ich Objekte über mehrere Kampagnen hinweg synchronisieren?', ], 'conversations' => [], 'custom' => [ 'answer' => 'Kanka wird mit einer Reihe vordefinierter Objekttypen erstellt, die miteinander interagieren. Um benutzerdefinierte Objekttypen zuzulassen, müsste die Anwendung vollständig geändert und das Ziel der Vereinfachung der Organisation verfehlt werden. Darüber hinaus ist Kanka flexibel mit Tags, die die meisten benutzerdefinierten Objekte darstellen können.', 'question' => 'Kann ich benutzerdefinierte Objekttypen erstellen?', ], 'delete-campaign' => [ 'answer' => 'Gehen Sie zu Ihrem Kampagnen-Dashboard und klicken Sie im linken Menü auf "Kampagne". Eine Schaltfläche "Löschen" wird angezeigt, wenn Sie das letzte Mitglied der Kampagne sind. Das Löschen einer Kampagne ist eine endgültige Aktion, mit der alle auf unseren Servern gespeicherten Daten, einschließlich Bilder, gelöscht werden.', 'question' => 'Wie kann ich eine Kampagne löschen?', ], 'discord' => [ 'answer' => 'Um Ihr Kanka-Konto mit :discord zu verknüpfen, müssen Sie zuerst oben rechts in der App auf Ihren Avatar klicken und auf die Schaltfläche Profil klicken. Navigieren Sie von dort zur :apps Unterseite und klicken Sie auf Verbinden.', 'question' => 'Wie kann ich meinen Kanka Account mit Discord verknüpfen?', ], 'early-access' => [ 'answer' => 'Mit Early Access können wir unsere großartigen Abonnenten belohnen, indem wir ihnen einen exklusiven Zeitraum von 30 Tagen gewähren, in dem sie die neuesten Module vor allen anderen ausprobieren können.', 'question' => 'Was ist ein Early Access?', ], 'entity-notes' => [ 'answer' => 'Alle Objekte verfügen über den Reiter "Objekt-Notizen", bei der es sich um kleine Textausschnitte handelt, die nur für Sie sichtbar (vor allem sinnvoll, wenn mehrere SLs an der Kampagne arbeiten), nur für Mitglieder der Administratorrolle oder für alle sichtbar sind. Du kannst deinen Spielern auch die Erlaubnis erteilen, Objekt-Notizen zu Objekten zu erstellen und zu bearbeiten, ohne dass sie ein ganzes Objekt bearbeiten müssen.', 'question' => 'Wie geht Kanka mit Informationen um, die nicht für jeden zugänglich sind?', ], 'fields' => [ 'answer' => 'Antwort', 'category' => 'Kategorie', 'locale' => 'Sprache', 'order' => 'Reihenfolge', 'question' => 'Frage', ], 'free' => [ 'answer' => <<<'TEXT' Ja! Wir glauben, dass unsere finanzielle Situation keinen Einfluss auf euer Vergnügen beim Rollenspiel oder beim Welten bauen haben sollte, weswegen wir die App immer kostenfrei halten werden. Dank unserer großzügigen Patrone auf :patreon, ist es uns möglich die monatlichen Serverkosten zu bezahlen und die Plattform werbefrei zu halten! Uns auf Patreon zu unterstützen ermöglicht es euch allerdings das Upload Limit von Dateien zu erhöhen, euer Name kommt auf die Patreon Wall of Fame, ihr bekommt schönere Standard Icons, könnt abstimmen was bei der Weiterentwicklung prioritisiert werden soll und mehr! TEXT , 'question' => 'Wird die App kostenfrei bleiben?', ], 'gods-and-religions' => [ 'answer' => 'Wir empfehlen, Götter als Charaktere und Religionen als Organisationen zu schaffen. Wenn Sie Ihre Gottheiten schnell finden möchten, empfehlen wir, sie mit einem geeigneten Tag und / oder Typ zu versehen.', 'question' => 'Wo kann man Götter und Religionen erstellen?', ], 'help' => [ 'answer' => 'Als Erstes: Danke, dass du helfen möchtest! Wir sind immer an Leuten interessiert, die uns bei Übersetzungen unterstützen, neue Funktionen testen oder die neuen Usern helfen können. Wir lieben es auch wenn Leute Kanka weiterempfehlen, um neue User an Orten zu erreichen an die wir nicht gedacht haben. Am besten ist es, wenn du auf :discord zu uns stößt, wo es einen Kanal für\'s Aushelfen gibt. Wir lieben auch unsere Patrone auf Patreon, wenn du uns unterstützen möchtest und ein paar Extras bekommen möchtest!', 'question' => 'Ich möchte helfen! Was kann ich tun?', ], 'map' => [ 'answer' => <<<'TEXT' Jeder Ort kann eine Karte (PNG, JPG oder SVG) enthalten, die selbst "Kartenpunkte" enthält, die mit Kontrolle über Größe, Form, Symbol und Farbe sowie als Links zu Elementen oder einfachen Beschriftungen platziert werden können. SVG-Dateien von Azgaars :azgaar und watabous :watabou sind kompatibel mit Kanka. Einige andere Programme komprimieren die generierten SVG-Dateien, was sie inkompatibel zu Kanka macht. Um diese Karten trotzdem mit Kanka benutzen zu können, öffne die Dateien in Inkscape oder Photoshop speicher sie als SVG-Dateien, bevor du sie auf Kanka hochlädtst. TEXT , 'question' => 'Kann ich Karten in Kanka hochladen?', ], 'mobile' => [ 'answer' => 'Derzeit gibt es keine extra Kanka-App für mobile Geräte, aber der Großteil der App funktioniert mobil. Eine Einschränkung ist die Linkhilfe (@), das nicht im Texteditor funktioniert. Wenn genug Unterstützung über Patreon kommt, hoffe ich, dass ich eines Tages jemanden bezahlen kann, der eine mobile App erstellt.', 'question' => 'Gibt es eine mobile App? Ist etwas in der Richtung geplant?', ], 'monsters' => [ 'answer' => 'Wir empfehlen die Verwendung des Spezies-Moduls für Völker, Spezies, Monster und alles Lebende, das kein Charakter ist.', 'question' => 'Wo kann man Monster erstellen?', ], 'multiworld' => [ 'answer' => 'Nein, brauchst du nicht! Du kannst so viele "Kampagnen" in der App haben, wie du möchtest. Jede Kampagne kann für eine Welt, ein Setting oder was immer du willst genutzt werden. Sobald du mehrere Kampagnen hast, kannst du einfach zwischen ihnen wechseln.', 'question' => 'Ich baue mehrere Welten in verschiedenen Settings auf. benötige ich für jede Welt einen anderes Konto?', ], 'nested' => [ 'answer' => 'Wenn Sie Ihre Objekte standardmäßig in einer verschachtelten Ansicht anzeigen möchten (z. B. die Schaltfläche Verschachtelte Ansicht in der Liste der Speicherorte), können Sie dies tun, indem Sie in die Optionen Profil und Layout wechseln. Dort können Sie die Option Verschachtelte Ansicht aktivieren. Dies gilt nur für Ihr Konto und nicht für Ihre Kampagnen.', 'question' => 'Kann ich festlegen, dass die Listen standardmäßig verschachtelt sind?', ], 'organise_play' => [], 'permissions' => [ 'answer' => 'Ja absolut, deswegen haben wir Kanka gemacht! Du kannst all deine Spieler zu deiner Kampagne einladen und ihnen Rollen und Berechtigungen erteilen. Wir haben das System für große Flexibiliät gebaut (sowohl opt-in als auch opt-out Konfigurationen möglich), um so viele Ansprüche und Situationen wie möglich abzudecken.', 'question' => 'Ich möchte Kanka nutzen, um meine RPG Welt aufzubauen. Meinen Spielern möchte ich ermöglichen manche Objekte und ihre Charakter zu bearbeiten. Geht das?', ], 'plans' => [ 'answer' => <<<'TEXT' Langfristig ist geplant, dass Kanka sich in ein vielseitiges Tool für Worldbuilding und Kampagnenmanagement entwickelt, das systemunabhängig ist und systemspezifische Inhalte enthält, die von der Community in Form von "Community-Vorlagen" verwaltet werden. Ein längeres Ziel ist es, Tools zu entwickeln, die sich in andere Plattformen wie Virtual Table Top-Apps integrieren lassen, um diese mit den Welten von Kanka zu verbinden. Was den zweiten Teil betrifft, so enden die meisten Hobbyprojekte im Burnout und der Schöpfer gibt sie auf. The :patreon wurde mit dem Ziel eingerichtet, dass wir Vollzeit an Kanka arbeiten können, ohne die finanzielle Sicherheit unserer Familien zu beeinträchtigen und die Serverkosten zu decken. Das Projekt ist auch Open Source und kann von der Community aufgegriffen werden, falls uns jemals etwas passieren sollte. TEXT , 'question' => 'Was sind die langfristigen Pläne?', ], 'public-campaigns' => [ 'answer' => 'Auf der Seite :public-campaigns können Sie sehen, wie andere Kanka für ihre Kampagnen verwenden.', 'question' => 'Wie benutzen andere Kanka?', ], 'renaming-modules' => [ 'answer' => 'Während dies für Englisch und andere Sprachen, die keine geschlechtsspezifischen Namen verwenden, einfach wäre, würde die Möglichkeit, den Namen von Modulen zu ändern, die grammatikalische Korrektheit und Benutzererfahrung für die meisten Sprachen beeinträchtigen, in denen Kanka auch verfügbar ist.', 'question' => 'Kann ich Module umbenennen? Zum Beispiel Familien in Clans oder Organisationen in Fraktionen?', ], 'sections' => [ 'community' => 'Community', 'general' => 'Allgemeines', 'other' => 'Andere', 'permissions' => 'Berechtigungen', 'pricing' => 'Preisgestaltung', 'worldbuilding' => 'Worldbuilding', ], 'show' => [ 'return' => 'Zurück zum FAQ', 'timestamp' => 'Letzte Aktualisierung am :date', 'title' => 'FAQ :name', ], 'unboost' => [ 'answer' => 'Durch das Aufheben des Boostings einer Kampagne werden keine Daten gelöscht, die während des Boostings erstellt wurden, sondern lediglich die Informationen und Funktionen ausgeblendet. Wenn Sie eine Kampagne erneut boosten, sind die Informationen und Funktionen mit demselben Setup wieder verfügbar wie vor dem Aufheben des Boostens einer Kampagne.', 'question' => 'Was passiert, wenn eine Kampagne nicht mehr geboosted wird?', ], 'user-switch' => [ 'answer' => 'Berechtigungen können schwierig werden, insbesondere bei großen Kampagnen. Als Kampagnenadministrator können Sie zur Mitgliederseite der Kampagne navigieren und auf die Schaltfläche "Wechseln" klicken, die neben Nicht-Administratormitgliedern der Kampagne angezeigt wird. Wenn Sie dies tun, melden Sie sich als dieser Benutzer an und können die Kampagne so sehen, wie sie es tun würde. Dies ist der einfachste Weg, um die Berechtigungen Ihrer Kampagne zu überprüfen.', 'question' => 'Meine Kampagnenberechtigungen sind festgelegt. Wie kann ich sie testen?', ], 'visibility' => [ 'answer' => 'Nur die Leute, die du zu deiner Kampagne eingeladen hast, können deine Welt sehen und bearbeiten. Deine Daten sind privat und immer unter deiner Kontrolle.', 'question' => 'Kann jeder meine Welt sehen?', ], ]; ================================================ FILE: lang/de/fields.php ================================================ [ 'placeholder' => 'Wählen Sie ein Bild aus der Kampagnengalerie', ], 'gallery-header' => [ 'description' => 'Wenn das Objekt kein Kopfzeilenbild hat, zeigen Sie stattdessen ein Bild aus der Kampagnengalerie an.', ], 'gallery-image' => [ 'description' => 'Wenn das Objekt kein Bild hat, zeigen Sie stattdessen ein Bild aus der Kampagnengalerie an.', ], 'header-image' => [ 'boosted-description' => 'Zeigen Sie ein Hintergrundbild in der Kopfzeile des Objekts mit einer :boosted-campaign an.', 'description' => 'Zeigen Sie ein Hintergrundbild in der Kopfzeile des Objekts an. Verwenden Sie für beste Ergebnisse ein sehr großes Bild.', 'title' => 'Kopfzeilenbild', ], 'tooltip' => [], ]; ================================================ FILE: lang/de/filters.php ================================================ [ 'bookmark' => 'Lesezeichen', ], 'alerts' => [ 'copy' => 'Die Filter wurden in Ihre Zwischenablage kopiert.', ], 'bookmark' => [ 'helper' => 'Erstelle ein neues Lesezeichen für diese Ansicht unter Verwendung der aktuellen Filter. Erstelle eine Erinnerung, um :name mit einem Kalender zu verknüpfen.', 'name' => ':module (gefiltert)', 'premium' => 'Um weitere Lesezeichen hinzuzufügen, musst du die Premium-Kampagnenfunktionen aktivieren.', 'success' => 'Lesezeichen erstellt.', ], 'helpers' => [ 'guest' => 'Bitte melden dich bei deinem Konto an, um die Ergebnisse zu filtern.', 'icon' => 'Gib diesem Lesezeichen ein spezielles :fontawesome Symbol, zum Beispiel :example.', 'icon-premium' => 'Gib diesem Lesezeichen ein spezielles :fontawesome Symbol, wie :example, mit einem :premium.', ], ]; ================================================ FILE: lang/de/footer.php ================================================ 'Über uns', 'blog' => 'Blog', 'boosters' => 'Booster', 'community' => 'Community', 'company' => 'Firma', 'contact' => 'Kontakt', 'copyright' => 'Urheberrecht :copy :year Owlchester SNC', 'documentation' => 'Dokumentation', 'features' => 'Besonderheiten', 'kb' => 'Wissensbasis', 'language-switcher' => [ 'other' => 'andere Sprachen', 'title' => 'Wähle deine Sprache', ], 'made' => 'Hergestellt mit ❤️ in Genf, Schweiz', 'newsletter' => 'Newsletter', 'platform' => 'Plattform', 'plugins' => 'Plugin-Bibliothek', 'premium' => 'Premium Kampagnen', 'press-kit' => 'Pressemappe', 'pricing' => 'Preisgestaltung', 'privacy' => 'Privatsphäre', 'public-campaigns' => 'Öffentliche Kampagnen', 'resources' => 'Ressourcen', 'roadmap' => 'Fahrplan', 'security' => 'Sicherheit', 'server-time' => 'Dies ist die Uhrzeit unseres Servers (:server)', 'showcase' => 'Schaufenster', 'status' => 'Service Status', 'terms' => 'Bedingungen', 'thanks' => 'Nur möglich dank unserer Abonnenten.', 'translator_call' => 'Kanka wird dank unserer großartigen Community in andere Sprachen übersetzt. Wenn Sie helfen möchten, Kanka in Ihre Sprache zu übersetzen, kontaktieren Sie uns über :discord!', 'whats-new' => 'Was ist Neu', ]; ================================================ FILE: lang/de/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Community Votes', ]; ================================================ FILE: lang/de/front/hall-of-fame.php ================================================ 'Ruhmeshalle', ]; ================================================ FILE: lang/de/front/kb.php ================================================ [], 'show' => [], 'title' => 'Datenbank', ]; ================================================ FILE: lang/de/front/newsletter.php ================================================ [ 'learn_more' => 'Erfahren Sie mehr', 'subscribe' => 'Abonnieren', ], 'fields' => [ 'firstname' => 'Vorname', 'lastname' => 'Nachname', 'notifications' => 'Benachrichtigungen', ], 'groups' => [ 'all' => 'Erhalte gelegentliche Updates über neue Funktionen, Community-Abstimmungen und Veranstaltungen usw', 'newsletter' => 'Newsletter', ], 'headline' => 'Abonnieren Sie einen oder alle unserer Newsletter, um über Kanka auf dem Laufenden zu bleiben.', 'title' => 'Email Updates', ]; ================================================ FILE: lang/de/front.php ================================================ [], 'actions' => [], 'campaigns' => [ 'public' => [ 'filters' => [ 'is-premium' => 'Dies ist eine Premium Kampagne', ], ], ], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => 'Verstanden!', 'link' => 'Mehr erfahren', 'message' => 'Diese Webseite benutzt Cookies um sicherzustellen, dass du die bestmögliche Erfahrung mit der Seite machst.', ], 'faq' => [], 'featured_campaigns' => [], 'features' => [ 'api' => [ 'link' => 'API-Dokumente', ], 'patreon' => [ 'api_calls' => 'Erhöhte API-Aufrufe (90)', 'boosts' => 'Kampagnen Booster', 'default_image' => 'Schöne Standardbilder für Objekte', 'discord' => 'Privater Discord-Kanal', 'free' => 'Kostenlos', 'hall_of_fame' => 'Name in :link', 'impact' => 'Auswirkungen auf zukünftige Funktionen', 'monthly_vote' => 'Teilnahme an der Community-Feature-Abstimmung', 'pagination' => 'Erhöhung der anzeigten Elemente pro Seite.', 'upload_limit' => 'Upload Größe', 'upload_limit_map' => 'Kartenupload Größe', ], ], 'first_block' => [], 'footer' => [], 'goodbye' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Kanka ist ein Community-gesteuerter RPG-Kampagnenmanager und ein Worldbuilding-Tool, mit dem Sie Ihre Tabletop-RPG-Kampagnen einfach organisieren, planen und genießen können', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Dashboard', 'login' => 'Login', 'register' => 'Registrieren', 'register_free' => 'Kostenlos registrieren', ], 'meta' => [ 'description' => 'Kanka ist ein flexibles digitales Tool zum weltenbauen und verwalten deiner Rollenspielkampagnen', 'title' => 'Kanka - Online Rollenspielkampagnen Verwalter und Weltenbautool', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Kostenlos', 'month' => 'Monat', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Worldbuilding, Tabletop-RPG, RPG-Kampagnenmanager', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/de/gallery.php ================================================ [ 'gallery' => 'Aus der Galerie', 'url' => 'Hochladen eines Bildes von einer URL', ], 'browse' => [ 'layouts' => [ 'large' => 'Große Vorschauen', 'small' => 'Kleine Vorschauen', ], 'search' => [ 'placeholder' => 'Suche nach einem Bild in der Galerie', ], 'title' => 'Bildergallerie', 'unauthorized' => 'Keine deiner Rollen hat die Berechtigung „Galerie durchsuchen“.', ], 'cta' => [ 'action' => 'Mehr Speicherplatz freischalten', 'helper' => 'Schalte bis zu :size GiB Speicherplatz mit einer :premium-campaign frei.', 'title' => 'Speicher voll', ], 'delete' => [ 'success' => '[0] Gelöschte 0 Elemente|[1] Gelöschte ein Element|{2,*} Gelöschte :count Elemente', ], 'download' => [ 'errors' => [ 'copy_failed' => 'Unsere Server konnten das angegebene Bild nicht herunterladen.', 'gallery_full_free' => 'Der Speicherplatz in der Galerie ist voll. Aktiviere Premium-Funktionen für mehr Speicherplatz.', 'gallery_full_premium' => 'Der Speicherplatz in der Galerie ist voll. Entferne zunächst nicht verwendete Dateien.', 'invalid_format' => 'Die Datei ist kein gültiges Dateiformat.', 'too_big' => 'Diese Datei ist zu groß', 'unauthorized' => 'Keine deiner Rollen hat die Berechtigung „In die Galerie hochladen“.', ], ], 'file' => [ 'saved' => 'gesichert', ], 'filters' => [ 'only_unused' => 'Nur ungenutzte Dateien anzeigen', 'sort' => 'Sortieren nach', ], 'move' => [ 'success' => '[0] 0 Elemente verschoben|[1] Ein Element verschoben|{2,*} Bewegte :count Elemente', ], 'update' => [ 'home' => 'Ordner Startseite', 'success' => '[0] 0 Elemente aktualisiert|[1] Ein Element aktualisiert|{2,*} Aktualisiert :count Elemente', ], ]; ================================================ FILE: lang/de/general.php ================================================ 'Alle abwählen', 'documentation' => 'Erfahre mehr über diese Funktion in unserer Dokumentation', 'done' => 'fertig', 'learn-more' => 'Mehr erfahren', 'no' => 'Nein', 'required' => 'Erforderlich', 'select_all' => 'Alle auswählen', 'success' => [ 'created' => ':name erstellt', 'deleted' => ':name entfernt', 'deleted-cancel' => ':name entfernt. :cancel.', 'updated' => ':name aktualisiert', ], 'tutorial' => 'Tutorial ansehen', 'yes' => 'Ja', ]; ================================================ FILE: lang/de/genres.php ================================================ 'Alternative Geschichte', 'cyberpunk' => 'Cyberpunk', 'fantasy' => 'Fantasy', 'historical' => 'Historisch', 'many_worlds' => 'Viele Welten', 'modern' => 'Modern', 'occult' => 'Okkulte', 'post_apocalyptic' => 'Post-apokalyptischen', 'pulp' => 'Pulp', 'science_fantasy' => 'Science fantasy', 'science_fiction' => 'Science-Fiction', 'space_opera' => 'Weltraumoper', 'steampunk' => 'Steampunk', 'superhero' => 'Superheld', 'urban_fantasy' => 'Urbane Fantasie', 'western' => 'Western', ]; ================================================ FILE: lang/de/header.php ================================================ [ 'title' => 'Kanka Neuigkeiten', ], 'notifications' => [ 'dismiss' => 'Abweisen', 'no-unread' => 'Keine ungelesenen Benachrichtigungen', 'read_all' => 'Alles lesen', ], 'qq' => [ 'tooltip' => 'Ein Objekt erstellen oder einen Beitrag veröffentlichen', ], 'toggle_navigation' => 'Navigation umschalten', 'user' => [ 'settings' => 'Einstellungen', 'sign-out' => 'ausloggen', 'upgrade' => 'Upgrade', 'your-profile' => 'Dein Profil', ], ]; ================================================ FILE: lang/de/helpers.php ================================================ [], 'api-filters' => [ 'description' => 'Die folgenden Filter sind für den API-Endpunkt :name verfügbar.', 'title' => 'API Filters', ], 'attributes' => [ 'link' => 'Attributoptionen', ], 'calendar-widget' => [ 'info' => 'Warum werden diese Erinnerungen angezeigt?', 'title' => 'Kalender-Widget', ], 'dice' => [], 'entity_templates' => [], 'filters' => [ 'title' => 'Verwendung von Filtern', ], 'link' => [ 'description' => 'Mit einem "@" kannst du ganz einfach Links zu anderen Einträgen setzen. Ein "#" zeigt dir stattdessen eine Namensliste mit Monaten aus deinen Kalendern an.', ], 'map' => [], 'pins' => [], 'public' => 'Sie die Tutorial Videos über öffentliche Kampagnen auf Youtube an.', 'troubleshooting' => [ 'description' => 'Ein Mitglied von Kankas Team hat Sie auf diese Seite geschickt. Wählen Sie eine Kampagne aus der Dropdown-Liste aus, um ein Token zu generieren, damit wir Ihrer Kampagne vorübergehend als Administrator beitreten können.', 'errors' => [ 'token_exists' => 'Es besteht bereits ein Token für :campaign.', ], 'save_btn' => 'Token erstellen', 'select_campaign' => 'Kampagne auswählen', 'subtitle' => 'Bitte senden Sie Hilfe!', 'success' => 'Bitte kopieren Sie den folgenden Token und senden Sie ihn an jemanden aus Kankas Team.', 'title' => 'Troubleshooting', ], 'widget-filters' => [ 'description' => 'Sie können Objekte filtern, die im kürzlich geänderten Widget angezeigt werden, indem Sie eine Liste der Felder der Objekte und der Werte bereitstellen. Zum Beispiel können Sie :example verwenden, um nach toten Charakteren des NPC-Typs zu filtern.', 'link' => 'Widget Filter', 'title' => 'Dashboard Widget Filter', ], ]; ================================================ FILE: lang/de/history.php ================================================ [ 'show-old' => 'Changes', ], 'cta' => 'Zeige ein Protokoll aller letzten Änderungen an der Kampagne an.', 'empty' => 'Kein Wert', 'fields' => [ 'action' => 'Aktion', 'details' => 'Details', 'when' => 'Wann', 'who' => 'Wer', ], 'filters' => [ 'all-actions' => 'Alle Aktionen', 'all-users' => 'Alle Mitglieder', 'no-results' => 'Keine Ergebnisse zum Anzeigen. Versuche es mit anderen Filtern oder kehre zurück, nachdem du Änderungen an den Objekten der Kampagne vorgenommen hast.', ], 'helpers' => [ 'base' => 'Diese Oberfläche enthält die letzten Änderungen an Objekten der Kampagne für bis zu :amount Monate, wobei die neuesten Änderungen zuerst angezeigt werden.', 'changes' => 'Die folgenden Felder hatten zuvor diese Werte.', ], 'log' => [ 'create' => ':user erstellt :entity', 'create_post' => ':user erstellte den Beitrag ":post" zu :entity', 'delete' => ':user gelöscht :entity', 'delete_post' => ':user löschte den Beitrag zu :entity', 'reorder_post' => ':user ordnete den Beitrag zu :entity neu', 'restore' => ':user wiederhergestellt :entity', 'update' => ':user aktualisiert :entity', 'update_post' => ':user aktualisierte den Beitrag ":post" zu :entity', 'update_tree' => ':user hat den Stammbaum von :entity aktualisiert.', ], 'title' => 'Historie', 'unknown' => [ 'entity' => 'ein unbekanntes Objekt', ], ]; ================================================ FILE: lang/de/items.php ================================================ [ 'title' => 'Neuen Gegenstand erstellen', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_equipped' => 'Ausgestattet', 'price' => 'Preis', 'size' => 'Größe', 'weight' => 'Gewicht', ], 'helpers' => [], 'hints' => [], 'index' => [], 'inventories' => [], 'lists' => [ 'empty' => 'Füge Waffen, Artefakte oder wichtige Gegenstände zu deiner Welt hinzu.', ], 'placeholders' => [ 'price' => 'Preis des Gegenstandes', 'size' => 'Größe, Gewicht, Maße', 'type' => 'Waffe, Trank, Artefakt', 'weight'=> 'Gewicht des Gegenstands', ], 'show' => [ 'tabs' => [ 'inventories' => 'Objekte', ], ], ]; ================================================ FILE: lang/de/journals.php ================================================ [ 'title' => 'Erstelle ein neues Logbuch', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Autor', 'date' => 'Datum', ], 'helpers' => [], 'index' => [], 'journals' => [], 'lists' => [ 'empty' => 'Erstelle Tagebucheinträge, um Abenteuer, Gedanken der Charaktere oder Zusammenfassungen und Vorbereitungen für Spielsitzungen festzuhalten.', ], 'placeholders' => [ 'author' => 'Wer hat das Logbuch geschrieben', 'date' => 'Datum des Logbuchs', 'type' => 'Session, One Shot, Entwurf', ], 'show' => [], ]; ================================================ FILE: lang/de/languages.php ================================================ [ 'ca' => 'Katalanisch', 'cs' => 'Tschechisch', 'de' => 'Deutsch', 'el' => 'Griechisch', 'en' => 'Englisch', 'en-US' => 'amerikanisches Englisch', 'es' => 'Spanisch', 'fr' => 'Französisch', 'gl' => 'Galizisch', 'he' => 'Hebräisch', 'hr' => 'Kroatisch', 'hu' => 'Ungarisch', 'it' => 'Italienisch', 'nb' => 'Norwegisch (Bokmal)', 'nl' => 'Holländisch', 'pl' => 'Polnisch', 'pt-BR' => 'Brasilianisches Portugisiesch', 'ru' => 'Russisch', 'sk' => 'Slovakisch', 'tr' => 'Türkisch', ], 'header'=> 'Sprachen', ]; ================================================ FILE: lang/de/lists.php ================================================ [ 'learn' => 'Erfahre mehr über dieses Modul', 'public'=> 'Sieh, wie andere es machen', ], 'empty' => [ 'title' => 'keine :plural form bis jetzt', ], ]; ================================================ FILE: lang/de/locations.php ================================================ [], 'create' => [ 'title' => 'Erstelle einen neuen Ort', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [ 'is_destroyed' => 'Zerstörte', ], 'helpers' => [ 'characters' => 'Sieh alle Charaktere in diesem Ort und den Unterorten an oder nur die direkt im Ort.', ], 'hints' => [ 'is_destroyed' => 'Dieser Ort ist zerstört.', ], 'index' => [], 'items' => [], 'journals' => [], 'lists' => [ 'empty' => 'Füge deine erste Stadt, Taverne oder versteckte Ruine hinzu, um deine Welt zu verankern.', ], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'type' => 'Stadt, Königreich, Ruine', ], 'show' => [], ]; ================================================ FILE: lang/de/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Wechsle in den Bearbeitungsmodus', 'exit-edit-mode' => 'Verlasse den Bearbeitungsmodus', 'finish-drawing' => 'Beende das Zeichnen des Polygons', ], 'notifications' => [ 'start-drawing' => 'Klicke auf die Karte, um mit dem Zeichnen eines Polygons zu beginnen', ], 'toggle' => 'Alle Gruppen öffnen/schließen', ]; ================================================ FILE: lang/de/maps/groups.php ================================================ [ 'add' => 'neue Gruppe hinzufügen', ], 'bulks' => [ 'delete' => '{1} entfernt :count Gruppe .|[2,*] entfernt :count Gruppen.', 'patch' => '{1} aktualisiere :count group.|[2,*] aktualisiere :count groups.', ], 'create' => [ 'helper' => 'Füge eine neue Gruppe :name hinzu. Dieser Gruppe können dann Markierungen zugewiesen werden.', 'success' => 'Gruppe :name erzeugen', 'title' => 'neue Gruppe', ], 'delete' => [ 'success' => 'Gruppe :name gelöscht', ], 'edit' => [ 'success' => 'Gruppe :name aktualisiert', 'title' => 'Gruppe :name editieren', ], 'fields' => [ 'is_shown' => 'Zeige Gruppenmarker', 'parent' => 'Elterngruppe', 'position' => 'Position', ], 'helper' => [ 'amount_v3' => 'Markierungen können mithilfe von Kartengruppen gruppiert werden. Jede Gruppe kann dann beim Erkunden einer Karte angeklickt werden, um schnell alle Markierungen darin ein- oder auszublenden.', ], 'hints' => [ 'is_shown' => 'Wenn diese Option aktiviert ist, werden die Gruppenmarker standardmäßig auf der Karte angezeigt.', ], 'index' => [ 'title' => 'Gruppe von :name', ], 'pitch' => [ 'max' => [ 'helper' => 'Du kannst keine weiteren Gruppen hinzufügen, es sei denn, du entfernst eine bestehende Gruppe.', 'limit' => 'Diese Karte hat ihre Gruppengrenze erreicht', ], 'upgrade' => [ 'limit' => 'Du hast das Limit von :limit groups für diese Karte erreicht', 'upgrade' => 'Wenn du auf eine Premium-Kampagne upgradest, kannst du bis zu :limit groups hinzufügen und noch mehr kreative Flexibilität freischalten.', ], ], 'placeholders' => [ 'name' => 'Geschäfte, Schatz, NSC,', 'position' => 'Optionales Feld zum Festlegen der Reihenfolge, in der die Gruppen angezeigt werden.', 'position_list' => 'nach :name', ], 'reorder' => [ 'save' => 'neue Reihenfolge speichern', 'success' => '{1} ordne neu :count group.|[2,*] ordne neu :count groups.', 'title' => 'Gruppe neu ordnen', ], ]; ================================================ FILE: lang/de/maps/layers.php ================================================ [ 'add' => 'neue Ebene hinzufügen', ], 'base' => 'Basisebene', 'bulks' => [ 'delete' => '{1} entferne :count layer.|[2,*] entferne :count layers.', 'patch' => '{1} aktualisiere :count layer.|[2,*] aktualisiere :count layers.', ], 'create' => [ 'success' => 'Ebene :name erstellen', 'title' => 'neue Ebene', ], 'delete' => [ 'success' => 'Ebene :name löschen', ], 'edit' => [ 'success' => 'Ebene :name aktualisieren', 'title' => 'Ebene :name editieren', ], 'fields' => [ 'position' => 'Position', 'type' => 'Ebenentyp', ], 'helper' => [ 'amount_v2' => 'Lade Ebenen auf eine Karte hoch, um das unter den Markierungen angezeigte Hintergrundbild zu wechseln.', 'is_real' => 'Ebenen sind bei Verwendung von OpenStreetMaps nicht verfügbar.', ], 'index' => [ 'title' => 'ebene von :name', ], 'pitch' => [ 'max' => [ 'helper' => 'Du kannst keine weiteren Ebenen hinzufügen, wenn du nicht eine bestehende Ebene entfernst.', 'limit' => 'Diese Karte hat ihre Ebenenmaximum erreicht', ], 'upgrade' => [ 'limit' => 'Du hast das Limit von :limit layers für diese Karte erreicht', 'upgrade' => 'Mit einem Upgrade auf eine Premium-Kampagne kannst du bis zu :limit layers hinzufügen und so noch mehr kreative Flexibilität freischalten.', ], ], 'placeholders' => [ 'name' => 'Untergrund, Ebene 2, Schiffbruch', 'position' => 'Optionales Feld zum Festlegen der Reihenfolge, in der die Ebenen angezeigt werden.', 'position_list' => 'nach :name', ], 'reorder' => [ 'save' => 'speichere neue Reichenfolge', 'success' => '{1} neu ordnen :count layer.|[2,*] neu ordnen :count layers.', 'title' => 'Ebenen neu ordnen', ], 'short_types' => [ 'overlay' => 'Überlagerung', 'overlay_shown' => 'Überlagerung (automatisch gezeigt)', 'standard' => 'Standard', ], 'types' => [ 'overlay' => 'Überlagerung (über der aktiven Ebene angezeigt)', 'overlay_shown' => 'Standardmäßig wird die Überlagerung angezeigt', 'standard' => 'Standardebene (zwischen Ebenen wechseln)', ], ]; ================================================ FILE: lang/de/maps/markers.php ================================================ [ 'entry' => 'Schreibe ein benutzerdefiniertes Eingabefeld für diesen Marker.', 'remove' => 'Marker entfernen', 'reset-polygon' => 'Position zurücksetzen', 'save_and_explore' => 'Speichern und Erkunden', 'start-drawing' => 'Zeichnen starten', 'update' => 'Marker editieren', ], 'bulks' => [ 'delete' => '{1} entferne :count marker.|[2,*] entferne :count markers.', 'patch' => '{1} aktualisiere :count marker.|[2,*] aktualisiere :count markers.', ], 'circle_sizes' => [ 'custom' => 'benutzerdefiniert', 'huge' => 'riesig', 'large' => 'groß', 'small' => 'klein', 'standard' => 'standard', 'tiny' => 'winzig', ], 'create' => [ 'success' => 'Marker :name erstellt', 'title' => 'neuer Marker', ], 'delete' => [ 'success' => 'Marker :name löschen', ], 'details' => [ 'from-entity' => 'Vom Objekt', ], 'edit' => [ 'success' => 'Marker :name aktualisiert', 'title' => 'Marker :name editieren', ], 'fields' => [ 'bg_colour' => 'Hintergrundfarbe', 'circle_radius' => 'Kreisradius', 'copy_elements' => 'kopiere Elemente', 'custom_icon' => 'Benutzerdefiniertes Symbol', 'custom_shape' => 'Benutzerdefinierte Form', 'font_colour' => 'Icon Farbe', 'group' => 'Markergruppe', 'icon' => 'Icon', 'is_draggable' => 'Verschiebbar', 'latitude' => 'Breitengrad', 'longitude' => 'Längengrad', 'opacity' => 'Fügen Sie der Karte Markierungen hinzu, indem Sie auf eine beliebige Stelle klicken.', 'pin_size' => 'Pingröße', 'polygon_style' => [ 'stroke' => 'Strichfarbe', 'stroke-opacity' => 'Strichdeckkraft', 'stroke-width' => 'Strichbreite', ], 'popupless' => 'Tooltip-Popup', 'size' => 'Größe', ], 'helpers' => [ 'base' => 'Fügen Sie der Karte Markierungen hinzu, indem Sie auf eine beliebige Stelle klicken.', 'copy_elements' => 'kopiere Gruppen, Layers, und Marker', 'copy_elements_to_campaign' => 'Kopiere Gruppen, Ebenen und Markierungen der Karten. Mit einem Objekt verknüpfte Marker werden in einen Standardmarker konvertiert.', 'css' => 'Definiere eine benutzerdefinierte CSS-Klasse, die der Markierung hinzugefügt wird.', 'custom_icon_v2' => 'Verwende Symbole von :fontawesome, :rpgawesome oder ein benutzerdefiniertes SVG-Symbol. Wie das geht, erfährst du in den :docs.', 'custom_radius' => 'Wählen Sie die benutzerdefinierte Größenoption aus der Dropdown-Liste aus, um eine Größe zu definieren.', 'draggable' => 'Aktivieren Sie diese Option, um das Verschieben eines Markers im Erkundungsmodus zu ermöglichen.', 'is_popupless' => 'Deaktiviere die Anzeige des Tooltips der Markierung, wenn du mit der Maus darüber fährst.', 'label' => 'Eine Beschriftung wird als Textblock auf der Karte angezeigt. Der Inhalt ist der Markername des Objektnamens.', 'polygon' => [ 'edit' => 'Klicken Sie auf die Karte, um diese Position zu den Koordinaten des Polygons hinzuzufügen.', ], ], 'hints' => [ 'entry' => 'Bearbeite die Markierung, um einen benutzerdefinierten Eintrag dafür zu schreiben.', ], 'icons' => [ 'custom' => 'Benutzerdefiniert', 'entity' => 'Objekt', 'exclamation' => 'Ausruf', 'marker' => 'Marker', 'question' => 'Frage', ], 'index' => [ 'title' => 'Marker von :name', ], 'pitches' => [ 'poly' => 'Zeichne benutzerdefinierte Polyong-Formen, um Ränder und andere ungleichmäßige Formen darzustellen.', ], 'placeholders' => [ 'custom_icon' => 'versuchen sie :example1 or :example2', 'custom_shape' => '100,100 200,240 340,110', 'name' => 'Name des Markers', ], 'presets' => [ 'helper' => 'Klicke auf eine Voreinstellung, um sie zu laden, oder erstelle eine neue.', ], 'shapes' => [ '0' => 'Kreis', '1' => 'Quadrat', '2' => 'Dreieck', '3' => 'Benutzerdefiniert', ], 'sizes' => [ '0' => 'Sehr klein', '1' => 'Standard', '2' => 'Klein', '3' => 'Groß', '4' => 'Enorm', ], 'tabs' => [ 'circle' => 'Kreis', 'label' => 'Etikette', 'marker' => 'Marker', 'polygon' => 'Polygon', 'preset' => 'Vorlage', ], ]; ================================================ FILE: lang/de/maps.php ================================================ [ 'back' => 'zurück zu :name', 'edit' => 'Karte editieren', 'explore' => 'erkunden', ], 'create' => [ 'title' => 'neue Karte', ], 'destroy' => [], 'edit' => [], 'errors' => [ 'chunking' => [ 'error' => 'Beim Aufteilen der Karte ist ein Fehler aufgetreten. Bitte kontaktiere das Team auf :discord für Unterstützung.', 'running' => [ 'edit' => 'Die Karte kann nicht bearbeitet werden, während sie aufgeteilt wird.', 'explore' => 'Die Karte kann nicht angezeigt werden, während sie aufgeteilt wird.', 'time' => 'Dies kann je nach Größe der Karte einige Minuten bis mehrere Stunden dauern.', ], ], 'dashboard' => [ 'missing' => 'Diese Karte benötigt ein Bild, um im Dashboard gerendert werden zu können.', ], 'explore' => [ 'missing' => 'Bitte fügen Sie der Karte ein Bild hinzu, bevor Sie sie erkunden können.', ], ], 'fields' => [ 'center_marker' => 'Marker', 'center_x' => 'Standard-Längengradposition', 'center_y' => 'Standard-Breitengradposition', 'centering' => 'Zentrierung', 'distance_measure' => 'Entfernungsmessung', 'distance_name' => 'Etikett für Entfernungseinheit', 'grid' => 'Gitter', 'has_clustering' => 'Cluster-Marker', 'initial_zoom' => 'Initial Zoom', 'is_real' => 'Verwenden Sie OpenStreetMaps', 'max_zoom' => 'Maximal Zoom', 'min_zoom' => 'Minimal Zoom', 'tabs' => [ 'coordinates' => 'Koordinaten', 'marker' => 'Marker', ], ], 'helpers' => [ 'center' => 'Durch Ändern der folgenden Werte wird gesteuert, auf welchen Bereich der Karte der Fokus liegt. Wenn Sie diese Werte leer lassen, wird die Mitte der Karte fokussiert.', 'centering' => 'Das Zentrieren auf eine Markierung hat Vorrang vor den Standardkoordinaten.', 'chunked_zoom' => 'Gruppiere Markierungen automatisch, wenn sie nahe beieinander liegen.', 'distance_measure' => 'Wenn Sie der Karte eine Entfernungsmessung geben, wird das Messwerkzeug im Erkundungsmodus aktiviert.', 'distance_measure_2' => 'Damit 100 Pixel 1 Kilometer entsprechen, gib einen Wert von 0,0041 ein.', 'grid' => 'Definieren Sie eine Rastergröße, die im Erkundungsmodus angezeigt wird.', 'has_clustering' => 'Gruppiere Markierungen automatisch, wenn sie nahe beieinander liegen.', 'initial_zoom' => 'Die anfängliche Zoomstufe, mit der eine Karte geladen wird. Der Standardwert ist :default, während der höchste zulässige Wert :max und der niedrigste zulässige Wert :min ist.', 'is_real' => 'Wählen Sie diese Option, wenn Sie anstelle des hochgeladenen Bildes eine echte Weltkarte verwenden möchten. Diese Option deaktiviert Ebenen.', 'max_zoom' => 'Eine Karte kann bis zu diesem Wert maximal vergrößert werden. Der Standardwert ist :default, während der höchstzulässige Wert :max ist.', 'min_zoom' => 'Eine Karte kann bis zu diesem Wert maximal verkleinert werden. Der Standardwert ist :default, während der höchstzulässige Wert :min ist.', 'missing_image' => 'Speichern Sie die Karte mit einem Bild, bevor Sie Ebenen und Markierungen hinzufügen können.', ], 'index' => [], 'lists' => [ 'empty' => 'Lade eine Karte hoch, um Orte zu visualisieren und die Geografie deiner Welt darzustellen.', ], 'maps' => [], 'panels' => [ 'groups' => 'Gruppen', 'layers' => 'Ebenen', 'legend' => 'Legende', 'markers' => 'Marker', 'settings' => 'Einstellungen', ], 'placeholders' => [ 'center_marker' => 'Leer lassen, um die Karte in der Mitte zu laden', 'center_x' => 'Lassen Sie das Feld leer, um die Karte in der Mitte zu laden (X-Koordinate)', 'center_y' => 'Lassen Sie das Feld leer, um die Karte in der Mitte zu laden (Y-Koordinate)', 'distance_name' => 'Km, Meilen, Fuß, Hamburger', 'grid' => 'Abstand in Pixel zwischen Gitterelementen. Leer lassen, um das Raster auszublenden.', 'name' => 'Name der Karte', 'type' => 'Dungeon, Stadt, Galaxie', ], 'show' => [ 'tabs' => [ 'maps' => 'Karten', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'Die Karte wird aufgeteilt. Dieser Vorgang kann einige Minuten bis Stunden dauern.', ], ], ]; ================================================ FILE: lang/de/misc.php ================================================ [ 'member' => 'Werde ein Mitglied', 'remove_v5' => 'Kanka wird nur von uns beiden entwickelt. Unterstütze unser Vorhaben und genieße ein werbefreies Erlebnis für weniger als die Kosten für einen Kaffee.', ], ]; ================================================ FILE: lang/de/notes.php ================================================ [ 'title' => 'Erstelle eine neue Notiz', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'untergeordnete Notiz', ], 'helpers' => [], 'hints' => [], 'index' => [], 'lists' => [ 'empty' => 'Speicher Ideen, Referenzen, Regeln oder Informationen, die nirgendwo anders hineinpassen.', ], 'placeholders' => [ 'note' => 'Wähle eine übergeordnete Notiz', 'type' => 'Religion, Spezies, Politisches System', ], 'show' => [], ]; ================================================ FILE: lang/de/notifications.php ================================================ [ 'discord' => [ 'invalid' => 'Dein Discord-Token ist abgelaufen. Bitte synchronisiere dein Discord- und Kanka-Konto erneut.', ], ], 'campaign' => [ 'application' => [ 'approved' => 'Ihre Bewerbung für die Kampagne :campaign wurde genehmigt.', 'approved_message' => 'Deiner Bewerbung für die :campaign Kampagne wurde zugestimmt. Nachricht bereitgestellt: :reason', 'new' => 'Neue Bewerbung für :campaign', 'rejected' => 'Ihr Bewerbung für die :campaign Kampagne wurde abgelehnt. Grund dafür ist :reason', 'rejected_no_message' => 'Deiner Bewerbung für die Kampagne :campaign wurde widersprochen.', ], 'asset_export' => 'Ein Export eines Kampagnen-Assets ist verfügbar. Der Link ist verfügbar für :time Minuten.', 'boost' => [ 'add' => 'Kampagne :campaign wird geboosted durch :user.', 'remove' => ':user boosted die :campaign nicht mehr.', 'superboost' => 'Die Kampagne :campaigne wird von :user superboosted.', ], 'created' => 'Du hast :campaign erstellt.', 'deleted' => 'Die Kampagne :campaign wurde gelöscht.', 'export' => 'Ein Export der Kampagne steht zur Verfügung. Der Link ist :time Minuten gültig.', 'export_error' => 'Beim Export deiner Kampagne ist ein Fehler aufgetreten. Bitte kontaktiere uns, wenn das Problem weiterhin besteht.', 'hidden' => 'Die Kampagne :campaign ist jetzt auf der Seite „Öffentliche Kampagnen“ ausgeblendet.', 'import' => [ 'failed' => 'Der Import für Kampagne :campaign ist fehlgeschlagen.', 'success' => 'Der Import der Kampagne :campaign wurde abgeschlossen.', ], 'join' => ':user ist der Kampagne :campaign beigetreten.', 'leave' => ':user hat die Kampagne :campaign verlassen.', 'new_owner' => 'Du wurdest zum Administrator von :campaign ernannt.', 'plugin' => [ 'deleted' => 'Das Plugin :plugin wurde vom Marktplatz gelöscht und aus Ihrer Kampagne :campaign entfernt.', ], 'premium' => [ 'add' => 'Premium-Funktionen wurden für die :campaign-Kampagne von :user freigeschaltet.', 'remove' => ':user schaltet keine Premiumfunktionen mehr für die :campaign-Kampagne frei.', ], 'removed-image' => 'Das Bild oder der Header von :entity wurde aufgrund eines Urheberrechtsanspruchs entfernt.', 'role' => [ 'add' => 'Du wurdest zur Rolle :role in der Kampagne :campaign hinzugefügt.', 'remove' => 'Du wurdest aus der Rolle :role in der Kampagne :campaign entfernt.', ], 'troubleshooting' => [ 'joined' => 'Das Kanka Teammitglied :user ist der Kampagne :campaign beigetreten.', ], ], 'clear' => [ 'action' => 'Clear all', 'success' => 'Benachrichtigungen entfernt.', 'title' => 'lösche Benachrichtigungen', ], 'features' => [ 'approved' => 'Deine Idee :feature wurde genehmigt.', 'finished' => 'Ihre Idee :feature ist jetzt in Kanka verfügbar!', 'rejected' => 'Deine Idee :feature wurde abgelehnt, Grund: :reason.', ], 'header' => 'Du hast :count Benachrichtigungen.', 'index' => [ 'title' => 'Benachrichtigungen', ], 'map' => [ 'chunked' => 'Karte :name ist mit dem aufteilen fertig und kann jetzt verwendet werden.', ], 'no_notifications' => 'Es gibt aktuell keine Benachrichtigungen.', 'plugins' => [ 'comments' => [ 'new_comment' => ':user hat einen neuen Kommentar zu dem Plugin :plugin hinterlassen.', 'new_reply' => ':user hat auf Ihren Kommentar in :plugin geantwortet.', ], ], 'subscriptions' => [ 'charge_fail' => 'Bei der Verarbeitung Ihrer Zahlungsmethode ist ein Fehler aufgetreten. Bitte warten Sie einen Moment, während wir es erneut versuchen. Wenn sich nichts ändert, kontaktieren Sie uns bitte.', 'deleted' => 'Ihr Abonnement für Kanka wurde nach zu vielen fehlgeschlagenen Versuchen, Ihre Karte zu belasten, gekündigt. Bitte gehen Sie zu Ihren Abonnementeinstellungen und versuchen Sie, Ihre Zahlungsdetails zu aktualisieren.', 'ended' => 'Ihr Abonnement für Kanka ist beendet. Ihre Kampagnen-Boosts und Discord-Rollen wurden entfernt. Wir hoffen, Sie bald wieder zu sehen!', 'failed' => 'Ihr Abonnement für Kanka wurde nach zu vielen fehlgeschlagenen Versuchen, Ihre Zahlungsmethode zu belasten, gekündigt. Bitte gehen Sie zu Ihren Abonnementeinstellungen und versuchen Sie, Ihre Zahlungsdetails zu aktualisieren.', 'started' => 'Ihr Abonnement wurde gestartet', 'trial' => 'Deine kostenlose Testversion von Kanka ist abgelaufen. Wir hoffen, dass es dir gefallen hat und freuen uns auf deinen nächsten Besuch!', ], 'unread' => 'Neue Benachrichtigung', ]; ================================================ FILE: lang/de/onboarding/attributes.php ================================================ 'Mit Attributen kannst du kleine, wiederverwendbare Informationen zu :name hinzufügen, wie z. B. Alter, Trefferpunkte, Fraktionsrang oder beliebige benutzerdefinierte Statistiken, die du verfolgst. Sie eignen sich ideal für Daten, auf die du über mehrere Objekten hinweg verweisen, sortieren oder als Vorlage verwenden möchtest.', 'title' => 'Strukturierte Daten für dieses Objekt speichern', ]; ================================================ FILE: lang/de/onboarding/characters.php ================================================ 'Das reicht für den Anfang.', 'text' => 'Konzentriere dich auf das Wesentliche :name, kurze Beschreibung und ein oder zwei charakteristische Merkmale. Details wie Verbindungen, Eigenschaften und Porträts kannst du später hinzufügen.', 'title' => 'Erstelle deinen ersten Charakter', ]; ================================================ FILE: lang/de/onboarding/locations.php ================================================ 'Halte es vorerst einfach.', 'text' => 'Fange klein an. Benenne den Ort und füge einen Satz hinzu, der erklärt, warum er wichtig ist. Sobald die Welt Gestalt annimmt, kannst du sie mit Karten, Einwohnern und verschachtelten Orten erweitern.', 'title' => 'Erstelle deinen ersten Ort', ]; ================================================ FILE: lang/de/onboarding/posts.php ================================================ 'Mit Beiträgen kannst du öffentliche Texte von privaten Notizen, GM-internen Geheimnissen und unterstützenden Informationen trennen. Erstelle so viele Beiträge, wie du benötigst, und lege fest, wer sie sehen darf.', 'title' => 'Verwende Beiträge für Geheimnisse und zusätzliche Details', ]; ================================================ FILE: lang/de/onboarding/reminders.php ================================================ 'Erstelle Erinnerungen für Fristen, Termine in der Geschichte, Jahrestage oder alles andere, was mit :name in Verbindung steht und das du nicht vergessen möchtest. Die Erinnerungen, die du hier hinzufügst, werden auf dieser Seite und an jedem Kalenderdatum angezeigt, mit dem du sie verknüpfst.', 'title' => 'Verfolge hier zeitkritische Details', ]; ================================================ FILE: lang/de/onboarding/tags.php ================================================ 'Nicht-Spieler-Charaktere', ]; ================================================ FILE: lang/de/organisations.php ================================================ [ 'title' => 'Erstelle eine neue Organisation', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_defunct' => 'stillgelegt', 'members' => 'Mitglieder', ], 'helpers' => [], 'hints' => [ 'is_defunct' => 'Diese Organisation ist aufgelöst.', ], 'index' => [], 'lists' => [ 'empty' => 'Gründe Gilden, Fraktionen oder Geheimgesellschaften, um die Machtstruktur deiner Welt zu gestalten.', ], 'members' => [ 'actions' => [ 'add_multiple' => 'Mitglied hinzufügen', ], 'create' => [ 'helper' => 'Füge ein oder mehrere Mitglieder zu :name.', 'success_multiple' => '{1} Mitglied :count wurde zu :name.|[2,*] Mitglied :count wurde zu :name.', ], 'destroy' => [ 'success' => 'Mitglied aus Organisation entfernt.', ], 'edit' => [ 'helper' => 'Ändere den Mitgliedsstatus für :name.', 'title' => 'Aktualisiere Mitglied für :name', ], 'fields' => [ 'parent' => 'Vorgesetzter', 'pinned' => 'gepinned', 'role' => 'Rolle', 'status' => 'Mitgliedsstatus', ], 'helpers' => [ 'all_members' => 'Alle Charaktere, die Mitglieder dieser Organisation und ihrer Unterorganisationen sind.', 'members' => 'Alle Charaktere, die Mitglieder dieser Organisation sind.', 'pinned' => 'Wählen Sie aus, ob dieses Mitglied im angehefteten Abschnitt der Übersicht der zugehörigen Objekten angezeigt werden soll.', ], 'pinned' => [ 'both' => 'beide', 'none' => 'keiner', ], 'placeholders' => [ 'parent' => 'Wer ist der Vorgesetzte dieses Mitglieds?', 'role' => 'Anführer, Mitglied, Hoher Septon, Meisterspion', ], 'status' => [ 'active' => 'aktive Mitglieder', 'inactive' => 'inaktive Mitglieder', 'unknown' => 'unbekannter Status', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'Kult, Gang, Rebellion, Anhängerschaft', ], 'quests' => [], 'show' => [], ]; ================================================ FILE: lang/de/pagination.php ================================================ 'Nächste »', 'previous' => '« Vorherige', ]; ================================================ FILE: lang/de/partials.php ================================================ [ 'description' => 'Es gab Probleme mit deiner Eingabe.', 'title' => 'Hoppla!', ], ]; ================================================ FILE: lang/de/passwords.php ================================================ 'Passwort muss mindestens 6 Zeichen lang sein und mit der Bestätigung übereinstimmen', 'reset' => 'Dein Passwort wurde zurückgesetzet!', 'sent' => 'Wir haben dir einen Rücksetzungslink zugeschickt!', 'token' => 'Dieser Rücksetztoken ist ungültig.', 'user' => 'Wir können leider keinen NUtzer mit der Email Adresse finden.', ]; ================================================ FILE: lang/de/patreon.php ================================================ [ 'elemental' => 'Elementar', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Lindwurm', ], ]; ================================================ FILE: lang/de/permissions.php ================================================ [ 'delete' => 'Erlaubnis zum Löschen dieses Elements', 'edit' => 'Erlaubnis, dieses Element zu bearbeiten', 'view' => 'Erlaubnis zur Ansicht dieses Elements', ], 'members' => [ 'inherited' => ':member kann dies bereits tun, indem es Teil der :role-Rolle ist.', ], 'roles' => [ 'inherited' => 'Die :role-Rolle kann dies bereits für das gesamte :module-Modul tun.', ], ]; ================================================ FILE: lang/de/pins.php ================================================ 'Erfahre mehr über Pins in unserer Dokumentation.', 'options' => [ 'no' => 'nicht mehr angeheftet', 'yes' => 'Angeheftet an die Übersichtsseite des Objekts', ], ]; ================================================ FILE: lang/de/post_layouts.php ================================================ 'Charakterorganisationen', 'connection_map' => 'Verbindungskarte', 'helper' => 'Dieser Beitrag ist so eingerichtet, dass er die :subpage-Unterseite des Objekts anzeigt.', 'location_characters' => 'Standortzeichen', 'location_events' => 'Veranstaltungen am Standort', 'location_quests' => 'Standort-Quests', 'pitch' => [ 'custom' => 'Zeige Inhalte aus den Unterseiten dieses Objekts direkt in der Übersicht mit Beitrags-Layouts an. Zeige beispielsweise das Inventar von :entity an.', 'title' => 'Erweiterte Post-Layouts', ], 'premium' => 'Einige Layout-Optionen sind deaktiviert, da sie eine Premium-Kampagne erfordern.', 'quest_elements' => 'Quest Elemente', ]; ================================================ FILE: lang/de/posts.php ================================================ [ 'title' => 'Neuer Post', ], 'fields' => [ 'name' => 'Name', ], 'helpers' => [ 'new' => 'Füge einen neuen Beitrag zu diesem Objekt hinzu.', ], 'placeholders' => [ 'name' => 'Name des Posts', ], 'position' => [ 'dont_change' => 'Nicht verändern', 'first' => 'Erster', 'last' => 'Letzter', ], ]; ================================================ FILE: lang/de/presets.php ================================================ [ 'create' => 'Erstelle eine Vorlage', ], 'create' => [ 'success' => 'Vorlage :name erstellt', 'title' => 'Neue Vorlage', ], 'destroy' => [ 'success' => 'Vorlage :name gelöscht', ], 'edit' => [ 'success' => 'Vorlage :name geändert', 'title' => 'Ändere Vorlage :name', ], 'fields' => [ 'name' => 'Volagenname', ], 'lists' => [ 'empty' => 'Derzeit sind keine Vorlage in der Kampagne verfügbar.', ], 'placeholders' => [ 'name' => 'Der Name der Vorlage', ], ]; ================================================ FILE: lang/de/profiles.php ================================================ [], 'avatar' => [ 'success' => 'Avatar aktualisiert.', ], 'campaign_switcher_order_by' => [], 'edit' => [ 'success' => 'Profil aktualisert', ], 'editors' => [], 'fields' => [ 'avatar' => 'Avatar', 'bio' => 'Biografie', 'email' => 'Email', 'hide_subscription' => 'Verstecke meinen Namen in der :hall_of_fame', 'last_login_share' => 'Teile mit anderen Kampagnenmitgliedern, wann ich mich zuletzt angemeldet habe.', 'login_sharing' => 'Letzte Login-Freigabe', 'name' => 'Name', 'new_password' => 'Neues Passwort (optional)', 'new_password_confirmation' => 'Neues Passwort bestätigen', 'newsletter' => 'Ich würde gern manchmal per Email kontaktiert werden.', 'password' => 'Aktuelles Passwort', 'profile-name' => 'Profilname', 'settings' => 'Einstellungen', 'subscription_hiding' => 'Abonnement versteckt', 'theme' => 'Theme', ], 'helpers' => [ 'profile-name' => 'Ändere die Art und Weise, wie dein Name in deinem :profile und dem :marketplace erscheint. Wenn du das Feld leer lässt, wird stattdessen dein Kontoname verwendet.', ], 'newsletter' => [ 'helpers' => [ 'header' => 'Abonnieren Sie die folgenden E-Mail-Newsletter, um über Kanka informiert zu werden.', ], 'options' => [ 'monthly' => 'Kanka Newsletter', ], 'title' => 'Newsletter', 'updated' => 'Newsletter-Einstellungen aktualisiert.', ], 'password' => [ 'success' => 'Passwort aktualisiert', ], 'placeholders' => [ 'bio' => 'Eine kurze Biografie von Ihnen, die in Ihrem öffentlichen Profil angezeigt wird.', 'email' => 'Deine Email Adresse', 'name' => 'Dein Name, wie er dargestellt wird', 'new_password' => 'Dein neues Passwort', 'new_password_confirmation' => 'Bestätige dein neues Passwort', 'password' => 'Gib dein aktuelles Passwort für Änderungen ein', ], 'sections' => [ 'dangerzone' => 'Gefahrenzone', 'delete' => [ 'confirm' => 'Ja, lösche meinen Account', 'delete' => 'Lösche meinen Account', 'goodbye' => 'Wenn ja, schreiben Sie bitte :code in das Feld unten.', 'helper' => 'Durch das Löschen Ihres Kontos werden auch alle Kampagnen gelöscht, bei denen Sie das einzige Mitglied sind. Diese Aktion ist permanent und kann nicht rückgängig gemacht werden.', 'subscribed' => 'Bitte kündige dein :subscription , bevor du dein Konto löschst.', 'title' => 'Lösche deinen Account', 'warning' => 'Wenn du deinen Account löschst, werden alle Daten gelöscht. Bist du sicher?', ], 'password' => [ 'title' => 'Ändere dein Passwort', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'Die Biografie ist auf Ihrem :link sichtbar.', 'profile' => 'öffentliches Profil', ], 'success' => 'Einstellungen geändert.', ], 'theme' => [ 'success' => 'Theme geändert.', 'themes' => [ 'dark' => 'Dunkel', 'default' => 'Standard', 'future' => 'Zukunft', 'midnight' => 'Mitternacht Blau', ], ], 'title' => 'Aktualisiere dein Profil', 'workflows' => [ 'created' => 'Gehe zum erstellten Objekt', 'default' => 'Liste der Objekte', ], ]; ================================================ FILE: lang/de/quests.php ================================================ [ 'title' => 'Erstelle einen neuen Quest', ], 'destroy' => [], 'edit' => [], 'elements' => [ 'create' => [ 'success' => 'Objekt :entity zur Quest hinzugefügt', 'title' => 'Neues Element für :name', ], 'destroy' => [ 'success' => 'Quest Element :entity entfernt', ], 'edit' => [ 'success' => 'Quest Element :entity aktualisiert', 'title' => 'Questelement für aktualisieren :name', ], 'fields' => [ 'entity_or_name' => 'Wählen Sie entweder ein Objekt der Kampagne aus oder geben Sie diesem Element einen Namen.', ], ], 'fields' => [ 'copy_elements' => 'Kopiere Elemente, die an die Queste angehängt sind', 'date' => 'Datum', 'element_role' => 'Rolle', 'instigator' => 'Impulsgeber', 'is_completed' => 'Abgeschlossen', 'location' => 'Startpunkt', 'role' => 'Rolle', ], 'helpers' => [ 'is_completed' => 'Wählen Sie aus, ob die Quest als abgeschlossen gilt.', ], 'hints' => [], 'index' => [], 'items' => [], 'lists' => [ 'empty' => 'Erstelle Quests, um Ziele, Handlungsstränge oder Charaktermotivationen festzuhalten.', ], 'locations' => [], 'organisations' => [], 'placeholders' => [ 'date' => 'Reales Datum der Quest', 'entity' => 'Name eines Elements aus der Quest', 'location' => 'Der Startpunkt der Suche', 'role' => 'Die Rolle des Objekts in der Quest', 'type' => 'Charakterentwicklung, Sidequest, Hauptquest', ], 'show' => [ 'actions' => [ 'add_element' => 'Element hinzufügen', ], 'tabs' => [ 'elements' => 'Elemente', ], ], ]; ================================================ FILE: lang/de/races.php ================================================ [], 'create' => [ 'title' => 'Neue Spezies', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'members' => 'Mitglieder', ], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Diese Rasse ist ausgestorben.', ], 'index' => [], 'lists' => [ 'empty' => 'Definiere die Arten, Kulturen oder Völker, die deine Welt bevölkern.', ], 'members' => [ 'create' => [ 'helper' => 'Füge ein oder mehrere Charaktere zu :name hinzu.', 'submit' => 'Mitlgied hinzufügen', 'success' => '{0} Kein Mitglied wurde hinzugefügt.|{1} 1 Mitglied wurde hinzugefügt.|[2,*] :count Mitglieder wurden hinzugefügt.', 'title' => 'Neue Mitglieder', ], ], 'placeholders' => [ 'type' => 'Mensch, Fee, Borg', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/de/redirects.php ================================================ 'Deine Sitzung ist ausgelaufen. Bitte erneut versuchen.', 'unknown_entity' => 'Entschuldige, wir wissen nicht was :entity ist', ]; ================================================ FILE: lang/de/releases.php ================================================ [ 'event' => 'Event', 'livestream' => 'Livestream', 'other' => 'Andere', 'release' => 'Veröffentlichung', 'vote' => 'Community Abstimmung', ], 'index' => [ 'description' => 'Die letzten Neuigkeiten zu kanka.io', 'title' => 'Versionen', ], 'post' => [ 'footer' => 'Von :name :date', ], 'show' => [ 'return' => 'Zurück zu den Versionen', 'title' => 'Version :name', ], ]; ================================================ FILE: lang/de/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chroniken der Dunkelheit', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/de/search/fulltext.php ================================================ 'Suche nach Objekten, Beiträgen, Attributen und mehr nach dem Begriff :term.', 'title' => 'Volltextsuche', ]; ================================================ FILE: lang/de/search.php ================================================ 'Überall suchen', 'lookup' => [ 'empty' => 'keine Ergebnisse', 'hint' => 'Gib mindestens 3 Buchstaben ein, um nach Objekten in der Kampagne zu suchen.', 'keyboard' => 'Drücke :k zum Suchen, :esc zum Verwerfen', 'recents' => 'Letzte', 'results' => 'Ergebnisse', ], 'no_results' => 'Keine Ergebnisse.', 'placeholder' => 'SUCHE', 'preview' => [ 'links' => 'Links', 'no-connections' => 'Keine angehefteten Beziehungen zum Anzeigen', ], 'title' => 'Suchen', ]; ================================================ FILE: lang/de/seo.php ================================================ 'Dashboard von :campaign', 'entity-list' => 'Erkunde das :module von :campaign', ]; ================================================ FILE: lang/de/settings/account.php ================================================ 'Kontrolliere deine E-Mail-, Passwort-, Sicherheits- und andere Kontoeinstellungen.', 'title' => 'Konto-Informationen', ]; ================================================ FILE: lang/de/settings/appearance.php ================================================ [ 'learn-more' => 'Erfahre mehr über diese Einstellung in unserer Dokumentation.', 'save' => 'Einstellungen speichern', ], 'campaign-switcher' => [ 'alphabetical' => 'Alphabetisch (A-Z)', 'date_created' => 'Erstellungsdatum (älteste zuerst)', 'date_joined' => 'Beitrittsdatum (älteste zuerst)', 'r_alphabetical' => 'Alphabetisch (A-Z)', 'r_date_created' => 'Erstellungsdatum (älteste zuerst)', 'r_date_joined' => 'Beitrittsdatum (älteste zuerst)', ], 'dismissible' => [ 'main' => 'Kontrolliere, wie Kanka aussieht und sich anfühlt. Bitte beachte, dass Kampagnen einige dieser Einstellungen überschreiben können.', ], 'editors' => [ 'default' => 'Standard (:name)', 'legacy' => 'Vermächtnis (:name)', ], 'explore' => [ 'grid' => 'Raster (Standard)', 'table' => 'Tabelle', ], 'fields' => [ 'campaign-order' => 'Reihenfolge der Kampagnenliste', 'date-format' => 'Datumsformatierung', 'editor' => 'Texteditor', 'entity-explore' => 'Objektlisten', 'mentions' => 'Erwähnungen beim Bearbeiten', 'new-entity-workflow' => 'Neuer Objektworkflow', 'pagination' => 'Ergebnisse pro Seite', 'theme' => 'Theme-Präferenz', ], 'helpers' => [ 'advanced-mentions' => 'Steuere beim Bearbeiten von Texten, ob Erwähnungen als Name des Objekts oder als erweiterte Erwähnung wiedergegeben werden.', 'campaign-order' => 'Ändere die Reihenfolge, in der Kampagnen im Kampagnenwechsler aufgelistet werden.', 'date-format' => 'Wenn verfügbar, steuere das Format, in dem reale Daten angezeigt werden sollen.', 'entity-explore' => 'Steuer die Art und Weise, wie Objektlisten in Kampagnen angezeigt werden.', 'new-entity-workflow' => 'Steuer, zu welcher Schnittstelle du weitergeleitet wirst, nachdem du ein neues Objekt erstellt hast.', 'overridable' => 'Einzelne Kampagnen können diese Einstellung überschreiben.', 'pagination' => 'Definiere für Listen, die sich über mehrere Seiten erstrecken, wie viele auf jeder Seite sichtbar sind.', 'theme' => 'Wähle aus, wie Kanka für dich aussieht.', ], 'mentions' => [ 'advanced' => 'Erweiterte Erwähnungen anzeigen :code', 'default' => 'Erwähnungen als Objektname', ], 'nested' => [], 'success' => 'Darstellungsoptionen gespeichert.', 'values' => [ 'pagination' => ':amount Ergebnisse pro Seite', 'pagination-sub' => ':amount (verfügbar für Abonnenten)', ], ]; ================================================ FILE: lang/de/settings/boosters.php ================================================ [ 'boost_name' => 'Boost :name', ], 'available' => 'Verfügbare Booster :amount/:total', 'benefits' => [ 'boosted' => 'Durch das Boosten einer Kampagne mit :one Booster werden der Zugriff auf den :marketplace, Themenoptionen, größere Uploads für alle Mitglieder, die Wiederherstellung gelöschter Objekte und :more freigeschaltet.', 'more' => 'weitere erstaunliche Funktionen', 'superboosted' => 'Durch das Superboosten einer Kampagne mit :amount-Boostern werden alle Vorteile einer Booster-Kampagne freigeschaltet, sowie eine Kampagnengalerie, vollständige Protokolländerungen an Objekten und :more.', ], 'boost' => [ 'actions' => [ 'confirm' => 'Booste es!', 'remove' => 'Stoppe boosten von :campaign', 'subscribe' => 'Kanka abonnieren', 'upgrade' => 'Aktualisieren Sie Ihr Abonnement', ], 'confirm' => 'Wie aufregend! Du bist dabei, :campaign zu verstärken. Dadurch wird einer (:Kosten) deiner verfügbaren Kampagnen-Booster zugewiesen.', 'duration' => 'Zugewiesene Booster bleiben zugewiesen, bis du sie manuell entfernst oder dein Abonnement endet.', 'errors' => [ 'boosted' => 'Oh oh, sieht so aus, als ob die Kampagne bereits geboosted wurde!', 'out-of-boosters' => 'Ach nein! Du hast nicht genügend Booster zur Verfügung. Du hast :available und benötigen :cost. Beende entweder das Boosten anderer Kampagnen oder führe ein Upgrade durch.', ], 'pitch' => 'Werde Abonnent, um Kampagnen-Booster freizuschalten.', 'success' => 'Die :campaign Kampagne wird jetzt geboostet. Genieße all die neuen fantastischen Funktionen!', 'title' => 'Boost :campaign', 'upgrade' => 'Aktualisieren Sie Ihr Abonnement', ], 'campaign' => [ 'boosted' => 'Geboostet von :user seid :time', 'premium' => 'Premium danke an :user seit :time', 'standard' => 'Standard', 'superboosted' => 'Supergeboostet von :user seid :time', 'unboosted' => 'boosten beenden', ], 'intro' => [ 'anyone' => 'Du bist nicht darauf beschränkt, nur von dir erstellte Kampagnen zu fördern. Du kannst jede Kampagne fördern, an der du teilnimmst oder die du sehen kannst. Dazu gehören Kampagnen, bei denen du ein Spieler bist, oder :public, die dir Spaß machen.', 'data' => 'Wenn eine Kampagne nicht mehr geboostet wird, wird der Zugriff auf geboosterte Funktionen entfernt. Es werden jedoch keine Inhalte gelöscht, sodass der Zugriff auf die Kampagne in der Zukunft wiederhergestellt wird.', 'first' => 'Erweiterte Funktionen werden freigeschaltet, indem du deine Booster einer Kampagne Boosten oder Superboosten zuweist. Die Anzahl der Booster, die du hast, wird durch dein :Abonnement bestimmt. Diese Nummer steht dir als Abonnent jederzeit zur Verfügung. Das Boosten einer Kampagne weist du einen deiner Booster zu, während das Superboosten einer Kampagne drei davon zuweist.', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Stelle eine zuvor gelöschtest Objekt für bis zu :amount Tage wieder her', 'customisable' => 'Vollständige Anpassung des Aussehens einer Kampagne', 'icons' => 'Zugriff auf Tausende schöner Symbole für Karten und Zeileisten', 'title' => 'Geboostete Kampagnen erhalten', 'upload' => 'Größere Upload-Größe für alle Kampagnenmitglieder', ], 'description' => 'Weise Kampagnen Booster zu und helfe dabei, erstaunliche Funktionen für alle Beteiligten freizuschalten. Nicht beeindruckt von geboosteten Kampagnen? Wir haben Sie mit supergeboosteten Kampagnen abgedeckt!', 'more' => 'Sehe dir die vollständige Liste der Vergünstigungen auf unserer :Booster-Seite an.', 'title' => 'Bringe eine Kampagne mit Anpassungen und Vorteilen für alle Mitglieder auf die nächste Stufe', ], 'ready' => [ 'available' => 'Deine verfügbaren Kampagnen-Booster.', 'pricing' => 'Alle deine Abonnementstufen beinhalten mindestens einen Kampagnen-Booster und einen Startbetrag pro Monat.', 'pricing-amount' => ':currency:amount', 'title' => 'Booste Kampagne', ], 'superboost'=> [ 'actions' => [ 'confirm' => 'Superbooste es!', 'instead' => 'Superboost es für :count!', 'remove' => 'Stoppe superboosten :campaign', ], 'confirm' => 'Wie aufregend! Du bist dabei, :campaign zu superboosten. Dadurch werden drei (:Kosten) deiner verfügbaren Kampagnen-Booster zugewiesen.', 'errors' => [ 'boosted' => 'Oh oh, sieht so aus, als ob :campaign bereits superboosted ist!', ], 'success' => 'Die :campaign Kampagne ist jetzt supergeboostet. Genieße all die neuen fantastischen Funktionen!', 'title' => 'Superboost :campaign', 'upgrade' => 'Bereit für das ultimative Kanka-Erlebnis? Superboosting :campaign weist :cost zusätzliche Kampagnen-Booster zu.', ], 'title' => 'Kampagnen-Booster', 'unboost' => [ 'confirm' => 'Ja, ich bin sicher', 'status' => [ 'boosting' => 'boosting', 'superboosting' => 'superboosting', ], 'success' => 'Die :campaign Kampagne wird nicht länger geboostet, und ihre Booster sind wieder verfügbar', 'title' => 'boosten der Kampagne beenden', 'warning' => 'Möchtest du :action :campaign wirklich beenden? Dadurch werden deine zugewiesenen Booster freigegeben und alle Inhalte und Funktionen im Zusammenhang mit den Vorteilen ausgeblendet, bis die Kampagne erneut geboostet wird.', ], ]; ================================================ FILE: lang/de/settings/premium.php ================================================ [ 'remove' => 'Premium entfernen', 'unlock' => 'Premium gehen', ], 'create' => [ 'actions' => [ 'confirm' => 'Premium gehen!', ], 'confirm' => 'Wie aufregend! Du bist dabei, Premium-Funktionen für :campaign freizuschalten. Dies wird eine deiner verfügbaren Premium-Kampagnen verwenden.', 'duration' => 'Premium-Kampagnen bleiben in diesem Zustand, bis Du sie manuell entfernst oder Ihr Abonnement endet.', 'pitch_2026' => 'Erhalte unbegrenzte Rollen, Mitglieder, benutzerdefinierte Designs, Plugins und vieles mehr für deine Kampagnen.', 'success' => 'Die :campaign Kampagne ist jetzt Premium. Genieße all die neuen, fantastischen Funktionen!', ], 'exceptions' => [ 'already' => 'Für diese Kampagne wurden bereits Premium-Funktionen freigeschaltet.', 'out-of-stock' => 'Du hast nicht genügend Premium-Kampagnen zur Verfügung, um diese Kampagne freizuschalten. Entweder entfernst du den Premium-Status von einer anderen Kampagne oder :upgrade.', ], 'pitch' => [ 'description' => 'Erwerbe Premium-Kampagnen und hilf, erstaunliche Funktionen für alle Beteiligten freizuschalten.', 'title' => 'Premium-Kampagnen erhalten', ], 'ready' => [ 'available' => 'Ihre verfügbaren Premium-Kampagnen.', 'pricing' => 'Alle unsere Abo-Stufen beinhalten mindestens eine Premium-Kampagne und beginnen mit :Betrag pro Monat.', 'pricing-amount' => ':currency:amount', 'title' => 'Premium gehen', ], 'remove' => [ 'confirm' => 'Ja, ich bin mir sicher', 'cooldown' => 'Die Premium-Features aus der :campaign können nach dem :date entfernt werden.', 'success' => 'Die Premiumfunktionen wurden aus der Kampagne :campaign entfernt. Du kannst jetzt Premium-Funktionen in einer anderen Kampagne freischalten.', 'title' => 'Entfernen von Premiumfunktionen', 'warning' => 'Bist du sicher, dass du die Premium-Funktionen von :campaign entfernen möchtest? Dadurch kannst du eine andere Kampagne freischalten und alle Inhalte und Funktionen im Zusammenhang mit den Vergünstigungen ausblenden, bis der Premium-Status der Kampagne wieder aktiviert ist.', ], ]; ================================================ FILE: lang/de/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Zwei-Faktor authentifizierung deaktivieren', 'disable-confirm' => 'Zum Bestätigen erneut klicken', 'finish' => 'Beende die Einrichtung und melde dich an', ], 'activation_helper' => 'Um die Einrichtung der Zwei-Faktor-Authentifizierung deines Kontos abzuschließen, befolge bitte diese Anweisungen.', 'disable' => [ 'helper' => 'Wenn du die Zwei-Faktor-Authentifizierung deaktivieren möchtest, klicke auf die Schaltfläche unten. Denke daran, dass dein Konto dadurch für jeden angreifbar wird, der deine Anmeldeinformationen kennt.', 'title' => 'Zwei-Faktor-Authentifizierung deaktivieren', ], 'enable_instructions' => 'Um den Aktivierungsprozess zu starten, generiere deinen Authentifizierungs-QR-Code und scanne ihn dann in die Google Authenticator-App (:ios, :android) oder eine andere ähnliche Authentifizierungs-App.', 'enabled' => 'Die Zwei-Faktor-Authentifizierung ist derzeit für dein Konto aktiviert.', 'error_enable' => 'Ungültiger Code, versuche es erneut', 'fields' => [ 'otp' => 'Gib das One Time Password (OTP) ein, das von der Authentifizierungs-App bereitgestellt wird', 'qrcode' => 'Scanne den folgenden QR-Code mit deiner Authentifizierungs-App, um ein One Time Password (OTP) zu generieren.', ], 'generate_qr' => 'QR Code generieren', 'helper' => 'Die Zwei-Faktor-Authentifizierung (2FA) stärkt die Zugriffssicherheit, indem zwei Methoden (auch als Faktoren bezeichnet) erforderlich sind, um deine Identität bei jeder Anmeldung zu überprüfen.', 'learn_more' => 'Erfahre mehr über die Zwei-Faktor-Authentifizierung.', 'social' => 'Die Kanka-Zwei-Faktor-Authentifizierung ist nur für Benutzer aktiviert, die sich mit ihrer E-Mail-Adresse und ihrem Passwort anmelden. Ändere deine Anmeldemethode in deinen Kontoeinstellungen, bevor du diese Option aktivieren kannst.', 'success_disable' => 'Zwei-Faktor-Authentifizierung erfolgreich deaktiviert', 'success_enable' => 'Zwei-Faktor-Authentifizierung erfolgreich aktiviert. Bitte melde dich erneut an, um die Einrichtung abzuschließen.', 'success_key' => 'Dein sicherer QR-Code wurde erfolgreich generiert. Bitte schließe deine Einrichtung ab, um die Zwei-Faktor-Authentifizierung zu aktivieren.', 'title' => 'Zwei-Faktor-Authentifizierung', ], 'actions' => [ 'social' => 'Zu Kanka Login wechseln', 'update_email' => 'Email aktualisieren', 'update_password' => 'Passwort aktualisieren', ], 'email' => 'Email ändern', 'email_success' => 'Email aktualisiert.', 'password' => 'Passwort ändern', 'password_success' => 'Passwort aktualisiert.', 'social' => [ 'error' => 'Du benutzt bereits das Kanka Login für dieses Konto.', 'helper' => 'Dein Konto ist momentan von :provider. Du kannst aufhören dieses zu benutzen und auf ein Standard Kanka Login wechseln, indem du ein Kennwort setzt.', 'success' => 'Dein Konto benutzt jetzt das Kanka Login.', 'title' => 'Social Konto', ], 'title' => 'Account', ], 'api' => [ 'helper' => 'Willkommen bei den Kanka APIs. Generieren Sie ein persönliches Zugriffstoken, das Sie in Ihrer API-Anfrage verwenden können, um Informationen zu den Kampagnen zu sammeln, an denen Sie beteiligt sind.', 'link' => 'Lies die API Dokumentation', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Verbinden', 'remove' => 'Entfernen', ], 'benefits' => 'Kanka bietet einige Integrationsmöglichkeiten für Dienste von Drittanbietern. Weitere Integrationen von Drittanbietern sind für die Zukunft geplant.', 'discord' => [ 'confirm' => 'Möchtest du dein Konto wirklich von Discord trennen? Dadurch werden alle Rollen entfernt, mit denen du synchronisiert wurdest.', 'errors' => [ 'add' => 'Beim Verknüpfen Ihres Discord-Kontos mit Kanka ist ein Fehler aufgetreten. Bitte versuche es erneut.', ], 'success' => [ 'add' => 'Ihr Discord-Konto wurde verknüpft.', 'remove' => 'Ihr Discord-Konto wurde nicht verbunden.', ], 'text' => 'Greifen Sie automatisch auf Ihre Abonnementrollen zu.', 'unlock' => 'Schalte Discord-Rollen frei', ], 'title' => 'App Integration', ], 'billing' => [ 'placeholder' => 'Wenn du deinen Belegen zusätzliche Kontakt- oder Steuerinformationen hinzufügen möchtest (Firmenadresse, Umsatzsteuer-Identifikationsnummer usw.), gib diese unten ein und sie erscheinen auf allen deinen Belegen.', 'save' => 'Zahlungsinformationen speichern', 'title' => 'Zahlungsinformationen', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'Kampagne :name ist bereits geboostet', 'exhausted_boosts' => 'Sie haben keine Boosts mehr zu geben. Entfernen Sie Ihren Boost aus einer Kampagne, bevor Sie ihn einer anderen geben.', 'exhausted_superboosts' => 'Sie haben keine Boosts mehr. Sie benötigen 3 Booster, um eine Kampagne zu boosten.', ], ], 'countries' => [ 'austria' => 'Österreich', 'belgium' => 'Belgien', 'france' => 'Frankreich', 'germany' => 'Deutschland', 'italy' => 'Italien', 'netherlands' => 'Niederlande', 'spain' => 'Spanien', ], 'invoices' => [], 'layout' => [ 'title' => 'Layout', ], 'marketplace' => [], 'menu' => [ 'account' => 'Account', 'api' => 'API', 'appearance' => 'Aussehen', 'apps' => 'Apps', 'boosters' => 'Booster', 'notifications' => 'Benachrichtigungen', 'other' => 'Andere', 'patreon' => 'Patreon', 'payment_options' => 'Zahlungsmöglichkeiten', 'personal_settings' => 'Persönliche Einstellungen', 'premium' => 'Premiumkampagnen', 'profile' => 'Profil', 'settings' => 'Einstellungen', 'subscription' => 'Abonnement', 'subscription_status' => 'Abonnement Status', ], 'patreon' => [ 'deprecated' => 'Veraltete Funktion - Wenn Sie Kanka unterstützen möchten, tun Sie dies bitte mit einem :subscription. Die Patreon-Verknüpfung ist weiterhin für unsere Benutzer aktiv, die ihr Konto vor dem Umzug von Patreon verknüpft haben.', 'pledge' => 'Pledge :name', 'remove' => [ 'button' => 'Trennen Sie die Verknüpfung Ihres Patreon-Kontos', 'success' => 'Ihr Patreon-Konto wurde getrennt.', 'text' => 'Wenn Sie die Verknüpfung Ihres Patreon-Kontos mit Kanka aufheben, werden Ihre Boni, Ihr Name in der Hall of Fame, Kampagnen-Boosts und andere Funktionen im Zusammenhang mit der Unterstützung von Kanka entfernt. Keiner Ihrer verstärkten Inhalte geht verloren (z. B. Objekt header). Wenn Sie sich erneut anmelden, haben Sie Zugriff auf alle Ihre vorherigen Daten, einschließlich der Möglichkeit, Ihre zuvor verstärkten Kampagnen zu boosten.', 'title' => 'Trennen Sie Ihr Patreon-Konto von Kanka', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Aktualisiere dein Profil', ], 'avatar' => 'Profilbild', 'success' => 'Profil aktualisiert.', 'title' => 'Persönliches Profil', ], 'referrals' => [ 'title' => 'Empfehlungen', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Abonnement beenden', 'subscribe' => 'Abonnieren', 'update_currency' => 'Speichern Sie die bevorzugte Währung', ], 'billing' => [ 'helper' => 'Ihre Zahlungsinformationen werden sicher verarbeitet und gespeichert durch :stripe. Diese Zahlungsmethode wird für alle Ihre Abonnements verwendet.', 'saved' => 'Gespeicherte Zahlungsmethode', ], 'cancel' => [ 'grace' => [ 'text' => 'Dein Abonnement ist bereits so eingestellt, dass es am :Datum endet. Danach wird deine Premium-Kampagnen wieder zu Standard-Kampagnen und andere Vorteile, die mit der Unterstützung von Kanka verbunden sind, werden deaktiviert.', 'title' => 'Karenzzeit', ], 'options' => [ 'competitor' => 'Wechsel zu einem Wettbewerber', 'financial' => 'Finanzielle Situation verändert', 'missing_features' => 'Fehlende Funktionen', 'not_for' => 'Abo ist nichts für mich', 'not_playing' => 'Nicht mehr spielen oder die Kampagne macht eine Pause', 'not_using' => 'Kanka wird derzeit nicht verwendet', 'other' => 'Andere', 'testing' => 'teste kanka', ], 'text' => 'Es tut uns leid dich gehen zu sehen! Wenn Sie Ihr Abonnement kündigen, bleibt es bis zu Ihrem nächsten Abrechnungszyklus aktiv. Danach verlieren Sie Ihre Kampagnen-Boosts und andere Vorteile im Zusammenhang mit der Unterstützung von Kanka. Füllen Sie das folgende Formular aus, um uns mitzuteilen, was wir besser machen können oder was zu Ihrer Entscheidung geführt hat.', 'title' => 'Abonnement kündigen', ], 'cancelled' => 'Ihr Abonnement wurde gekündigt. Sie können ein Abonnement verlängern, sobald Ihr aktuelles Abonnement endet.', 'change' => [ 'text' => [ 'downgrade_monthly' => 'Du stufst dein Konto auf die Stufe :tier für :downgrade herunter, danach wird dir monatlich ein Betrag in Höhe von :amount in Rechnung gestellt.', 'downgrade_yearly' => 'Du stufst dein Konto auf die Stufe :tier für :downgrade herunter, danach wird dir jährlich ein Betrag in Höhe von :amount in Rechnung gestellt.', 'monthly' => 'Sie abonnieren die :tier Stufe , die monatlich in Rechnung gestellt wird für :amount.', 'upgrade_monthly' => 'Führe für :upgrade ein Upgrade auf die :tier Stufe durch, danach wird dir monatlich :amount in Rechnung gestellt.', 'upgrade_paypal' => 'Führe für :upgrade bis :date ein Upgrade auf die :tier Stufe durch.', 'upgrade_yearly' => 'Führe für :upgrade ein Upgrade auf die :tier Stufe durch, danach wird dir jährlich :amount in Rechnung gestellt.', 'yearly' => 'Sie abonnieren die :tier Stufe, die jährlich in Rechnung gestellt wird für :amount.', ], 'title' => 'Abonnementstufe ändern', ], 'coupon' => [ 'check' => 'Gutscheincode prüfen', 'invalid' => 'Ungültiger Gutscheincode.', 'label' => 'Promotional code', 'percent_off' => 'Wir reduzieren dein erstes Jahresabonnement um :percent%!', ], 'currencies' => [ 'brl' => 'BRL', 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Ändern Sie Ihre bevorzugte Rechnungswährung', ], 'errors' => [ 'callback' => 'Unser Zahlungsanbieter hat einen Fehler gemeldet. Bitte versuchen Sie es erneut oder kontaktieren Sie uns, wenn das Problem weiterhin besteht.', 'failed' => 'Wir haben derzeit Probleme mit unserem Abrechnungssystem. Bitte kontaktiere uns unter :email für Unterstützung.', 'subscribed' => 'Ihr Abonnement konnte nicht verarbeitet werden. Stripe lieferte den folgenden Hinweis.', ], 'fields' => [ 'active_since' => 'aktiv seit', 'active_until' => 'aktiv bis', 'billing' => 'Abrechnung', 'currency' => 'Abrechnungswährung', 'payment_method' => 'Zahlungsmethode', 'plan' => 'Derzeitiger Plan', 'reason' => 'Ursache', 'reset' => 'Rechnungsinformationen zurücksetzen', 'reset_billing' => 'Ich bin mir bewusst, dass bei einem Währungswechsel mein Abrechnungsverlauf verloren geht und ich meine Zahlungsmethode erneut eingeben muss.', ], 'helpers' => [ 'alternatives' => 'Bezahlen Sie Ihr Abonnement mit :method. Diese Zahlungsmethode wird am Ende Ihres Abonnements nicht automatisch verlängert. :method ist nur in Euro verfügbar.', 'alternatives-2' => 'Bezahle dein Abonnement mit der Methode :method. Hierbei handelt es sich um eine einmalige Zahlung, die sich am Ende des Abonnements nicht automatisch verlängert.', 'alternatives_warning' => 'Ein Upgrade Ihres Abonnements mit dieser Methode ist nicht möglich. Bitte erstellen Sie ein neues Abonnement, wenn Ihr aktuelles Abonnement endet.', 'alternatives_yearly' => 'Aufgrund der Einschränkungen bei wiederkehrenden Zahlungen ist die :method nur für Jahresabonnements verfügbar', 'currency_block' => 'Es ist nicht möglich, die Währung zu ändern, solange Sie ein aktives Kanka-Abonnement haben. Sie können Ihre Währung ändern, sobald Ihr aktuelles Abonnement endet.', 'currency_reset' => 'Wenn Sie die Währung Ihrer Wahl ändern, wird Ihr Rechnungsverlauf gelöscht und Sie müssen eine neue Zahlungsmethode eingeben.', 'paypal_v3' => 'Bezahle dein Jahresabonnement sicher mit PayPal.', 'stripe' => 'Deine Rechnungsinformationen werden von :stripe sicher verarbeitet und gespeichert.', ], 'manage_subscription' => 'Abonnement verwalten', 'payment_method' => [ 'actions' => [ 'add' => 'Hinzufügen', 'add_new' => 'Füge eine neue Zahlungsmethode hinzu', 'change' => 'Zahlungsmethode ändern', 'save' => 'Zahlungsmethode speichern', 'show_alternatives' => 'alternative Zahlungsoptionen', ], 'add_one' => 'Sie haben derzeit keine Zahlungsmethode gespeichert.', 'alternatives' => 'Sie können diese alternativen Zahlungsoptionen abonnieren. Diese Aktion belastet Ihr Konto einmal und erneuert Ihr Abonnement nicht jeden Monat automatisch.', 'card' => 'Karte', 'card_name' => 'Name auf der Karte', 'country' => 'Land des Wohnsitzes', 'ending' => 'gültig bis', 'helper' => 'Diese Karte wird für alle Ihre Abonnements verwendet.', 'new_card' => 'Fügen sie eine neue Zahlungsmethode hinzu', 'saved' => ':brand endet mit :last4', ], 'periods' => [ 'monthly' => 'Monatlich', 'yearly' => 'Jährlich', ], 'placeholders' => [ 'downgrade_reason' => 'Teile uns optional mit, warum du dein Abonnement herabstufst.', 'reason' => 'Sagen Sie uns optional, warum Sie Kanka nicht mehr unterstützen. Fehlt eine Funktion? Hat sich Ihre finanzielle Situation geändert?', ], 'plans' => [ 'cost_monthly' => ':currency :amount monatlich abgerechnet', 'cost_yearly' => ':currency :amount jährlich abgerechnet', ], 'sub_status' => 'Abonnementinformationen', 'subscription' => [ 'actions' => [ 'cancel' => 'Abonnement beenden', 'downgrading' => 'Bitte kontaktieren Sie uns für ein Downgrade', 'rollback' => 'Wechseln Sie zu Kobold', 'subscribe' => 'Wechseln Sie zu :tier monatlich', 'subscribe_annual' => 'Wechseln Sie zu :tier jährlich', ], ], 'success' => [ 'alternative' => 'Ihre Zahlung wurde registriert. Sie erhalten eine Benachrichtigung, sobald diese verarbeitet wurde und Ihr Abonnement aktiv ist.', 'callback' => 'Ihr Abonnement war erfolgreich. Ihr Konto wird aktualisiert, sobald unsere Zahlung uns über die Änderung informiert (dies kann einige Minuten dauern).', 'currency' => 'Ihre bevorzugte Währungseinstellung wurde aktualisiert.', 'subscribed' => 'Ihr Abonnement war erfolgreich. Vergessen Sie nicht, den Community Vote-Newsletter zu abonnieren, um benachrichtigt zu werden, wenn eine Abstimmung live geht. Sie können Ihre Newsletter-Einstellungen auf Ihrer Profilseite ändern.', ], 'tiers' => 'Abonnementstufen', 'trial_period' => 'Für Jahresabonnements gilt eine Stornierungsfrist von 14 Tagen. Kontaktieren Sie uns unter :email, wenn Sie Ihr Jahresabonnement kündigen und eine Rückerstattung erhalten möchten.', 'upgrade_downgrade' => [ 'button' => 'Upgrade- und Downgrade-Informationen', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Ihre Boni bleiben bis zum Ende Ihres Zahlungszeitraums aktiviert.', 'boosts' => 'Gleiches gilt für Ihre geboosteten Kampagnen. Geboostete Funktionen werden unsichtbar, aber nicht gelöscht, wenn eine Kampagne nicht mehr geboostet wird.', 'kobold' => 'Wechseln Sie zur Kobold-Stufe, um Ihr Abonnement zu kündigen.', 'premium' => 'Das Gleiche gilt für deine Premium-Kampagnen. Premium-Funktionen werden unsichtbar, aber nicht gelöscht, wenn eine Kampagne nicht mehr über den Premium Status verfügt.', ], 'title' => 'Wenn Sie Ihr Abonnement kündigen', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Ihre aktuelle Stufe bleibt bis zum Ende Ihres aktuellen Abrechnungszyklus aktiv. Danach werden Sie auf Ihre neue Stufe herabgestuft.', ], 'provide_reason' => 'Wenn möglich, teile uns bitte mit, warum du dein Abonnement herabstufst.', 'title' => 'Beim Downgrade auf eine niedrigere Stufe', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Ihre Zahlungsmethode wird sofort in Rechnung gestellt und Sie haben Zugriff auf Ihre neue Stufe.', 'prorate' => 'Beim Upgrade von Owlbear auf Elemental wird Ihnen nur die Differenz zu Ihrer neuen Stufe in Rechnung gestellt.', ], 'title' => 'Beim Upgrade auf eine höhere Stufe', ], ], 'warnings' => [ 'incomplete' => 'Wir konnten Ihre Kreditkarte nicht belasten. Bitte aktualisieren Sie Ihre Kreditkarteninformationen. Wir werden versuchen, sie in den nächsten Tagen erneut zu belasten. Wenn es erneut fehlschlägt, wird Ihr Abonnement gekündigt.', 'patreon' => 'Ihr Konto ist derzeit mit Patreon verknüpft. Bitte trennen Sie die Verknüpfung Ihres Kontos in Ihren :patreon-Einstellungen, bevor Sie zu einem Kanka-Abonnement wechseln.', ], ], ]; ================================================ FILE: lang/de/sidebar.php ================================================ [ 'count' => 'Mitglied von :member', 'created_campaigns' => 'deine Kampagnen', 'follow_more' => 'Kampagne folgen', 'followed_campaigns'=> 'Kampagnen denen gefolgt wird', 'new_campaign' => 'Neue Kampagne', 'public_campaigns' => 'öffentliche Kampagne', 'reorder' => 'neu anordnen', 'updated' => 'aktualisiert', ], 'dashboard' => 'Dashboard', 'entity-creator' => 'Schnell erstellen', 'gallery' => 'Galerie', 'game' => 'Spiel', 'other' => 'Anderes', 'recent' => 'Kürzliche Änderungen', 'relations' => 'Beziehungen', 'settings' => 'Einstellungen', 'time' => 'Zeit', 'world' => 'Welt', ]; ================================================ FILE: lang/de/starter.php ================================================ [ 'name' => ':user\'s Welt', ], 'character1' => [], 'character2' => [], 'item1' => [], 'kingdom1' => [], 'kingdom2' => [], 'name' => ':name (Beispiel)', 'note1' => [], ]; ================================================ FILE: lang/de/subscription.php ================================================ [ 'main' => 'Abonnieren Sie Kanka, um größere Bild-Uploads, ein werbefreies Erlebnis, :boosters und :more freizuschalten. Wir verwenden :stripe, um alle Abrechnungen abzuwickeln, ohne dass Kreditkarteninformationen gespeichert oder über unsere Server übertragen werden.', 'more' => 'weitere erstaunliche Funktionen', ], 'errors' => [ 'grace' => 'dein aktuelles Abonnement endet am :date , danach kannst du es erneut abschließen.', 'invalid_card_country' => [ 'brl' => 'Es tut uns leid, aber wir akzeptieren derzeit nur Zahlungen in BRL für Kunden mit brasilianischen Kreditkarten. Wenn Sie glauben, dass dies ein Fehler ist, kontaktieren Sie uns unter :email.', ], 'invalid_currency' => 'Du hattest zuvor ein Abonnement in :old, was dich daran hindert, ein neues Abonnement in :new zu haben. Bitte stelle deine Währung auf :old um, oder kontaktiere uns unter :email, wenn du die Währung wechseln möchtest.', ], ]; ================================================ FILE: lang/de/subscriptions/promos.php ================================================ [ 'inactive' => 'Diese Aktion ist nicht mehr aktiv.', 'invalid' => 'Unbekannte Aktion.', 'only-new' => 'Diese Aktion ist nur für neue Abonnenten verfügbar.', ], ]; ================================================ FILE: lang/de/subscriptions/renew.php ================================================ [ 'renew' => 'Abonnement verlängern', ], 'helper' => 'Du hast jedoch die Möglichkeit, Dein Abonnement zu verlängern, um die Vorteile ohne Unterbrechung zu nutzen.', 'title' => 'Erneuerung des Abonnements', ]; ================================================ FILE: lang/de/subscriptions.php ================================================ [ 'failed' => 'Stripe konnte ihre fällige Zahlung nicht einfordern. Folglich wurde ihr Abonnement deaktiviert.', ], ]; ================================================ FILE: lang/de/tags.php ================================================ [ 'actions' => [ 'add' => 'Füge neue Kategorie hinzu', 'add_entity' => 'Zum Objekt hinzufügen', ], 'create' => [ 'attach_success' => '{1} :count Objekte zum Tag :name hinzugefügt.|[2,*] :count Objekte zum Tag :name hinzugefügt.', 'attach_success_entity' => 'Erfolgreich aktualisierte Tags für :name.', 'entity' => 'Tags hinzufügen zu :name', 'helper' => 'Versehe einen oder mehrere Einträge mit dem Tag :name', 'title' => 'Tag-Einträge', ], ], 'create' => [ 'title' => 'Neue Kategorie', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Kinder', 'icon' => 'Symbol', 'is_auto_applied' => 'Automatisch auf neue Objekte anwenden', 'is_hidden' => 'Ausgeblendet von Header und Tooltip', ], 'helpers' => [ 'icon' => 'Verwende Symbole aus :fontawesome oder :rpgawesome. Das Symbol wird in Listen anstelle des Tag-Namens angezeigt.', 'no_children' => 'Es gibt derzeit kein Objekt, die mit diesem Tag getaggt sind.', 'no_posts' => 'Derzeit gibt es keine Beiträge mit diesem Tag.', ], 'hints' => [ 'children' => 'Diese Liste enthält alle Objekte, die direkt in dieser Kategorie und allen Unterkategorien sind.', 'is_auto_applied' => 'Aktiviere diese Option, um dieses Tag automatisch auf neu erstellte Objekte anzuwenden.', 'is_hidden' => 'Wenn es aktiviert ist, wird dieser Tag nicht in der Kopfzeile oder QuickInfo eines Objekts angezeigt.', 'tag' => 'Unten dargestellt sind alle Kategorien, die direkt unter dieser eingeordnet sind.', ], 'index' => [], 'lists' => [ 'empty' => 'Verwende Tags, um Einträge in deiner Welt zu gruppieren und zu filtern, um die Navigation zu vereinfachen.', ], 'placeholders' => [ 'icon' => 'Probieren Sie :example1 oder :example2 aus', 'type' => 'Überlieferung, Geschichte, Kriege, Religion, Flaggenkunde', ], 'show' => [ 'tabs' => [ 'children' => 'Kinder', ], ], 'tags' => [], 'transfer' => [ 'entities' => [ 'helper' => 'Trage Einträge, die mit dem Tag „name“ versehen sind, in ein anderes Tag über.', 'title' => 'Einträge übertragen', ], 'fail' => 'Die Übertragung von Objekten von :tag nach :newTag ist fehlgeschlagen', 'fail_post' => 'Übertragung von Beiträgen von :tag nach :newTag fehlgeschlagen', 'posts' => [ 'helper' => 'Artikel, die mit dem Tag :name versehen sind, in einen anderen Tag verschieben.', 'title' => 'Artikel übertragen', ], 'success' => 'Objekte wurden erfolgreich von :tag nach :newTag übertragen', 'success_post' => 'Erfolgreich Beiträge von :tag nach :newTag übertragen', 'transfer' => 'übertrage', ], ]; ================================================ FILE: lang/de/teams.php ================================================ [ 'lead' => 'Worldbuilding macht Spaß und ist bewährt', 'translations' => 'Übersetzungen', ], 'leads' => [ 'translators' => 'Kanka wurde dank dieser großartigen Mitwirkenden in mehrere Sprachen übersetzt.', ], 'patreon' => [], 'people' => [ 'itzamna' => [ 'title' => 'Junior-Entwickler', ], 'jay' => [ 'title' => 'Gründer & Hauptentwickler', ], 'jon' => [ 'title' => 'Mitbegründer & Business Manager', ], 'kaz' => [ 'title' => 'Fehlerzerstörer', ], 'laura' => [ 'title' => 'Sozialmedien', ], ], ]; ================================================ FILE: lang/de/tiers.php ================================================ [ 'subscribe' => [ 'choose' => 'Wähle :tier', 'monthly' => ':tier Monatlich', 'yearly' => ':tier Jährlich', ], ], 'current' => 'Ihr aktuelles Abonnement', 'features' => [ 'api_requests' => ':amount API Anfrage / min', 'boosters' => 'Kampagnen Booster', 'discord' => 'Discord Rollen', 'feature_influence' => 'Einfluss neuer Funktionen', 'file_size' => ':size Upload Dateigröße', 'nice_image' => 'Standardobjektbilder', 'no_ads' => 'keine ads', 'pagination' => ':amount Max paginierte Ergebnisse', 'roadmap' => 'Bewerten Sie Ideen in der Roadmap', ], 'periods' => [ 'billed_monthly' => 'monatlich abgerechnet', 'billed_yearly' => 'jährlich abgerechnet', ], 'pricing' => ':currency :amount / monatlich', 'ribbons' => [ 'best-value' => 'Bester Wert', 'current' => 'Aktuelles Abonnement', ], 'toggle' => [], ]; ================================================ FILE: lang/de/timelines/elements.php ================================================ [ 'copy_with_name' => 'Erweiterte Erwähnung mit Elementname kopieren', 'success' => 'Erweiterte Erwähnung des in die Zwischenablage kopierten Elements.', ], 'create' => [ 'success' => 'Element zum Zeitstrahl hinzugefügt', 'title' => 'neues Zeitstrahlelement', ], 'delete' => [ 'success' => 'Element :name entfernt', ], 'edit' => [ 'success' => 'Element aktualisiert', 'title' => 'Zeitstrahlelement editieren', ], 'fields' => [ 'date' => 'Datum', 'era' => 'Epoche', 'icon' => 'Icon', 'use_entity_entry' => 'Zeigen Sie den Eintrag des angehängten Objekts unten an. Der Text dieses Elements wird zuerst angezeigt, falls vorhanden.', 'use_event_date' => 'Verwende das Datum des verknüpften Ereignisses.', ], 'helpers' => [ 'date' => 'Wenn das Element mit einem Ereignisobjekt verknüpft ist, zeige das Datum des Ereignisses an.', 'entity_is_private' => 'Das Element des Objekts ist privat.', 'icon' => 'Kopieren Sie den HTML-Code eines Symbols von :fontawesome oder :rpgawesome.', 'is_collapsed' => 'Das Element wird standardmäßig reduziert angezeigt.', ], 'placeholders' => [ 'date' => 'z.B. 42. März oder 1332-1337', 'name' => 'Erforderlich, wenn kein Objekt ausgewählt ist', 'position' => 'Position in der Liste der Elemente für die Epoche. Lassen Sie das Feld leer, um es am Ende hinzuzufügen.', ], 'warning' => [], ]; ================================================ FILE: lang/de/timelines/eras.php ================================================ [ 'add' => 'neue Epoche hinzufügen', ], 'bulks' => [ 'delete' => '{0} :count eras entfernt.|{1} :count eras entfernt.|[2,*] :count eras entfernt.', ], 'create' => [ 'success' => 'Epoche :name erstellt', 'title' => 'neue Epoche', ], 'delete' => [ 'success' => 'Epoche :name gelöscht', ], 'edit' => [ 'success' => 'Epoche :name aktualisiert', 'title' => 'Epoche :name editieren', ], 'fields' => [ 'abbreviation' => 'Abkürzung', 'end_year' => 'Endjahr', 'is_collapsed' => 'eingeklappt', 'start_year' => 'Startjahr', ], 'helpers' => [ 'eras' => 'Der Zeitstahl muss erstellt werden, bevor Epochen hinzugefügt werden können.', 'is_collapsed' => 'Era ist standardmäßig eingeklappt (minimiert).', 'primary' => 'Trennen Sie Ihre Zeitstrahlen in Epochen. Ein Zeitstrahl benötigt mindestens eine Epoche, um ordnungsgemäß zu funktionieren.', ], 'index' => [ 'title' => 'Epochen von :name', ], 'placeholders' => [ 'abbreviation' => 'AD, BC, BCE', 'end_year' => 'Jahr, in dem die Epoche endet. Lassen Sie das Feld leer, wenn dies die aktuelle Epoche ist.', 'name' => 'Moderne, Bronzzeit, Galaktische Kriege', 'start_year' => 'Jahr, in dem die Epoche beginnt. Lassen Sie das Feld leer, wenn dies die erste Epoche ist.', ], 'reorder' => [], ]; ================================================ FILE: lang/de/timelines.php ================================================ [ 'add_element' => 'Fügen Sie der Epoche ein Element hinzu :era', 'back' => 'zurück zu :name', 'save_order' => 'neue Reihenfolge speichern', ], 'create' => [ 'title' => 'neuer Zeitstrahl', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_elements' => 'Elemente kopieren', 'copy_eras' => 'Epoche kopieren', 'eras' => 'Epochen', 'reverse_order' => 'Reihenfolge der Epochen umkehren', ], 'helpers' => [ 'no_era_v2' => 'Dieser Zeitstrahl hat derzeit keine Epochen. Füge eine oder mehrere Epochen hinzu, danach kannst du hier Elemente zu den Epochen hinzufügen.', 'reverse_order' => 'Aktivieren Sie diese Option, um Epochen in umgekehrter chronologischer Reihenfolge anzuzeigen (ältere Epoche zuerst).', ], 'index' => [], 'lists' => [ 'empty' => 'Erstelle eine visuelle Zeitleiste, um wichtige Ereignisse festzuhalten und zu verfolgen, wie sich deine Welt entwickelt hat.', ], 'placeholders' => [ 'type' => 'Grundschule, Weltchronik, Königreichserbe', ], 'reorder' => [ 'empty' => 'Füge dem Zeitstrahl Epochen und Elemente hinzu, um sie neu anordnen zu können.', 'success' => 'Zeitstrahl erfolgreich neu geordnet.', 'title' => 'Ordne den Zeitstrahl neu', ], 'show' => [ 'tabs' => [ 'reorder-elements' => 'Elemente neu ordnen', ], ], 'timelines' => [], ]; ================================================ FILE: lang/de/users/profile.php ================================================ [ 'wordsmith' => 'Ein wahrer Wortschmied! Gewinner einer Worldbuilding-Eingabeaufforderung.', ], 'fields' => [ 'achievements' => 'Erfolge', 'banned' => 'Dieser Benutzer wurde gesperrt', 'entities_created' => 'Objekt erstellt :help :count', 'member_since' => 'Mitglied seit :date', 'public_campaigns' => 'öffentliche Kampagnen', 'subscriber_since' => 'Abonnent seit :date', ], 'helpers' => [ 'entities_created' => 'Dieser Wert wird jeden Tag neu berechnet.', ], 'title' => ':name Profil', ]; ================================================ FILE: lang/de/validation.php ================================================ ':attribute muss akzeptiert werden.', 'active_url' => ':attribute ist keine gültige Internet-Adresse.', 'after' => ':attribute muss ein Datum nach dem :date sein.', 'after_or_equal' => ':attribute muss ein Datum nach dem :date oder gleich dem :date sein.', 'alpha' => ':attribute darf nur aus Buchstaben bestehen.', 'alpha_dash' => ':attribute darf nur aus Buchstaben, Zahlen, Binde- und Unterstrichen bestehen.', 'alpha_num' => ':attribute darf nur aus Buchstaben und Zahlen bestehen.', 'array' => ':attribute muss ein Array sein.', 'before' => ':attribute muss ein Datum vor dem :date sein.', 'before_or_equal' => ':attribute muss ein Datum vor dem :date oder gleich dem :date sein.', 'between' => [ 'numeric' => ':attribute muss zwischen :min & :max liegen.', 'file' => ':attribute muss zwischen :min & :max Kilobytes groß sein.', 'string' => ':attribute muss zwischen :min & :max Zeichen lang sein.', 'array' => ':attribute muss zwischen :min & :max Elemente haben.', ], 'boolean' => ":attribute muss entweder 'true' oder 'false' sein.", 'confirmed' => ':attribute stimmt nicht mit der Bestätigung überein.', 'date' => ':attribute muss ein gültiges Datum sein.', 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => ':attribute entspricht nicht dem gültigen Format für :format.', 'different' => ':attribute und :other müssen sich unterscheiden.', 'digits' => ':attribute muss :digits Stellen haben.', 'digits_between' => ':attribute muss zwischen :min und :max Stellen haben.', 'dimensions' => ':attribute hat ungültige Bildabmessungen.', 'distinct' => ':attribute beinhaltet einen bereits vorhandenen Wert.', 'email' => ':attribute muss eine gültige E-Mail-Adresse sein.', 'exists' => 'Der gewählte Wert für :attribute ist ungültig.', 'file' => ':attribute muss eine Datei sein.', 'filled' => ':attribute muss ausgefüllt sein.', 'gt' => [ 'numeric' => ':attribute muss mindestens :value sein.', 'file' => ':attribute muss mindestens :value Kilobytes groß sein.', 'string' => ':attribute muss mindestens :value Zeichen lang sein.', 'array' => ':attribute muss mindestens :value Elemente haben.', ], 'gte' => [ 'numeric' => ':attribute muss größer oder gleich :value sein.', 'file' => ':attribute muss größer oder gleich :value Kilobytes sein.', 'string' => ':attribute muss größer oder gleich :value Zeichen lang sein.', 'array' => ':attribute muss größer oder gleich :value Elemente haben.', ], 'image' => ':attribute muss ein Bild sein.', 'in' => 'Der gewählte Wert für :attribute ist ungültig.', 'in_array' => 'Der gewählte Wert für :attribute kommt nicht in :other vor.', 'integer' => ':attribute muss eine ganze Zahl sein.', 'ip' => ':attribute muss eine gültige IP-Adresse sein.', 'ipv4' => ':attribute muss eine gültige IPv4-Adresse sein.', 'ipv6' => ':attribute muss eine gültige IPv6-Adresse sein.', 'json' => ':attribute muss ein gültiger JSON-String sein.', 'lt' => [ 'numeric' => ':attribute muss kleiner :value sein.', 'file' => ':attribute muss kleiner :value Kilobytes groß sein.', 'string' => ':attribute muss kleiner :value Zeichen lang sein.', 'array' => ':attribute muss kleiner :value Elemente haben.', ], 'lte' => [ 'numeric' => ':attribute muss kleiner oder gleich :value sein.', 'file' => ':attribute muss kleiner oder gleich :value Kilobytes sein.', 'string' => ':attribute muss kleiner oder gleich :value Zeichen lang sein.', 'array' => ':attribute muss kleiner oder gleich :value Elemente haben.', ], 'max' => [ 'numeric' => ':attribute darf maximal :max sein.', 'file' => ':attribute darf maximal :max Kilobytes groß sein.', 'string' => ':attribute darf maximal :max Zeichen haben.', 'array' => ':attribute darf nicht mehr als :max Elemente haben.', ], 'mimes' => ':attribute muss den Dateityp :values haben.', 'mimetypes' => ':attribute muss den Dateityp :values haben.', 'min' => [ 'numeric' => ':attribute muss mindestens :min sein.', 'file' => ':attribute muss mindestens :min Kilobytes groß sein.', 'string' => ':attribute muss mindestens :min Zeichen lang sein.', 'array' => ':attribute muss mindestens :min Elemente haben.', ], 'not_in' => 'Der gewählte Wert für :attribute ist ungültig.', 'not_regex' => ':attribute hat ein ungültiges Format.', 'numeric' => ':attribute muss eine Zahl sein.', 'present' => ':attribute muss vorhanden sein.', 'regex' => ':attribute Format ist ungültig.', 'required' => ':attribute muss ausgefüllt sein.', 'required_if' => ':attribute muss ausgefüllt sein, wenn :other :value ist.', 'required_unless' => ':attribute muss ausgefüllt sein, wenn :other nicht :values ist.', 'required_with' => ':attribute muss angegeben werden, wenn :values ausgefüllt wurde.', 'required_with_all' => ':attribute muss angegeben werden, wenn :values ausgefüllt wurde.', 'required_without' => ':attribute muss angegeben werden, wenn :values nicht ausgefüllt wurde.', 'required_without_all' => ':attribute muss angegeben werden, wenn keines der Felder :values ausgefüllt wurde.', 'same' => ':attribute und :other müssen übereinstimmen.', 'size' => [ 'numeric' => ':attribute muss gleich :size sein.', 'file' => ':attribute muss :size Kilobyte groß sein.', 'string' => ':attribute muss :size Zeichen lang sein.', 'array' => ':attribute muss genau :size Elemente haben.', ], 'starts_with' => 'The :attribute must start with one of the following: :values', 'string' => ':attribute muss ein String sein.', 'timezone' => ':attribute muss eine gültige Zeitzone sein.', 'unique' => ':attribute ist schon vergeben.', 'uploaded' => ':attribute konnte nicht hochgeladen werden.', 'url' => ':attribute muss eine URL sein.', 'uuid' => ':attribute muss ein UUID sein.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ 'name' => 'Name', 'username' => 'Benutzername', 'email' => 'E-Mail-Adresse', 'first_name' => 'Vorname', 'last_name' => 'Nachname', 'password' => 'Passwort', 'password_confirmation' => 'Passwort-Bestätigung', 'city' => 'Stadt', 'country' => 'Land', 'address' => 'Adresse', 'phone' => 'Telefonnummer', 'mobile' => 'Handynummer', 'age' => 'Alter', 'sex' => 'Geschlecht', 'gender' => 'Geschlecht', 'day' => 'Tag', 'month' => 'Monat', 'year' => 'Jahr', 'hour' => 'Stunde', 'minute' => 'Minute', 'second' => 'Sekunde', 'title' => 'Titel', 'content' => 'Inhalt', 'description' => 'Beschreibung', 'excerpt' => 'Auszug', 'date' => 'Datum', 'time' => 'Uhrzeit', 'available' => 'verfügbar', 'size' => 'Größe', ], ]; ================================================ FILE: lang/de/visibilities.php ================================================ [ 'admin' => 'Nur die Kampagnenadministratoren können dieses Element sehen.', 'admin-self' => 'Nur du und die Kampagnenadministratoren können dieses Element sehen.', 'all' => 'Jeder kann dieses Element sehen.', 'members' => 'Nur Mitglieder der Kampagne können dieses Element sehen.', 'self' => 'Nur du kannst dieses Element sehen.', ], 'title' => 'Sichtbarkeit aktualisieren', 'toast' => 'Sichtbarkeit erfolgreich aktualisiert.', 'tooltip' => 'Klicke hier, um mehr über die verschiedenen Sichtbarkeitsoptionen zu erfahren.', ]; ================================================ FILE: lang/de/whiteboards/draw.php ================================================ [ 'add-circle' => 'Kreis hinzufügen', 'add-entity' => 'Eintrag hinzufügen', 'add-image' => 'Bild hinzufügen', 'add-square' => 'Quadrat hinzufügen', 'add-text' => 'Text hinzufügen', 'duplicate' => 'Ausgewählte duplizieren', 'end-drawing' => 'Zeichnung beenden', 'lock' => 'sperren', 'push-to-back' => 'in den Hintergrund bringen', 'push-to-front' => 'in den Vordergrund bringen', 'start-drawing' => 'Fang an zu zeichnen', 'unlock' => 'Entsperren', ], 'entity-search' => [ 'placeholder' => 'Gib den Namen oder den Alias eines Eintrags ein', 'title' => 'Eintragssuche', ], 'errors' => [ 'websockets' => [ 'disconnected' => 'Die Verbindung zum WebSocket wurde unterbrochen. Bitte versuche es erneut.', 'error' => 'Beim Herstellen der Verbindung zum WebSocket-Server ist ein Fehler aufgetreten.', 'unavailable' => 'Der WebSocket-Server ist derzeit nicht verfügbar. Bitte versuche es später erneut.', ], ], 'fields' => [ 'color' => 'Farbe', ], 'pen' => [ 'large-stroke' => 'Dicker Strich', 'thin-stroke' => 'Dünner Strich', ], 'reset' => [ 'helper' => 'Möchtest du das Whiteboard wirklich zurücksetzen? Dieser Vorgang kann nicht rückgängig gemacht werden.', 'title' => 'Reset Whiteboard', ], 'roles' => [ 'edit' => 'Dieser Benutzer kann das Whiteboard bearbeiten', 'view' => 'Dieser Benutzer kann das Whiteboard einsehen', ], 'toast' => [ 'copy' => [ 'success' => 'Elemente wurden in die Zwischenablage kopiert.', ], 'paste' => [ 'error' => 'Es ist ein Fehler aufgetreten', ], ], ]; ================================================ FILE: lang/de/whiteboards.php ================================================ [ 'draw' => 'Zeichnen', ], 'create' => [ 'title' => 'neues Whiteboard', ], 'cta' => [ 'text' => 'Um Whiteboards freizuschalten, muss eine Kampagne von einem Mitglied der Stufe :wyvern oder :elemental zum Premium-Mitglied gemacht werden.', 'title' => 'Whiteboards sind eine besondere Premium-Funktion.', ], 'lists' => [ 'empty' => 'Verwende ein Whiteboard, um Ideen, Zusammenhänge oder die Struktur einer Geschichte visuell zu organisieren.', ], 'placeholders' => [ 'type' => 'Idee, Beziehungen, Story-Struktur', ], ]; ================================================ FILE: lang/en/abilities.php ================================================ [ 'actions' => [ 'attach' => 'Attach entries', ], 'create' => [ 'attach_success' => '{1} Attached the ability :name to :count entry.|[2,*] Attached the ability :name to :count entries.', 'helper' => 'Attach :name to one or several entries.', 'title' => 'Attach entries', ], 'description' => 'Entries having the ability', 'title' => 'Ability :name Entries', ], 'create' => [ 'title' => 'New Ability', ], 'fields' => [ 'charges' => 'Charges', ], 'lists' => [ 'empty' => 'Add powers, spells, or talents. Many creators use this to model D&D classes.', ], 'placeholders' => [ 'charges' => 'Amount of charges. Reference properties with {Level}*{CHA}', 'name' => 'Fireball, Alert, Cunning Strike', 'type' => 'Spell, Feat, Attack', ], 'reorder' => [ 'parentless' => 'No Parent', 'success' => 'Abilities successfully reordered.', 'title' => 'Reorder the abilities', ], 'show' => [ 'tabs' => [ 'reorder' => 'Reorder Abilities', ], ], ]; ================================================ FILE: lang/en/account/email.php ================================================ [ 'update' => 'Update email', ], 'fields' => [ 'email' => 'New email address', ], 'helpers' => [ 'email' => 'Make sure it\'s spelt correctly.', ], 'subtitle' => 'Change your email address associated with your account.', 'title' => 'Update your email address', ]; ================================================ FILE: lang/en/account/password.php ================================================ [ 'update' => 'Update password', ], 'fields' => [ 'password' => 'New password', ], 'helpers' => [ 'password' => 'Use a password manager to generate a strong password.', 'password_confirmation' => 'Don\'t mess up!', ], 'subtitle' => 'Change your account password. This will log you out of all other devices.', 'title' => 'Update your account password', ]; ================================================ FILE: lang/en/account/social.php ================================================ 'Login by :provider', 'subtitle' => 'Switch from a login managed by :provider to one managed by Kanka, where you log in with your email and password.', 'title' => 'Switch to a Kanka login', ]; ================================================ FILE: lang/en/articles.php ================================================ [ 'move' => 'Move to entry', ], 'helpers' => [ 'permissions' => 'These permissions can override the :visibility setting of the article.', ], 'tabs' => [ 'layout' => 'Layout', 'main' => 'Main', 'permissions' => 'Special permissions', ], ]; ================================================ FILE: lang/en/assistance.php ================================================ [ 'campaign' => 'Campaign', ], 'opening' => 'A member of Kanka\'s team has sent you to this page with the goal of assisting you.', 'placeholders' => [ 'campaign' => 'Select a campaign you are an admin of', ], 'select' => 'Select a campaign you are an admin of from the dropdown below to generate a special one-time usage token, which will allow a member of the Kanka team to temporarily join the campaign as an admin.', 'success' => [ 'opening' => 'Your assistance token has been successfully generated. The Kanka team has been notified and will join your campaign shortly to help you out. We\'ll usually reach out via :discord if we need to coordinate anything directly.', 'secret' => 'Only a verified Kanka team member can use this token, it\'s useless to anyone else, so there\'s no need to treat it like a secret.', 'token' => 'Your assistance token:', ], 'title' => 'Assistance', ]; ================================================ FILE: lang/en/attribute_templates.php ================================================ [ 'entity_type' => [ 'unset' => 'Unset', ], ], 'create' => [ 'title' => 'New Property Kit', ], 'fields' => [ 'auto_apply' => 'Auto-apply', 'is_enabled' => 'Enabled', ], 'hints' => [ 'automatic' => 'The following :count properties were automatically applied from :link.', 'automatic_apply' => '{1} The following :count property was automatically applied from :link | [2,] The following :count properties were automatically applied from :link.', 'entity_type' => 'Automatically apply this kit\'s properties to new entries of the selected category.', 'is_disabled' => 'This kit is disabled.', 'is_enabled' => 'Enable this kit for use in the campaign.', 'parent_attribute_template' => 'This property kit can be a child of another property kit. When applying this property kit, it and all of its parents will be applied.', ], 'lists' => [ 'empty' => 'Create kit to reuse common properties across multiple entries.', ], 'placeholders' => [ 'name' => 'Name of the property kit', ], ]; ================================================ FILE: lang/en/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Error', 'rendering' => 'There was an error rendering the marketplace plugin. Please contact the plugin creator.', ], ], 'list' => [ 'sheets' => 'Character Sheets', ], 'pitch' => 'Find and add character sheets from the :marketplace on a :boosted-campaign.', ]; ================================================ FILE: lang/en/auth.php ================================================ [ 'permanent' => 'You\'ve been permanently banned.', 'temporary' => '{1} You\'ve been banned for :days day.|[2,*] You\'ve been banned for :days days.', ], 'confirm' => [ 'confirm' => 'Confirm', 'error' => 'Invalid password, please try again.', 'helper' => 'Please confirm your password before being able to continue.', 'title' => 'Password confirmation', ], 'continue' => [ 'facebook' => 'Continue with Facebook', 'google' => 'Continue with Google', 'x' => 'Continue with X', ], 'failed' => 'These credentials do not match our records.', 'helpers' => [ 'password' => 'Show / Hide password', ], 'login' => [ 'fields' => [ '2fa' => 'One time password', 'email' => 'Email', 'password' => 'Password', ], 'no-account' => 'Don\'t have an account?', 'or' => 'OR', 'password_forgotten' => 'Forgot your password?', 'sign-up' => 'Sign up', 'submit' => 'Login', 'title' => 'Login', ], 'register' => [ 'already' => 'Already have an account? :login', 'errors' => [ 'email_already_taken' => 'An account with this email is already registered.', 'general_error' => 'An error occurred while registering your account. Please try again.', ], 'fields' => [ 'email' => 'Email', 'name' => 'Username', 'password' => 'Password', ], 'log-in' => 'Log in', 'submit' => 'Register', 'title' => 'Register', 'tos' => 'By registering an account, you agree to our :terms and :privacy.', ], 'reset' => [ 'fields' => [ 'email' => 'Email Address', 'password' => 'Password', 'password_confirmation' => 'Confirm your password', ], 'send' => 'Send Password Reset Link', 'submit' => 'Reset password', 'title' => 'Reset password', ], 'tfa' => [ 'helper' => 'Two-factor authentication is enabled. Please provide the One Time Password (OTP) provided by your authenticator app.', 'title' => 'Two-Factor Authentication', ], 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 'x-twitter' => 'X formerly known as Twitter', ]; ================================================ FILE: lang/en/banners.php ================================================ 'Use promo code :code at checkout to get 20% off the first year of a yearly subscription! Offer valid for first time subscribers and until Monday 2nd of December 23:59 UTC.', ]; ================================================ FILE: lang/en/billing/information.php ================================================ [ 'update' => 'Update info', ], 'helper' => 'Your business address, VAT number etc can be added to all your receipts.', 'title' => 'Update billing information', ]; ================================================ FILE: lang/en/billing/invoices.php ================================================ [ 'download' => 'Download PDF', ], 'description' => 'Showing invoices within the past 24 months.', 'empty' => 'No invoices found', 'fields' => [ 'amount' => 'Amount', 'date' => 'Date', 'invoice' => 'Invoice', 'status' => 'Status', ], 'paypal' => 'Please note that only payments made through Stripe, and not PayPal, are visible here.', 'status' => [ 'paid' => 'Paid', 'pending' => 'Pending', ], 'title' => 'Billing history', ]; ================================================ FILE: lang/en/billing/menu.php ================================================ 'Billing history', 'overview' => 'Overview', 'payment-method' => 'Payment method', ]; ================================================ FILE: lang/en/billing/payment_methods.php ================================================ 'Payment method', 'types' => [ 'card' => 'Credit/debit card', ], ]; ================================================ FILE: lang/en/bookmarks.php ================================================ [ 'customise' => 'Customise sidebar', ], 'create' => [ 'title' => 'New bookmark', ], 'edit' => [ 'title' => 'Bookmark :name', ], 'fields' => [ 'active' => 'Active', 'dashboard' => 'Dashboard', 'default_dashboard' => 'Default dashboard', 'filters' => 'Filters', 'menu' => 'Subpage', 'position' => 'Position', 'random_type' => 'Random Category', 'selector' => 'Bookmark Configuration', 'target' => 'Target', ], 'helpers' => [ 'active' => 'Inactive bookmarks won\'t appear in the interface.', 'css' => 'Add a CSS class that will be added to the bookmark\'s link in the sidebar.', 'dashboard' => 'Have the bookmarks target a custom dashboards.', 'default_dashboard' => 'Link to the default dashboard instead. A custom dashboard still needs to be selected.', 'entity' => 'Set up this bookmark to go directly to an entry. The :menu field controls which subpage of the entry is opened.', 'position' => 'Use this field to control in which ascending order the links appear in the menu.', 'random' => 'Use this field to have a bookmark pointing to a random entry. You can filter the link to only go to a specific category.', 'selector' => 'Configure where this bookmark goes when a user clicks on it in the sidebar.', 'type' => 'Set up this bookmark to go directly to a list of entries. To filter the results, copy parts of the url on the filtered entry list after the :? sign into the :filter field.', ], 'lists' => [ 'empty' => 'Save bookmarks to your most used entries or filtered lists for faster access.', ], 'placeholders' => [ 'filters' => 'location_id=15&type=city', 'menu' => 'Menu subpage (use the last text of the url)', 'tab' => '(deprecated)', ], 'random_no_entity' => 'No random entry found.', 'random_types' => [ 'any' => 'Any entry', ], 'reorder' => [ 'success' => 'Bookmarks reordered.', 'title' => 'Reorder bookmarks', ], 'targets' => [ 'dashboard' => 'A dashboard', 'entity' => 'A single entry', 'random' => 'A random entry', 'select' => 'Choose an option', 'type' => 'Category entries', ], 'visibilities' => [ 'is_active' => 'Show the bookmark in the sidebar', ], ]; ================================================ FILE: lang/en/bragi/backstory.php ================================================ 'Write a short character backstory inspired by this information. Adapt your tone and details to fit the selected game systems and genres. You may include elements like the character\'s appearance, origin, beliefs, relationships, goals, flaws, or notable experiences — whichever best fits the character.', 'setup' => [ 'gender' => 'Gender: :gender', 'genres' => 'Genres: :genres', 'name' => 'Character name: :name', 'prompt' => 'Prompt: ":prompt"', 'pronouns' => 'Pronouns: :pronouns', 'systems' => 'Game systems: :systems', ], 'system' => 'You are a professional TTRPG storyteller and character writer. You craft immersive, emotionally resonant character backstories for tabletop role-playing games. Your style adapts to the game\'s tone and setting. You typically write 2–4 paragraphs and up to 400 words, weaving in appearance, history, beliefs, motivations, and flaws.', ]; ================================================ FILE: lang/en/bragi.php ================================================ [ 'generate' => 'Generate', 'insert' => 'Use', ], 'errors' => [ 'invalid-sub' => 'To access this feature, you need to have an Wyvern or Elemental subscription.', 'out-of-tokens' => 'You are out of tokens! You\'ll automatically get more on :date.', ], 'here' => 'here', 'intro' => 'Hi! I\'m :name, an AI here to help you generate backstories for your characters in Kanka. You can learn more about me :here.', 'kankappy' => 'They are secretly a disciple of Kankappy.', 'loading' => 'Please hang tight, I\'m thinking hard and it can take up to a minute!', 'placeholders' => [ 'prompt' => 'Provide a prompt that will be transformed into a backstory.', ], 'token-limit' => 'You currently have :amount tokens. Each time I generate a backstory, that uses a token, so use them wisely!', ]; ================================================ FILE: lang/en/calendars/weather.php ================================================ [ 'helper' => 'Add weather information that will show up on the calendar.', 'success' => 'Weather information added.', 'title' => 'Weather', ], 'destroy' => [ 'success' => 'Weather information removed.', ], 'edit' => [ 'success' => 'Weather information updated.', 'title' => 'Update Weather', ], 'fields' => [ 'effect' => 'Effect', 'name' => 'Name', 'precipitation' => 'Precipitation', 'temperature' => 'Temperature', 'weather' => 'Weather', 'wind' => 'Wind', ], 'options' => [ 'weather' => [ 'bolt' => 'Thunder', 'cloud' => 'Cloudy', 'cloud-rain' => 'Rainy', 'cloud-showers-heavy' => 'Heavy Rain', 'cloud-sun' => 'Cloudy and Sunny', 'cloud-sun-rain' => 'Cloud, Sun and Rain', 'meteor' => 'Meteor', 'smog' => 'Smog', 'snowflake' => 'Snow', 'sun' => 'Sunny', 'wind' => 'Windy', ], ], 'placeholders' => [ 'effect' => 'Magical or natural effect', 'name' => 'Optional custom weather text', 'precipitation' => 'Amount of water', 'temperature' => 'Daily high and low', 'wind' => 'Wind speeds', ], ]; ================================================ FILE: lang/en/calendars.php ================================================ [ 'add_epoch' => 'Epoch', 'add_intercalary' => 'Add intercalary days', 'add_month' => 'Month', 'add_moon' => 'Moon', 'add_reminder' => 'Add a reminder', 'add_season' => 'Season', 'add_weather' => 'Set weather', 'add_week' => 'Named week', 'add_weekday' => 'Week day', 'add_year' => 'Named year', 'set_today' => 'Set as current day', 'today' => 'Today', 'update_weather' => 'Update weather', ], 'checkboxes' => [ 'is_recurring' => 'Takes place every year', ], 'create' => [ 'title' => 'New Calendar', ], 'edit' => [ 'today' => 'Current date updated.', ], 'event' => [ 'create' => [ 'success' => 'Reminder created.', 'title' => 'New reminder', ], 'destroy' => 'Reminder removed from \':name\'.', 'edit' => [ 'success' => 'Reminder updated.', 'title' => 'Updating :name\'s reminder', ], 'errors' => [ 'invalid_entity' => 'Invalid entry selection', ], 'helpers' => [ 'other_calendar' => 'You are editing a reminder that was added on :calendar. Modifying this reminder will modify it in both places.', ], 'success' => 'Reminder \':event\' added to :calendar.', ], 'events' => [ 'bulks' => [ 'delete' => '{1} Deleted :count reminder.|[2,*] Deleted :count reminders.', 'patch' => '{1} Updated :count reminder.|[2,*] Updated :count reminders.', ], 'end' => '(end)', 'filters' => [ 'show_after' => 'Show today and after', 'show_all' => 'Show all', 'show_before' => 'Show before today', ], 'start' => '(start)', ], 'fields' => [ 'comment' => 'Comment', 'current_day' => 'Current Day', 'current_month' => 'Current Month', 'current_year' => 'Current Year', 'date' => 'Current Date', 'day' => 'Day', 'default_layout' => 'Default layout', 'format' => 'Format', 'is_incrementing' => 'Advancing date', 'is_recurring' => 'Recurring', 'leap_year' => 'Leap years', 'leap_year_amount' => 'Add Days', 'leap_year_month' => 'Month', 'leap_year_offset' => 'Every', 'leap_year_start' => 'Leap Year', 'length' => 'Days', 'length_days' => ':count day|:count days', 'month' => 'Month', 'months' => 'Months', 'moons' => 'Moons', 'parameters' => 'Parameters', 'recurring_until' => 'Recurring Until Year', 'reset' => 'Weekly Reset', 'seasons' => 'Seasons', 'show_birthdays' => 'Show Birthdays', 'skip_year_zero' => 'Skip Year Zero', 'start_offset' => 'Start Offset', 'suffix' => 'Suffix', 'week_names' => 'Week Names', 'weekdays' => 'Week Days', 'year' => 'Year', ], 'helpers' => [ 'default_layout' => 'Select which layout the calendar should use by default when viewed.', 'format' => 'Add custom date formatting for calendar entries.', 'month_type' => 'Intercalary months don\'t use week days, but still influence moons and seasons.', 'moon_offset' => 'By default, the first fullmoon appears on the first day of year 0. Changing the offset will alter when the first full moon is displayed. This value can negative (up to the length of the first month) or positive (up to the length of the first month).', 'start_offset' => 'By default, the calendar starts on the first weekday of year 0. Changing this field influences where the calendar\'s first day is placed.', ], 'hints' => [ 'event_length' => 'How many days a reminder lasts. A reminder will only be displayed on its first two years.', 'is_incrementing' => 'Automatically switch to the next day at 00:00 UTC.', 'leap_year' => 'Set up leap years for the calendar.', 'months' => 'The calendar should have at least 2 months.', 'moons' => 'Adding moons will make them show up in the calendar on every full and new moon. If the full moon period is bigger than 10 days, first and third quarter moons will also be displayed.', 'parent_calendar' => 'Giving the calendar a parent calendar will include the reminders and weather effects of the parent calendar.', 'reset' => 'Always start the beginning of the month or year on the first week day.', 'seasons' => 'Create seasons for the calendar by providing when each of them start. Kanka will take care of the rest.', 'show_birthdays' => 'Show the yearly birthdays of characters that have a birthday reminder on this calendar up to their death date.', 'skip_year_zero' => 'By default, the calendar\'s first year is year zero. Enable this option to skip year zero.', 'weekdays' => 'Set the weekday names. At least 2 weekdays are required.', 'weeks' => 'Define some names for the more important weeks of the calendar.', 'years' => 'Some years are so important that they have their own name.', ], 'layouts' => [ 'month' => 'Month', 'monthly' => 'Monthly by default', 'year' => 'Year', 'yearly' => 'Yearly by default', ], 'lists' => [ 'empty' => 'Create a calendar to track dates, festivals, or in-game events over time.', ], 'modals' => [ 'switcher' => [ 'title' => 'Change year', ], ], 'month_types' => [ 'intercalary' => 'Intercalary', 'standard' => 'Standard', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Full moon', 'fullmoon_name' => ':moon full moon', 'month' => 'Monthly', 'newmoon' => 'New moon', 'newmoon_name' => ':moon new moon', 'none' => 'None', 'unnamed_moon' => 'Moon :number', 'year' => 'Yearly', ], ], 'resets' => [ '' => 'None', 'month' => 'Monthly', 'year' => 'Yearly', ], ], 'panels' => [ 'intercalary' => 'Intercalary Days', 'leap_year' => 'Leap Year', 'months' => 'Months', 'weeks' => 'Weeks', 'years' => 'Named Years', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Duration in days', 'month' => 'At the end of which month', 'name' => 'Name of intercalation', ], 'month' => [ 'alias' => 'Month Alias', 'length'=> 'Days', 'name' => 'Month Name', 'type' => 'Type', ], 'moon' => [ 'fullmoon' => 'Full moon every (days)', 'name' => 'Moon Name', 'offset' => 'First full moon offset', ], 'seasons' => [ 'day' => 'Day start', 'month' => 'Month start', 'name' => 'Season Name', ], 'weeks' => [ 'name' => 'Week name', 'number' => 'Week number', ], 'year' => [ 'name' => 'Year Name', 'number' => 'Year', ], ], 'placeholders' => [ 'colour' => 'Colour', 'comment' => 'Birthday, festival, solstice', 'date' => 'The current date', 'leap_year_amount' => 'Number of days added on a leap year', 'leap_year_month' => 'Month on which days are added', 'leap_year_offset' => 'Every how many years is a leap year', 'leap_year_start' => 'First year that is a leap year', 'length' => 'Reminder length in days', 'months' => 'Number of months in a year', 'recurring_until' => 'Last recurring year (leave empty for forever recurring)', 'seasons' => 'Number of seasons', 'suffix' => 'Current Era suffix (AC, BC)', 'type' => 'Type of the calendar', 'weekdays' => 'Number of days in a week', ], 'show' => [ 'missing_details' => 'This calendar couldn\'t be displayed. Calendars need at least 2 months and 2 weekdays to render properly.', 'moon_1first_quarter' => ':moon first quarter', 'moon_full' => ':moon full moon', 'moon_last_quarter' => ':moon last quarter', 'moon_new' => ':moon new moon', 'tabs' => [ 'events' => 'Reminders', 'weather' => 'Weather', ], ], 'sorters' => [ 'after' => 'Today & after', 'before'=> 'Today & before', ], 'validators' => [ 'format' => 'The date format is invalid.', 'moon_offset' => 'The moon first fullmoon offset can\'t be bigger than the length of the calendar\'s first month.', ], 'warnings' => [ 'event_length' => 'Reminders that span multiple years are only visible on the first two years. Learn more in our :documentation.', ], ]; ================================================ FILE: lang/en/callouts.php ================================================ [ 'subscription' => 'See plans & pricing', 'upgrade' => 'Upgrade to premium', ], 'booster' => [ 'actions' => [ 'boost' => 'Boost :campaign', 'superboost' => 'Superboost :campaign', ], 'learn-more' => 'What are boosters?', 'limitation' => 'To access this feature, the campaign needs to be boosted.', 'limitations' => [ 'boosted' => 'To access this feature, the campaign needs to be boosted.', 'superboosted' => 'To access this feature, the campaign needs to be superboosted.', ], 'multiple' => 'To access these features, the campaign needs to be boosted.', 'pitches' => [ 'element-class' => 'Give this element a custom CSS class with a :boosted-campaign.', 'icon' => 'Unlock millions of custom icons like :example from :fontawesome with a :boosted-campaign.', ], 'titles' => [ 'boosted' => 'Boosted feature', 'superboosted' => 'Superboosted feature', ], ], 'premium' => [ 'learn-more' => 'See all premium features', 'limitation' => 'To access this feature, premium features need to be enabled for :campaign.', 'multiple' => 'To access these features, premium features need to be enabled for :campaign.', 'title' => 'Premium feature', 'unlock' => 'Unlock premium features for :campaign', ], 'subscribe' => [ 'pitch-image' => 'Subscribe to unlock up to :max MiB file upload sizes.', 'share-booster' => 'Boost :campaign to increase the file upload size for all members of the campaign.', 'share-premium' => 'Increase the file upload size for all members of the campaign with a premium campaign.', ], ]; ================================================ FILE: lang/en/campaigns/achievements.php ================================================ 'Congratulations!', 'connections' => '{0} No relation created|{1} One relation created|[2,*] :amount relations created', 'created' => '{0} No :plural created|{1} One :singular created|[2,*] :amount :plural created', 'dead' => '{0} No murder mysteries|{1} One muder mystery|[2,*] :amount murder mysteries', 'goal' => 'Goal :number', 'goal_reached' => 'Unlocked achievement', 'level' => 'level :number', 'markers' => '{0} No map markers created|{1} One map marker created|[2,*] :amount map markers created', 'painter' => '{0} No themes created|{1} One theme created|[2,*] :amount themes created', 'pitch' => 'Campaign achievements are a fun way to celebrate milestones in your worldbuilding journey. Track progress, engage your players, and showcase your accomplishments with a premium campaign.', 'plugins' => '{0} No plugins installed|{1} One plugin installed|[2,*] :amount plugins installed', 'remaining' => [ 'generic' => 'More and the next level will be unlocked.', ], 'spotlight' => [ 'active' => [ 'cta' => 'View spotlight', ], 'private' => [ 'cta' => 'Review public settings', 'helper' => 'Make your campaign public to be eligible for the Spotlight.', ], 'public' => [ 'cta' => 'Learn how spotlight works', 'helper' => 'Selected campaigns are featured on the Kanka Showcase and blog.', ], ], 'spotlighted' => '{0} Not spotlighted yet|[1,*] Spotlighted', 'tagged' => '{0} No entries tagged|{1} One entry tagged|[2,*] :amount entries tagged', 'titles' => [ 'calendars' => 'Time keeper', 'characters' => 'Name giver', 'connections' => 'Cupid', 'creatures' => 'Breeder', 'dead' => 'Murderer', 'events' => 'Lore master', 'families' => 'Family planning', 'locations' => 'Builder', 'markers' => 'Cartographer', 'organisations' => 'Mergers and acquisitions', 'plugins' => 'Plugin connoisseur', 'quests' => 'Mastermind', 'spotlighted' => 'Spotlighted', 'tags' => 'Under control', 'themes' => 'Painter', ], 'tutorial' => 'Achievements track notable actions within this campaign such as creating entries or using key features. They are informational only and update automatically as you explore and build.', ]; ================================================ FILE: lang/en/campaigns/applications.php ================================================ [ 'accept' => 'Accept', 'reject' => 'Deny', ], 'apply' => [ 'apply' => 'Apply', 'help' => 'This campaign is open to new members. Apply to join is by filling out the form. You will be notified when the admins review your application.', 'remove_text' => 'your submission', 'success' => [ 'apply' => 'Your application has been saved. You can still change it or cancel it at any time. You shall be notified when the admins review it.', 'remove'=> 'Your application has been removed.', 'update'=> 'Your application has been updated. You can still change it or cancel it at any time. You shall be notified when the admins review it.', ], 'title' => 'Join :name', ], 'dashboard_widget' => [ 'add' => 'Add widget', 'has_widget' => 'Join widget is on the dashboard.', 'has_widget_help' => 'Your campaign dashboard is showcasing the join widget to potential players.', 'no_widget' => 'No join widget on dashboard.', 'no_widget_help' => 'Your campaign is open but doesn\'t showcase it on the dashboard. Add a widget so players can discover and apply to join.', 'success' => 'Join widget added to the dashboard.', 'title' => 'Dashboard widget', ], 'experience' => [ 'intermediate' => 'Intermediate (Played a few times)', 'new' => 'Newbie (First time)', 'veteran' => 'Veteran (Years of experience)', ], 'fields' => [ 'additional_notes' => 'Anything else', 'application' => 'Application', 'availability_days' => 'Days Available', 'character_concept' => 'Character Concept', 'experience_level' => 'Experience Level', 'external_link' => 'Character Sheet / Link', 'intro' => 'Campaign Introduction', 'new_application' => 'New player application', 'player_count' => 'Number of Players', 'playstyle_tags' => 'Play-styles', 'pref_rp_combat' => 'Focus Balance', 'pref_tone' => 'Tone Preference', 'reason' => 'Approval / Rejection reason', 'schedule' => 'Schedule', 'schedule-placeholder' => 'Every Friday at 7 PM', 'time_end' => 'To', 'time_start' => 'From', 'timezone' => 'Timezone', ], 'filters' => [ 'all' => 'Show All', 'approved' => 'Show approved', 'pending' => 'Show pending', 'rejected' => 'Show rejected', 'title' => 'Filters', ], 'headers' => [ 'availability' => 'Availability & Schedule', 'preferences' => 'Playstyle Preferences', ], 'helpers' => [ 'applications_closed' => 'Your campaign is public, but new players cannot submit applications until you set the status to "Open".', 'availability_days' => 'Select the days you are generally available to play.', 'experience_level' => 'How familiar are you with this specific game system?', 'external_link' => 'Link to D&D Beyond, Google Docs, or other external character sheets.', 'fill_setup' => 'Please fill the public campaign setup form to be able to open your campaign to the public.', 'filters_incomplete' => 'Your campaign is open for applications, but you haven\'t finished setting up filters (like system, timezone, or tags). Completing these will make it much easier for the right players to find you.', 'modal' => 'A campaign which is open to applications and public can have users apply to join the campaign.', 'no_applications_title' => 'No pending requests', 'no_applications_v2' => 'There are currently no pending requests to join the campaign, but you can help players find their next great adventure! Detailed campaign info and search filters make it much easier for users to discover and take interest in your story.', 'reason' => 'If provided, the applicant will be notified with this reason.', 'role' => 'If approving, the role the applicant gets added to.', ], 'labels' => [ 'casual' => 'Casual / Beer & Pretzels', 'combat_focused' => 'Combat Focused', 'rp_heavy' => 'RP Heavy', 'serious' => 'Serious / Immersive', ], 'open' => [ 'closed' => 'Campaign is closed', 'open' => 'Campaign is open', 'title' => 'Open campaign', ], 'placeholders' => [ 'additional_notes' => 'Triggers, hard limits, or specific questions.', 'character_concept' => 'Briefly describe who you want to play, their backstory, and how they fit into the world.', 'intro' => 'A brief explanation of what your campaign is all about, shown on top of the application form.', 'note' => 'Write down your application to join the campaign', 'player_count' => '4-6 players', 'reason' => 'Your reason', ], 'public' => [ 'private' => 'Campaign is private.', 'public' => 'Campaign is public.', 'title' => 'Public campaign', ], 'setup' => [ 'done' => 'Public campaign settings filled.', 'prioritise' => 'Prioritise this campaign', 'prioritise_conflict' => 'You can only prioritise one campaign at a time. Disable prioritisation on :campaign first.', 'prioritise_help' => 'Prioritised campaigns appear at the top of the public campaign list, ahead of other open campaigns.', 'prioritise_upgrade' => 'This feature is exclusive to :link subscribers.', 'prioritised' => 'Prioritised campaign', 'setup' => 'Set up public campaign settings to open campaign to the public.', 'success' => 'Campaign setup saved. Once all fields are filled out, the campaign can be opened to the public.', 'success_complete' => 'The campaign can be opened to the public!', 'title' => 'Public campaign setup', 'tutorial' => 'Only once all of these fields are filled out can the campaign be open to the public.', ], 'timezone' => 'Timezone and Language', 'title' => 'Join requests', 'toggle' => [ 'closed' => 'Closed to applications', 'label' => 'Status', 'open' => 'Open to applications', 'success' => 'Campaign application status updated.', 'success_open' => 'The campaign is now public and users can apply to join it.', 'title' => 'Application status', ], 'tutorial' => 'Campaign applications let people request access to this campaign. Applicants submit a short form, and admin can review, accept, or decline each request. Approved users are added to the campaign with the role you assign during review.', 'update' => [ 'approve' => 'Select the role the user will be added as in the campaign.', 'approved' => 'Application approved.', 'reject' => 'Write an optional message to the user as to why you are rejecting their application.', 'rejected' => 'Application rejected', ], 'warnings' => [ 'applications_closed' => 'Applications are currently closed.', 'filters_incomplete' => 'Campaign filters are incomplete.', ], 'weekdays' => [ 'fri' => 'Fri', 'mon' => 'Mon', 'sat' => 'Sat', 'sun' => 'Sun', 'thu' => 'Thu', 'tue' => 'Tue', 'wed' => 'Wed', ], ]; ================================================ FILE: lang/en/campaigns/builder.php ================================================ 'Visually build a theme for the campaign with this interface. Scroll down to see how the changes would impact various elements of the campaign. When a colour is selected, a "contrasting" colour is automatically selected for colouring text. Learn more about theming in our :docs.', 'pitch' => 'Psst, we have a theme builder if all you want to do is change some of the colours of the campaign 😉', 'pitch-go' => 'Take me to the theme builder', 'reset' => 'Theme builder styling reset.', 'success' => 'Theme builder styling saved.', 'title' => 'Theme builder', ]; ================================================ FILE: lang/en/campaigns/categories.php ================================================ [ 'permission-disabled' => 'This category is disabled.', ], 'helpers' => [ 'aliases' => 'Add aliases and secret identities to entries of the world.', 'media' => 'Upload media documents (images, pdfs, audio) and external links to entries.', ], 'tab' => 'Categories', ]; ================================================ FILE: lang/en/campaigns/dashboard-header.php ================================================ [ 'success' => 'Billboard updated.', 'title' => 'Update billboard', ], ]; ================================================ FILE: lang/en/campaigns/default-images.php ================================================ [ 'add' => 'New placeholder', ], 'call-to-action' => 'Give every character, location, or other category a default thumbnail, so your lists never look empty.', 'create' => [ 'error' => 'Error saving the new placeholder image. Is :type already set?', 'helper' => 'Upload an image that will be used as the placeholder image for entries of the selected category.', 'success' => 'New placeholder for :type created.', 'title' => 'New placeholder image', ], 'destroy' => [ 'success' => 'Placeholder image for :type removed.', ], 'empty' => 'No categories currently have a placeholder image.', 'helper' => 'Used for all entries of this category without an image.', 'reset' => [ 'helper' => 'Are you sure you want to remove the placeholder images for all campaign categories?', 'success' => 'Successfully removed all categories\' placeholder images.', 'title' => 'Reset placeholder images', 'warning' => 'This action is permanent and cannot be undone.', ], 'title' => 'Placeholder images', 'tutorial' => 'Set default images for entries without custom pictures. These thumbnails appear immediately across the campaign and keep lists visually consistent.', ]; ================================================ FILE: lang/en/campaigns/defaults.php ================================================ [ 'character_personality_visibility' => 'Default character personality visibility', 'connections' => 'Entry relations view', 'connections_mode' => 'Relation map style', 'descendants' => 'Sublist filtering default', 'entity_privacy' => 'New entry visibility', 'gallery_visibility' => 'Default gallery image visibility', 'post_collapsed' => 'New article layout', 'private_mention_visibility' => 'Private entry mentions', 'related_visibility' => 'Related content visibility', ], 'helpers' => [ 'character_visibility' => 'Sets visibility for personality traits when creating characters.', 'connections' => 'Choose whether entry relation pages show a visual map or a list by default.', 'connections_mode' => 'Set the default layout style for relation maps (available with premium).', 'descendants' => 'When viewing entry sublists (like a location\'s characters), show only direct children or all descendants.', 'display' => 'Set default display options for entry pages.', 'entity' => 'Controls what visibility Kanka applies automatically to new content.', 'entity_privacy' => 'Sets visibility for newly created characters, locations, etc.', 'gallery_visibility' => 'Default visibility value when uploading images to the gallery.', 'post_collapsed' => 'When creating articles, set the article as collapsed or expanded.', 'privacy' => 'Set default visibilities for new content. These settings apply when you create new content and can be changed for individual items.', 'private_mention_visibility' => 'When you mention a private entry in visible content, control whether the entry name is shown or hidden.', 'related_visibility' => 'Controls visibility for articles, properties, relations added to entries.', ], 'sections' => [ 'display' => 'Entry Display Defaults', 'entity' => 'Entry defaults', 'media' => 'Media defaults', 'mention' => 'Mention behaviour', ], 'tutorial' => 'Streamline content creation with smart defaults. Choose default visibility settings for entries, articles, images, and other content. These preferences will be automatically applied when you create new content, saving you time while keeping your campaign organised.', 'update' => [ 'success' => 'Campaign defaults updated.', ], 'values' => [ 'collapsed' => [ 'collapsed' => 'Collapsed', 'default' => 'Default', 'expanded' => 'Expanded', ], 'connections' => [ 'explorer' => 'Relation map (premium)', 'list' => 'List interface', ], 'descendants' => [ 'all' => 'Show all descendants by default', 'direct' => 'Show direct descendants by default', ], 'mentions' => [ 'private' => 'Hide target name', 'visible' => 'Show target name', ], ], ]; ================================================ FILE: lang/en/campaigns/delete.php ================================================ 'backup', 'confirm' => 'If you are sure you want to permanently delete :campaign, write :code in the field below.', 'confirm-button' => 'Permanently delete :name', 'helper' => 'Deleting a campaign is a permanent action that cannot be reverted. This will remove all of the data related to the campaign from our servers, including images and assets. We recommend making a :backup before continuing.', 'issue' => 'The following issue needs to be fixed before the campaign can be deleted.', 'members' => 'All other members need to be removed from the campaign.', 'success' => ':name was permanently deleted.', 'title' => 'Deletion', ]; ================================================ FILE: lang/en/campaigns/export.php ================================================ [ 'download' => 'Download', 'export' => 'Export the campaign', ], 'confirm' => [ 'notification' => 'Members of the :admin role will be notified when the export is ready for download.', 'title' => 'Export :name', 'type' => 'Export type', 'warning' => 'You are about to export all data from :name. This may take a few minutes depending on campaign size. You\'ll receive a notification when it\'s ready and can continue using Kanka in the meantime.', ], 'errors' => [ 'limit' => 'The campaign has already been exported once today. Please try again tomorrow.', 'premium' => 'Markdown export is a feature exclusive to premium campaigns.', ], 'expired' => 'Link expired', 'helpers' => [ 'json' => 'For backup & restoring - can be used as a campaign import', 'markdown' => 'For sharing & reading - human readable format', 'premium' => 'Available for premium campaigns only.', ], 'progress' => 'Progress', 'size' => 'Size', 'status' => [ 'failed' => 'Failed', 'finished' => 'Finished', 'running' => 'Running', 'scheduled' => 'Scheduled', ], 'success' => 'The export has been queued for processing. All members of the :admin role will be notified once the file is ready for downloading.', 'title' => 'Export', 'type' => 'Type', 'types' => [ 'json' => 'JSON', 'md' => 'Markdown', ], ]; ================================================ FILE: lang/en/campaigns/gallery.php ================================================ [ 'close' => 'Close', 'file-link' => 'File link', 'focus_point' => 'Focus point', 'image-link' => 'Image link', 'reset_focus' => 'Reset focus point', 'save' => 'Save', 'upgrade' => 'Upgrade storage space', ], 'breadcrumb' => 'Gallery', 'bulk' => [ 'destroy' => [ 'confirm' => 'Are you sure you want to permanently remove the selected elements? This action cannot be undone.', 'success' => '{0}No files removed.|{1}One file removed.|{2,*} :count files removed.', ], ], 'cta' => 'Manage and reuse images throughout the campaign.', 'destroy' => [ 'folder' => 'Folder :name deleted.', 'success' => 'File :name deleted.', ], 'errors' => [ 'max' => 'Please only select up to :count files at a time.', 'permissions' => 'Your roles are missing the :permission permission to be allowed to upload images to the gallery.', 'storage' => 'There is not enough storage space to upload the selected image(s). Available storage space: :available.', ], 'fields' => [ 'created_by' => 'Uploaded by', 'details' => 'Details', 'ext' => 'Ext', 'file_type' => 'File type', 'folder' => 'Folder', 'image_mentioned_in' => '{0} This image isn\'t mentioned in any entry.|{1} Mentioned in one entry/article.|[2,*] mentioned in :count entries/articles.', 'image_used_in' => '{0} This image isn\'t used in any entry.|{1} Used as the image of one entry.|[2,*] Used as the image of :count entries.', 'link' => 'Link', 'name' => 'Name', 'size' => 'Size', 'unused' => 'Not used anywhere', 'used_in' => 'Used in', ], 'focus' => [ 'locked' => 'A premium campaign is required to set the focus point of an image.', 'removed' => 'Image focus removed.', 'updated' => 'Image focus updated.', ], 'new_folder' => [ 'title' => 'New folder', ], 'no_folder' => 'No folder', 'pitch' => 'Upload images to the gallery directly from the text editor.', 'placeholders' => [ 'search' => 'Search image name...', ], 'storage' => [ 'of' => 'of', 'title' => 'Storage', ], 'title' => 'Campaign :campaign Gallery', 'update' => [ 'folder' => 'Folder modified.', 'success' => 'File modified.', ], 'uploader' => [ 'add' => 'Add new', 'new_folder' => 'New Folder', 'or' => 'or', 'select_file' => 'Select a file', 'well' => 'Drop file to upload', ], ]; ================================================ FILE: lang/en/campaigns/import.php ================================================ [ 'import' => 'Upload the export', ], 'csv' => [ 'continue' => 'Continue', 'fields_helper' => 'Select a column to assign to each of the fillable fields of the entry.', 'no_preview' => 'No preview data available', 'preview' => 'Preview', 'select_module' => 'Category selection', 'select_one' => 'Select one', 'selected_tags' => 'Selected tags', 'set_column' => 'Set column', 'set_fields' => 'Set fields', 'submit' => 'Submit CSV import', 'traits' => 'Character Traits', 'traits_helper' => 'You can add traits to characters, the header you select will be used as the trait, while its corresponding row value will be the traits value as well.', 'type_helper' => 'Select the category you want to import the new entries into.', 'validation_error' => 'At least 1 column must be fully populated', ], 'description_v2' => 'Import entries, articles, properties, galleries, and other data from an export or new entries from a .CSV file into this campaign. The import runs in the background and may take some time. You and any other admins will be notified when it finishes.', 'fields' => [ 'file_v2' => 'CSV file or export ZIP file', 'updated' => 'Last updated', ], 'form' => 'Upload form', 'limitation_v2' => 'Only zip and csv files are accepted. Max :size.', 'progress' => [ 'uploading' => 'Uploading', ], 'status' => [ 'failed' => 'Failed', 'finished' => 'Finished', 'invalid' => 'Invalid Data', 'processing' => 'Processing', 'queued' => 'Queued', 'ready' => 'Ready for mapping', 'running' => 'Running', 'validating' => 'Validating', ], 'subscription' => [ 'pitch' => 'Restore a campaign backup or bring in an export from another campaign. Available on :wyvern or :elemental plans.', ], 'title' => 'Import', ]; ================================================ FILE: lang/en/campaigns/invites.php ================================================ [ 'helper' => 'Create an invite link to send to your players, so that they can join the campaign.', ], ]; ================================================ FILE: lang/en/campaigns/limits.php ================================================ 'Free campaigns can have up to :limit :thing. Premium campaigns get unlimited :thing, or you can remove one to make room.', 'title' => 'Limit reached', ]; ================================================ FILE: lang/en/campaigns/logs.php ================================================ [ 'list' => 'This page shows a record of major changes in this campaign for up to :amount days. Logs capture high-level actions, not detailed field-by-field edits.', 'nothing' => 'There are no logs to show. Please keep in mind that logs are only kept for up to :amount days.', 'title' => 'No logs', ], 'pitch' => 'See who changed what across your campaign, with up to :amount days of history.', 'premium' => [ 'helper' => 'Upgrade to premium to see your full campaign history.', ], 'title' => 'Audit log', ]; ================================================ FILE: lang/en/campaigns/members.php ================================================ [ 'limited' => ':amount of :total members.', 'title' => 'Available members', 'unlimited' => ':amount of unlimited members.', ], 'roles' => [ 'admin' => 'You cannot directly add users to the :admin role here. That happens in the :admin role\'s interface.', 'helper' => 'Add or remove roles of the member :user.', 'success' => 'Roles successfully updated for :user.', 'title' => 'Edit member roles', ], ]; ================================================ FILE: lang/en/campaigns/modules.php ================================================ [ 'create' => 'Create a category', 'customise' => 'Customise', ], 'create' => [ 'helper' => 'Create a new custom category to store entries that don\'t fit in the other categories.', 'success' => 'New category created.', 'title' => 'New category', ], 'delete' => [ 'confirm' => 'Write :code if you are sure you want to permanently delete the :name custom category.', 'confirm-button' => '{0} Permanently delete :name|{1} Permanently delete :name and :count entry|[2,*] Permanently delete :name and :count entries', 'entities' => '{1} This will permanently delete :count entry.|[2,*] This will permanently delete :count entries.', 'helper' => 'Are you sure you want to remove the :name custom category? This will also permanently delete all entries, bookmarks and widgets linked to this category.', 'success' => 'Category :name deleted.', 'title' => 'Category deletion', ], 'errors' => [ 'disabled' => 'The :name category is disabled. :fix', 'empty-custom' => 'Add custom categories to organise data that doesn\'t fit in the default ones.', 'limit' => 'Campaigns are currently limited to only :max custom categories while we iron out this new feature.', 'limit-title' => 'Custom category limit reached', 'subscription-limit' => 'The campaign has reached the maximum amount of custom categories available. The person unlocking premium features can subscribe to a higher tier to increase this limit.', ], 'fields' => [ 'icon' => 'Category icon', 'image' => 'Placeholder image', 'plural' => 'Category plural name', 'singular' => 'Category singular name', 'status' => 'Category status', 'update_name' => 'Rename category bookmark with new name', ], 'helpers' => [ 'custom' => 'This is a custom category.', 'icon' => 'Give this category a special :fontawesome icon, for example :example.', 'plural' => 'Used in navigation and lists (.e.g, "view all potions")', 'roles' => 'Select roles that should have permission to view entries of this new category. This can later be changed in the role permissions.', 'singular' => 'Used when referring to a single item (e.g., "new potion")', 'status' => 'Disabled categories are hidden from navigation and menus. No data is deleted.', 'tutorial' => 'Categories control which features are visible in the campaign. Enable the ones you use and hide the rest. Turning a category off never deletes data; it only removes it from navigation and creation menus.', ], 'pitch' => 'Rename this category and choose a custom icon to better match your theme and style. Perfect for tailoring the experience to your world and players.', 'pitch-custom' => 'Create custom category for any of your world\'s needs. Track deities, potions, succession laws, or whatever makes your campaign unique. Premium gives you complete flexibility.', 'pitch-title' => 'Unlock custom categories', 'rename' => [ 'helper' => 'Customise how this category appears throughout the campaign. Leave fields blank to use default values.', 'success' => 'Category customised.', 'title' => 'Customise :module', ], 'reset' => [ 'default' => 'This will only reset the default categories, not any custom ones.', 'success' => 'The campaign categories have been reset.', 'title' => 'Reset custom category names and icons', 'warning' => 'Are you sure you want to reset the campaign categories to their original names and icons?', ], 'sections' => [ 'custom' => 'Custom categories', 'default' => 'Default categories', 'early-access' => 'Early access', 'features' => 'Features', ], 'states' => [ 'disable' => 'Disable', 'disabled' => 'Category is disabled', 'enable' => 'Enable', 'enabled' => 'Category is enabled', ], 'status' => [ 'enabled' => 'Category enabled', ], ]; ================================================ FILE: lang/en/campaigns/overview.php ================================================ [ 'title' => 'Followers', ], 'member' => [ 'title' => 'Membership', ], 'premium' => [ 'enable' => 'Enable premium features', ], 'status' => [ 'title' => 'Visibility', ], ]; ================================================ FILE: lang/en/campaigns/permissions.php ================================================ [ 'whiteboards' => [ ] ] ]; ================================================ FILE: lang/en/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'Disable themes', 'enable' => 'Enable themes', 'update' => 'Update plugins', ], 'changelog' => 'Changelog', 'disable' => 'Disable theme', 'enable' => 'Enable theme', 'find-plugins' => 'Find plugins', 'import' => 'Import', 'update' => 'Update plugin', 'update-to' => 'Update to version :version', 'update_available' => 'Update available', ], 'bulks' => [ 'delete' => '{1} Removed :count plugin.|[2,*] Removed :count plugins.', 'disable' => '{1} Disabled :count theme.|[2,*] Disabled :count themes.', 'enable' => '{1} Enabled :count themes.|[2,*] Enabled :count themes.', 'update' => '{1} Updated :count plugin.|[2,*] Updated :count plugins.', ], 'destroy' => [ 'success' => 'Plugin :plugin removed.', ], 'disabled' => [ 'success' => 'Plugin :plugin disabled.', ], 'empty_list' => 'The campaign doesn\'t currently have any plugins. Go to the plugin library to install a few and come back to activate them.', 'enabled' => [ 'success' => 'Plugin :plugin enabled.', ], 'errors' => [ 'invalid_plugin' => 'Invalid plugin.', ], 'fields' => [ 'name' => 'Plugin', 'obsolete' => 'This plugin has been marked as obsolete by the Kanka team, meaning it no longer works as originally intended by its author.', 'status' => 'Status', 'type' => 'Type', ], 'import' => [ 'button' => 'Import', 'created' => 'Created the following entries:', 'fields' => [ 'only_new' => 'Only new entries', 'private' => 'Private entries', ], 'helper' => 'You are about to import :count entries from the :plugin plugin. If this plugin was previously imported, changes you have since made to the imported entries may be lost.', 'no_new_entities' => 'There are no new entries to be imported.', 'option_only_import' => 'Only import new entries, skipping previously imported entries.', 'option_private' => 'Import all entries as private.', 'success' => '{1} Imported :count entry from the plugin :plugin.|[2,*] Imported :count entries from the plugin :plugin.', 'title' => 'Import content pack', 'updated' => 'Updated the following entries:', ], 'info' => [ 'description' => 'Showing the latest updates for the :plugin plugin.', 'helper' => 'When a new version of a plugin is released, you can update it to the latest version for the campaign.', 'installed' => 'Installed', 'title' => 'Plugin :plugin updates', 'updates' => 'Updates', 'versions' => 'Versions', ], 'pitch' => 'Extend your campaign with community-built themes, character sheets, and content from the :marketplace.', 'status' => [ 'always' => 'This plugin type is always active unless removed.', 'disabled' => 'Disabled', 'enabled' => 'Enabled', ], 'templates' => [ 'name' => ':name by :user', ], 'title' => 'Plugins - :name', 'types' => [ 'attribute' => 'Character Sheet', 'pack' => 'Content Pack', 'theme' => 'Theme', ], 'update' => [ 'success' => 'Plugin :plugin updated.', ], ]; ================================================ FILE: lang/en/campaigns/public.php ================================================ [ 'new' => 'Make it public for the community to discover, or keep it private for invited members only.', 'permissions' => 'Making your campaign public doesn\'t automatically share content. Configure what public viewers can see in the :public role settings.', ], 'title' => 'Control who can access the campaign', 'update' => [ 'private' => 'The campaign is now private, and only visible to members of it.', 'public' => 'The campaign is now public. It might take some time to appear in the :public-campaigns page.', 'unlisted' => 'The campaign is now unlisted. Anyone with a link can access it, but it won\'t show up in the :public-campaigns page.', ], ]; ================================================ FILE: lang/en/campaigns/recovery.php ================================================ [ 'recover' => 'Recover', 'recover_selected' => 'Recover selected', ], 'error' => 'An error occurred trying to recover entries.', 'fields' => [ 'deleted' => 'Deleted', 'deleted_at' => 'Deleted :date by :user', ], 'name_link' => ':name was successfully recovered', 'order' => [ 'newest' => 'Order by: Newest', 'newest_first' => 'Newest first', 'oldest' => 'Order by: Oldest', 'oldest_first' => 'Oldest first', 'type' => 'Order by: Type', 'type_order' => 'Type', ], 'premium' => 'Recovering elements is a premium campaign feature.', 'success_v2'=> '{1} :count element was recovered.|[2,*] :count elements were recovered.', 'title' => 'Recovery', 'tutorial' => 'View and restore recently deleted elements. Entries, articles, and other supporting data can be recovered for :amount days before they are permanently removed. Restoring a element returns it with all its data intact.', ]; ================================================ FILE: lang/en/campaigns/roles.php ================================================ [ 'status' => 'Visibility: :status', ], 'create' => [ 'helper' => 'Create a new role for the campaign.', ], 'overview' => [ 'limited' => ':amount of :total roles created.', 'title' => 'Available roles', 'unlimited' => ':amount of unlimited roles created.', ], 'permissions' => [ 'campaign-features' => 'Features', 'content-modules' => 'Content categories', 'toggle' => [ 'action' => 'Toggle all', 'tooltip' => 'Toggle the :action permission for all categories.', ], ], 'public' => [ 'helpers' => [ 'click' => 'Click any category to toggle public access to all entries within it.', 'intro' => 'Control what non-members can see in the campaign.', 'main' => 'Select which categories are visible to anyone viewing the campaign, whether they\'re logged in or not. This includes both public visitors and Kanka users who aren\'t campaign members.', 'preview' => 'Preview as non-member', ], ], 'show' => [ 'title' => ':role permissions - :campaign', ], 'toggle' => [ 'disabled' => 'Members of the :role role can no longer :action :entities', 'enabled' => 'Members of the :role role can now :action :entities', ], 'warnings' => [ 'adding-to-admin' => 'Members of the :name role have access to everything in the campaign, and cannot be removed by other members of the role. After :amount minutes, only they can remove themselves from the role.', ], ]; ================================================ FILE: lang/en/campaigns/share.php ================================================ [ 'change_visibility' => 'Change visibility', 'copy' => 'Copy link', 'copy_public_link' => 'Copy public link', 'make_public' => 'Make public and copy link', ], 'helpers' => [ 'private_explanation' => 'Only members can access private campaigns.', 'public_explanation' => 'This campaign is public. Anyone with the link can browse it.', 'unlisted_explanation' => 'Anyone with the link can browse this campaign, but it doesn\'t appear in public directories.', ], 'labels' => [ 'member_link' => 'Only members can open this', 'public_link' => 'Public link', ], 'status' => [ 'private' => 'Private campaign', 'public' => 'Anyone with the link can view this campaign', ], 'success' => [ 'copied_members' => 'Member-only link copied.', 'copied_public' => 'Public link copied, anyone with the link can view.', 'made_public' => 'Campaign is now public.', ], 'title' => 'Share campaign', ]; ================================================ FILE: lang/en/campaigns/sidebar.php ================================================ [ 'reset' => 'Reset to default', ], 'call-to-action' => 'Reorganise, rename, and restyle your sidebar to match how you run your campaign.', 'helpers' => [ 'bookmarks' => 'Bookmarks are not listed here because each bookmark has its own :position setting that controls where it appears in the sidebar.', 'image' => 'Add an image to represent the campaign. This image will be used in the sidebar and in the campaign switcher interface. You can change this at any time by editing the campaign.', 'reordering'=> 'Reorder the sidebar by dragging the icons on the left of each item.', ], 'image-success' => 'The new campaign image has been saved. This image can be changed again by editing the campaign.', 'reset' => [ 'success' => 'Campaign sidebar setup reset.', 'title' => 'Reset sidebar setup', 'warning' => 'Are you sure you want to reset the sidebar setup to the default values?', ], 'success' => 'Campaign sidebar setup saved.', 'title' => 'Campaign :campaign sidebar setup', 'tooltips' => [ 'image' => 'Change this background image', ], ]; ================================================ FILE: lang/en/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Calendars', 'title' => 'Time Keeper', ], 'murderer' => [ 'goal' => 'Dead characters', 'title' => 'Murderer', ], ], 'fields' => [ 'created' => 'Created on', 'creator' => 'Created by', 'entries' => 'Total entries', 'from-elements' => 'From elements', 'from-entities' => 'From entries', 'from-posts' => 'From articles', 'general' => 'General', 'words' => 'Total words', ], 'title2' => 'Statistics', 'titles' => [ 'calendars' => 'Time Keeper level :level', 'characters'=> 'Name Giver level :level', 'dead' => 'Murderer level :level', 'families' => 'Family Planning level :level', 'locations' => 'Builder level :level', 'quests' => 'Mastermind level :level', 'races' => 'Breeder level :level', ], 'tutorial' => 'Campaign statistics show entry counts and recent activity. Data updates every :amount hours. Use this to track growth and usage over time.', ]; ================================================ FILE: lang/en/campaigns/styles.php ================================================ [ 'builder' => 'Theme Builder', 'current' => 'Current theme: :theme', 'disable' => 'Disable', 'enable' => 'Enable', 'new' => 'New style', ], 'bulks' => [ 'delete' => '{1} Removed :count style.|[2,*] Removed :count styles.', 'disable' => '{1} Disabled :count style.|[2,*] Disabled :count styles.', 'enable' => '{1} Enabled :count style.|[2,*] Enabled :count styles.', ], 'create' => [ 'success' => 'New style created.', 'title' => 'New style', ], 'delete' => [ 'success' => 'Style :name deleted.', ], 'errors' => [ 'max_content' => 'The CSS rule can\'t be longer than :amount characters.', 'max_reached' => 'Max number of styles (:max) reached.', ], 'fields' => [ 'content' => 'CSS rule', 'is_enabled' => 'Enabled', 'length' => 'Length', 'modified' => 'Modified', 'name' => 'Name', 'order' => 'Order', ], 'helpers' => [ 'here' => 'on our blog', 'is_enabled' => 'Enable this theme on every page.', 'main' => 'You can create custom CSS styling for your premium campaign. These styles are loaded after any themes from the plugin library that are enabled for the campaign. You can learn more about styling your campaign :here.', 'tutorial' => 'Control the visual style of the campaign. Choose colors, layout preferences, and other presentation options. These changes affect only this campaign and can be updated at any time.', ], 'pitch' => 'Make your campaign look and feel entirely your own with custom CSS.', 'placeholders' => [ 'name' => 'Name of the style', ], 'reorder' => [ 'save' => 'Save new order', 'success' => '{1} Reordered :count style.|[2,*] Reordered :count styles.', 'title' => 'Reorder styles', ], 'theme' => [ 'none' => 'Use user\'s preference', 'override' => 'Theme override', 'success' => 'Theme override updated.', 'title' => 'Update the theme override', ], 'title' => 'Theming', 'toggle' => [ 'disable' => 'Style disabled successfully.', 'enable' => 'Style enabled successfully.', ], 'update' => [ 'success' => 'Style :name updated.', 'title' => 'Update style', ], ]; ================================================ FILE: lang/en/campaigns/vanity.php ================================================ 'The name :vanity is available!', 'forever' => 'Once set, this cannot be changed. :docs', 'helper-v2' => 'Give the campaign a memorable, custom web address. Instead of :default, the campaign could appear as: :example', 'rule' => 'The :field field needs at least one alphabetical character.', 'rule2' => 'The :field field doesn\'t allow the following character: /.', 'set' => 'The campaign\'s vanity URL is permanently set to :vanity.', ]; ================================================ FILE: lang/en/campaigns/visibilities.php ================================================ [ 'public' => 'Listed in public campaigns', 'unlisted' => 'Hidden from public campaigns', ], 'helpers' => [ 'premium' => 'To enable this setting, premium features need to be unlocked for this campaign.', 'private' => 'Only invited members can access', 'public' => 'Anyone with a link can view', ], 'titles' => [ 'private' => 'Private', 'public' => 'Public', 'unlisted' => 'Public (unlisted)', ], ]; ================================================ FILE: lang/en/campaigns/webhooks.php ================================================ [ 'action' => 'Change status', 'add' => 'Create webhook', 'bulks' => [ 'delete_success' => '{1} Deleted :count webhook.|[2,*] Deleted :count webhooks.', 'disable' => 'Disable', 'disable_success' => '{1} Disabled :count webhook.|[2,*] Disabled :count webhooks.', 'enable' => 'Enable', 'enable_success' => '{1} Enabled :count webhook.|[2,*] Enabled :count webhooks.', ], 'test' => 'Test webhook', 'update' => 'Update webhook', ], 'create' => [ 'success' => 'Webhook created successfully', 'title' => 'Add new webhook', ], 'destroy' => [ 'success' => 'Webhook deleted successfully', ], 'edit' => [ 'success' => 'Webhook updated successfully', 'title' => 'Update webhook', ], 'error' => [ 'pitch' => 'Unlock premium features to access webhooks.', ], 'fields' => [ 'enabled' => 'Enabled', 'event' => 'Event', 'events' => [ 'deleted' => 'Deleted entry', 'edited' => 'Edited entry', 'new' => 'New entry', ], 'message' => 'Message', 'private_entities' => [ 'helper' => 'Don\'t trigger the webhook when updating private entries.', 'skip' => 'Skip private entries', ], 'type' => 'Type', 'types' => [ 'custom' => 'Message', 'payload' => 'Payload', ], 'url' => 'Url', ], 'helper' => [ 'active' => 'The webhook is active and will trigger on the selected event.', 'message' => 'Add a custom message with support for mappings', 'status' => 'Toggle the active status of the webhook', 'tutorial' => 'Use webhooks to send real-time updates from the campaign to external tools. Events trigger automatically when entries are created, updated, or deleted. You can add multiple webhooks and test them from this page.', ], 'placeholders' => [ 'message' => '{who} made changes to {name}, check it out at {url}', 'url' => 'Target webhook\'s url', ], 'premium' => 'Get notified in Discord or other apps when your campaign changes, for new entries, updates, and more.', 'test' => [ 'success' => 'Test request sent.', ], 'title' => 'Webhooks', 'toggle' => [ 'disable' => 'Webhook disabled successfully.', 'enable' => 'Webhook enabled successfully.', ], ]; ================================================ FILE: lang/en/campaigns.php ================================================ [ 'success' => ':name created.', 'title' => 'New Campaign', ], 'edit' => [ 'success' => ':name updated.', ], 'entity_personality_visibilities' => [ 'private' => 'New characters have their personality private by default.', ], 'entity_visibilities' => [ 'private' => 'New entries are private', ], 'errors' => [ 'access' => 'You don\'t have access to this campaign.', 'premium' => 'This feature is only available to premium campaigns.', 'unknown_id' => 'Unknown campaign.', ], 'exports' => [], 'fields' => [ 'billboard' => 'Billboard description', 'boosted' => 'Boosted by', 'entity_count' => 'Number of entries', 'entry' => 'Description of the world', 'followers' => 'Followers', 'genre' => 'Genres', 'header_image' => 'Billboard background image', 'image' => 'Sidebar image', 'locale' => 'Content language', 'name' => 'Name', 'open' => 'Open to applications', 'premium' => 'Premium unlocked by :name', 'public' => 'Campaign visibility', 'public_campaign_filters' => 'Discovery settings', 'superboosted' => 'Superboosted by', 'system' => 'Game system', 'theme' => 'Theme', 'vanity' => 'Custom URL', ], 'following' => 'Following', 'helpers' => [ 'boosted' => 'Some features are unlocked because this campaign is being boosted. Find out more on the :settings page.', 'css' => 'Write your own CSS that will be loaded into the pages of your campaign. Please note that any abuse of this feature can lead to a removal of your custom CSS. Repeated or grave offenses can lead to a removal of your campaign.', 'dashboard' => 'Customise the way the billboard dashboard widget is displayed by filling out the following fields.', 'excerpt' => 'The contents of this field will be displayed on the dashboard in the billboard widget, so write a few sentences introducing your world. If this field is empty, the first 1000 characters of the description field will be used instead.', 'header_image' => 'Image displayed as a background in the Billboard dashboard widget.', 'hide_history' => 'If enabled, only members of the :admin role will have access to an entry\'s history (log of changes).', 'hide_members' => 'If enabled, only members of the :admin role will have access to the list of the members.', 'locale' => 'The language the campaign is written in. This is used for generating content and grouping public campaigns.', 'name' => 'Your campaign/world can have any name as long as it contains at least 4 letters or numbers.', 'no_entry' => 'Looks like the campaign doesn\'t have a description yet! Let\'s fix that.', 'premium' => 'Some features require premium features to be unlocked. Learn more about :settings.', 'public_campaign_filters' => 'Help others find the campaign among other public campaigns by providing the following information.', 'public_no_visibility' => 'Heads up! The campaign is public, but the :public role can\'t access anything. :fix.', 'system' => 'If the campaign is publicly visible, the system is shown in the :link page.', 'systems' => 'To avoid cluttering users with options, some features of Kanka are only available with specific RPG systems (ie the D&D 5e monster stat block). Adding supported systems here will enable those features.', 'theme' => 'Force the theme for the campaign, overriding a user\'s preference.', 'view_public' => 'To view the campaign as a public viewer would, open :link in an incognito window.', ], 'invites' => [ 'actions' => [ 'copy' => 'Copy the link to your clipboard', 'link' => 'Invite people', ], 'create' => [ 'buttons' => [ 'create' => 'Generate link', ], 'success_link' => 'Link created: :link', 'title' => 'Invite friends to :campaign', ], 'destroy' => [ 'success' => 'Invitation removed.', ], 'error' => [ 'inactive_token' => 'This token has already been used, or the campaign no longer exists.', 'invalid_token' => 'This token is no longer valid.', 'join' => 'Please log in or register a new account to join :campaign.', ], 'fields' => [ 'created' => 'Created', 'role' => 'Role', 'token' => 'Token', 'type' => 'Type', 'usage' => 'Expires after', ], 'helpers' => [ 'role' => 'Users need to join before they can be promoted to the admin role.', 'usage' => 'How many times the invite link can be used before it becomes inactive.', ], 'unlimited_validity' => 'Unlimited', 'usages' => [ 'five' => '5 uses', 'no_limit' => 'Never', 'once' => '1 use', 'ten' => '10 uses', ], ], 'leave' => [ 'action' => 'Leave the campaign', 'confirm' => 'Are you sure you want to leave :name? You won\'t be able to access it anymore, unless one of it\'s admins invites you again.', 'confirm-button' => 'Yes, leave now', 'error' => 'Can\'t leave the campaign.', 'fix' => 'Go to the members management', 'no-admin-left' => 'Leaving the campaign isn\'t possible because doing so would leave it without any admins. Add another member to the admin role first.', 'success' => 'You have left :name.', 'title' => 'Leaving the campaign', ], 'members' => [ 'actions' => [ 'remove' => 'Remove from campaign', 'switch' => 'View campaign as user', 'switch-back' => 'Back to my account', 'switch-entity' => 'View as', ], 'fields' => [ 'banned' => 'User is banned', 'joined' => 'Joined', 'last_login' => 'Last Login', 'name' => 'User', 'role' => 'Role', 'roles' => 'Roles', ], 'helpers' => [ 'switch' => 'View the campaign as this member', ], 'impersonating' => [ 'message' => 'You are viewing and interacting with :campaign as :name. Some features have been disabled, but the rest acts exactly as they would see it.', 'title' => 'Impersonating :name', ], 'invite' => [ 'description' => 'Invite friends and players to :campaign by creating an invitation link and sending them the generated URL! Upon accepting their invitation, they will be added as a member in the invitation\'s requested role.', 'more' => 'More roles can be created on the :link page.', 'title' => 'Invites', ], 'removal' => 'You are removing ":member" from the campaign.', 'roles' => [ 'member' => 'Member', 'owner' => 'Admin', 'player' => 'Player', 'public' => 'Guest', 'viewer' => 'Viewer', ], 'switch_back_success' => 'Switched back to your account.', ], 'overview' => [ 'entity-count' => '{0} No entries|{1} :amount entry|[2,*] :amount entries', 'follower-count' => '{0} No followers|{1} :amount follower|[2,*] :amount followers', ], 'panels' => [ 'dashboard' => 'Dashboard', 'privacy' => 'Privacy defaults', 'setup' => 'Setup', 'sharing' => 'Sharing', 'systems' => 'Systems', 'ui' => 'Interface', ], 'placeholders' => [ 'locale' => 'Language code', 'name' => 'The name of your world', 'system' => 'D&D, Pathfinder, Fate, DSA', ], 'privacy' => [ 'hidden' => 'Hidden', 'private' => 'Private', 'visible' => 'Visible', ], 'public' => [ 'helpers' => [ 'introduction' => 'Campaigns are private by default, and can be made public. This allows anyone to access them, and makes them available in the :public-campaigns page if they have entries visible to the :public-role role. A public campaign is visible to all, but for its content to be visible, the :public-role role needs adequate permissions.', ], ], 'roles' => [ 'actions' => [ 'add' => 'New role', 'duplicate' => 'Duplicate role', 'permissions' => 'Manage permissions', 'rename' => 'Rename role', 'save' => 'Save role', ], 'admin_role' => 'admin role', 'bulks' => [ 'delete' => '{1} Removed :count role.|[2,*] Removed :count roles.', 'edit' => '{1} Updated :count role.|[2,*] Updated :count roles.', ], 'create' => [ 'success' => 'Role :name created.', 'title' => 'New role', ], 'destroy' => [ 'success' => 'Role :name removed.', ], 'edit' => [ 'success' => 'Role :name updated.', 'title' => 'Rename role :name', ], 'fields' => [ 'copy_permissions' => 'Copy permissions', 'name' => 'Name', 'permissions' => 'Permissions', 'type' => 'Type', 'users' => 'Users', ], 'helper' => [ '1' => 'A campaign is split up into several roles. The :admin role automatically has access to everything in a campaign, but every other role can have specific permissions on different categories (character, location, etc).', '2' => 'Entries can have more fine-tuned permissions by viewing the "Permissions" tab of an entry. This tab appears once the campaign has several roles or members.', '3' => 'One can either go with an "opt-out" system, where roles are given access to viewing all of the entries, and use the "Private" checkbox on entries to hide them. Or one can not give roles many permissions, but set each entry to be visible individually.', '4' => 'Premium campaigns can have an unlimited amount of roles.', 'permissions_helper' => 'Duplicate all of the role\'s permissions, on both categories and entries.', ], 'hints' => [ 'campaign_not_public' => 'The public role has permissions but the campaign is private. You can change this setting on the Sharing tab when editing the campaign.', 'empty_role' => 'The role doesn\'t have any members in it yet.', 'role_admin' => 'Members of the :name role can automatically access every entry and feature in the campaign.', 'role_permissions' => 'Configure what members with the :name role can do in each category.', ], 'members' => 'Members', 'modals' => [ 'details' => [ 'campaign' => 'Campaign permissions allow the following.', 'entities' => 'Here is a quick recap of what members of this role get when a permission is set.', 'more' => 'For more details, view our tutorial video on Youtube', 'title' => 'Permission details', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Create', 'articles' => 'Articles', 'dashboard' => 'Dashboard', 'delete' => 'Delete', 'edit' => 'Edit', 'gallery' => [ 'browse' => 'Browse', 'manage' => 'Full control', 'upload' => 'Upload', ], 'manage' => 'Manage', 'members' => 'Members', 'permission' => 'Manage permissions', 'read' => 'View', 'toggle' => 'Change for all', ], 'helpers' => [ 'add' => 'Allow creating entries of this category. They will automatically be allowed to view and edit entries they create if they don\'t have the view or edit permission.', 'articles' => 'Allows adding, editing, and deleting articles even if the member can\'t edit the entry.', 'dashboard' => 'Allow editing the dashboards and dashboard widgets.', 'delete' => 'Allow removing all entries of this category.', 'edit' => 'Allow editing all entries of this category.', 'gallery' => [ 'browse' => 'Allow viewing the gallery, and setting an entry\'s image from the gallery.', 'manage' => 'Allow everything on the gallery as an admin can, including editing and deleting images.', 'upload' => 'Allows uploading images to the gallery. Will only see images they have uploaded if not combined with the browse permission.', ], 'manage' => 'Allow editing the campaign as an admin would, without allowing the members to delete the campaign.', 'members' => 'Allow inviting new members to the campaign.', 'not_public' => 'The campaign isn\'t public. Permissions for the public role can be set, but will be ignored. Go and edit the campaign to make it public.', 'permission' => 'Allow setting permissions on entries of this category they can edit.', 'read' => 'Allow viewing all entries of this category that aren\'t private.', ], ], 'placeholders' => [ 'name' => 'Name of the role', ], 'title' => 'Roles - :name', 'types' => [ 'owner' => 'Admin', 'public' => 'Public', 'standard' => 'Standard', ], 'users' => [ 'actions' => [ 'add' => 'Add member', 'remove' => ':user from the :role role', 'remove_user' => 'Remove member from role', ], 'create' => [ 'success' => ':user added to the role :role.', 'title' => 'Add a member to the :name role', ], 'destroy' => [ 'success' => ':user removed from the role :role.', ], 'errors' => [ 'cant_kick_admins' => 'To avoid abuse, it is not possible to remove other members from the :admin role. In case of issues, contact us on :discord or at :email.', 'needs_more_roles' => 'You need to add yourself to another role in the campaign before being able to remove yourself from the :admin role.', ], 'fields' => [ 'name' => 'Name', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Enable', ], 'boosted' => 'This feature is in early access and currently only available for :boosted.', 'deprecated' => [ 'help' => 'This category is deprecated, meaning it is no longer maintained, and that bugs aren\'t tested with each new update. Use this category with the knowledge that it will eventually get removed from Kanka.', 'title' => 'Deprecated', ], 'disabled' => 'The :module category is disabled.', 'enabled' => 'The :module category is enabled.', 'errors' => [ 'module-disabled' => 'The requested category is currently disabled in the settings. :fix.', ], 'helpers' => [ 'abilities' => 'Create abilities, be it feats, spells, or powers that can be assigned to entries.', 'assets' => 'Upload files, set links, and define aliases to individual entries.', 'bookmarks' => 'Create bookmarks to entries or filtered lists that appear in the sidebar.', 'calendars' => 'A place to define the calendars of the world.', 'characters' => 'Create and keep track of the people inhabiting the world with characters.', 'conversations' => 'Fictional conversations between characters or between members.', 'creatures' => 'Build your world\'s creatures, animals, and monsters with the creatures category.', 'dice_rolls' => 'For those who use Kanka for RPG games, a way to handle dice rolls.', 'entity_attributes' => 'Keep track of properties on entries of the world, for example HP or SPEED.', 'events' => 'Holidays, festivals, disasters, birthdays, wars.', 'families' => 'Clans or families, their relations and their members.', 'inventories' => 'Manage inventories on your entries.', 'items' => 'Weapons, vehicles, relics, potions.', 'journals' => 'Observations written by characters, or session prep for the dungeon master.', 'locations' => 'Planets, planes, continents, rivers, states, settlements, temples, taverns.', 'maps' => 'Create rich and beautiful maps with markers linking to the world\'s entries.', 'notes' => 'Lore, nature, history, magic, cultures.', 'organisations' => 'Cults, religions, factions, guilds.', 'quests' => 'To keep track of various quests with characters and locations.', 'races' => 'Track the origins, ethnicities, and racial traits of the world\'s characters with the race category.', 'tags' => 'Each entry can have several tags. Tags can belong to other tags, and entries can be filtered by tag.', 'timelines' => 'Represent the history of your world with timelines.', 'whiteboards' => 'Draw and write on whiteboards to visually plan your world and goals.', ], ], 'sharing' => [ 'filters' => 'Help others find the campaign on the :public-campaigns page by adding relevant tags. This only applies to public campaigns.', 'language' => 'Language of the world\'s content.', 'system' => 'The game system you\'re using (e.g., D&D 5e, Pathfinder).', ], 'show' => [ 'actions' => [ 'edit' => 'Edit campaign', ], 'tabs' => [ 'achievements' => 'Achievements', 'customisation' => 'Customisation', 'danger' => 'Danger', 'data' => 'Data', 'default-images' => 'Default thumbnails', 'defaults' => 'Defaults', 'deletion' => 'Deletion', 'export' => 'Export', 'import' => 'Import', 'logs' => 'Logs', 'management' => 'Management', 'members' => 'Members', 'plugins' => 'Plugins', 'recovery' => 'Recovery', 'roles' => 'Roles', 'sidebar' => 'Sidebar', 'stats' => 'Stats', 'styles' => 'Theming', 'webhooks' => 'Webhooks', ], 'title' => 'Overview - :name', ], 'status' => [ 'free' => 'Premium features disabled.', 'legacy' => [ 'title' => 'Boosted features (legacy)', ], 'premium' => 'Premium features unlocked by :name.', 'title' => 'Premium features', ], 'themes' => [ 'none' => 'None (defaults to user\'s preference)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Only visible to admins', 'visible' => 'Visible to members', ], 'fields' => [ 'entity_history' => 'Entry history logs', 'member_list' => 'Member list', ], 'helpers' => [ 'entity-history' => 'Control who can see view edit history for entries.', 'member-list' => 'Control who can view the member list.', 'theme' => 'Override individual member theme preferences, or let each member use their own preferred theme.', ], 'members' => [ 'hidden' => 'Only visible to admins', 'visible' => 'Visible to members', ], ], 'visibilities' => [ 'private' => 'Private', 'public' => 'Public', 'unlisted' => 'Public (unlisted)', ], ]; ================================================ FILE: lang/en/characters.php ================================================ [ 'add_appearance' => 'Add an appearance', 'add_personality' => 'Add a personality', ], 'create' => [ 'title' => 'New Character', ], 'families' => [ 'helper' => 'Reorder and control which families of :name are visible or hidden from non-admins.', 'reorder' => [ 'success' => 'Character families updated successfully.', ], 'title2' => 'Manage families', ], 'fields' => [ 'age' => 'Age', 'is_appearance_pinned' => 'Appearance overview', 'is_dead' => 'Dead', 'is_personality_pinned' => 'Personality overview', 'is_personality_visible' => 'Personality access', 'life' => 'Life', 'physical' => 'Physical', 'pronouns' => 'Pronouns', 'sex' => 'Gender', 'status' => 'Status', 'title' => 'Title', 'traits' => 'Traits', ], 'helpers' => [ 'age' => 'You can link this character with a calendar to automatically calculate their age instead. :more.', ], 'hints' => [ 'is_appearance_pinned' => 'Show on overview.', 'is_dead' => 'This character is dead.', 'is_missing' => 'This character is missing.', 'is_personality_visible' => 'The personality traits are visible to all, not only to members of the :admin role.', 'personality_not_visible' => 'Personality traits of this character are currently only visible to Admin users.', 'personality_visible' => 'Everyone can view this character\'s personality.', ], 'labels' => [ 'appearance' => [ 'entry' => 'Appearance description', 'name' => 'Appearance name', ], 'personality' => [ 'entry' => 'Personality trait description', 'name' => 'Personality trait name', ], ], 'lists' => [ 'empty' => 'Create your first hero, villain, or sidekick to bring your world to life.', ], 'organisations' => [ 'create' => [ 'success' => ':character added to :organisation.', 'title' => 'Membership', ], 'destroy' => [ 'success' => 'Membership removed.', ], 'edit' => [ 'success' => 'Membership updated.', 'title' => 'Update membership', ], 'fields' => [ 'role' => 'Role', ], ], 'personality_visibility' => [ 'admin' => 'Members of :admin role only', 'all' => 'Everyone can see', ], 'placeholders' => [ 'age' => 'Age', 'appearance_entry' => 'Description', 'appearance_name' => 'Hair, Eyes, Skin, Height', 'name' => 'Name of the character', 'personality_entry' => 'Details', 'personality_name' => 'Goals, Mannerisms, Fears, Bonds', 'physical' => 'Physical', 'pronouns' => 'He/Him, She/Her, They/Them', 'sex' => 'Gender', 'title' => 'Title', 'traits' => 'Traits', 'type' => 'NPC, Player Character, Deity', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Quests that the character is a quest giver of.', 'quest_member' => 'Quests that the character is a member of.', ], ], 'races' => [ 'helper' => 'Reorder and control which races of :name are visible or hidden from non-admins.', 'reorder' => [ 'success' => 'Character races updated successfully', ], 'title2' => 'Manage races', ], 'sections' => [ 'appearance' => 'Appearance', 'personality' => 'Personality', ], 'status' => [ 'alive' => 'Alive', 'dead' => 'Dead', 'missing' => 'Missing', ], 'warnings' => [ 'personality_hidden' => ':name\'s personality traits have been locked down.', ], ]; ================================================ FILE: lang/en/colours.php ================================================ 'Aqua', 'black' => 'Black', 'blue' => 'Blue', 'brown' => 'Brown', 'green' => 'Green', 'grey' => 'Grey', 'light-blue' => 'Light Blue', 'maroon' => 'Maroon', 'navy' => 'Navy', 'none' => 'None', 'orange' => 'Orange', 'pink' => 'Pink', 'purple' => 'Purple', 'red' => 'Red', 'teal' => 'Teal', 'white' => 'White', 'yellow' => 'Yellow', ]; ================================================ FILE: lang/en/concept.php ================================================ 'boosted campaign', 'premium-campaign' => 'premium campaign', 'premium-campaign-count' => '{0} No Premium Campaigns |{1} 1 Premium Campaign |[2,*] :count Premium Campaigns', 'premium-campaigns' => 'premium campaigns', 'premium-feature' => 'Premium feature', 'superboosted-campaign' => 'superboosted campaign', ]; ================================================ FILE: lang/en/confirm/editing.php ================================================ 'Go back', 'description' => 'Looks like someone else is currently editing this page! Do you want to go back or ignore this warning, at the risk of losing data?', 'ignore' => 'Edit anyway', 'members' => 'Members editing this page:', 'title' => 'Warning', 'user' => ':user since :since', ]; ================================================ FILE: lang/en/confirm.php ================================================ [ 'bulk' => 'Are you sure you want to delete the selected elements?', 'helper' => 'Are you sure you want to delete :name?', 'recover' => 'This action can be undone for up to :day days on the :recover page.', 'recoverable' => 'This action can be undone for up to :day days with a :premium-campaign.', 'title' => 'Remove element', ], ]; ================================================ FILE: lang/en/connections/web.php ================================================ [ 'add' => 'Connect existing', 'back' => 'Back to relations', 'download' => 'Download', 'download-pdf' => 'Download PDF', 'download-png' => 'Download image (.png)', 'reset-layout' => 'Reset layout', 'view' => 'View web', 'zoom-fit' => 'Zoom to fit', ], 'cta' => [ 'text' => 'This is a preview of the full campaign-wide relations web. Free campaigns can explore up to :amount nodes. Premium campaigns unlock the complete map with every relation and full navigation. Upgrade to see your entier world\'s structure at once.', 'title' => 'Preview limited to :amount relations', ], 'title' => 'Relations web', ]; ================================================ FILE: lang/en/conversations.php ================================================ [ 'title' => 'New Conversation', ], 'fields' => [ 'is_closed' => 'Closed', 'messages' => 'Messages', 'participants' => 'Participants', ], 'hints' => [ 'empty' => 'There are no participants in this conversation.', 'participants' => 'Please add participants to the conversation by pressing on the :icon icon on the upper-right.', ], 'lists' => [ 'empty' => 'Record dialogues, letters, or exchanges between characters and factions.', ], 'messages' => [ 'destroy' => [ 'success' => 'Message removed.', ], 'is_updated' => 'Updated', 'load_previous' => 'Load previous messages', 'placeholders' => [ 'message' => 'Your message', ], ], 'participants' => [ 'create' => [ 'success' => 'Participant :entity added to the conversation.', ], 'destroy' => [ 'success' => 'Participant :entity removed from the conversation.', ], 'helper' => 'Add and remove participants from :name.', 'modal' => 'Participants', 'title' => 'Participants of :name', ], 'placeholders' => [ 'name' => 'Name of the conversation', 'type' => 'In Game, Prep, Plot', ], 'show' => [ 'is_closed' => 'Conversation is closed.', ], 'tabs' => [ 'participants' => 'Participants', ], 'targets' => [ 'characters' => 'Characters', 'members' => 'Members', ], ]; ================================================ FILE: lang/en/cookieconsent.php ================================================ 'Allow cookies', 'dismiss' => 'Dismiss', 'header' => 'Cookie consent', 'link' => 'Learn more', 'message' => 'Kanka uses cookies to ensure you get the best experience on our website.', 'policy' => 'Cookie policy', 'reject' => 'Decline', ]; ================================================ FILE: lang/en/creatures.php ================================================ [ 'title' => 'New Creature', ], 'fields' => [ 'is_dead' => 'Dead', 'is_extinct' => 'Extinct', ], 'hints' => [ 'is_dead' => 'This creature is dead.', 'is_extinct' => 'This creature is extinct.', ], 'lists' => [ 'empty' => 'Add beasts, monsters, or mythical beings your heroes may face or befriend.', ], 'placeholders' => [ 'type' => 'Herbivore, Aquatic, Mythical', ], ]; ================================================ FILE: lang/en/crud.php ================================================ [ 'actions' => 'Actions', 'apply' => 'Apply', 'back' => 'Back', 'change' => 'Change', 'close' => 'Close', 'confirm' => 'Confirm', 'copy' => 'Copy', 'copy_mention' => 'Copy [ ] mention', 'copy_to_campaign' => 'Copy to campaign', 'disable' => 'Disable', 'enable' => 'Enable', 'explore_view' => 'Nested View', 'export' => 'Export (PDF)', 'find_out_more' => 'Find out more', 'go_to' => 'Go to :name', 'help' => 'Help', 'json-export' => 'Export (JSON)', 'markdown-export' => 'Export (Markdown)', 'move' => 'Move', 'new' => 'New', 'new_child' => 'New child', 'new_post' => 'New article', 'next' => 'Next', 'open' => 'Open', 'print' => 'Print', 'reorder' => 'Reorder', 'reset' => 'Reset', 'save-changes' => 'Save changes', 'share' => 'Share', 'transform' => 'Transform', ], 'add' => 'Add', 'alerts' => [ 'copy_attribute' => 'The property\'s mention was copied to your clipboard.', 'copy_invite' => 'The invite link was copied to your clipboard.', 'copy_mention' => 'The entry\'s advanced mention was copied to your clipboard.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Edit & tagging', 'kits' => 'Apply property kit', 'permissions' => 'Change permissions', ], 'age' => [ 'helper' => 'You can use + and - before the number to update the age by that amount.', ], 'buttons' => [ 'label' => 'For selected', ], 'edit' => [ 'locations' => 'Action for locations', 'tagging' => 'Action for tags', 'tags' => [ 'add' => 'Add', 'remove' => 'Remove', ], 'title' => 'Editing multiple entries', ], 'errors' => [ 'admin' => 'Only members of the Admin role can change the private status of entries.', 'general' => 'An error occurred processing your action. Please try again and contact us if the problem persists. Error message: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Override', ], 'helpers' => [ 'override' => 'If selected, permissions of the selected entries will be overwritten with these. If unchecked, the selected permissions will be added to the existing ones.', ], 'title' => 'Change permissions for several entries', ], ], 'bulk_templates' => [ 'bulk_title' => 'Apply a kit to multiple entries', ], 'cancel' => 'Cancel', 'copy_to_campaign' => [ 'bulk_title' => 'Copy entries to another campaign', 'panel' => 'Copy', 'title' => 'Copy \':name\' to another campaign', ], 'create' => 'Create', 'datagrid' => [ 'empty' => 'Nothing to show yet.', ], 'delete_modal' => [ 'callout' => 'Psst!', 'confirm' => 'Confirm removal', 'permanent' => 'This will permanently remove this element and you won\'t be able to undo this action.', 'recoverable' => 'Entries and articles can be recovered for up to :day days with a :boosted-campaign.', 'title' => 'Removal confirmation', ], 'dynamic' => [ 'permission' => 'You don\'t have the right permissions to create an entry in the :module category.', 'unknown' => 'Invalid entry of the :module category.', ], 'edit' => 'Edit', 'errors' => [ 'boosted_campaigns' => 'This feature is only available for :boosted.', 'unavailable_feature' => 'Unavailable feature', ], 'fields' => [ 'archived' => 'Archived', 'calendar_date' => 'Calendar Date', 'category' => 'Category', 'child' => 'Child', 'closed' => 'Closed', 'colour' => 'Colour', 'copy_abilities' => 'Copy Abilities', 'copy_inventory' => 'Copy Inventory', 'copy_links' => 'Copy Links', 'copy_permissions' => 'Copy Permissions (this will override values set in the permissions tab)', 'copy_posts' => 'Copy Articles (this includes the article permissions)', 'copy_reminders' => 'Copy Reminders', 'created_by' => 'Created by', 'creator' => 'Creator', 'date_range' => 'Date range', 'excerpt' => 'Excerpt', 'has_attributes' => 'Has properties', 'has_entity_files' => 'Has media', 'has_entry' => 'Has description', 'has_image' => 'Has an image', 'has_posts' => 'Has articles', 'header_image' => 'Header Image', 'image' => 'Image', 'is_closed' => 'Conversation will be closed and will no longer accept new messages.', 'is_private' => 'Private', 'is_private_v3' => 'Only show this to members of the :admin-role role. This overrides any other permission.', 'is_star' => 'Pinned', 'locations' => ':first in :second', 'name' => 'Name', 'names' => 'Names', 'parent' => 'Parent', 'position' => 'Position', 'replace_mentions' => 'Replace detail mentions in the description with those of the new entry', 'template' => 'Template', 'tooltip' => 'Tooltip', 'type' => 'Type', 'updated_by' => 'Updated by', 'visibility' => 'Visibility', 'word-count' => 'Word count: :number', ], 'files' => [ 'errors' => [ 'max' => 'You have reached the maximum number (:max) of files for this entry.', 'max_size' => 'The campaign has reached the maximum file storage capacity.', 'no_files' => 'No files.', ], 'hints' => [ 'limit' => 'Each entry can have a maximum of :max files uploaded to it.', 'limitations' => 'Supported formats: :formats. Max file size: :size.', ], ], 'filter' => 'Filter', 'filters' => [ 'all' => 'Filter to all descendants', 'clear' => 'Clear Filters', 'copy_helper' => 'Use the copied filters in your clipboard as values for filters on dashboard widgets and bookmarks.', 'copy_to_clipboard' => 'Copy filters to clipboard', 'direct' => 'Filter to direct descendants', 'filtered' => 'Showing :count of :total :entity.', 'lists' => [ 'desktop' => [ 'all' => 'Show all :count descendants', 'filtered' => 'Show direct :count descendants', ], 'paginated' => 'Switch between direct children and all levels of descendants. Results are paginated for performance.', ], 'mobile' => [ 'clear' => 'Clear', 'copy' => 'Clipboard', ], 'options' => [ 'any' => 'Any', 'children' => 'Matches this or its descendants', 'exclude' => 'Doesn\'t match', 'hide' => 'Hide', 'include' => 'Matches', 'none' => 'Empty', 'show' => 'Show', ], 'show' => 'Show Filters', 'sorting' => [ 'asc' => ':field Ascending', 'desc' => ':field Descending', 'helper' => 'Control in which order results appear.', ], 'title' => 'Advanced filters', ], 'fix-this-issue' => 'Fix this issue', 'forms' => [ 'actions' => [ 'calendar' => 'Add a calendar date', ], 'copy_options' => 'Copy Options', ], 'helpers' => [ 'copy_options' => 'Copy the following related elements from the source to the new entry.', 'linking' => 'Linking to other entries', 'parent' => 'Select a parent this entry will be a child to.', 'per-page' => 'Results per page', ], 'hidden' => 'Hidden', 'hints' => [ 'calendar_date' => 'A calendar date allows easy filtering in lists, and also maintains a reminder in the selected calendar.', 'image_dimension' => 'Recommended dimensions: :dimension pixels.', 'image_formats' => 'Supported formats: :formats.', 'image_limitations' => 'Supported formats: :formats. Max file size: :size.', 'image_recommendation' => 'Recommended dimensions: :width by :height pixels.', 'is_star' => 'Pinned elements will appear on the overview page.', 'kit' => 'The selected property kit will be applied when saving the entry.', 'tooltip' => 'Replace the automatically generated tooltip with the following contents. Any HTML code will be stripped, but you can still mention other entries using advanced mentions.', ], 'history' => [ 'created_clean' => 'Created by :name :date', 'created_date_clean' => 'Created :date', 'unknown' => 'Unknown', 'updated_clean' => 'Last modified by :name :date', 'updated_date_clean' => 'Last modified :date', 'view' => 'View changes', ], 'image' => [ 'error' => 'We weren\'t able to get the image you requested. It could be that the website doesn\'t allow us to download the image (typically for Squarespace and DeviantArt), or that the link is no longer valid. Please also make sure that the image isn\'t larger than :size.', ], 'is_private' => 'This entry is private and only visible to members of the Admin role.', 'keyboard-shortcut' => 'Keyboard shortcut :code', 'navigation' => [ 'cancel' => 'cancel', 'or_cancel' => 'or :cancel', 'skip_to_content' => 'Skip navigation', ], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Allow', 'deny' => 'Deny', 'ignore' => 'Skip', 'remove' => 'Remove', ], 'bulk_entity' => [ 'allow' => 'Allow', 'deny' => 'Deny', 'inherit' => 'Inherit', ], 'delete' => 'Delete', 'edit' => 'Edit', 'private' => 'Make private', 'toggle' => 'Toggle', 'view' => 'View', ], 'fields' => [ 'member' => 'Member', 'role' => 'Role', ], 'helpers' => [ 'setup' => 'Control how roles and members can interact with this entry. :allow will allow a member or role to do this action. :deny will deny them that action. :inherit will use the role or member\'s role\'s permission. A member set to :allow is able to do the action, even if one of their roles is set to :deny.', ], 'success' => 'Permissions saved.', 'title' => 'Permissions', 'too_many_members' => 'This world has too many members (>:number) to display in this interface. Please use the Permission button on the entry view to control permissions in detail.', ], 'placeholders' => [ 'calendar' => 'Choose a calendar', 'entry' => 'Use @ followed by three letters to mention other entries of the campaign.', 'fallback' => 'Choose :module', 'gallery_image' => 'Choose an image from the gallery', 'image_url' => 'Upload an image from a URL instead', 'journal' => 'Choose a journal', 'location' => 'Choose a location', 'multiple' => 'Choose one or several', 'name' => 'Name of the entry', 'organisation' => 'Choose an organisation', 'parent' => 'Choose a parent', 'search' => 'Type to search', 'tag' => 'Choose a tag', 'template' => 'Choose a template', 'timeline' => 'Choose a timeline', 'type' => 'Type of the entry', 'user' => 'Choose a user', ], 'remove' => 'Remove', 'reorder' => [ 'empty' => 'No elements to reorder.', ], 'save' => 'Save', 'save_and_close' => 'Save and Close', 'save_and_copy' => 'Save and Copy', 'save_and_new' => 'Save and New', 'save_and_update' => 'Save and Edit', 'save_and_view' => 'Save and View', 'search' => 'Search', 'select' => 'Select', 'tabs' => [ 'abilities' => 'Abilities', 'inventory' => 'Inventory', 'mentions' => 'Mentions', 'overview' => 'Overview', 'permissions' => 'Permissions', 'premium' => 'Premium', 'profile' => 'Profile', 'reminders' => 'Reminders', ], 'timestamps' => [ 'edited' => 'Edited :ago', ], 'titles' => [ 'editing' => 'Editing :name', 'new' => 'New :module', ], 'update' => 'Update', 'users' => [ 'unknown' => 'Unknown', ], 'view' => 'View', 'visibilities' => [ 'admin' => 'Admins', 'admin-self' => 'Only me & Admins', 'all' => 'All', 'members' => 'Members of the campaign', 'self' => 'Only me', ], ]; ================================================ FILE: lang/en/dashboard.php ================================================ [ 'customise' => 'Customise dashboard', 'follow' => 'Follow', 'join' => 'Join', 'unfollow' => 'Stop following', ], 'dashboards' => [ 'actions' => [ 'edit' => 'Edit name & permissions', 'new' => 'New Dashboard', ], 'create' => [ 'helper' => 'Create a new dashboard for :name, and assign which roles can see it or have it as their default dashboard.', 'success' => 'New dashboard :name created.', 'title' => 'New dashboard', ], 'custom' => [ 'text' => 'You are currently editing the :name dashboard.', ], 'default' => [ 'text' => 'You are currently editing the default dashboard of :campaign.', 'title' => 'Default Dashboard', ], 'delete' => [ 'success' => 'Dashboard :name removed.', ], 'fields' => [ 'copy_widgets' => 'Copy widgets', 'name' => 'Dashboard name', 'visibility' => 'Visibility', ], 'helpers' => [ 'copy_widgets' => 'Duplicate the widgets from the :name dashboard into this new one.', ], 'pitch' => 'Create multiple dashboards with custom permissions for each role.', 'placeholders' => [ 'name' => 'Name of the dashboard', ], 'update' => [ 'success' => 'Dashboard :name updated.', 'title' => 'Update dashboard', ], 'visibility' => [ 'default' => 'Default', 'none' => 'None', 'visible' => 'Visible', ], ], 'helpers' => [ 'follow' => 'Following a campaign will make it appear in the campaign switcher below your own campaigns.', 'join' => 'This campaign is open to new members. Click to apply to join it.', ], 'setup' => [ 'actions' => [ 'add' => 'Widget', 'back_to_dashboard' => 'Back to dashboard', 'edit' => 'Edit a widget', 'new' => 'New :type widget', ], 'reorder' => [ 'helper' => 'Drag me to move me around', 'success' => 'Widgets reordered.', ], 'title' => 'Dashboard Setup', 'tutorial' => [ 'blog' => 'our tutorial', 'text' => 'Need help setting up the dashboard? Read :blog for some help and inspiration.', ], ], 'title' => 'Dashboard', 'widgets' => [ 'advanced_options_boosted' => 'Enable more options like showing pins with a :boosted_campaign.', 'calendar' => [ 'actions' => [ 'next' => 'Change date to next day', 'previous' => 'Change date to previous day', ], 'previous_events' => 'Previous', 'upcoming_events' => 'Upcoming', ], 'campaign' => [ 'helper' => 'This widget displays a billboard. This widget is always shown on the default dashboard.', ], 'create' => [ 'helper' => 'Select a widget type to add to the :name dashboard.', 'helper-default' => 'Select a widget type to add to the default dashboard.', 'success' => 'Widget added to the dashboard.', 'title' => 'New widget', ], 'delete' => [ 'success' => 'Widget removed from the dashboard.', ], 'fields' => [ 'class' => 'CSS class', 'dashboard' => 'Dashboard', 'name' => 'Custom widget name', 'optional-entity' => 'Link to entry', 'order' => 'Ordering', 'size' => 'Size', 'width' => 'Width', ], 'helpers' => [ 'class' => 'Define a custom CSS class added to the widget.', 'filters' => 'Click to learn about the available filter options.', ], 'orders' => [ 'name_asc' => 'Name ascending', 'name_desc' => 'Name descending', 'oldest' => 'Oldest modified', 'recent' => 'Recently modified', ], 'preview' => [ 'displays' => [ 'expand' => 'Expandable entry', 'full' => 'Full entry', ], 'fields' => [ 'display' => 'Display', ], ], 'random' => [ 'helpers' => [ 'name' => 'You can reference the random entry\'s name with {name}', ], 'type' => [ 'all' => 'All', ], ], 'recent' => [ 'advanced_filter' => 'Advanced filter', 'advanced_filters' => [ 'mentionless' => 'Mentionless (entries that don\'t mention other entries)', 'unmentioned' => 'Unmentioned (entries that aren\'t mentioned by other entries)', ], 'all-entities' => 'All entries', 'entity-header' => 'Use entry header as image', 'filters' => 'Filters', 'help' => 'Only show the first entry as a preview instead of a list.', 'helpers' => [ 'entity-header' => 'If the entry has a header image (premium campaign feature), set this widget to use that image instead of the entry\'s image.', 'show_attributes' => 'Show the entry\'s pinned properties below the entry.', 'show_members' => 'If the entry is a family or organisation, show its members below the entry.', 'show_relations' => 'Show the entry\'s pinned relations below the entry.', ], 'show_attributes' => 'Show pinned properties', 'show_members' => 'Show members', 'show_relations' => 'Show pinned relations', 'singular' => 'Preview', 'tags' => 'Filter the list of entries on specified tags.', 'title' => 'Entry list', ], 'tabs' => [ 'advanced' => 'Advanced', 'setup' => 'Setup', ], 'unmentioned' => [ 'title' => 'Unmentioned entries', ], 'update' => [ 'success' => 'Widget modified.', ], 'widths' => [ '0' => 'Auto', '12'=> 'Full (100%)', '3' => 'Tiny (25%)', '4' => 'Small (33%)', '6' => 'Half (50%)', '8' => 'Wide (66%)', '9' => 'Large (75%)', ], ], ]; ================================================ FILE: lang/en/dashboards/onboarding.php ================================================ [ 'continue' => 'Start building', 'skip' => 'Skip for now', ], 'families' => [ 'varren' => [ 'title' => 'House Varren', ], ], 'fields' => [ 'name' => 'World name', ], 'intro' => 'Your world is ready. Make it yours before you dive in.', 'placeholders' => [ 'name' => 'You can change this anytime.', ], 'quests' => [ 'crown' => [ 'title' => 'Retrieve the shattered crown', ], ], 'roles' => [ 'co-writer' => 'Co-writer', 'contributor' => 'Contributor', ], 'selection' => [ 'campaign' => 'Tabletop RPG campaign', 'campaign-description' => 'Manage your campaign, characters, and sessions for your TTRPG group.', 'helper' => '(Your choice will personalise demo content and your dashboard layout.)', 'intro' => 'What are you creating?', 'story' => 'Novel or story universe', 'story-description' => 'Develop your story\'s characters, settings, and timeline as you write.', 'title' => 'World type', 'worldbuilding' => 'Worldbuilding project', 'worldbuilding-description' => 'Build and organize the lore, places, and people of your own world.', ], 'splash' => 'Welcome, :name 👋 Your world is ready.', 'success' => 'Welcome! We\'ve customised your world for a quick start.', 'title' => 'Make this world yours', 'ttrpg' => [ 'tags' => [ 'npcs' => 'NPCs', ], ], 'widgets' => [ 'active-quests' => 'Active quests', ], ]; ================================================ FILE: lang/en/dashboards/premium.php ================================================ 'Build tailored overviews for your campaign and home pages for your players. Pin entries, maps, calendars, gallery and more in any layout you choose.', 'title' => 'Custom dashboards', ]; ================================================ FILE: lang/en/dashboards/setup.php ================================================ [ 'add' => 'Add widget', ], 'sections' => [ 'switch' => 'Switch to...', ], 'tooltips' => [ 'add' => 'Click to add a new widget to the dashboard.', 'switch' => 'Click to switch to another dashboard.', ], ]; ================================================ FILE: lang/en/dashboards/widgets/calendar.php ================================================ 'Shows upcoming reminders.', 'name' => 'Calendar', ]; ================================================ FILE: lang/en/dashboards/widgets/campaign.php ================================================ 'Shows a billboard preview of the campaign.', 'name' => 'Billboard', ]; ================================================ FILE: lang/en/dashboards/widgets/gallery.php ================================================ 'Display images from a gallery folder as a carousel.', 'fields' => [ 'folder' => 'Gallery folder', 'show_name' => 'Image names', ], 'helpers' => [ 'premium' => 'Showcase your world\'s art right on your dashboard.', 'show_name' => 'Display the image name below each image.', ], 'name' => 'Gallery', ]; ================================================ FILE: lang/en/dashboards/widgets/header.php ================================================ 'Shows a text header.', 'name' => 'Header', ]; ================================================ FILE: lang/en/dashboards/widgets/help.php ================================================ 'Shows help and community links.', 'name' => 'Help & Community', 'title' => 'Help & Community', ]; ================================================ FILE: lang/en/dashboards/widgets/join.php ================================================ 'Apply to join!', 'description' => 'Shows the join campaign application button along with the campaign\'s introduction.', 'name' => 'Players Wanted', 'register' => 'Register to apply', 'title' => 'Players Wanted', 'update' => 'Update your application', ]; ================================================ FILE: lang/en/dashboards/widgets/onboarding.php ================================================ 'Shows a todo list for getting started with worldbuilding.', 'name' => 'Getting Started', 'tasks' => [ 'campaign' => [ 'name' => 'Your first world is ready.', ], 'character' => [ 'helper' => 'Add someone who exists in your world.', 'name' => 'Create your first character.', ], 'invite' => [ 'helper' => 'Add people to your campaign with roles.', 'name' => 'Invite a friend or co-author.', ], 'location' => [ 'helper' => 'Ground your world with a location.', 'name' => 'Create your first location.', ], 'rename' => [ 'helper' => 'Set a proper name for your campaign.', 'name' => 'Rename your world.', ], 'widgets' => [ 'helper' => 'Add or rearrange widgets.', 'name' => 'Customise your dashboard.', ], ], ]; ================================================ FILE: lang/en/dashboards/widgets/preview.php ================================================ 'Shows a specific entry.', 'name' => 'Selected entry', ]; ================================================ FILE: lang/en/dashboards/widgets/random.php ================================================ 'Shows a random entry from the campaign.', 'name' => 'Random entry', ]; ================================================ FILE: lang/en/dashboards/widgets/recent.php ================================================ 'Shows a list of recently modified entries.', 'name' => 'Recently modified entries', ]; ================================================ FILE: lang/en/dashboards/widgets/welcome.php ================================================ 'Shows a welcome message with tips.', 'focus' => [ 'text' => 'Here, that\'s me!', 'title' => 'Hey', ], 'intros' => [ '1' => 'Say hello to your new worldbuilding home, :user! We\'ve set up your first campaign and included two example :characters and :locations. These are also visible here on the dashboard.', '2' => 'To get started, click on the big :new-entity button (or press :letter on your keyboard), and click on :characters to create your first character. It\'s that easy! You can find all your characters, locations, and other :entities from the sidebar on the left of the page.', '3' => 'Here are our top 5 tricks for using Kanka', ], 'name' => 'Welcome', 'title' => 'Welcome to :kanka! 🎉', 'tricks' => [ '1' => 'When writing descriptions, don\'t re-write names of elements of the campaign. Instead, type :code and three letters to :mention other entries of the campaign. These mentions will automatically update when you change names around.', '2' => 'To edit the campaign\'s name, theme, or image, click on :world in the sidebar, followed by the :edit button.', '3' => 'Write secret information on entries as :posts instead of in the main text field.', '4' => 'Invite your friends to the campaign by clicking on :world and :members. From there, you can create invitation links.', '5' => 'You can remove this welcome message and show other information on this page (called the dashboard). Scroll down and click on the :button button.', 'mention' => 'mention', ], ]; ================================================ FILE: lang/en/datagrids.php ================================================ [ 'back_to' => 'Back to :name', 'filters' => 'Filters', ], 'bulks' => [ 'selected' => 'selected', ], 'columns' => [ 'reset' => 'Reset to default', 'title' => 'Control visible columns', ], 'display' => [ 'per_page' => 'Results per page', 'sort_by' => 'Sort by', 'title' => 'Display', ], 'filters' => [ 'clear' => 'Clear filters', ], 'modes' => [ 'flatten' => 'Switch to a flattened layout', 'grid' => 'Switch to the grid view', 'nested' => 'Switch to a nested layout', 'table' => 'Switch to the table view', ], 'subscription' => [ 'cta' => 'Explore subscriptions', 'helper' => 'See up to :max entities at a glance with a Kanka subscription, plus a ton of other perks to supercharge your worldbuilding.', 'title' => 'Subscription required', ], 'tooltips' => [ 'nested' => 'This entry has children. Click on the image to view them.', ], ]; ================================================ FILE: lang/en/datetime.php ================================================ 'day', 'days' => 'days', 'elapsed_ago' => ':duration ago', 'hour' => 'hour', 'hours' => 'hours', 'just_now' => 'just now', 'minute' => 'minute', 'minutes' => 'minutes', 'month' => 'month', 'months' => 'months', 'second' => 'second', 'seconds' => 'seconds', 'week' => 'week', 'weeks' => 'weeks', 'year' => 'year', 'years' => 'years', ]; ================================================ FILE: lang/en/default.php ================================================ 'Page Title', ]; ================================================ FILE: lang/en/dice_roll_results.php ================================================ [ 'title' => 'Dice Roll Results', ], ]; ================================================ FILE: lang/en/dice_rolls.php ================================================ [ 'title' => 'New Dice Roll', ], 'destroy' => [ 'dice_roll' => 'Dice roll removed.', ], 'fields' => [ 'created_at' => 'Rolled At', 'parameters' => 'Parameters', 'results' => 'Results', 'rolls' => 'Rolls', ], 'hints' => [ 'parameters' => 'What dice options are available?', ], 'index' => [ 'actions' => [ 'results' => 'Results', ], ], 'lists' => [ 'empty' => 'Create and save rolls for the campaign to keep track of results directly in Kanka.', ], 'placeholders' => [ 'name' => 'Name of the Dice Roll', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Roll', ], 'error' => 'Dice roll unsuccessful. Can\'t parse the parameters.', 'fields' => [ 'creator' => 'Creator', 'date' => 'Date', 'result' => 'Result', ], 'hint' => 'All the rolls done for this dice roll template.', 'success' => 'Dice rolled.', ], 'show' => [ 'tabs' => [ 'results' => 'Results', ], ], ]; ================================================ FILE: lang/en/emails/activity/email.php ================================================ 'Your Kanka account email has been changed to :email.', 'title' => 'Email changed', ]; ================================================ FILE: lang/en/emails/activity/password.php ================================================ 'Your Kanka account password has been changed.', 'help' => 'If this wasn\'t you, please contact us at :email.', 'title' => 'Password changed', ]; ================================================ FILE: lang/en/emails/purge/first.php ================================================ 'If you are regularly using your account, do not worry, we are only deleting accounts and campaigns that are not in active use.', 'help' => 'Need help using Kanka? Join our :discord or reach out to us at :email', 'intro_account' => 'We are reaching out to inform you that your account will be deleted in :amount days, as you haven\'t used it in the last :duration months.', 'intro_campaigns' => 'We are reaching out to inform you that your account and the following campaigns will be deleted in :amount days, as you haven\'t used it in the last :duration months.', 'keep' => 'If you wish to keep your account active, please log in within the next :amount days.', 'title' => 'Your Kanka account will be deleted in :amount days', 'warning' => [ 'account' => 'Once this is done, all the data associated with your account, under :email, will be permanently deleted.', 'campaigns' => 'Once this is done, all the data associated with your account, under email :email, as well as the following campaigns will be permanently deleted.', ], ]; ================================================ FILE: lang/en/emails/purge/second.php ================================================ 'This is a final reminder that the Kanka account under the email :email will be deleted in :amount days as you haven\'t used it in the last :duration months.', 'title' => 'Final warning: Your Kanka account will be deleted in :amount days', ]; ================================================ FILE: lang/en/emails/subscriptions/expiring.php ================================================ 'update your card details', 'primary' => 'This is an automatic warning that your :brand **** :last is expiring soon.', 'title' => 'Expiring card for your subscription', 'valid' => 'If you wish to keep your subscription, please :action.', ]; ================================================ FILE: lang/en/emails/subscriptions/paypal-expiring.php ================================================ 'Yours truly,', 'cta' => 'Renew your subscription', 'dear' => 'Dear :name', 'intro' => 'Your Kanka PayPal subscription expires on **:date**. After that date your account will revert to the free tier.', 'loss' => [ 'ads' => 'Ad-free experience', 'campaign' => 'Premium campaign **:campaign**|Premium campaigns **:campaign** and :count more', 'discord' => 'Your **:role** Discord role', 'players' => ':count player will lose access|:count players will lose access', 'plugins' => ':count plugin|:count plugins', 'title' => 'Here is what you will lose:', ], 'title' => 'Your Kanka subscription expires soon', ]; ================================================ FILE: lang/en/emails/subscriptions/upcoming.php ================================================ 'If you do not wish to renew your subscription, please sign in to your Kanka account and :link.', 'closing' => 'Yours truly,', 'dear' => 'Dear :name', 'link' => 'cancel your subscription', 'notice' => 'Important notice on renewals:', 'primary' => 'This is an automatic reminder that we are going to automatically charge your :brand **** :last on :date, for your Kanka subscription.', 'title' => 'Yearly payment for your Kanka subscription', 'valid' => 'Please make sure your credit card will be valid on the date of payment.', ]; ================================================ FILE: lang/en/emails/subscriptions/validation.php ================================================ 'Kanka account email validation.', ]; ================================================ FILE: lang/en/emails/validation.php ================================================ 'Validation failed, please try again.', 'modal' => 'A validated email is required for subscriptions. Please check your inbox for a link to confirm your email before continuing the subscription process.', 'success' => 'Email successfully validated.', ]; ================================================ FILE: lang/en/emails/welcome/2024.php ================================================ 'Happy worldbuilding, and thank you for taking part in this journey with us,', 'header' => 'Welcome to the best place to create your campaign, :name!', 'lead_1' => 'Kanka is the brainchild of passionate RPG players who wanted an easy and collaborative approach to worldbuilding, without compromising on features.', 'lead_2' => 'Kanka is what came of that. We\'re here to help you organize your campaign, and let you focus on bringing your world to life.', 'ps' => 'PS: If you want to get in touch, you can find us on :discord, or at :email.', 'what_1' => 'To help you get started, we created your first campaign and included two example characters and locations. You can :start if you prefer.', 'what_2' => 'You should also check out these resources:', 'what_3' => 'Our :kb if you have basic questions about the features of Kanka and your account.', 'what_4' => 'The :doc covers everything in more depth.', 'what_5' => 'And Finally :campaigns showcase what others have done with Kanka.', 'what_new' => 'start a new one', 'what_now' => 'What now?', 'why' => 'You received this email because you signed up on our website.', ]; ================================================ FILE: lang/en/emails/welcome.php ================================================ [ 'basics' => [ 'text_1' => 'With a tool as extensive as Kanka, it can be hard to know where to start or what to do. Our :kb covers the most basic questions you might have, and for more help, you can head over to our :doc.', 'title' => 'The basics', ], 'chat' => [ 'text_1' => 'We love hearing from our users. We\'re most active on :discord, where you will find plenty of our dedicated users, an onboarding team, as well as the founders of Kanka, who can answer any questions that you may have. You can also email us at :email.', 'title' => 'Want to chat?', ], 'intro' => [ 'header' => 'Welcome to the best worldbuilding community, :name!', 'link' => 'Go to your world!', 'text_1' => 'Say hello to your new worldbuilding home, :name! Community is in our DNA, and we\'re delighted to have you join us. Kanka is the brainchild of passionate RPG players who believe in a simplified and communal way to approach worldbuilding, without compromising on features.', 'text_2' => 'We\'ve set up your first campaign and included two example characters and locations to help you get started.', ], 'preview' => 'Be part of the best worldbuilding community, :name!', ], 'header' => 'Welcome to Kanka :name!', 'header_sub' => 'Congratulations, you have taken the first step in the creation of your world on :kanka!', 'pricing' => 'pricing', 'section_1' => 'Where to now?', 'section_11' => 'Create your world,', 'section_2' => 'The most important resource is :discord, where you will find plenty of our dedicated users, an onboarding team, as well as the founder of Kanka, who can answer any questions that you may have.', 'section_4' => 'Our :youtube has videos covering the basics of Kanka. While not all topics are covered yet, we regularly add new videos.', 'section_4_v2' => 'Our :knowledge-base covers the most basic questions you might have, and for a more complete help, you can head over to our :documentation!', 'section_6' => 'Contact us', 'section_7' => 'If you haven\'t found an answer to your questions, or simply want to get in touch, you can email us at :email. We are a tiny team, but we make sure to answer every email we get, so do not hesitate!', 'section_8' => 'One last thing', 'section_9_v2' => 'We have made sure that all core features in Kanka are free. However, if you want to support us in this project and gain access to additional features and our gratitude, you can become a subscriber. Learn more on the :pricing page.', 'social_account' => 'If you are having issues logging into your account, a small reminder that you are using a :provider login. You can change this in your account settings.', 'title' => 'Welcome to Kanka', ]; ================================================ FILE: lang/en/entities/abilities.php ================================================ [ 'add' => 'Add abilities', 'reset' => 'Recharge', 'sync' => 'Add from races', ], 'charges' => [ 'left' => ':amount left', ], 'create' => [ 'helper' => 'Attach one of several abilities to :name.', 'success' => 'Ability :ability added to :entity.', 'success_multiple' => 'Abilities :abilities added to :entity.', 'title' => 'Add abilities', ], 'fields' => [ 'note' => 'Note', 'position' => 'Position', ], 'groups' => [ 'unorganised' => 'Unorganised', ], 'helpers' => [ 'note' => 'You can reference entries using advanced mentions (ex :code) and properties of the entry (ex :attr) in this field.', 'recharge' => 'Reset all charges for abilities that have been used.', 'sync' => 'Import abilities that are defined on the character\'s races.', ], 'import' => [ 'errors' => [ 'no_race' => 'The character has no race.', 'not_character' => 'The entry isn\'t a character.', ], 'helper' => 'Attach abilities from the following races :name belongs to:', 'no_abilities' => 'Currently there are no abilities to import from the races :name belongs to.', 'race_abilities' => '{1} :name (:count ability)|[2,*] :name (:count abilities)', 'success' => '{1} :count race ability imported.|[2,*] :count race abilities imported.', ], 'recharge' => [ 'success' => 'All charges have been reset.', ], 'reorder' => [ 'parentless' => 'No Parent', 'success' => 'Abilities successfully reordered', ], 'show' => [ 'helper' => 'Attach abilities to :name to represent what powers they possess.', 'reorder' => 'Reorder', 'title' => ':name Abilities', ], 'types' => [ 'unorganised' => 'Abilities are grouped by their parent field, and fallback to being here.', ], 'update' => [ 'success' => 'Entry ability :ability updated.', 'title' => 'Entry Ability for :name', ], ]; ================================================ FILE: lang/en/entities/actions.php ================================================ [ 'set' => 'Set as archetype', 'toggle' => 'Toggled template status.', 'unset' => 'Remove as template', ], 'archive' => [ 'success' => ':name has been archived.', 'title' => 'Archive', ], 'convert' => 'Convert category', 'copy-campaign' => 'Copy to campaign', 'json-export' => 'JSON export', 'markdown-export' => 'Markdown export', 'tooltips' => [ 'edit' => 'Edit this entry', ], 'transfer' => 'Transfer to campaign', 'unarchive' => [ 'success' => ':name is no longer archived.', 'title' => 'Unarchive', ], ]; ================================================ FILE: lang/en/entities/aliases.php ================================================ [ 'add' => 'Add an alias', ], 'create' => [ 'helper' => 'Create an alias for :name, which will make it findable in the global search and through :code mentions.', 'success' => 'Alias :name added to :entity.', 'title' => 'New alias', ], 'destroy' => [ 'success' => 'Alias :name removed.', ], 'fields' => [ 'name' => 'Name', ], 'helpers' => [ 'primary' => 'Setting one or several aliases on the entry will make it findable in the global search (top bar) and through :code mentions.', ], 'limit' => 'Alias limit reached for standard campaigns (:amount/:max). :upgrade for unlimited aliases on this campaign.', 'pitch' => 'Add aliases to this entry to make it easier to find in search and when using mentions. Perfect for nicknames, titles, or alternate spellings.', 'placeholders' => [ 'name' => 'New alias', ], 'update' => [ 'success' => 'Alias :name updated for :entity.', 'title' => 'Update alias for :name', ], ]; ================================================ FILE: lang/en/entities/assets.php ================================================ [ 'alias' => 'Alias or Secret identity', 'file' => 'Add a file', 'link' => 'External link', ], 'copy_alias' => [ 'success' => 'Alias mention copied to the clipboard.', 'title' => 'Click to copy alias mention to the clipboard.', ], 'show' => [ 'title' => ':name Assets', ], ]; ================================================ FILE: lang/en/entities/attributes.php ================================================ [ 'apply_kit' => 'Apply a property kit', 'load' => 'Load', 'manage' => 'Manage', 'more' => 'Others', 'remove_all' => 'Delete all', 'save_and_edit' => 'Apply and Edit', 'save_and_story'=> 'Apply and View', 'show_hidden' => 'Show hidden properties', 'toggle_privacy'=> 'Private/Public', ], 'errors' => [ 'api' => 'Invalid data', 'loop' => 'There is an endless loop in this property calculation!', 'no_attribute_selected' => 'Select one or more properties first.', 'too_many_v2' => 'Max fields reached (:count/:max). Delete some properties first before being able to add more.', ], 'fields' => [ 'community_templates' => 'Community Templates', 'is_private' => 'Private properties', 'is_star' => 'Pinned', 'kit' => 'Kit', 'preferences' => 'Preferences', 'property' => 'Property', 'value' => 'Value', ], 'filters' => [ 'name' => 'Property name', 'value' => 'Property value', ], 'helpers' => [ 'delete_all' => 'Are you sure you want to delete all of this entry\'s properties?', 'is_private' => 'Only allow members of the :admin-role role to see this entry\'s properties.', 'setup' => 'You can represent elements like HP or intelligence of an entry with properties. Add properties manually by clicking on the :manage button, or apply those from a property kit.', ], 'index' => [ 'success' => 'Properties for :entity updated.', 'title' => 'Properties for :name', ], 'labels' => [ 'checkbox' => 'Checkbox name', 'name' => 'Property name', 'section' => 'Section name', 'value' => 'Property value', ], 'live' => [ 'success' => 'Property :attribute updated.', 'title' => 'Updating :attribute', ], 'placeholders' => [ 'attribute' => 'Number of conquests, Challenge Rating, Initiative, Population', 'block' => 'Multiline name', 'checkbox' => 'Checkbox name', 'icon' => [ 'class' => 'FontAwesome or RPG Awesome class: fas fa-users', 'name' => 'Icon name', ], 'kit' => 'Select a kit', 'number' => 'Number value', 'random' => [ 'name' => 'Property name', 'value' => '1-100 or list of values separated by a comma', ], 'section' => 'Section name', 'value' => 'Value of the properties', ], 'ranges' => [ 'text' => 'Available options: :options', ], 'sections' => [ 'unorganised' => 'Unorganised', ], 'show' => [ 'hidden' => 'Hidden properties', 'title' => ':name properties', ], 'template' => [ 'load' => [ 'success' => 'Kit loaded', 'title' => 'Load from kit', ], 'pitch' => 'Load properties from a property kit or plugins installed from the :plugin.', 'success' => 'Property kit :name applied to :entity', 'title' => 'Apply a property kit on :name', ], 'title' => 'Properties', 'toasts' => [ 'bulk_deleted' => 'Property deleted', 'bulk_privacy' => 'Property privacy toggled', 'lock' => 'Property locked', 'pin' => 'Property pinned', 'unlock' => 'Property unlocked', 'unpin' => 'Property unpinned', ], 'types' => [ 'attribute' => 'Text', 'block' => 'Block', 'checkbox' => 'Checkbox', 'icon' => 'Icon', 'kits' => 'Kits', 'number' => 'Number', 'random' => 'Random', 'section' => 'Section', 'text' => 'Paragraph', ], 'update' => [ 'success' => 'Properties for :entity updated.', ], 'visibility' => [ 'entry' => 'Property is displayed on the entry menu.', 'private' => 'Property only visible to members of the "Admin" role.', 'public' => 'Property visible to all members.', 'tab' => 'Property is displayed only on the Properties tab.', ], ]; ================================================ FILE: lang/en/entities/children.php ================================================ 'Children', ]; ================================================ FILE: lang/en/entities/events.php ================================================ [ 'helper' => 'Create a reminder to link :name to a calendar.', ], 'fields' => [ 'type' => 'Reminder Type', ], 'helpers' => [ 'characters' => 'Setting the type as either the date of birth or of death for this character will automatically calculate their age. :more.', 'founding' => 'Setting the type as :type will automatically calculate the entry\'s age since founding.', ], 'show' => [ 'actions' => [ 'add' => 'Add reminder', ], 'title' => ':name Reminders', ], 'types' => [ 'birth' => 'Birth', 'birthday' => 'Birthday', 'death' => 'Death', 'founded' => 'Founded', 'primary' => 'Primary', ], 'years-ago' => '{1} :count year ago|[2,*] :count years ago', ]; ================================================ FILE: lang/en/entities/files.php ================================================ [ 'max' => [ 'helper' => 'You can\'t attach any more files unless you remove an existing one.', 'limit' => 'This entry has reached its file limit', ], 'upgrade' => [ 'limit' => 'You\'ve reached the limit of :limit files for this entity', 'premium' => 'Upgrade to a premium campaign to attach unlimited files and unlock even more creative flexibility.', 'upgrade' => 'Upgrade to a premium campaign to attach up to :limit files and unlock even more creative flexibility.', ], ], 'create' => [ 'helper' => 'Add a file to :name. The file will count towards the gallery storage limit.', 'success_plural' => '{1} File :name added.|[2,*] :count files added.', 'title' => 'New file', ], 'destroy' => [ 'success' => 'File :name removed.', ], 'fields' => [ 'file' => 'File', 'files' => 'Files', 'name' => 'File name', ], 'max' => [ 'title' => 'Limit reached', ], 'update' => [ 'success' => 'File :name updated.', 'title' => 'Update file', ], ]; ================================================ FILE: lang/en/entities/image.php ================================================ [ 'change_focus' => 'Change focus point', 'change_visibility' => 'Change visibility', 'copy_url' => 'Copy image URL', 'copy_url_success' => 'Image URL copied to your clipboard.', 'replace_image' => 'Replace image', 'save-replace' => 'Replace image', 'save_focus' => 'Save focus point', 'view' => 'View image', ], 'call-to-action' => 'Click on the entry\'s image to set it\'s focus point instead of using the automated guess.', 'focus' => [ 'breadcrumb' => 'Image focus', 'helper' => 'Click on the image to set the focus point. Click on the focus point to remove it.', 'panel_title' => 'Image focus', 'success' => 'Image focus updated.', 'title' => 'Image Focus for :name', 'unboosted' => 'Setting an image focus point is reserverd to :boosted-campaigns.', 'warning' => 'The focus point for :gallery images is shared by all entries that use that same image.', ], 'gallery_permissions' => [ 'admin' => 'This gallery image is only visible to the members of the :admin role.', 'adminself' => 'This gallery image is only visible to :creator and the members of the :admin role.', 'member' => 'This gallery image is only visible to the members of the campaign.', 'self' => 'This gallery image is only visible to you.', ], 'replace' => [ 'breadcrumb' => 'Image replacement', 'panel_title' => 'Entry image replacement', 'success' => 'Image replaced.', 'title' => 'Image replacement', ], 'visibility' => [ 'helper' => 'Change the gallery image\'s visibility, controlling who can view it.', 'updated' => 'Image visibility updated.', ], ]; ================================================ FILE: lang/en/entities/inventories.php ================================================ [ 'copy_from_entity' => 'Copy from another entry', 'copy_inventory' => 'Copy inventory', 'generate' => 'Generate', 'multiple' => 'Add items', ], 'copy' => [ 'helper' => 'Copy the whole inventory of an entry to :name.', ], 'create' => [ 'helper' => 'Add an item to :name\'s inventory. It can optionally be linked to an existing object from the campaign.', 'success' => 'Item :item added to :entity.', 'success_bulk' => '{0} No item added to :entity.|{1} Added :count item to :entity.|[2,*] Added :count items to :entity.', 'title' => 'Add to inventory', ], 'default_position' => 'Unorganised', 'destroy' => [ 'success' => 'Item :item removed from :entity.', 'success_position' => 'Items in :position removed from :entity.', ], 'fields' => [ 'amount' => 'Quantity', 'copy_entity_entry_v2' => 'Use object entry', 'description' => 'Description', 'is_equipped' => 'Equipped', 'item_amount' => 'Number of Items', 'match_all' => 'Match all tags', 'name' => 'Name', 'position' => 'Position', 'qty' => 'QTY', 'replace' => 'Replace Inventory', ], 'generate' => [ 'helper' => 'Generate an inventory for :name based on existing items in the campaign.', 'title' => 'Generate inventory', ], 'helpers' => [ 'amount' => 'Number of items', 'copy_entity_entry_v2' => 'Display the object\'s entry instead of the custom description.', 'description' => 'Add a custom description to the item', 'is_equipped' => 'Mark this item as being equipped.', 'name' => 'Give the name to the item. A name is required if no object is selected', 'replace' => 'Replaces current inventory with the generated one', ], 'placeholders' => [ 'amount' => 'Any amount', 'description' => 'Used, Damaged, Attuned', 'name' => 'Sleeping bag', 'position' => 'Select or create a position', ], 'show' => [ 'helper' => 'To create this entry\'s inventory, start by adding an item to it.', 'title' => ':name Inventory', 'unsorted' => 'Unsorted', ], 'togglers' => [ 'hide' => [ 'price' => 'Hide price', 'quantity' => 'Hide quantity', 'size' => 'Hide size', 'weight' => 'Hide weight', ], 'show' => [ 'price' => 'Show price', 'quantity' => 'Show quantity', 'size' => 'Show size', 'weight' => 'Show weight', ], ], 'tooltips' => [ 'equipped' => 'This item is equipped', ], 'tutorials' => [ 'all' => 'Track what :name owns, stores, or offers by adding items to this inventory.', ], 'update' => [ 'success' => 'Item :item updated for :entity.', 'title' => 'Update an item on :name', ], ]; ================================================ FILE: lang/en/entities/links.php ================================================ [ 'add' => 'Add a link', ], 'call-to-action' => 'Add external links to this entry, like a character sheet on D&D Beyond or a relevant wiki page. Linked resources will be shown directly on the entry\'s overview for easy access.', 'create' => [ 'helper' => 'Add an external link to :name, for example to their DnDBeyond page.', 'success' => 'Link :name added to :entity.', 'title' => 'New link', ], 'destroy' => [ 'success' => 'Link :name removed.', ], 'fields' => [ 'icon' => 'Icon', 'name' => 'Name', 'position' => 'Position', 'url' => 'URL', ], 'go' => [ 'actions' => [ 'confirm' => 'I\'m sure', 'trust' => 'Don\'t ask me again', ], 'description' => 'This link will take you to :link. Are you sure you want to go there?', 'title' => 'Leaving Kanka', ], 'helpers' => [ 'icon' => 'Customise the link icon with a special :fontawesome icon, for example :example. Find our more about available icons in our :docs.', 'parent' => 'Display this bookmark after an element of the sidebar, rather than in the bookmark section of the sidebar.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Premium campaigns can add links to entries that point to external websites.', 'title' => 'Links for :name', ], 'update' => [ 'success' => 'Link :name updated for :entity.', 'title' => 'Update link for :name', ], ]; ================================================ FILE: lang/en/entities/logs.php ================================================ [ 'create' => 'Created', 'create_post' => 'Created article', 'delete' => 'Deleted', 'delete_post' => 'Deleted article', 'reorder_post' => 'Reordered articles', 'restore' => 'Restored', 'reveal' => 'Show details', 'update' => 'Updated', 'update_post' => 'Updated article', 'view' => 'View changes', ], 'call-to-action' => 'Premium campaigns can view the full changes made to this entry for up to :amount day.', 'fields' => [ 'action' => 'Action', 'date' => 'Date', ], 'filters' => [ 'keywords' => 'Keywords', ], 'impersonated' => 'Impersonated by :name', 'none' => 'None', 'show' => [ 'title' => ':name - Changes', ], 'tooltips' => [ 'post' => 'Go to the article', ], ]; ================================================ FILE: lang/en/entities/map-points.php ================================================ 'This entry is pinned on the following maps.', 'title' => ':name Map Points', ]; ================================================ FILE: lang/en/entities/mentions.php ================================================ [ 'element' => 'Element', 'type' => 'Type', ], 'helper' => 'This entry is mentioned in the following other entries, articles, or campaign description.', 'mentioned_in_v2' => 'This entry is mentioned in :count elements. :more.', 'see_more' => 'View details', 'show' => [ 'title' => 'Entry :name Mentions', ], 'title' => 'Mentioned entry', ]; ================================================ FILE: lang/en/entities/move.php ================================================ [ 'copy' => 'Copy', 'transfer' => 'Transfer', ], 'errors' => [ 'permission' => 'You aren\'t allowed to create :type entries in :target.', 'permission_update' => 'You aren\'t allowed to transfer this entry.', 'same_campaign' => 'Select another campaign to transfer the entry to.', 'unknown_campaign' => 'Unknown campaign.', ], 'fields' => [ 'campaign' => 'Target campaign', 'copy' => 'Copy option', 'select_one' => 'Select a campaign', ], 'helpers' => [ 'copy' => 'Keep a copy in the current campaign.', ], 'panel' => [ 'description' => 'Transfer this entry to another campaign. You can optionally keep a copy here.', 'description_bulk_copy' => 'Select a campaign you want to copy the selected entries to.', 'title' => 'Transfer an entry to another campaign', ], 'success' => 'Entry :name transferred to :campaign.', 'success_copy' => 'Entry :name copied to the :campaign campaign.', 'title' => 'Transfer :name', 'warnings' => [ 'custom' => 'This entry is not from a default category, but of a custom ":module" category. It will be created as a Note entry in the target campaign.', ], ]; ================================================ FILE: lang/en/entities/notes.php ================================================ [ 'add' => 'New article', 'add_role' => 'Add role', 'add_user' => 'Add user', ], 'collapsed' => [ 'closed' => 'Article is collapsed to just the header', 'open' => 'Article is expanded', ], 'copy_mention' => [ 'copy' => 'Copy advanced mention', 'copy_with_name' => 'Copy advanced mention with article name', 'success' => 'Advanced mention to article copied to the clipboard.', ], 'create' => [ 'success' => 'Article \':name\' added to :entity.', ], 'destroy' => [ 'success' => 'Article :name removed from :entity.', ], 'edit' => [ 'success' => 'Article :name for :entity updated.', ], 'fields' => [ 'creator' => 'Creator', 'display' => 'Display', 'name' => 'Name', 'position' => 'Position', ], 'footer' => [ 'created' => 'Created by :user on :date', 'updated' => 'Updated by :user on :date', ], 'hint' => 'Information that doesn\'t quite fit in the standard fields of an entry or that should be kept private can be added as articles.', 'hints' => [ 'reorder' => 'You can reorder articles of an entry by clicking on the :icon icon in entry\'s header.', ], 'move' => [ 'copy' => 'Keep a copy of the article on the current entry.', 'copy_success' => 'Article :name moved to :entity successfully.', 'copy_title' => 'Keep a copy', 'description' => 'Select an entry to move this article to', 'entity' => 'Target entry', 'move_success' => 'Article :name moved to :entity successfully.', ], 'placeholders' => [ 'name' => 'Name of the article, observation or remark', ], 'show' => [ 'advanced' => 'Advanced Permissions', 'title' => 'Article :name for :entity', ], 'states' => [ 'collapsed' => 'Collapsed', 'expanded' => 'Expanded', ], ]; ================================================ FILE: lang/en/entities/permissions.php ================================================ [ 'text' => 'This entry is set to private. Custom permissions can still be defined, but as long as the entry is private, those will be ignored, and only members of the :admin role will be able to see the entry.', 'warning' => 'Warning', ], 'quick' => [ 'empty-permissions' => 'No role or user outside of the admins have access to this entry.', 'manage' => 'Manage Permissions', 'screen-reader' => 'Open privacy settings', 'success' => [ 'private' => ':entity is now hidden.', 'public' => ':entity is now visible.', ], 'title' => 'Permission overview', 'viewable-by' => 'Viewable by', ], 'toggle' => [ 'label' => 'Entry privacy', 'private' => [ 'description' => 'Only visible to members of :admin role.', 'title' => 'Private', ], 'public' => [ 'description' => 'Visible to the following roles and members.', 'title' => 'Public', ], ], ]; ================================================ FILE: lang/en/entities/pins.php ================================================ 'Links', 'title' => 'Pins', ]; ================================================ FILE: lang/en/entities/profile.php ================================================ [ 'edit_profile' => 'Edit profile', ], 'aliases' => 'Aliases', 'history' => 'History', 'show' => [ 'tab_name' => 'Profile', 'title' => ':name Profile', ], ]; ================================================ FILE: lang/en/entities/quests.php ================================================ 'This entry is part of the following quests.', 'title' => ':name Quests', ]; ================================================ FILE: lang/en/entities/relations.php ================================================ [ 'mode-map' => 'Relations map', 'mode-table' => 'Table of relations and related elements', ], 'bulk' => [ 'delete' => '{0} Deleted :count relations|{1} Deleted :count relation.|[2,*] Deleted :count relations.', 'fields' => [ 'delete_mirrored' => 'Delete mirrored', 'unmirror' => 'Unlink mirrored', 'update_mirrored' => 'Update mirrored', ], 'helpers' => [ 'delete_mirrored' => 'Also delete mirrored relations.', 'unmirror' => 'Unlink mirrored relations.', 'update_mirrored' => 'Update mirrored relations.', ], 'success' => [ 'editing' => '{0} :count relations were updated|{1} :count relation was updated.|[2,*] :count relationss were updated.', 'editing_partial' => '{0} :count/:total relationss were updated|{1} :count/:total relation was updated.|[2,*] :count/:total relations were updated.', ], ], 'call-to-action' => 'Visually explore how this entry connects to others in the campaign. See relationships, mentions, and shared history in a dynamic and interactive map.', 'connections' => [ 'map_point' => 'Map point', 'mention' => 'Mention', 'quest_element' => 'Quest element', 'timeline_element' => 'Timeline element', ], 'create' => [ 'helper' => 'Create a relation between :name and one or several entries.', 'new_title' => 'New relation', 'success_bulk' => '{1} Added :count relation to :entity.|[2,*] Added :count relations to :entity.', ], 'delete_mirrored' => [ 'helper' => 'This relation is mirrored on the target entry. Select this option to also remove the mirrored relation.', 'option' => 'Delete mirrored relation', ], 'destroy' => [ 'mirrored' => 'This will also delete the mirrored relation and is permanent.', 'success' => 'Relation :target removed for :entity.', ], 'fields' => [ 'attitude' => 'Attitude', 'is_pinned' => 'Pinned', 'link' => 'Reciprocal link', 'mirror_relation' => 'Reciprocal role', 'owner' => 'Origin', 'role' => 'Role', 'target' => 'Target', 'targets' => 'Connection to...', 'two_way' => 'Reciprocal', 'unmirror' => 'Untie this relation.', ], 'filters' => [ 'connection' => 'Relation relation', 'name' => 'Relation target', ], 'helper' => 'Set up relations between entries with attitudes and visibility. Relations can also be pinned to the entry\'s menu.', 'helpers' => [ 'description' => 'Detail the nature of the relation between the two entries.', 'link' => 'Create a matching relation on the targets.', 'mirror_relation' => 'How the target sees this entry (leave blank to copy above).', 'no_relations' => 'This entry doesn\'t currently have any relations to other entries of the campaign.', ], 'hints' => [ 'attitude' => 'This optional field can be used to define the default order relations appear in by descending order.', 'two_way' => 'Create a mirrored relation on the targets. Updating a mirrored relation doesn\'t update the original relation.', ], 'index' => [ 'title' => 'Relations', ], 'linked' => [ 'break' => 'Break link', 'helper' => 'This relation is synced with :link', 'label' => 'Linked relation', 'unmirror-helper' => 'Converting this to a standalone relation will not delete anything.', ], 'options' => [ 'mentions' => 'Default + related + mentions', 'only_relations' => 'Only direct relations', 'related' => 'Default + related', 'relations' => 'Default', 'show' => 'Show', ], 'panels' => [ 'related' => 'Related', ], 'placeholders' => [ 'attitude' => '-100 to 100, 100 being very positive', 'role' => 'Rival, Best Friend, Sibling', ], 'show' => [ 'title' => ':name relations', ], 'types' => [ 'family_member' => 'Family member', 'organisation_member' => 'Organisation Member', ], 'update' => [ 'success' => 'Relation :target updated for :entity.', 'title' => 'Update relations between :source and :target', ], ]; ================================================ FILE: lang/en/entities/reminders.php ================================================ [ 'add' => 'Link to a calendar', 'remove' => 'Remove calendar date', ], 'helpers' => [ 'pitch' => 'Leave the real-world calendar behind and link this entry to a calendar from your world to stay immersed in your fictional timeline.', ], ]; ================================================ FILE: lang/en/entities/share.php ================================================ [ 'copy' => 'Copy link', 'make_public' => 'Make campaign public', ], 'fields' => [ 'campaign_access' => 'Campaign settings', 'visibility_mode' => 'Fix visibility', ], 'helpers' => [ 'campaign_access' => 'To share this with the public, the campaign itself must be made public first.', 'entity_permissions_warning' => 'Making this campaign public lets anyone view it. Entries marked as private stay hidden.', 'hidden_explanation' => 'The campaign is public, but this entry is currently hidden from non-members.', 'hidden_unlisted_explanation' => 'The campaign is unlisted, only people with the link can find it.', 'member-link' => 'Share this with members only', 'private_explanation' => 'Only members can can access this entry.', 'public_explanation' => 'Both the campaign and this entry are public. Anyone with the link can view it.', 'unlisted_explanation' => 'The campaign is unlisted and this entry is visible. Anyone with the link can view it.', ], 'labels' => [ 'member_link' => 'Member-only link', 'public_link' => 'Public link', 'share_link' => 'Share link', ], 'options' => [ 'keep_private' => 'Keep campaign private', 'make_all_public' => 'Show all :module to non-members', 'make_campaign_public' => 'Make campaign public', 'make_entity_public' => 'Show :name to non-members', ], 'status' => [ 'hidden' => 'Not visible to non-members', 'private' => 'This campaign is private', 'public' => 'Visible to non-members', 'unlisted' => 'Visible to anyone with the link', ], 'success' => [ 'copied' => 'Link copied to clipboard!', 'copied_members' => 'Member-only link copied.', 'copied_public' => 'Public link copied, anyone with the link can view the entry.', 'updated' => 'Visibility settings updated successfully.', ], 'title' => 'Share entry', ]; ================================================ FILE: lang/en/entities/statuses.php ================================================ [ 'alive' => 'Alive', 'dead' => 'Dead', 'missing' => 'Missing', ], 'creature' => [ 'dead' => 'Dead', 'extinct' => 'Extinct', ], 'family' => [ 'extinct' => 'Extinct', ], 'item' => [ 'destroyed' => 'Destroyed', 'lost' => 'Lost', 'owned' => 'Owned', ], 'location' => [ 'destroyed' => 'Destroyed', ], 'organisation' => [ 'defunct' => 'Defunct', ], 'quest' => [ 'abandoned' => 'Abandoned', 'completed' => 'Completed', 'not_started' => 'Not started', 'ongoing' => 'Ongoing', ], 'race' => [ 'extinct' => 'Extinct', ], 'remove' => 'Remove', ]; ================================================ FILE: lang/en/entities/story.php ================================================ [ 'collapse_all' => 'Collapse all', 'expand_all' => 'Expand all', 'load_more' => 'Load more', 'login_for_more' => 'Login to view more articles', ], 'reorder' => [ 'helper' => 'Drag and drop articles to reorder them on the entry\'s overview page.', 'icon_tooltip' => 'Reorder articles', 'panel_title' => 'Reorder articles', 'save' => 'Save new order', 'success' => 'Articles reordered.', ], 'update' => [ 'title' => 'Update :entity entry', ], ]; ================================================ FILE: lang/en/entities/tags.php ================================================ [ 'helper' => 'Add or remove tags on :name.', 'title' => 'Add or remove tags', ], ]; ================================================ FILE: lang/en/entities/timelines.php ================================================ 'Timelines that have elements linked to this entry are shown below.', 'show' => [ 'title' => ':name Timelines', ], ]; ================================================ FILE: lang/en/entities/tooltips.php ================================================ ':name has no content yet.', 'fix' => 'Add a description', 'formatting' => 'Supported HTML: text (:text), structure (:layout), lists/tables, and images.', 'helper' => 'Override the default auto-generated preview text shown when hovering over links to this entry.', 'label' => 'Teaser', 'placeholder' => 'Briefly describe this entry in a sentence or two.', 'premium' => 'Unlock the ability to customise the hover teaser with a :boosted-campaign.', ]; ================================================ FILE: lang/en/entities/transform.php ================================================ [ 'convert' => 'Convert to category', ], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Unknown or invalid category.', ], 'success' => '{1} :count entry transformed to new category :type.|[2,*] :count entries transformed to new category :type.', ], 'confirm' => [ 'checkbox' => 'I understand that by transforming :entity to another category, the following elements will be lost:', 'label' => 'Confirm data loss', ], 'documentation' => 'Documentation: Converting entry categories', 'fields' => [ 'current' => 'Current category', 'select_one' => 'Select new category', 'target' => 'New category', ], 'panel' => [ 'bulk_description' => 'Convert the category of multiple entries. Please be aware that some data might be lost due to the different fields between categories.', 'bulk_title' => 'Change categories of multiple entries', 'title' => 'You can convert this entry to another category.', 'warning' => 'Some data may not carry over if the new category uses different fields.', ], 'success' => 'Entry :name transformed.', 'title' => 'Convert :name', ]; ================================================ FILE: lang/en/entities.php ================================================ 'Abilities', 'ability' => 'Ability', 'archetype' => 'Archetype', 'archetypes' => 'Archetypes', 'article' => 'Article', 'articles' => 'Articles', 'attribute_template' => 'Property kit', 'attribute_templates' => 'Property kits', 'bookmark' => 'Bookmark', 'bookmarks' => 'Bookmarks', 'calendar' => 'Calendar', 'calendars' => 'Calendars', 'campaign' => 'Campaign', 'campaigns' => 'Campaigns', 'character' => 'Character', 'characters' => 'Characters', 'conversation' => 'Conversation', 'conversations' => 'Conversations', 'creator' => [ 'actions' => [ 'create' => 'Create :type', 'full' => 'Go to the full form', 'more' => 'Add more details', ], 'back' => 'Back to selection', 'bulk_names' => 'Add one name per line', 'duplicate' => 'Heads up! There are other entries in this category with a similar name.', 'helper_v2' => 'Quickly create the foundation of a new entry without interrupting your current flow.', 'helpers' => [ 'archetype' => 'Select an archetype the new entries will be copies of', ], 'missing_v2' => 'Only categories that are enabled and that you have permission to create are available in this interface. :learn-more.', 'modes' => [ 'archetypes' => 'Archetype selection', 'bulk' => 'Bulk add', 'default' => 'Quick add', ], 'name' => [ 'new' => 'New name', 'remove' => 'Remove', ], 'success_multiple' => '{1} New entry :link created.|[2,*] New entries :link created.', 'success_multiple_posts' => '{1} New article :link created.|[2,*] New articles :link created.', 'title' => 'New Entry', 'titles' => [ 'everything' => 'Everything', 'quick-access' => 'Quick access', ], 'tooltip' => 'Create a new entry without leaving the current page.', 'tooltips' => [ 'create' => 'Create the entry and go back to the entry selection screen', 'create_more' => 'Create the entry and start creating another one of the same type', 'edit' => 'Create the entry and start editing it', ], ], 'creature' => 'Creature', 'creatures' => 'Creatures', 'dice_roll' => 'Dice Roll', 'dice_rolls' => 'Dice Rolls', 'entries' => 'Entries', 'entry' => 'Entry', 'event' => 'Event', 'events' => 'Events', 'families' => 'Families', 'family' => 'Family', 'inventories' => 'Inventories', 'item' => 'Object', 'items' => 'Objects', 'journal' => 'Journal', 'journals' => 'Journals', 'location' => 'Location', 'locations' => 'Locations', 'map' => 'Map', 'maps' => 'Maps', 'media' => 'Media', 'note' => 'Note', 'notes' => 'Notes', 'organisation' => 'Organisation', 'organisations' => 'Organisations', 'properties' => 'Properties', 'quest' => 'Quest', 'quest_element' => 'Quest element', 'quests' => 'Quests', 'race' => 'Race', 'races' => 'Races', 'relation' => 'Relation', 'relations' => 'Relations', 'reminders' => 'Reminders', 'status' => 'Status', 'tag' => 'Tag', 'tags' => 'Tags', 'templates' => 'Templates', 'timeline' => 'Timeline', 'timeline_element' => 'Timeline element', 'timelines' => 'Timelines', 'whiteboard' => 'Whiteboard', 'whiteboards' => 'Whiteboards', ]; ================================================ FILE: lang/en/entries/archetypes.php ================================================ [ 'how' => 'How to define archetypes', ], 'success' => [ 'set' => ':name set as an archetype.', 'unset' => ':name no longer set as an archetype.', ], ]; ================================================ FILE: lang/en/entries/archive.php ================================================ 'This entry is archived and no longer shows up in lists or searches.', ]; ================================================ FILE: lang/en/entries/bulk.php ================================================ [ 'copy_to_campaign' => '{1} :count entry copied to :campaign.|[2,*] :count entries copied to :campaign.', 'delete' => '{1} Deleted :count entry.|[2,*]Deleted :count entries.', 'editing' => '{1} :count entry was updated.|[2,*] :count entries were updated.', 'editing_partial' => '{1} :count/:total entry was updated.|[2,*] :count/:total entries were updated.', 'permissions' => '{1} Permissions changed for :count entry.|[2,*] Permissions changed for :count entries.', 'private' => '{1} :count entry is now private.|[2,*] :count entries are now private.', 'public' => '{1} :count entry is now visible.|[2,*] :count entries are now visible.', 'templates' => '{1} :count entry had a set applied.|[2,*] :count entries has a set applied.', ], ]; ================================================ FILE: lang/en/entries/fields.php ================================================ [ 'placeholder' => 'Name of the entry', ], 'type' => [ 'placeholder' => 'Type of the entry', ], ]; ================================================ FILE: lang/en/entries/tabs.php ================================================ 'Aliases', 'identity' => 'Identity', 'media' => 'Media', 'properties' => 'Properties', 'relations' => 'Relations', ]; ================================================ FILE: lang/en/errors.php ================================================ [ 'body' => 'It looks like you don\'t have permission to access this page!', 'title' => 'Permission Denied', ], '403-form' => [ 'help' => 'This might be due to your session timing out. Please try logging in again in another window before saving.', ], '404' => [ 'body' => 'Sorry, the page you are looking for could not be found.', 'title' => 'Page Not Found', ], '500' => [ 'body' => [ '1' => 'Whoops, looks like something went wrong.', '2' => 'A report with the encountered error was sent to us, but sometimes it helps if we can know a little bit more about what you were doing.', ], 'title' => 'Error', ], '503' => [ 'body' => [ '1' => 'Kanka is currently under maintenance, which usually means an update is underway!', '2' => 'Sorry for the inconvenience. Everything will return to normal in just a few minutes.', ], 'json' => 'Kanka is currently under maintenance, please try again in a few minutes.', 'title' => 'Maintenance', ], 'back-to-campaigns' => 'Go back to one of your campaigns', 'footer' => 'If you need further assistance, please contact us at :email or on the :discord', 'log-in' => 'Logging in to your account might reveal what you are looking for.', 'post_layout' => 'Invalid article layout.', 'private-campaign' => [ 'auth' => [ 'helper' => 'You don\'t have access to this campaign.', ], 'guest' => [ 'helper' => 'The campaign you are trying to access is private and you are not logged in.', 'login' => 'Logging in might let you access the contents.', ], 'title' => 'Private campaign', ], ]; ================================================ FILE: lang/en/events.php ================================================ [ 'title' => 'New Event', ], 'events' => [ 'helper' => 'Events that have this entry as their parent event are displayed here.', ], 'fields' => [ 'date' => 'Date', ], 'helpers' => [ 'date' => 'This field can contain anything and is not linked to your calendars. To link this event to a calendar, go add it on the calendar or on the reminders subpage of this event.', ], 'lists' => [ 'empty' => 'Add significant moments such as battles, coronations, or discoveries to your world\'s history.', ], 'placeholders' => [ 'date' => 'A date for the event', 'type' => 'Ceremony, Festival, Disaster, Battle, Birth', ], 'tabs' => [ 'calendars' => 'Calendar Entries', ], ]; ================================================ FILE: lang/en/export.php ================================================ 'Content', 'hidden_campaign' => 'Hidden Campaign', 'index' => 'Entry Index', ]; ================================================ FILE: lang/en/families/trees.php ================================================ [ 'clear' => 'Erase all', 'first' => 'Add a founder', 'founder' => 'Add a new founder', 'rename-relation' => 'Rename relation', 'reset' => 'Discard changes', 'save' => 'Save', ], 'modal' => [ 'first-title' => 'Select an entry', 'helper' => 'Replace the entry with another from the campaign', 'relation' => 'Relation', 'title' => 'Replace entry', ], 'modals' => [ 'clear' => [ 'confirm' => 'Are you sure you want to reinitialise all the data from the family tree?', ], 'entity' => [ 'add' => [ 'founder' => 'Founder', 'member' => 'Member', 'success' => 'Entry added.', 'title' => 'Add an entry', ], 'child' => [ 'success' => 'Child added.', 'title' => 'Add a child', ], 'edit' => [ 'helper' => 'Check this option if the relation is unknown. A character can be added later.', 'success' => 'Entry updated.', 'title' => 'Update an entry', ], 'founder' => [ 'title' => 'Add a new founder', ], 'remove' => [ 'confirm' => 'Are you sure you want to remove this entry from the family tree?', 'success' => 'Entry removed.', ], ], 'relations' => [ 'add' => [ 'success' => 'Relation added.', 'title' => 'Add a relation', ], 'edit' => [ 'success' => 'Relation updated.', 'title' => 'Update a relation', ], 'unknown' => 'Unknown', ], 'reset' => [ 'confirm' => 'Are you sure you want to discard any changes made to the family tree?', ], ], 'pitch' => 'Build a detailed family tree to visualize relationships and lineage within your world\'s families.', 'success' => [ 'cleared' => 'Family tree erased.', 'reseted' => 'Family tree has been reset.', 'saved' => 'Family tree saved.', ], 'title' => ':name Family Tree', 'unknown' => 'unestablished', ]; ================================================ FILE: lang/en/families.php ================================================ [ 'title' => 'New Family', ], 'hints' => [ 'is_extinct' => 'This family is extinct.', 'members' => 'Members of a family are listed here. A character can be added to a family by editing the desired character and using the "Family" dropdown.', ], 'lists' => [ 'empty' => 'Track lineages, clans, or noble houses that connect your characters together.', ], 'members' => [ 'create' => [ 'helper' => 'Add one or several members to :name.', 'success' => '{0} No member was added.|{1} 1 member was added.|[2,*] :count members were added.', 'title' => 'New Members', ], ], 'placeholders' => [ 'name' => 'Name of the family', 'type' => 'Royal, Noble, Extinct', ], 'show' => [ 'tabs' => [ 'tree' => 'Family tree', ], ], ]; ================================================ FILE: lang/en/fields.php ================================================ [ 'label' => 'Description', ], 'entry' => [ 'label' => 'Entry', ], 'gallery' => [ 'placeholder' => 'Choose an image from the gallery', ], 'gallery-header' => [ 'description' => 'If the entry has no header image, display an image from the gallery instead.', ], 'gallery-image' => [ 'description' => 'If the entry has no image, display an image from the gallery instead.', ], 'header-image' => [ 'boosted-description' => 'Display a background image in the entry\'s header with a :boosted-campaign.', 'description' => 'Display a background image in the entry\'s header. For best results, use an image which is very large.', 'title' => 'Header Image', ], ]; ================================================ FILE: lang/en/filters.php ================================================ [ 'bookmark' => 'Bookmark this view', ], 'alerts' => [ 'copy' => 'The filters have been copied to your clipboard.', ], 'bookmark' => [ 'helper' => 'Create a new bookmark to this view using the current filters.', 'name' => ':module (filtered)', 'premium' => 'Adding more bookmarks requires enabling premium campaign features.', 'success' => 'Bookmark created.', ], 'helpers' => [ 'guest' => 'Please log into your account to filter results.', 'icon' => 'Give this bookmark a special :fontawesome icon, for example :example.', 'icon-premium' => 'Give this bookmark a special :fontawesome icon, like :example, with a :premium.', ], ]; ================================================ FILE: lang/en/footer.php ================================================ 'About us', 'blog' => 'Blog', 'boosters' => 'Boosters', 'community' => 'Community', 'company' => 'Company', 'contact' => 'Contact', 'copyright' => 'Copyright :copy :year Owlchester SNC', 'documentation' => 'Documentation', 'features' => 'Features', 'kb' => 'Knowledge base', 'language-switcher' => [ 'other' => 'Other languages', 'title' => 'Select your language', ], 'made' => 'Made with ❤️ in Geneva, Switzerland', 'newsletter' => 'Newsletter', 'platform' => 'Platform', 'plugins' => 'Plugins Library', 'premium' => 'Premium campaigns', 'press-kit' => 'Press kit', 'pricing' => 'Pricing', 'privacy' => 'Privacy', 'public-campaigns' => 'Public campaigns', 'resources' => 'Resources', 'roadmap' => 'Roadmap', 'security' => 'Security', 'server-time' => 'This is our server (:server)\'s time', 'showcase' => 'Showcase', 'status' => 'Service status', 'terms' => 'Terms', 'thanks' => 'Only possible thanks to our subscribers.', 'translator_call' => 'Kanka is translated in other languages thanks to our amazing community. If you want to help translate Kanka into your language, contact us over on our :discord!', 'whats-new' => 'What\'s new', ]; ================================================ FILE: lang/en/front/community-votes.php ================================================ 'Community Votes', ]; ================================================ FILE: lang/en/front/hall-of-fame.php ================================================ 'Hall of Fame', ]; ================================================ FILE: lang/en/front/kb.php ================================================ 'Knowledge base', ]; ================================================ FILE: lang/en/front/newsletter.php ================================================ [ 'learn_more' => 'Learn more', 'subscribe' => 'Subscribe', ], 'fields' => [ 'firstname' => 'First Name', 'lastname' => 'Last Name', 'notifications' => 'Notifications', ], 'groups' => [ 'all' => 'Receive occasional updates about updates, promotions, and events.', 'newsletter' => 'Newsletter', ], 'headline' => 'Subscribe to one or all of our newsletters to stay up to date with :kanka.', 'title' => 'Email Updates', ]; ================================================ FILE: lang/en/front.php ================================================ [ 'public' => [ 'filters' => [ 'is-premium' => 'This is a premium campaign!', ], ], ], 'cookie' => [ 'dismiss' => 'Got it!', 'link' => 'Learn more', 'message' => 'This website uses cookies to ensure you get the best experience on our website.', ], 'features' => [ 'api' => [ 'link' => 'API docs', ], 'patreon' => [ 'api_calls' => 'Increased API calls (90 per minute)', 'boosts' => 'Campaign Boosters', 'default_image' => 'Nicer default images for entries in lists', 'discord' => 'Private :discord channel', 'free' => 'Free', 'hall_of_fame' => 'Name in the :link', 'impact' => 'High impact of future features (through Discord)', 'monthly_vote' => 'Participate in community votes', 'pagination' => 'Increased pagination results', 'upload_limit' => 'Upload sizes', 'upload_limit_map' => 'Map upload sizes', ], ], 'home' => [ 'seo' => [ 'meta-description' => 'Are you a game master, worldbuilder, or a storyteller? We offer a tabletop campaign manager and worldbuilding tool that makes it easy to organise, plan, and enjoy your TTRPG campaigns. We are community driven, and best of all, our core features are free!', ], ], 'menu' => [ 'dashboard' => 'Dashboard', 'login' => 'Login', 'register' => 'Register', 'register_free' => 'Register for free', ], 'meta' => [ 'description' => ':kanka is a flexible digital world builder and online tabletop rpg campaign manager', 'title' => ':kanka - Online tabletop RPG campaign manager and worldbuilding tool', ], 'pricing' => [ 'tier' => [ 'free' => 'Free', 'month' => 'month', ], ], 'seo' => [ 'keywords' => 'Worldbuilding, Tabletop RPG, RPG Campaign Manager', ], ]; ================================================ FILE: lang/en/gallery.php ================================================ [ 'gallery' => 'From gallery', 'url' => 'Upload an image from a URL', ], 'browse' => [ 'layouts' => [ 'large' => 'Large previews', 'small' => 'Small previews', ], 'search' => [ 'placeholder' => 'Search for an image in the gallery', ], 'title' => 'Gallery', 'unauthorized' => 'None of your roles have the "browse gallery" permission.', ], 'cta' => [ 'action' => 'Unlock more storage space', 'helper' => 'Unlock up to :size GiB storage space with a :premium-campaign.', 'title' => 'Storage full', ], 'delete' => [ 'success' => '[0] Deleted 0 elements|[1] Deleted one element|{2,*} Deleted :count elements', ], 'download' => [ 'errors' => [ 'copy_failed' => 'Our servers couldn\'t download the given image.', 'gallery_full_free' => 'The gallery storage space is full. Enable premium features for more storage.', 'gallery_full_premium' => 'The gallery storage space is full. Remove unused files first.', 'invalid_format' => 'The file isn\'t a valid file format.', 'invalid_url' => 'The provided URL could not be decoded as an image.', 'too_big' => 'The file is too large (:size MiB vs :max MiB)', 'unauthorized' => 'None of your roles have the "upload to gallery" permission.', ], ], 'file' => [ 'saved' => 'Saved', ], 'filters' => [ 'only_unused' => 'Only show unused files', 'sort' => 'Sort by', ], 'move' => [ 'success' => '[0] Moved 0 elements|[1] Moved one element|{2,*} Moved :count elements', ], 'update' => [ 'home' => 'Home folder', 'success' => '[0] Updated 0 elements|[1] Updated one element|{2,*} Updated :count elements', ], ]; ================================================ FILE: lang/en/general.php ================================================ 'Deselect All', 'documentation' => 'Learn more about this feature in our documentation', 'done' => 'Done', 'learn-more' => 'Learn more', 'no' => 'No', 'required' => 'Required', 'select_all' => 'Select All', 'success' => [ 'created' => ':name created.', 'deleted' => ':name removed.', 'deleted-cancel' => ':name removed. :cancel.', 'updated' => ':name updated.', ], 'tutorial' => 'Watch tutorial', 'yes' => 'Yes', ]; ================================================ FILE: lang/en/genres.php ================================================ 'Alternate history', 'cyberpunk' => 'Cyberpunk', 'fantasy' => 'Fantasy', 'historical' => 'Historical', 'many_worlds' => 'Many worlds', 'modern' => 'Modern', 'occult' => 'Occult', 'post_apocalyptic' => 'Post-apocalyptic', 'pulp' => 'Pulp', 'science_fantasy' => 'Science fantasy', 'science_fiction' => 'Science fiction', 'space_opera' => 'Space opera', 'steampunk' => 'Steampunk', 'superhero' => 'Superhero', 'urban_fantasy' => 'Urban fantasy', 'western' => 'Western', ]; ================================================ FILE: lang/en/header.php ================================================ [ 'title' => 'Kanka news', ], 'notifications' => [ 'dismiss' => 'Dismiss', 'no-unread' => 'No unread notifications', 'read_all' => 'View all', ], 'qq' => [ 'tooltip' => 'Create an entry or post', ], 'toggle_navigation' => 'Toggle navigation', 'user' => [ 'settings' => 'Account settings', 'sign-out' => 'Sign out', 'upgrade' => 'Upgrade', 'your-profile' => 'Your profile', ], ]; ================================================ FILE: lang/en/helpers.php ================================================ [ 'description' => 'The following filters are available for the :name API endpoint.', 'title' => 'API Filters', ], 'attributes' => [ 'link' => 'Property options', ], 'calendar-widget' => [ 'info' => 'Why are these reminders being shown?', 'title' => 'Calendar Widget', ], 'filters' => [ 'title' => 'How to use filters', ], 'link' => [ 'description' => 'You can easily link to other entries in the campaign using the following shorthands.', ], 'public' => 'Watch a tutorial video on Youtube explaining public campaigns.', 'troubleshooting' => [ 'description' => 'A member of Kanka\'s team has sent you to this page. Select a campaign from the dropdown to generate a token so we can temporarily join your campaign as an admin.', 'errors' => [ 'token_exists' => 'A token already exists for :campaign.', ], 'save_btn' => 'Generate token', 'select_campaign' => 'Select a campaign', 'subtitle' => 'Please send help!', 'success' => 'Please copy the following token and send it to someone on Kanka\'s team.', 'title' => 'Assistance', ], 'widget-filters' => [ 'description' => 'You can filter entries displayed on the recently modified widget by providing a list of fields of the entry and values. For example, you can use :example to filter on dead characters of the NPC type.', 'link' => 'widget filters', 'title' => 'Dashboard Widget Filters', ], ]; ================================================ FILE: lang/en/history.php ================================================ [ 'show-old' => 'Changes', ], 'cta' => 'Keep track of everything that\'s changed in the campaign with a detailed activity log of recent edits, additions, and updates.', 'empty' => 'No value', 'fields' => [ 'action' => 'Action', 'category' => 'Category', 'details' => 'Details', 'when' => 'When', 'who' => 'Who', ], 'filters' => [ 'all-actions' => 'All actions', 'all-users' => 'All members', 'no-results' => 'No results to display. Try with other filters, or come back after making changes to your entries.', ], 'helpers' => [ 'base' => 'This interface contains recent changes to entries of the campaign for up to :amount days, showing the most recent changes first.', 'changes' => 'The following fields previously had these values.', ], 'log' => [ 'create' => ':user created :entity', 'create_post' => ':user created a article on :entity', 'delete' => ':user deleted :entity', 'delete_post' => ':user deleted a article on :entity', 'reorder_post' => ':user reordered the articles of :entity', 'restore' => ':user restored :entity', 'update' => ':user updated :entity', 'update_post' => ':user updated a post on :entity', 'update_tree' => ':user updated the family tree of :entity', ], 'title' => 'History', 'unknown' => [ 'entity' => 'an unknown entry', ], ]; ================================================ FILE: lang/en/items.php ================================================ [ 'creators' => [ 'action' => 'Action for creators', 'remove' => 'Remove all creators', ], ], 'create' => [ 'title' => 'New Object', ], 'fields' => [ 'creators' => 'Creators', 'is_equipped' => 'Equipped', 'price' => 'Price', 'size' => 'Size', 'weight' => 'Weight', ], 'lists' => [ 'empty' => 'Add weapons, artifacts, or items of importance to your world.', ], 'placeholders' => [ 'price' => 'Price of the object', 'size' => 'Size, Dimensions, Capacity', 'type' => 'Weapon, Potion, Artefact', 'weight'=> 'Weight of the object', ], 'show' => [ 'tabs' => [ 'inventories' => 'Inventories', ], ], ]; ================================================ FILE: lang/en/journals.php ================================================ [ 'title' => 'New Journal', ], 'fields' => [ 'author' => 'Author', 'date' => 'Date', ], 'lists' => [ 'empty' => 'Create journal entries to track adventures, character thoughts, or session summaries and session prep.', ], 'placeholders' => [ 'author' => 'Who wrote the journal', 'date' => 'Real world date of the journal', 'type' => 'Session, One Shot, Draft', ], ]; ================================================ FILE: lang/en/languages.php ================================================ [ 'ca' => 'Catalan', 'cs' => 'Czech', 'de' => 'German', 'el' => 'Greek', 'en' => 'English', 'en-US' => 'American English', 'es' => 'Spanish', 'fr' => 'French', 'gl' => 'Galician', 'he' => 'Hebrew', 'hr' => 'Croatian', 'hu' => 'Hungarian', 'it' => 'Italian', 'nb' => 'Norwegian (Bokmal)', 'nl' => 'Dutch', 'pl' => 'Polish', 'pt-BR' => 'Brazilian Portuguese', 'ru' => 'Russian', 'sk' => 'Slovak', 'tr' => 'Turkish', ], 'header'=> 'Languages', ]; ================================================ FILE: lang/en/lists.php ================================================ [ 'learn' => 'Learn about this category', 'public'=> 'See how others do it', ], 'empty' => [ 'title' => 'No :plural yet.', ], ]; ================================================ FILE: lang/en/locations.php ================================================ [ 'title' => 'New Location', ], 'fields' => [ 'is_destroyed' => 'Destroyed', 'title' => 'Title', ], 'helpers' => [ 'characters' => 'View all characters in this location and its children locations, or just those directly located here.', ], 'hints' => [ 'is_destroyed' => 'This location is destroyed.', ], 'lists' => [ 'empty' => 'Add your first city, tavern, or hidden ruin to anchor your world.', ], 'placeholders' => [ 'title' => 'Title', 'type' => 'City, Kingdom, Ruin', ], ]; ================================================ FILE: lang/en/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Enter edit mode', 'exit-edit-mode' => 'Exit edit mode', 'finish-drawing' => 'Finish drawing polygon', ], 'notifications' => [ 'start-drawing' => 'Click on the map to start drawing a polygon', ], 'toggle' => 'Open/close all groups', ]; ================================================ FILE: lang/en/maps/groups.php ================================================ [ 'add' => 'Add a new group', ], 'bulks' => [ 'delete' => '{1} Removed :count group.|[2,*] Removed :count groups.', 'patch' => '{1} Updated :count group.|[2,*] Updated :count groups.', ], 'create' => [ 'helper' => 'Add a new group to :name. Markers can then be assigned to this group.', 'success' => 'Group :name created.', 'title' => 'New Group', ], 'delete' => [ 'success' => 'Group :name deleted.', ], 'edit' => [ 'success' => 'Group :name updated.', 'title' => 'Edit Group', ], 'fields' => [ 'is_shown' => 'Show group markers', 'parent' => 'Parent Group', 'position' => 'Position', ], 'helper' => [ 'amount_v3' => 'Markers can be grouped together using map groups. Each group can then be clicked when exploring a map to quickly show or hide all markers in it.', ], 'hints' => [ 'is_shown' => 'Show markers in this group by default on the map.', ], 'index' => [ 'title' => 'Groups of :name', ], 'pitch' => [ 'max' => [ 'helper' => 'You can\'t add any more groups unless you remove an existing one.', 'limit' => 'This map has reached its group limit', ], 'upgrade' => [ 'limit' => 'You\'ve reached the limit of :limit groups for this map', 'upgrade' => 'Upgrade to a premium campaign to add up to :limit groups and unlock even more creative flexibility.', ], ], 'placeholders' => [ 'name' => 'Shops, Treasure, NPCs', 'position' => 'First', 'position_list' => 'After :name', ], 'reorder' => [ 'save' => 'Save new order', 'success' => '{1} Reordered :count group.|[2,*] Reordered :count groups.', 'title' => 'Reorder groups', ], ]; ================================================ FILE: lang/en/maps/layers.php ================================================ [ 'add' => 'Add a new layer', ], 'base' => 'Base Layer', 'bulks' => [ 'delete' => '{1} Removed :count layer.|[2,*] Removed :count layers.', 'patch' => '{1} Updated :count layer.|[2,*] Updated :count layers.', ], 'create' => [ 'success' => 'Layer :name created.', 'title' => 'New Layer', ], 'delete' => [ 'success' => 'Layer :name deleted.', ], 'edit' => [ 'success' => 'Layer :name updated.', 'title' => 'Edit Layer :name', ], 'fields' => [ 'position' => 'Position', 'type' => 'Layer type', ], 'helper' => [ 'amount_v2' => 'Upload layers to a map to switch the background image displayed below the markers, or as overlays above the map but beneath the markers.', 'is_real' => 'Layers aren\'t available when using OpenStreetMaps.', ], 'index' => [ 'title' => 'Layers of :name', ], 'pitch' => [ 'max' => [ 'helper' => 'You can\'t add any more layers unless you remove an existing one.', 'limit' => 'This map has reached its layer limit', ], 'upgrade' => [ 'limit' => 'You\'ve reached the limit of :limit layers for this map', 'upgrade' => 'Upgrade to a premium campaign to add up to :limit layers and unlock even more creative flexibility.', ], ], 'placeholders' => [ 'name' => 'Underground, Level 2, Shipwreck', 'position' => 'First', 'position_list' => 'After :name', ], 'reorder' => [ 'save' => 'Save new order', 'success' => '{1} Reordered :count layer.|[2,*] Reordered :count layers.', 'title' => 'Reorder layers', ], 'short_types' => [ 'overlay' => 'Overlay', 'overlay_shown' => 'Overlay (auto show)', 'standard' => 'Standard', ], 'types' => [ 'overlay' => 'Overlay (displayed above the active layer)', 'overlay_shown' => 'Overlay shown by default', 'standard' => 'Standard layer (toggle between layers)', ], ]; ================================================ FILE: lang/en/maps/markers.php ================================================ [ 'entry' => 'Write a custom description for this marker.', 'remove' => 'Remove marker', 'reset-polygon' => 'Reset positions', 'save_and_explore' => 'Save and Explore', 'start-drawing' => 'Start drawing', 'update' => 'Edit marker', ], 'bulks' => [ 'delete' => '{1} Removed :count marker.|[2,*] Removed :count markers.', 'patch' => '{1} Updated :count marker.|[2,*] Updated :count markers.', ], 'circle_sizes' => [ 'custom' => 'Custom', 'huge' => 'Huge', 'large' => 'Large', 'small' => 'Small', 'standard' => 'Standard', 'tiny' => 'Tiny', ], 'create' => [ 'success' => 'Marker :name created.', 'title' => 'New Marker', ], 'delete' => [ 'success' => 'Marker :name deleted.', ], 'details' => [ 'from-entity' => 'From entry', ], 'edit' => [ 'success' => 'Marker :name updated.', 'title' => 'Edit Marker :name', ], 'fields' => [ 'bg_colour' => 'Background colour', 'circle_radius' => 'Circle radius', 'copy_elements' => 'Copy elements', 'custom_icon' => 'Custom icon', 'custom_shape' => 'Polygon shape', 'font_colour' => 'Icon colour', 'group' => 'Marker group', 'has_tooltip' => 'Has tooltip popup', 'icon' => 'Icon', 'is_draggable' => 'Draggable', 'latitude' => 'Latitude', 'longitude' => 'Longitude', 'opacity' => 'Opacity', 'pin_size' => 'Pin Size', 'polygon_style' => [ 'stroke' => 'Stroke colour', 'stroke-opacity' => 'Stroke opacity', 'stroke-width' => 'Stroke width', ], 'popupless' => 'Tooltip popup', 'size' => 'Size', ], 'helpers' => [ 'base' => 'Add markers to the map by clicking on any spot.', 'copy_elements' => 'Copy groups, layers, and markers.', 'copy_elements_to_campaign' => 'Copy groups, layers, and markers of the maps. Markers linked to an entry will be converted to a standard marker.', 'css' => 'Define a custom CSS class added to the marker.', 'custom_icon_v2' => 'Use icons from :fontawesome, :rpgawesome, or a custom SVG icon. Find out how in the :docs.', 'custom_radius' => 'Select the custom size option from the dropdown to define a size.', 'draggable' => 'This marker can be moved on the map\'s exploration page.', 'is_popupless' => 'Disable the marker\'s tooltip showing up on mouse hover.', 'label' => 'A label is displayed as a block of text on the map. The content will be the marker\'s name or the entry\'s name.', 'polygon' => [ 'edit' => 'Edit the polygon by dragging its edges and nodes.', ], ], 'hints' => [ 'entry' => 'Edit the marker to write a custom description for it.', ], 'icons' => [ 'custom' => 'Custom icon', 'entity' => 'Entry\'s picture', 'exclamation' => 'Exclamation icon', 'marker' => 'Marker icon', 'question' => 'Question icon', ], 'index' => [ 'title' => 'Markers of :name', ], 'pitches' => [ 'poly' => 'Use polygons to outline borders, territories, or uneven regions on the map. Available as part of premium campaign features.', ], 'placeholders' => [ 'custom_icon' => 'Try :example1 or :example2', 'custom_shape' => '100,100 200,240 340,110', 'name' => 'Required if no entry selected', ], 'presets' => [ 'helper' => 'Click on a preset to load it, or create a new one.', ], 'shapes' => [ '0' => 'Circle', '1' => 'Square', '2' => 'Triangle', '3' => 'Custom', ], 'sizes' => [ '0' => 'Tiny', '1' => 'Standard', '2' => 'Small', '3' => 'Large', '4' => 'Huge', ], 'tabs' => [ 'circle' => 'Circle', 'label' => 'Label', 'marker' => 'Marker', 'polygon' => 'Polygon', 'preset' => 'Preset', ], ]; ================================================ FILE: lang/en/maps.php ================================================ [ 'back' => 'Back to :name', 'edit' => 'Edit map', 'explore' => 'Explore', ], 'create' => [ 'title' => 'New Map', ], 'errors' => [ 'chunking' => [ 'error' => 'There was an error while chunking the map. Please contact the team on :discord for support.', 'running' => [ 'edit' => 'The map cannot be edited while it\'s been chunked.', 'explore' => 'The map cannot be displayed while it\'s been chunked.', 'time' => 'This can take several minutes to several hours, depending on the size of the map.', ], ], 'dashboard' => [ 'missing' => 'This map needs an image to be able to render on the dashboard.', ], 'explore' => [ 'missing' => 'Please add an image to the map before being able to explore it.', ], ], 'fields' => [ 'center_marker' => 'Marker', 'center_x' => 'Default Longitude Position', 'center_y' => 'Default Latitude Position', 'centering' => 'Centering', 'distance_measure' => 'Distance measurement', 'distance_name' => 'Distance unit label', 'grid' => 'Grid', 'has_clustering' => 'Cluster markers', 'initial_zoom' => 'Initial zoom', 'is_real' => 'Use OpenStreetMaps', 'max_zoom' => 'Maximal zoom', 'min_zoom' => 'Minimal zoom', 'tabs' => [ 'coordinates' => 'Coordinates', 'marker' => 'Marker', ], ], 'helpers' => [ 'center' => 'Changing the following values will control which area of the map is focused on. Leaving these values empty will result in the center of the map being focued on.', 'centering' => 'Centering on a marker will take priority on default coordinates.', 'chunked_zoom' => 'Automatically cluster markers together when they are close to each other.', 'distance_measure' => 'Giving the map a distance measurement will enable the measurement tool in the exploration mode.', 'distance_measure_2' => 'For 100 pixels to measure 1 kilometer, input a value of 0.0041.', 'grid' => 'Define a grid size that will be displayed in the exploration mode. A value below 10 will result in a greyed out map.', 'has_clustering' => 'Automatically cluster markers together when they are close to each other.', 'initial_zoom' => 'The initial zoom level a map is loaded with. The default value is :default, while the highest allowed value is :max and the lowest allowed value is :min.', 'is_real' => 'Select this option if you want to use a real world map instead of the uploaded image. This option disable layers.', 'max_zoom' => 'The most a map can be zoomed in on. The default value is :default, while the highest allowed value is :max.', 'min_zoom' => 'The most a map can be zoomed out of. The default value is :default, while the lowest allowed value is :min.', 'missing_image' => 'Save the map with an image before being able to add layers and markers.', ], 'lists' => [ 'empty' => 'Upload a map to visualize locations and reveal the geography of your world.', ], 'panels' => [ 'groups' => 'Groups', 'layers' => 'Layers', 'legend' => 'Legend', 'markers' => 'Markers', 'settings' => 'Settings', ], 'placeholders' => [ 'center_marker' => 'Leave empty to load the map in the middle', 'center_x' => 'Leave empty to load the map in the middle', 'center_y' => 'Leave empty to load the map in the middle', 'distance_name' => 'Km, miles, feet, hamburgers', 'grid' => 'Distance in pixel between grid elements. Leave empty to hide the grid.', 'name' => 'Name of the map', 'type' => 'Dungeon, City, Galaxy', ], 'show' => [ 'tabs' => [ 'maps' => 'Maps', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'Map is being chunked. This process can take several minutes to hours.', ], ], ]; ================================================ FILE: lang/en/misc.php ================================================ [ 'member' => 'Become a member.', 'remove_v5' => 'Kanka is built by just the two of us. Support our quest and enjoy an ad-free experience for less than the cost of a fancy coffee.', ], ]; ================================================ FILE: lang/en/notes.php ================================================ [ 'title' => 'New Note', ], 'fields' => [ 'notes' => 'Sub Notes', ], 'lists' => [ 'empty' => 'Store ideas, references, rules, or information that doesn\'t fit anywhere else.', ], 'placeholders' => [ 'note' => 'Choose a parent note', 'type' => 'Religion, Race, Political system', ], ]; ================================================ FILE: lang/en/notifications.php ================================================ [ 'discord' => [ 'invalid' => 'Your Discord token has expired. Please re-sync your Discord and Kanka account.', ], ], 'campaign' => [ 'application' => [ 'approved' => 'Your application to the :campaign has been approved.', 'approved_message' => 'Your application to the :campaign has been approved. Message provided: :reason', 'new' => 'New application for :campaign.', 'rejected' => 'Your application to the :campaign has been rejected. Reason provided: :reason', 'rejected_no_message' => 'Your application to the :campaign has been rejected.', ], 'asset_export' => 'An export for :campaign\'s assets is available. The link is available for :time minutes.', 'boost' => [ 'add' => ':user is now boosting :campaign.', 'remove' => ':user is no longer boosting :campaign.', 'superboost' => ':user is now superboosting :campaign.', ], 'created' => 'You have created :campaign.', 'deleted' => ':campaign was deleted.', 'export' => 'An export for :campaign is available. The link is available for :time minutes.', 'export_error' => 'An error occurred while exporting :campaign. Please contact us if this problem persists.', 'hidden' => ':campaign is now hidden from the public campaigns page.', 'import' => [ 'csv_ready' => 'The CSV import for :campaign is ready.', 'csv_success' => 'Successfully imported :count entities via CSV import to :campaign.', 'failed' => 'The import for :campaign failed.', 'success' => 'The import finished for :campaign.', ], 'join' => ':user joined :campaign.', 'leave' => ':user left :campaign.', 'new_owner' => 'You have been made an admin of :campaign.', 'plugin' => [ 'deleted' => 'The plugin :plugin was deleted from the marketplace and removed from :campaign.', ], 'premium' => [ 'add' => ':user has unlocked premium features for :campaign.', 'remove' => ':user has stopped unlocking premium features for :campaign.', ], 'removed-image' => 'The image or header of :entity was removed due to a copyright claim.', 'role' => [ 'add' => 'You have been added to the :role role of :campaign.', 'remove' => 'You have been removed from the :role role of :campaign.', ], 'troubleshooting' => [ 'joined' => 'The Kanka team-member :user joined :campaign.', ], ], 'clear' => [ 'action' => 'Clear all', 'success' => 'Notifications removed.', 'title' => 'Clear notifications', ], 'features' => [ 'approved' => 'Your idea :feature has been approved.', 'finished' => 'Your idea :feature is now available in Kanka!', 'rejected' => 'Your idea :feature has been rejected, reason: :reason.', ], 'header' => 'You have :count notifications', 'index' => [ 'title' => 'Notifications', ], 'map' => [ 'chunked' => 'Map :name has finished chunking and is now usable.', ], 'no_notifications' => 'Notifications will appear here once you have some.', 'plugins' => [ 'comments' => [ 'new_comment' => ':user has left a new comment on the plugin :plugin.', 'new_reply' => ':user has replied to your comment in :plugin.', ], ], 'subscriptions' => [ 'charge_fail' => 'An error occurred while processing your payment. Please wait a moment while we try again. If nothing changes, please contact us.', 'deleted' => 'Your subscription to Kanka was automatically cancelled after too many failed attempts to charge your card. Please go to your Subscription settings and try updating your payment details.', 'ended' => 'Your subscription to Kanka has ended. Your premium campaigns and Discord roles have been disabled. We hope to see you back soon!', 'failed' => 'We couldn\'t charge your payment details. Please update them in your Payment Method settings.', 'started' => 'Your subscription to Kanka has started.', 'trial' => 'Your free trial to Kanka has ended. We hope you loved it and hope to see you back soon!', ], 'unread' => 'New notification', ]; ================================================ FILE: lang/en/onboarding/attributes.php ================================================ 'Properties let you add small, reusable pieces of information to :name such as age, hit points, faction rank, or any custom stats you track. They\'re ideal for data you want to reference, sort, or template across multiple entries.', 'title' => 'Store structured data for this entry', ]; ================================================ FILE: lang/en/onboarding/characters.php ================================================ 'That\'s enough to get started.', 'text' => 'Focus on the basics: name, a short description, and one or two defining traits. You can add details like relations, properties, and portraits later.', 'title' => 'Create your first character', ]; ================================================ FILE: lang/en/onboarding/locations.php ================================================ 'Keep it simple for now.', 'text' => 'Start small. Name the place and add one sentence about what makes it important. You can expand with maps, residents, and nested locations once the world takes shape.', 'title' => 'Create your first location', ]; ================================================ FILE: lang/en/onboarding/posts.php ================================================ 'Articles let you separate public text from private notes, GM-only secrets, and supporting information. Create as many articles as you need and control who can see each one.', 'title' => 'Use articles for secrets and extra details', ]; ================================================ FILE: lang/en/onboarding/reminders.php ================================================ 'Create reminders for deadlines, in-story dates, anniversaries, or anything tied to :name that you don\'t want to forget. Reminders you add here will show on this page and on any calendar date you link them to.', 'title' => 'Track time-sensitive details here', ]; ================================================ FILE: lang/en/onboarding/tags.php ================================================ 'NPCs', ]; ================================================ FILE: lang/en/organisations.php ================================================ [ 'title' => 'New Organisation', ], 'fields' => [ 'is_defunct' => 'Defunct', 'members' => 'Members', ], 'hints' => [ 'is_defunct' => 'This organisation is defunct.', ], 'lists' => [ 'empty' => 'Create guilds, factions, or secret societies to shape your world\'s power structure.', ], 'members' => [ 'actions' => [ 'add_multiple' => 'Add members', ], 'create' => [ 'helper' => 'Add one or several members to :name.', 'success_multiple' => '{1} Added :count member to :name.|[2,*] Added :count members to :name.', ], 'destroy' => [ 'success' => 'Member removed from :name.', ], 'edit' => [ 'helper' => 'Change the membership status for :name.', 'title' => 'Update Member for :name', ], 'fields' => [ 'parent' => 'Superior', 'pinned' => 'Pinned', 'role' => 'Role', 'status' => 'Membership status', ], 'helpers' => [ 'all_members' => 'All characters that are members of this organisations and it\'s sub-organisations.', 'members' => 'All characters that are members of this organisation.', 'pinned' => 'This member can be shown in the profile pins of its associated entries.', ], 'pinned' => [ 'both' => 'Pinned on both', 'none' => 'Pinned nowhere', ], 'placeholders' => [ 'parent' => 'Who is this member\'s superior', 'role' => 'Leader, Member, High Septon, Spymaster', ], 'status' => [ 'active' => 'Active member', 'inactive' => 'Inactive member', 'unknown' => 'Unknown status', ], ], 'placeholders' => [ 'type' => 'Cult, Gang, Rebellion, Fandom', ], ]; ================================================ FILE: lang/en/pagination.php ================================================ 'Next »', 'previous' => '« Previous', 'showing' => 'Showing', 'to' => 'to', 'of' => 'of', 'results' => 'results', ]; ================================================ FILE: lang/en/partials.php ================================================ [ 'description' => 'There were some problems with your input.', 'title' => 'Whoops!', ], ]; ================================================ FILE: lang/en/passwords.php ================================================ 'Passwords must be at least six characters and match the confirmation.', 'reset' => 'Your password has been reset!', 'sent' => 'We have e-mailed your password reset link!', 'token' => 'This password reset token is invalid.', 'user' => 'We can\'t find a user with that e-mail address.', ]; ================================================ FILE: lang/en/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/en/permissions.php ================================================ [ 'delete' => 'Permission to delete this element', 'edit' => 'Permission to edit this element', 'view' => 'Permission to view this element', ], 'members' => [ 'inherited' => ':member can already do this by being part of the :role role.', ], 'roles' => [ 'inherited' => 'The :role role can already do this on the whole :module category.', ], ]; ================================================ FILE: lang/en/pins.php ================================================ 'Learn more about pins in our documentation.', 'options' => [ 'no' => 'Unpinned', 'yes' => 'Pinned to the entry\'s overview page', ], ]; ================================================ FILE: lang/en/playstyles.php ================================================ 'Casual / Drop-In Friendly', 'character-focused' => 'Character-Focused', 'combat-focused' => 'Combat-Focused', 'episodic-one-shot-friendly' => 'Episodic / One-Shot Friendly', 'exploration-focused' => 'Exploration-Focused', 'linear-gm-led' => 'Linear / GM-Led', 'long-term-campaign' => 'Long-Term Campaign', 'narrative-first' => 'Narrative-First', 'roleplay-heavy' => 'Roleplay-Heavy', 'roleplay-light' => 'Roleplay-Light', 'rules-light' => 'Rules-Light', 'sandbox-player-driven' => 'Sandbox / Player-Driven', 'serious-immersive' => 'Serious / Immersive', 'story-driven' => 'Story-Driven', 'tactical-crunchy' => 'Tactical / Crunchy', ]; ================================================ FILE: lang/en/post_layouts.php ================================================ 'Character organisations', 'connection_map' => 'Relation map', 'helper' => 'This article is set up to display the :subpage subpage of :name.', 'location_characters' => 'Location characters', 'location_events' => 'Location events', 'location_quests' => 'Location quests', 'pitch' => [ 'custom' => 'Show content from this entry\'s subpages directly on the overview with article layouts. For example, show :entry\'s inventory.', 'title' => 'Advanced article layouts', ], 'premium' => 'Some layout options are disabled because they required a premium campaign.', 'quest_elements' => 'Quest Elements', ]; ================================================ FILE: lang/en/posts/templates.php ================================================ [ 'set' => 'Set as a reusable template', 'unset' => 'Remove as a reusable template', ], 'helper' => 'The following articles have been defined as templates that can be re-used.', 'tab' => 'Load from templates', 'tooltips' => [ 'click-to-edit' => 'Edit this template article', ], ]; ================================================ FILE: lang/en/posts.php ================================================ [ 'title' => 'New Article', ], 'fields' => [ 'description' => 'Description', 'layout' => 'Article layout', 'name' => 'Article name', ], 'helpers' => [ 'new' => 'Add a new article to this entry.', 'visibility' => 'Change the visibility of the :name article.', ], 'move' => [ 'copy' => [ 'helper' => 'Keep a copy of the article on :name.', ], 'helper' => 'Move or copy the article :name to a different entry.', 'title' => 'Move article', ], 'permissions' => [ 'actions' => [ 'members' => 'Add members', 'roles' => 'Add roles', ], 'helpers' => [ 'members' => 'Add one or multiple members to have special permissions on this article.', 'roles' => 'Add one or multiple roles to have special permissions on this article.', ], ], 'placeholders' => [ 'name' => 'Name of the article', ], 'position' => [ 'dont_change' => 'Don\'t change', 'first' => 'First', 'last' => 'Last', ], 'remove' => [ 'title' => 'Remove article', ], 'visibility' => [ 'helper' => 'Change the visibility for the :name article.', 'title' => 'Article visibility', ], ]; ================================================ FILE: lang/en/presets.php ================================================ [ 'create' => 'Create a new preset', ], 'create' => [ 'success' => 'Preset :name created.', 'title' => 'New preset', ], 'destroy' => [ 'success' => 'Preset :name destroyed.', ], 'edit' => [ 'success' => 'Preset :name modified.', 'title' => 'Edit :name preset', ], 'fields' => [ 'name' => 'Preset name', ], 'lists' => [ 'empty' => 'There are currently no available presets in the campaign.', ], 'placeholders' => [ 'name' => 'The preset\'s name', ], ]; ================================================ FILE: lang/en/profiles.php ================================================ [ 'success' => 'Avatar updated.', ], 'edit' => [ 'success' => 'Profile updated', ], 'fields' => [ 'avatar' => 'Avatar', 'bio' => 'Bio', 'email' => 'Email', 'hide_subscription' => 'Hide my name from the :hall_of_fame.', 'last_login_share' => 'Share with other members when I last logged in.', 'link' => 'Social link', 'login_sharing' => 'Last login sharing', 'name' => 'Name', 'new_password' => 'New Password', 'new_password_confirmation' => 'New Password Confirmation', 'newsletter' => 'I wish to sometimes be contacted by email.', 'password' => 'Current password', 'profile-name' => 'Profile name', 'pronouns' => 'Pronouns', 'settings' => 'Settings', 'subscription_hiding' => 'Subscription hiding', 'theme' => 'Theme', ], 'helpers' => [ 'link' => 'Change the way a link to your social profile appears on your :profile and the :marketplace. If left blank, no link will show.', 'profile-name' => 'Change the way your name appears on your :profile and the :marketplace. If left blank, your account name will be used instead.', 'pronouns' => 'Change the way your pronouns appear on your :profile and the :marketplace. If left blank, no pronouns will show.', ], 'link' => [ 'button' => ':name\'s social profile', ], 'newsletter' => [ 'helpers' => [ 'header' => 'Subscribe to the following email newsletters to be notified of what\'s going on with Kanka.', ], 'options' => [ 'monthly' => 'Kanka newsletter', ], 'title' => 'Newsletters', 'updated' => 'Newsletter preferences updated.', ], 'password' => [ 'success' => 'Password updated', ], 'placeholders' => [ 'bio' => 'A short bio of yourself displayed on your public profile.', 'email' => 'Your email address', 'name' => 'Your name as displayed', 'new_password' => 'Your new password', 'new_password_confirmation' => 'Confirm your new password', 'password' => 'Provide your current password for any changes', ], 'sections' => [ 'dangerzone' => 'Danger Zone', 'delete' => [ 'confirm' => 'Delete my account now', 'delete' => 'Delete my account', 'goodbye' => 'If so, please write :code on the box below.', 'helper' => 'Deleting your account will also delete any campaign you are the only member of. This action is permanent and can\'t be undone.', 'subscribed' => 'Please cancel your :subscription before being able to delete your account.', 'title' => 'Delete your account', 'warning' => 'By deleting your account, all your data will be lost. Are you sure?', ], 'password' => [ 'title' => 'Change your password', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'The biography is visible on your :link.', 'profile' => 'public profile', ], 'success' => 'Settings changed.', ], 'theme' => [ 'success' => 'Theme changed.', 'themes' => [ 'dark' => 'Dark', 'default' => 'Light', 'future' => 'Future', 'midnight' => 'Midnight Blue', ], ], 'title' => 'Update your profile', 'workflows' => [ 'created' => 'View the newly created entry', 'default' => 'View the list of entries', ], ]; ================================================ FILE: lang/en/quests.php ================================================ [ 'title' => 'New Quest', ], 'elements' => [ 'create' => [ 'success' => 'Element :entity added to the quest.', 'title' => 'New element for :name', ], 'destroy' => [ 'success' => 'Element :entity removed.', ], 'edit' => [ 'success' => 'Element :entity updated.', 'title' => 'Update element for :name', ], 'fields' => [ 'copy_entity_entry' => 'Use entry description', 'entity_or_name' => 'Either select either an entry of the campaign, or give a name for this element.', ], 'helpers' => [ 'copy_entity_entry' => 'Display the linked entry\'s description instead of the custom description.', ], 'placeholders' => [ 'name' => 'Element name', ], ], 'fields' => [ 'copy_elements' => 'Copy elements attached to the quest', 'date' => 'Date', 'element_role' => 'Role', 'instigator' => 'Instigator', 'is_completed' => 'Completed', 'location' => 'Starting location', 'role' => 'Role', 'status' => 'Status', ], 'helpers' => [ 'is_completed' => 'The quest is considered as completed.', 'status' => 'The quest\'s current status.', ], 'hints' => [ 'is_abandoned' => 'This quest has been abandoned.', 'is_completed' => 'This quest is completed.', 'is_ongoing' => 'This quest is ongoing.', ], 'lists' => [ 'empty' => 'Create quests to record objectives, storylines, or character motivations.', ], 'placeholders' => [ 'date' => 'Real world date for the quest', 'entity' => 'Name of an element from the quest', 'location' => 'The quest\'s starting location', 'role' => 'This entry\'s role in the quest', 'type' => 'Character Arc, Sidequest, Main', ], 'show' => [ 'actions' => [ 'add_element' => 'Add an element', ], 'tabs' => [ 'elements' => 'Elements', ], ], 'status' => [ 'abandoned' => 'Abandoned', 'completed' => 'Completed', 'not_started' => 'Not Started', 'ongoing' => 'Ongoing', ], ]; ================================================ FILE: lang/en/races.php ================================================ [ 'title' => 'New Race', ], 'fields' => [ 'is_extinct' => 'Extinct', 'members' => 'Members', ], 'hints' => [ 'is_extinct' => 'This race is extinct.', ], 'lists' => [ 'empty' => 'Define the species, cultures, or peoples that inhabit your world.', ], 'members' => [ 'create' => [ 'helper' => 'Add one or several characters to :name.', 'submit' => 'Add members', 'success' => '{0} No member was added.|{1} 1 member was added.|[2,*] :count members were added.', 'title' => 'New Members', ], ], 'placeholders' => [ 'type' => 'Human, Fey, Borg', ], ]; ================================================ FILE: lang/en/redirects.php ================================================ 'Your session timed out. Please try again.', 'unknown_entity' => 'Sorry, we don\'t know what a \':entry\' is.', ]; ================================================ FILE: lang/en/referrals.php ================================================ [ 'copy' => 'Copy', ], 'benefits' => 'Build worlds together.', 'fields' => [ 'link' => 'Your referral link:', ], 'stats' => [ 'badge' => 'Badge: Worldbuilder :level', 'empty' => 'No one yet. Share your link to get started.', 'invited' => 'You\'ve invited:', 'subscribers' => 'Subscribers referred: :amount', 'users' => '[1] one user|{2,*} :amount users', ], 'title' => 'Invite friends to Kanka', 'toasts' => [ 'copied' => 'Link copied to clipboard', ], ]; ================================================ FILE: lang/en/releases.php ================================================ [ 'event' => 'Event', 'livestream' => 'Livestream', 'other' => 'Other', 'release' => 'Release', 'vote' => 'Community vote', ], 'index' => [ 'description' => 'The latest updates to kanka.io', 'title' => 'Releases', ], 'post' => [ 'footer' => 'By :name :date', ], 'show' => [ 'return' => 'Back to releases', 'title' => 'Release :name', ], ]; ================================================ FILE: lang/en/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chronicles of Darkness', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/en/search/fulltext.php ================================================ 'Searching entries, articles, details and more for the term :term.', 'title' => 'Fulltext search', ]; ================================================ FILE: lang/en/search.php ================================================ 'Search everywhere', 'lookup' => [ 'empty' => 'No results', 'hint' => 'Type at least 3 letters to search for entries in the campaign.', 'keyboard' => 'press :k to search, :esc to dismiss', 'lists' => 'Lists', 'recents' => 'Recents', 'results' => 'Results', ], 'no_results' => 'No results.', 'placeholder' => 'SEARCH', 'placeholders' => [ 'entry' => 'Search for an entry', ], 'preview' => [ 'links' => 'Links', 'no-connections' => 'No pinned relations to display', ], 'title' => 'Search', ]; ================================================ FILE: lang/en/seo.php ================================================ 'Dashboard of :campaign', 'entity-list' => 'Explore the :module of :campaign', ]; ================================================ FILE: lang/en/settings/account.php ================================================ 'Control your email, password, security and other account settings.', 'title' => 'Account Info', ]; ================================================ FILE: lang/en/settings/api.php ================================================ [ 'title' => 'Authorised Applications', ], 'clients' => [ 'empty' => 'You have not created any OAuth clients.', 'form' => [ 'name' => 'Client Name', 'name_helper' => 'Something your users will recognise and trust.', 'name_placeholder' => 'Name of the client', 'redirect' => 'Redirect URL', 'redirect_helper' => 'Your application\'s authorisation callback URL.', 'redirect_placeholder' => 'http://my-super-app.com/callback', ], 'new' => 'Create New Client', 'title' => 'OAuth Clients', 'update'=> 'Update Client', ], 'fields' => [ 'client' => 'Client ID', 'client_name' => 'Client Name', 'scopes' => 'Scopes', 'secret' => 'Secret', 'token_name' => 'Token Name', ], 'new' => [ 'copy' => 'Token copied to the clipboard.', 'title' => 'Your new personal access token:', ], 'revoke' => 'Revoke', 'revoke-confirm' => 'Confirm revocation', 'tokens' => [ 'empty' => 'You have not created any personal access tokens.', 'form' => [ 'name' => 'Token Name', 'name_placeholder' => 'Name the token', ], 'new' => 'Create New Token', 'title' => 'Personal Access Tokens', ], ]; ================================================ FILE: lang/en/settings/appearance.php ================================================ [ 'learn-more' => 'Learn more about this setting in our documentation.', 'save' => 'Save settings', ], 'campaign-switcher' => [ 'alphabetical' => 'Alphabetically (A-Z)', 'date_created' => 'Date created (oldest first)', 'date_joined' => 'Date joined (oldest first)', 'r_alphabetical' => 'Alphabetically (Z-A)', 'r_date_created' => 'Date created (newest first)', 'r_date_joined' => 'Date Joined (newest first)', ], 'dismissible' => [ 'main' => 'Control the way Kanka looks and feels. Please note that campaigns can override some of these settings.', ], 'editors' => [ 'default' => 'Default (:name)', 'helpers' => [ 'feedback' => 'Help us improve it by giving feedback (2min)', 'legacy' => 'The legacy text editor (TinyMCE) doesn\'t support mentions on mobile devices, campaign galleries or other advanced features.', 'tiptap' => 'This is our new experimental text editor that is actively being worked on and updated regularly. It doesn\'t yet contain all the features you might be accustomed to.', ], 'legacy' => 'Legacy (:name)', 'tiptap' => 'Experimental 2026', ], 'explore' => [ 'grid' => 'Grid (default)', 'table' => 'Table', ], 'fields' => [ 'campaign-order' => 'Campaign list order', 'date-format' => 'Date formatting', 'editor' => 'Text editor', 'entity-explore' => 'Entry lists', 'mentions' => 'Mentions', 'new-entity-workflow' => 'New entry workflow', 'pagination' => 'Results per page', 'theme' => 'Theme preference', ], 'helpers' => [ 'advanced-mentions' => 'When writing texts, control how @mentions are displayed.', 'campaign-order' => 'Change the order in which campaigns are listed in the campaign switcher.', 'date-format' => 'When available, control the format in which to display real world dates.', 'editors' => 'Switch between text editors for when creating or editing large text fields.', 'entity-explore' => 'Control the way in which entry lists are displayed on campaigns.', 'new-entity-workflow' => 'Control which interface you are taken to after creating a new entry.', 'overridable' => 'Individual campaigns can override this preference.', 'pagination' => 'For lists that span multiple pages, define how many are visible on each page.', 'theme' => 'Choose how Kanka looks to you.', ], 'mentions' => [ 'advanced' => 'Advanced mentions :code', 'default' => 'Standard mentions :mention', ], 'success' => 'Appearance options saved.', 'values' => [ 'pagination' => ':amount results per page', 'pagination-sub' => ':amount (available for subscribers)', ], ]; ================================================ FILE: lang/en/settings/boosters.php ================================================ [ 'boost_name' => 'Boost :name', ], 'available' => 'Premium campaigns :amount/:total', 'benefits' => [ 'boosted' => 'Boosting a campaign with :one booster will unlock access to the :marketplace, theming options, larger uploads for all members, recovering deleted entities, and :more.', 'more' => 'more amazing features', 'superboosted' => 'Superboosting a campaign with :amount boosters will unlock all the benefits of a boosted campaign, as well as a campaign gallery, full logs changes are made to entries, and :more.', ], 'boost' => [ 'actions' => [ 'confirm' => 'Boost it!', 'remove' => 'Stop boosting :campaign', 'subscribe' => 'Subscribe to Kanka', 'upgrade' => 'Upgrade your subscription', ], 'confirm' => 'How exciting! You\'re about to boost :campaign. This will assign one (:cost) of your available campaign boosters.', 'duration' => 'Assigned boosters remain assigned until you manually remove them, or when your subscription ends.', 'errors' => [ 'boosted' => 'Oh oh, looks like :campaign is already boosted!', 'out-of-boosters' => 'Oh no! You don\'t have enough boosters available. You have :available and need :cost. Either stop boosting other campaigns, or :upgrade.', ], 'pitch' => 'Become a subscriber to unlock campaign boosters.', 'success' => 'The :campaign campaign is now boosted. Enjoy all the new awesome features!', 'title' => 'Boost :campaign', 'upgrade' => 'upgrade your subscription', ], 'campaign' => [ 'boosted' => 'Boosted by :user since :time', 'premium' => 'Premium features unlocked thanks to :user since :time', 'standard' => 'Standard', 'superboosted' => 'Superboosted by :user since :time', 'unboosted' => 'Unboosted', ], 'intro' => [ 'anyone' => 'You aren\'t limited to only boosting campaigns you\'ve created. You can boost any campaign you are a part of or can see. This includes campaigns where you are a player, or :public you enjoy.', 'data' => 'When a campaign is no longer boosted, access to boosted features is removed. However, no content is deleted, so boosting the campaign again in the future restores access to it.', 'first' => 'Advanced features are unlocked by assigning your boosters to boost or superboost to a campaign. The amount of boosters you have is determined by your :subscription. This number is available to you at all time while you are a subscriber. Boosting a campaign will assign one of your boosters to it, while superboosting a campaign assigns three of them.', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Recover deleted entries and posts for up to :amount days', 'customisable' => 'Custom themes and CSS', 'icons' => 'Thousands of icons for maps and timelines', 'plugins' => 'Extend your campaign with community-built plugins', 'title' => 'Boosted campaigns get', 'upload' => 'Larger uploads for all members', 'visual' => 'Visualise family trees and relationships between entries', ], 'description' => 'Assign boosters to campaigns and help unlock amazing features for everyone involved. Not impressed by boosted campaigns? We\'ve got you covered with superboosted campaigns!', 'more' => 'Check out the full list of perks on our :boosters page.', 'title' => 'Unlock the full potential of your campaigns', ], 'ready' => [ 'available' => 'Your available campaign boosters.', 'pricing' => 'All of our subscription levels include at least one campaign booster and start at :amount per month.', 'pricing-amount' => ':currency:amount', 'title' => 'Boost a campaign', ], 'superboost'=> [ 'actions' => [ 'confirm' => 'Superboost it!', 'instead' => 'Superboost it for :count!', 'remove' => 'Stop superboosting :campaign', ], 'confirm' => 'How exciting! You\'re about to superboost :campaign. This will assign three (:cost) of your available campaign boosters.', 'errors' => [ 'boosted' => 'Oh oh, looks like :campaign is already superboosted!', ], 'success' => 'The :campaign campaign is now superboosted. Enjoy all the new awesome features!', 'title' => 'Superboost :campaign', 'upgrade' => 'Ready for the ultimate Kanka experience? Superboosting :campaign will assign :cost additional campaign boosters.', ], 'title' => 'Campaign Boosters', 'unboost' => [ 'confirm' => 'Yes, I\'m sure', 'status' => [ 'boosting' => 'boosting', 'superboosting' => 'superboosting', ], 'success' => 'The :campaign campaign is no longer boosted, and your boosters are available again.', 'title' => 'Unboosting a campaign', 'warning' => 'Are you sure you want to stop :action :campaign? This will release your assigned boosters, and hide all content and features related to the perks until the campaign is boosted again.', ], ]; ================================================ FILE: lang/en/settings/premium.php ================================================ [ 'remove' => 'Remove premium', 'unlock' => 'Go premium', ], 'create' => [ 'actions' => [ 'confirm' => 'Go premium!', ], 'confirm' => 'How exciting! You\'re about to unlock premium features for :campaign. This will use one of your available premium campaigns.', 'duration' => 'Premium campaigns stay that way until you manually remove them, or your subscription ends.', 'pitch_2026' => 'Get unlimited roles, members, custom themes, plugins, and more for your campaigns.', 'success' => 'The :campaign campaign is now premium. Enjoy all the new awesome features!', ], 'exceptions' => [ 'already' => 'Premium features have already been unlocked for this campaign.', 'out-of-stock' => 'You don\'t have enough premium campaigns available to unlock this campaign. Either remove the premium status from another campaign, or :upgrade.', ], 'pitch' => [ 'description' => 'Premium features apply to the whole campaign, including all of its members.', 'title' => 'Premium campaigns get', ], 'ready' => [ 'available' => 'Your available premium campaigns.', 'pricing' => 'All of our subscription levels include at least one premium campaign and start at :amount per month.', 'pricing-amount' => ':currency:amount', 'title' => 'Go premium', ], 'remove' => [ 'confirm' => 'Yes, I\'m sure', 'cooldown' => 'The premium features from :campaign can be removed after :date.', 'success' => 'Premium features have been removed from :campaign. You can now unlock premium features on another one.', 'title' => 'Removing premium features', 'warning' => 'Are you sure you want to remove premium features from :campaign? This will allow you to unlock another one, and hide all content and features related to the perks until the campaign\'s premium status is re-enabled.', ], ]; ================================================ FILE: lang/en/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Disable two-factor authentication', 'disable-confirm' => 'Click again to confirm', 'finish' => 'Finish setup and log in', ], 'activation_helper' => 'To finish setting up your account\'s two-factor authentication, please follow these instructions.', 'disable' => [ 'helper' => 'If you want to disable two-factor authentication click the button below. Keep in mind that this will leave your account vulnerable to anyone that knows your login information.', 'title' => 'Disable two-factor authentication', ], 'enable_instructions' => 'To start the activation process, generate your authentication QR code, then scan it into the Google Authenticator App (:ios, :android), or another similar authenticator app.', 'enabled' => 'Two-factor authentication is currently enabled on your account.', 'error_enable' => 'Invalid Code, try again', 'fields' => [ 'otp' => 'Enter the One Time Password (OTP) provided by the authenticator app', 'qrcode' => 'Scan the following QR Code with your authenticator app to generate a One Time Password (OTP)', ], 'generate_qr' => 'Generate QR code', 'helper' => 'Two-factor authentication (2FA) strengthens access security by requiring two methods (also referred to as factors) to verify your identity on each login.', 'learn_more' => 'Learn more about two-factor authentication.', 'social' => 'Kanka two-factor authentication is only enabled for users that login using their e-mail and password. Change your login method in your account settings before being able to enable this option.', 'success_disable' => 'Two-factor authentication successfully disabled.', 'success_enable' => 'Two-factor authentication enabled successfully. Please log in again to finish the setup.', 'success_key' => 'Your secure QR code was successfully generated. Please complete your setup to activate two-factor authentication.', 'title' => 'Two-factor authentication', ], 'actions' => [ 'social' => 'Switch to Kanka Login', 'update_email' => 'Update email', 'update_password' => 'Update password', ], 'email' => 'Change email', 'email_success' => 'Email updated.', 'password' => 'Change password', 'password_success' => 'Password updated.', 'social' => [ 'error' => 'You are already using the Kanka login for this account.', 'helper' => 'Your account is currently managed by :provider. You can stop using it and switch to the standard Kanka login by setting up a password.', 'success' => 'Your account now uses the Kanka login.', 'title' => 'Social to Kanka', ], 'title' => 'Account', ], 'api' => [ 'helper' => 'Welcome to the Kanka APIs. Generate a Personal Access Token to use in your API request to gather information about the campaigns you are a part of.', 'link' => 'Read the API documentation', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Connect', 'remove' => 'Remove', ], 'benefits' => 'Kanka provides a few integrations to third party services. More third party integrations are planned for the future.', 'discord' => [ 'confirm' => 'Are you sure you want to disconnect your account from Discord? This will remove any roles you have been synced with.', 'errors' => [ 'add' => 'An error occurred linking up your Discord account with Kanka. Please try again. If this keeps happening, please be aware that Discord has a limit on 100 joined servers when using their APIs.', ], 'success' => [ 'add' => 'Your Discord account has been linked.', 'remove' => 'Your Discord account has been unlinked.', ], 'text' => 'Link your Discord account with Kanka to automatically get access to your subscription roles and private channels.', 'unlock' => 'Unlock Discord roles', ], 'title' => 'App Integration', ], 'billing' => [ 'placeholder' => 'If you need additional contact or tax information added to your receipts (bussines address, VAT number, etc.), enter it below and it will appear on all of your receipts.', 'save' => 'Save billing information', 'title' => 'Billing Information', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => ':name is already being boosted.', 'exhausted_boosts' => 'You are out of boosts to give. Remove your boost from a campaign before giving it to another.', 'exhausted_superboosts' => 'You are out of boosts. You need 3 boosters to superboost a campaign.', ], ], 'countries' => [ 'austria' => 'Austria', 'belgium' => 'Belgium', 'france' => 'France', 'germany' => 'Germany', 'italy' => 'Italy', 'netherlands' => 'The Netherlands', 'spain' => 'Spain', ], 'layout' => [ 'title' => 'Layout', ], 'menu' => [ 'account' => 'Account', 'api' => 'API', 'appearance' => 'Appearance', 'apps' => 'Apps', 'boosters' => 'Boosters', 'notifications' => 'Notifications', 'other' => 'Other', 'patreon' => 'Patreon', 'payment_options' => 'Payment Options', 'personal_settings' => 'Personal Settings', 'premium' => 'Premium campaigns', 'profile' => 'Public profile', 'settings' => 'Settings', 'subscription' => 'Subscription', 'subscription_status' => 'Subscription Status', ], 'patreon' => [ 'deprecated' => 'Deprecated feature - if you wish to support Kanka, please do so with a :subscription. Patreon linking is still active for our Patrons who have linked their account before the move away from Patreon.', 'pledge' => 'Pledge: :name', 'remove' => [ 'button' => 'Unlink your Patreon account', 'success' => 'Your Patreon account has been unlinked.', 'text' => 'Unlinking your Patreon account with Kanka will remove your bonuses, name on the hall of fame, campaign boosts, and other features linked to supporting Kanka. None of your boosted content will be lost (e.g. entry headers). By subscribing again, you will have access to all your previous data, including the ability to unlock your previously premium campaigns.', 'title' => 'Unlink your Patreon account with Kanka', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Update profile', ], 'avatar' => 'Profile Picture', 'success' => 'Profile updated.', 'title' => 'Public Profile', ], 'referrals' => [ 'title' => 'Referrals', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Cancel subscription', 'subscribe' => 'Subscribe', 'update_currency' => 'Save billing currency', ], 'billing' => [ 'helper' => 'Your billing information is processed and stored safely through :stripe. This payment method is used for all of your subscriptions.', 'saved' => 'Saved payment method', ], 'cancel' => [ 'grace' => [ 'text' => 'Your subscription is already set to end on :date, after which your premium campaigns will revert to standard campaigns and other benefits related to supporting Kanka will be disabled.', 'title' => 'Grace period', ], 'options' => [ 'competitor' => 'Switching to a competitor', 'financial' => 'Subscription is too expensive', 'missing_features' => 'Missing features', 'not_for' => 'Subscription is not for me', 'not_playing' => 'No longer playing or campaign on hiatus', 'not_using' => 'Not currently using Kanka', 'other' => 'Other', 'testing' => 'Just testing Kanka', ], 'text' => 'Sorry to see you go! Cancelling your subscription will keep it active until :date, after which your premium campaigns will revert to standard campaigns and other benefits related to supporting Kanka will be disabled. Feel free to fill out the following form to inform us what we can do better, or what led to your decision.', 'title' => 'Cancelling subscription', ], 'cancelled' => 'Your subscription has been cancelled. You can renew a subscription once your current subscription ends after :date.', 'change' => [ 'text' => [ 'downgrade_monthly' => 'You are downgrading to the :tier tier for :downgrade, thereafter billed monthly for :amount.', 'downgrade_yearly' => 'You are downgrading to the :tier tier for :downgrade, thereafter billed annually for :amount.', 'monthly' => 'You are subscribing at the :tier tier, billed monthly for :amount.', 'upgrade_monthly' => 'You are upgrading to the :tier tier for :upgrade, thereafter billed monthly for :amount.', 'upgrade_paypal' => 'You are upgrading to the :tier tier for :upgrade until :date.', 'upgrade_yearly' => 'You are upgrading to the :tier tier for :upgrade, thereafter billed annually for :amount.', 'yearly' => 'You are subscribing at the :tier tier, billed annually for :amount.', ], 'title' => 'Change Subscription Tier', ], 'coupon' => [ 'check' => 'Check promo code', 'invalid' => 'Invalid promotional code.', 'label' => 'Promotional code', 'percent_off' => 'We will discount your first yearly subscription by :percent%!', ], 'currencies' => [ 'brl' => 'BRL', 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Change your preferred billing currency', ], 'errors' => [ 'callback' => 'Our payment provider reported an error. Please try again or contact us if the problem persists.', 'failed' => 'We are currently experiencing issues with our billing system. Please contact us at :email for assistance.', 'subscribed' => 'Couldn\'t process your subscription. Stripe provided the following hint.', ], 'fields' => [ 'active_since' => 'Active since', 'active_until' => 'Active until', 'billing' => 'Billing', 'currency' => 'Billing Currency', 'payment_method' => 'Payment method', 'plan' => 'Current plan', 'reason' => 'Reason', 'reset' => 'Reset billing information', 'reset_billing' => 'I understand that changing currency will lose my billing history and require me to re-enter my payment method.', ], 'helpers' => [ 'alternatives' => 'Pay for your subscription using :method. This payment method won\'t auto-renew at the end of your subscription. :method is only available in Euros.', 'alternatives-2' => 'Pay for your subscription using :method. This is a one time payment that doesn\'t automatically renew at at the end of the subscription.', 'alternatives_warning' => 'Upgrading your subscription when using this method is not possible. Please subscribe again when your current one ends.', 'alternatives_yearly' => 'We only accept yearly subscriptions when subscribing with :method', 'currency_block' => 'It is not possible to change currency while you have an active Kanka subscription, you can change your currency once your current subscription ends.', 'currency_reset' => 'Changing your currency of choice will delete your billing history and will require you to re-enter a payment method.', 'paypal_v3' => 'Safely pay for your yearly subscription using PayPal.', 'stripe' => 'Your billing information is processed and stored safely through :stripe.', ], 'manage_subscription' => 'Manage subscription', 'payment_method' => [ 'actions' => [ 'add' => 'Add', 'add_new' => 'Add a new payment method', 'change' => 'Change payment method', 'save' => 'Save payment method', 'show_alternatives' => 'Alternative payment options', ], 'add_one' => 'You currently have no payment method saved.', 'alternatives' => 'You can subscribe using these alternative payment options. This action will charge your account once and not auto-renew your subscription every month.', 'card' => 'Card', 'card_name' => 'Name on card', 'country' => 'Country of residence', 'ending' => 'Ending in', 'helper' => 'This card will be used for all of your subscriptions.', 'new_card' => 'Add a new payment method', 'saved' => ':brand **** :last4', ], 'paypal_expiring' => 'Your PayPal subscription expires on :date. Renew now to avoid losing access.', 'periods' => [ 'monthly' => 'Monthly', 'yearly' => 'Yearly', ], 'placeholders' => [ 'downgrade_reason' => 'Optionally tell us why you are downgrading your subscription.', 'reason' => 'Optionally tell us why you are no longer supporting Kanka.', ], 'plans' => [ 'cost_monthly' => ':currency :amount billed monthly', 'cost_yearly' => ':currency :amount billed yearly', ], 'sub_status' => 'Subscription information', 'subscription' => [ 'actions' => [ 'cancel' => 'Cancel subscription', 'downgrading' => 'Please contact us for downgrading', 'rollback' => 'Change to Kobold', 'subscribe' => 'Change to :tier monthly', 'subscribe_annual' => 'Change to :tier yearly', ], ], 'success' => [ 'alternative' => 'Your payment was registered. You will get a notification as soon as it is processed and your subscription is active.', 'callback' => 'Your subscription was successful. Your account will be updated as soon as our payment provider informs us of the change (this might take a few minutes).', 'currency' => 'Your preferred currency setting was updated.', 'subscribed' => 'Your subscription was successful! Don\'t forget to subscribe our newsletter to be notified of changes in Kanka. Also, you can check out our discord and become part of the community', ], 'tiers' => 'Subscription Tiers', 'trial_period' => 'Yearly subscriptions have a 14 day cancellation policy. Contact us at :email if you wish to cancel your yearly subscription and get a refund.', 'upgrade_downgrade' => [ 'button' => 'Upgrade & Downgrade Information', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Your bonuses stay enabled until the end of your payment period.', 'boosts' => 'The same happens for your boosted campaigns. Boosted features become invisible but aren\'t deleted when a campaign is no longer boosted.', 'kobold' => 'To cancel your subscription, change to the Kobold tier.', 'premium' => 'The same happens for your premium campaigns. Premium features become invisible but aren\'t deleted when a campaign is no longer premium.', ], 'title' => 'When cancelling your subscription', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Your current tier will stay active until the end of your current billing cycle, after which you will be downgraded to your new tier.', ], 'provide_reason' => 'If you can, please share with us why you are downgrading your subscription.', 'title' => 'When downgrading to a lower tier', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Your payment method will be billed immediately and you will have access to your new tier.', 'prorate' => 'When upgrading to a higher tier, you will only be billed the difference to your new tier.', ], 'title' => 'When upgrading to a higher tier', ], ], 'warnings' => [ 'incomplete' => 'We couldn\'t charge your credit card. Please update your credit card information, and we will try charging it again in the next few days. If it fails again, your subscription will be cancelled.', 'patreon' => 'Your account is currently linked with Patreon. Please unlink your account in your :patreon settings before switching to a Kanka subscription.', ], ], ]; ================================================ FILE: lang/en/sidebar.php ================================================ [ 'count' => 'Member of :member', 'created_campaigns' => 'Your Campaigns', 'follow_more' => 'Find campaigns', 'followed_campaigns'=> 'Followed Campaigns', 'new_campaign' => 'New Campaign', 'public_campaigns' => 'Public Campaigns', 'reorder' => 'Reorder', 'updated' => 'Updated', ], 'dashboard' => 'Dashboard', 'entity-creator' => 'Quick Creator', 'gallery' => 'Gallery', 'game' => 'Game', 'other' => 'Other', 'recent' => 'Recent changes', 'relations' => 'Relations', 'settings' => 'Settings', 'time' => 'Time', 'world' => 'World', ]; ================================================ FILE: lang/en/spotlights.php ================================================ [ 'actions' => [ 'retract' => 'Retract application', ], 'description' => 'Your application has been submitted and is now under review. You will receive a notification when it has been approved or rejected.', 'title' => 'Application applied', ], 'apply' => [ 'errors' => [ 'empty' => 'The question :field needs more content', ], ], 'approved' => [ 'description' => 'Congratulations! Your application has been approved and is now featured on the :spotlight page.', 'title' => 'Application approved', ], 'faq' => [ 'finisher' => 'Submitted doesn\'t guarantee selection. We read every application, but can\'t feature them all.', 'how' => [ 'a' => [ 'end' => 'Not follower count. Not popularity. Not membership status', 'lead' => 'We select 1-3 campaigns per month.', 'req1' => 'Clear identity and themes', 'req2' => 'Thoughtful worldbuilding', 'req3' => 'Interesting stories or approaches', 'requirements' => 'Selection is editorial, not competitive. We look for:', ], 'q' => 'How are campaigns selected?', ], 'reapply' => [ 'a' => 'Yes. If your campaign isn\'t selected, you\'re welcome to apply again later, especially if your world has evolved.', 'q' => 'Can I apply more than once?', ], 'selected' => [ 'a' => [ 'end' => 'You\'ll be notified before publication.', 'lead' => 'If selected:', 'req1' => 'Your campaign receives the Spotlighted Campaign achievement', 'req2' => 'We publish a feature on the :blog and :showcase', 'req3' => 'We might lightly edit your answers for clarity', ], 'q' => 'What happens if my campaign is selected?', ], 'what' => [ 'a' => 'The Spotlight highlights exceptional campaigns built with Kanka. Selected campaigns are featured on the Kanka Showcase and in a short interview-style blog post.', 'q' => 'What is the Spotlight?', ], 'who' => [ 'a' => [ 'end' => 'No minimum size. No system restriction.', 'lead' => 'Any public campaign on Kanka can apply', 'req1' => 'Be publically accessible', 'req2' => 'Show active use (content, history, or players)', 'req3' => 'Represent the kind of worlds others can learn from', 'requirements' => 'Your campaign should:', ], 'q' => 'Who can apply?', ], ], 'form' => [ 'actions' => [ 'apply' => 'Submit application', 'retract' => 'Retract application', 'save' => 'Save draft', ], 'draft' => 'This is a draft of your application. You can save it and come back to it later.', 'not-public' => 'This campaign isn\'t publically visible and cannot apply to the spotlight.', 'preset' => 'Tell us a little bit about :campaign and why you think it deserves to be featured. You can save and come back to these questions later.', 'required' => 'This field is required.', 'title' => 'Spotlight application form', ], 'overview' => [ 'cta' => 'Apply for spotlight with :name', 'not-public' => ':name isn\'t a publically visible campaign.', 'showcase' => 'View Showcase', ], 'placeholders' => [ 'inspiration' => 'Books, games, history, music, vibes', 'kanka' => 'Tell us a bit about why Kanka ended up being the right tool for your world', 'proud' => 'Could be lore, players, longevity, status', 'stories' => 'Tragedy, heroism, politics, found family, pure unadulterated chaos', 'time' => 'Months, years, decades, over the span of multiple lifetimes?', 'world' => 'Themes, emotions, conflicts (the hook)', ], 'questions' => [ 'inspiration' => 'What inspires this world', 'kanka' => 'Why do you run games in Kanka?', 'proud' => 'What are you most proud of?', 'share' => 'Allow the Kanka team to use your answer in their marketing materials.', 'stories' => 'What kind of stories emerge at the table?', 'time' => 'How long have you been building this world?', 'world' => 'What is this world really about?', ], 'rejected' => [ 'description' => 'Your application has been rejected. Please try again later.', 'title' => 'Application rejected', ], 'retract' => [ 'success' => 'Your application has been successfully retracted. You can now edit your application again.', ], 'rules' => <<<'TEXT' We select 1–3 campaigns each month to feature on the Kanka :showcase. Selection isn’t guaranteed. Spotlighted campaigns receive a permanent achievement and a published interview. TEXT , 'started' => 'To get started, select one of your campaigns.', 'title' => 'Apply for the Spotlight', ]; ================================================ FILE: lang/en/starter.php ================================================ [ 'name' => ':user\'s world', ], 'character1' => [ 'age' => '[20s/30s/40s]', 'background' => [ 'cur' => 'Currently [occupation/role]', 'loc' => 'Gew up in [hometown/region]', 'seeking' => 'Seeking [goal/motivation]', 'title' => 'Background', ], 'description' => [ 'intro' => '[A brief introduction to your character - who they are, where they\'re from, and what they want.]', 'template' => 'This is a template character you can customize. Replace the placeholder details below with your own character\'s information. You can always add more fields later.', 'tip' => 'Tip: Start with just a name and one-sentence description. You can expand the details as your world develops.', ], 'name' => '[Your Character Name]', 'personality' => [ 'trait1' => [ 'name' => 'Trait 1', 'value' => '[Brave/Cautious/Ambitious]', ], 'trait2' => [ 'name' => 'Trait 2', 'value' => '[Loyal/Independant/Cunning]', ], 'trait3' => [ 'name' => 'Trait 3', 'value' => '[Optimistic/Cynical/Pragmatic]', ], ], 'physical' => [ 'build' => [ 'name' => 'Build', 'value' => '[Lean/Average/Muscular]', ], 'features' => [ 'name' => 'Notable features', 'value' => '[Scares, tattoos, distinctive clothing]', ], ], ], 'character2' => [ 'description' => [ 'first' => 'A supporting character who helps or travels with :mention. Customize these details to fit your story.', 'second'=> 'Tip: Supporting characters don\'t need as much detail as protagonists. Focus on what makes them useful or interesting to your story.', ], 'name' => '[Allied Character Name]', 'relation' => '[Friend/Mentor/Rival]', 'skills' => [ 'first' => '[Skill 1: Combat/Magic/Healing/Crafting]', 'second'=> '[Skill 2: Social/Knowledge/Technical]', 'third' => '[Skill 3: Unique talent or specialty]', 'title' => 'Skills & Abilities', ], ], 'city' => [ 'description' => 'The beating heart of the kingdom, where merchants, nobles, and common folk mingle in bustling markets and grand plazas. The old city walls still stand, though the city has long since grown beyond them.', 'districts' => [ 'first' => 'Noble Quarter: Manor houses and gardens', 'fourth'=> 'Dockside: River port, warehouses', 'second'=> 'Market District: Trade, crafts, taverns', 'third' => 'Old Town: Original walled city', 'title' => 'Districts', ], 'locations' => [ 'first' => 'The Royal Palace (center of Noble Quarter)', 'second'=> 'The Grand Bazaar (Market District)', 'third' => 'The Rusty Sword Inn (popular adventurer hangout)', 'title' => 'Notable locations', ], 'name' => '[Your Capital City]', 'type' => 'Capital', ], 'kingdom' => [ 'description' => 'A prosperous realm known for its fertile farmlands and ancient forests. The royal family has ruled for three generations, maintaining peace through diplomacy and trade.', 'features' => [ 'capital' => [ 'name' => 'Capital', ], 'exp' => [ 'name' => 'Primary export', 'value' => 'Grain, timber', ], 'gov' => [ 'name' => 'Government', 'value' => 'Hereditary monarchy', ], 'pop' => [ 'name' => 'Population', 'value' => '~50\'000', ], 'title' => 'Notable features', ], 'name' => '[Your Kingdom Name]', 'recent' => [ 'first' => 'Increased bandit activity on eastern roads', 'second'=> 'Failed harvest in southern provinces', 'title' => 'Recent Events', ], 'type' => 'Kingdom', ], 'name' => ':name (example)', ]; ================================================ FILE: lang/en/subscription.php ================================================ [ 'main' => 'Subscribe to Kanka to unlock higher image uploads, an ad-free experience, :boosters and :more. We use :stripe to handle all billing, with no credit card information stored or transiting through our servers.', 'more' => 'more amazing features', ], 'errors' => [ 'grace' => 'Your current subscription ends on :date, after which point you can re-subscribe.', 'invalid_card_country' => [ 'brl' => 'We\'re sorry but we currently only accept BRL payments for customers with Brazilian credit cards. If you think this is a mistake, contact us at :email.', ], 'invalid_currency' => 'You previously had a subscription in :old, preventing you from having a new subscription in :new. Please switch your currency to :old, or contact us at :email if you wish to switch currencies.', ], ]; ================================================ FILE: lang/en/subscriptions/cancellation.php ================================================ [ 'label' => 'Anything else you\'d like to share?', 'placeholder' => 'We read every response.', ], 'intro' => 'Thanks for being a subscriber! Your subscription will remain active until :date, after which your premium campaigns will revert to standard and these benefits will be disabled.', 'loss' => [ 'ads' => [ 'title' => 'Ad-free experience for you and your players', ], 'discord' => [ 'title' => '":role" role in our Discord community', ], 'downgrade' => 'You can downgrade your subscription instead of cancelling it to keep most of your awesome benefits.', 'premium' => [ 'players' => '{1}:count player will lose access to premium features|[2,*]:count players will lose access to premium features', 'plugins' => '{1}Access to :count installed plugin|[2,*]Access to :count installed plugin', 'storage' => 'Your :current', 'title' => '{0}Premium status on ":campaign"|{1}Premium status on ":campaign" and :count other campaign|[2,*]Premium status on ":campaign" and :count other campaigns', ], 'roadmap' => 'Check the :roadmap, it might already be planned.', 'title' => 'Before you cancel, here\'s what will change:', ], 'pause' => [ 'button' => 'Pause subscription for 1-3 months', 'helper' => 'Keep everything. No charges. Resume anytime.', ], 'secondary' => [ 'competitor' => [ 'legend_keeper' => 'Legend Keeper', 'notion_obsidian' => 'Notion or Obsidian', 'other' => 'Something else', 'world_anvil' => 'World Anvil', ], 'financial' => [ 'forgot' => 'I forgot I was still subscribed', 'lower_price' => 'A lower price would help', 'not_often' => 'I just don\'t use it often enough to justify the cost', ], 'label' => 'Can you tell us a bit more?', 'not_for' => [ 'better_fit' => 'I found a better fit elsewhere', 'expected_different' => 'I expected something different', 'terminology' => 'The terminology doesn\'t match how I think', 'too_complex' => 'Too complex to get started', ], 'not_playing' => [ 'campaign_finished' => 'The campaign finished', 'group_fell_apart' => 'Group fell apart', 'on_break' => 'Taking a break, may return', ], 'not_using' => [ 'lost_motivation' => 'I lost motivation for the project', 'on_break' => 'My campaign is on a break but I may return', 'too_busy' => 'I\'ve been too busy lately', ], ], 'select_reason' => 'Please select a reason to continue.', ]; ================================================ FILE: lang/en/subscriptions/cancelled.php ================================================ [ 'adfree' => 'An ad-free experience', 'discord' => 'Subscriber-only :discord roles', 'helper' => 'Your subscription will remain active until :date. Until then, you\'ll continue to enjoy all subscription perks, including:', 'limit' => 'Increased upload limits', 'more' => 'And more!', 'premium' => 'Premium campaigns and their features', 'title' => 'Your perks are still active', ], 'change' => [ 'action' => 'Resubscribe now', 'helper' => 'We\'d love to have you back! You can resubscribe at any time to pick up right where you left off.', 'title' => 'Changed your mind?', ], 'contact' => [ 'feedback' => 'If there\'s something we could have done better, we\'d love to hear from you:', 'helper' => 'Even without a subscription, you\'re still a part of the Kanka community. Keep building your worlds, and feel free to reconnect whenever the time is right.', 'send' => 'Contact us with your feedback', 'title' => 'Stay in touch', ], 'next' => [ 'data' => 'Your data will stay safe, nothing is deleted, and you can re-subscribe anytime', 'discord' => 'You\'ll no longer have access to subscriber-only features and channels', 'helper' => 'Once your subscription ends:', 'premium' => 'Any campaigns with premium enabled will lose their premium status', 'title' => 'What happens after that?', ], 'seo_title' => 'Sorry to see you go', 'subtitle' => 'Thanks for having been a subscriber, your support has meant a lot to us. Here\'s what to expect now:', 'title' => 'Sorry to see you go, :name', ]; ================================================ FILE: lang/en/subscriptions/confirm.php ================================================ [ 'pay' => 'Pay :currency:amount now', 'paypal' => 'Pay :currency:amount with PayPal', 'subscribe' => 'Subscribe for :currency:amount', ], 'helpers' => [ 'auto-renew' => [ 'monthly' => 'Your subscription auto-renews every month. Your next billing date is :date.', 'none' => 'Paying with PayPal is a one-time payment and doesn\'t auto-renew. You can resubscribe once your subscription ends after :date.', 'yearly' => 'Your subscription auto-renews every 12 months. Your next billing date is :date.', ], 'paypal' => 'You will be redirected to PayPal to complete this transaction.', 'refund' => 'We offer a 14 day no-questions-asked refund policy on all yearly subscriptions. Simply email us at :email to initiate a refund process.', 'tiny' => 'Thanks for supporting a tiny team of passionate worldbuilders.', ], 'title' => ':name subscription', ]; ================================================ FILE: lang/en/subscriptions/faq.php ================================================ [ 'answer' => <<<'TEXT' Absolutely! You'll find a cancel button right on this page if you're currently subscribed. All subscription benefits remain active until the end of your billing period. Please note that PayPal subscriptions automatically end at their conclusion as they don't support automatic renewal. TEXT , 'question' => 'Can I cancel my subscription at any time?', ], 'cost' => [ 'answer' => 'Kanka offers three subscription tiers named after D&D monsters: Owlbear, Wyvern, and Elemental. Pricing varies based on your preferred currency (USD, EUR, or BRL). You\'ll enjoy two months for free when choosing an annual subscription instead of monthly billing.', 'question' => 'How much does a subscription cost?', ], 'data' => [ 'answer' => 'Rest assured, we never delete your data when a subscription ends. Premium campaigns simply revert to standard functionality, with premium features temporarily disabled. When you resubscribe, all your premium settings and data are immediately restored exactly as you left them.', 'question' => 'What happens to my data if I cancel my subscription?', ], 'discount' => [ 'answer' => 'Yes! We reward our annual subscribers with two months for free compared to monthly billing. This is our way of thanking you for your long-term commitment to Kanka.', 'question' => 'Are there any discounts for annual subscriptions?', ], 'downgrade' => [ 'answer' => 'You can change your subscription tier anytime. When upgrading, we\'ll only charge you the difference between your current and new plan for the remainder of your billing period. When downgrading, your new lower rate takes effect at your next renewal date, with no interruption to your current benefits.', 'question' => 'How do I upgrade/downgrade my subscription?', ], 'fail' => [ 'answer' => 'If a payment doesn\'t go through, we\'ll notify you by email right away and automatically attempt to charge your card up to three additional times. If these attempts are unsuccessful, your subscription will be paused. You can easily resolve this by updating your :billing information with a valid payment method.', 'question' => 'What happens if my payment fails?', ], 'help' => [ 'answer' => 'Your subscription pays for our time, our servers, and the freedom to keep Kanka sustainable without chasing growth at all costs. It lets us fix bugs faster, build features we actually believe in, and stay responsive to the community instead of investors. Quite simply: it keeps Kanka alive and getting better.', 'question' => 'How does my subscription help Kanka?', ], 'methods' => [ 'answer' => <<<'TEXT' We accept credit card and PayPal payments in USD, EUR, and BRL. Your payment security is important to us; all credit card processing is handled securely by our trusted payment provider, :stripe. TEXT , 'question' => 'What payment methods are accepted?', ], 'refund' => [ 'answer' => 'Yes! We offer a 14-day, no-questions-asked 100% refund policy for all yearly subscriptions. Simply drop us an email at :email asking for your refund, and we\'ll take care of everything for you.', 'question' => 'Do you offer refunds?', ], 'renewal' => [ 'answer' => 'Yes, for credit card subscriptions, we\'ll automatically renew your plan at the same rate when your billing period ends. PayPal subscriptions are the exception, they require manual renewal as PayPal doesn\'t support automatic billing continuation for our service.', 'question' => 'Will I be charged automatically when my subscription renews?', ], 'security' => [ 'answer' => 'Your financial security is our priority. We partner with :stripe, a PCI-compliant payment processor that maintains the highest standards in payment security. All sensitive payment details are handled and stored by Stripe under GDPR-compliant protocols, not on our servers.', 'question' => 'How secure is my payment information?', ], 'sharing' => [ 'answer' => 'Absolutely! Your subscription allows you to enable premium campaigns that benefit everyone involved. All campaign members enjoy premium features within that campaign, regardless of their personal subscription status, making Kanka perfect for collaborative worldbuilding.', 'question' => 'Can I share my account/subscription with others?', ], 'title' => 'Frequently Asked Questions', 'trial' => [ 'answer' => 'While we don\'t offer a traditional trial, Kanka\'s free version provides robust worldbuilding and campaign management tools to get you started. When you\'re ready for more, subscribing unlocks premium features like increased image upload limits, an ad-free experience, exclusive Discord roles, and additional enhancements to your worldbuilding toolkit.', 'question' => 'Is there a free trial available?', ], 'update' => [ 'answer' => 'Updating your billing details is simple, just visit your :billing page in your account settings. There you can modify payment methods, update card information, or change billing addresses as needed.', 'question' => 'How do I update my billing information?', ], 'why' => [ 'answer' => 'Kanka is built and maintained by a tiny, independent team. Subscriptions are what allow us to work on it long-term, improve it steadily, and keep it free of dark patterns. You\'re not paying a corporation, you\'re directly funding the people who design, build, and support the platform.', 'question' => 'Why does Kanka charge for subscriptions?', ], ]; ================================================ FILE: lang/en/subscriptions/finish.php ================================================ [ 'action' => 'Connect your Discord account', 'enjoy' => 'Head over to Discord to enjoy your new perks', 'helper' => 'As a subscriber, you unlock exclusive roles and private channels in our Discord community, but first, you\'ll need to connect your Discord account.', 'title' => 'Get your Discord perks', ], 'header' => 'You\'ve successfully subscribed, welcome to the inner circle of worldbuilders!', 'help' => [ 'contact-us' => 'contact us', 'helper' => 'If you have any questions or feedback, :contact-us or visit our :docs. We\'re here to make your worldbuilding experience magical.', 'title' => 'Need help?', ], 'next' => 'Your support helps us grow and keep improving Kanka for everyone. Here is what you can do next to unclock all the benefits from your subscription:', 'premium' => [ 'action' => 'Unlock', 'helper' => 'Unlock premium features like custom categories, access to the :plugins, and more!', 'title' => 'Enable premium features on a campaign', ], 'roadmap' => [ 'action' => 'View the roadmap & suggest features', 'helper' => 'We\'re building Kanka for you. Check out the public roadmap and suggest or vote on features you\'d love to see!', 'title' => 'Help shape the future of Kanka', ], 'title' => 'Thank you for supporting Kanka!', ]; ================================================ FILE: lang/en/subscriptions/free-trial.php ================================================ [ 'accept' => 'Start your free trial', 'magic' => 'No credit card required. Just pure magic.', ], 'final' => [ 'magic' => 'No commitments, no traps. Just more magic for your campaign.', 'title' => 'Start my 15-day trial now', ], 'header' => 'We\'ve seen your dedication in the realms of Kanka. To honor your journey, we\'re granting you a :what. No gold, gems, or credit card required.', 'included' => [ 'title' => 'What\'s included', 'upsell'=> [ 'action' => 'See all subscription tiers', 'pitch' => 'This is just the :tier tier. Ready to unlock even more?', ], ], 'pitch' => [ 'title' => 'Enjoy a 15-day free trial on us! Unlock all premium features and see what you\'ve been missing.', ], 'started' => [ 'header' => 'You\'ve successfully started your free trial, welcome to the inner circle of worldbuilders!', 'title' => 'Welcome to your free trial!', ], 'tease' => [ 'helper' => 'Want to unlock higher file uploads and more premium campaign slots? Visit the :subscription page to see what else awaits you.', 'title' => 'But you want more', ], 'title' => 'A New Quest Awaits, Adventurer!', 'what' => ':amount-day free trial of the :tier tier', 'why' => [ 'helper' => 'You\'ve created, explored, and grown your campaign. Your loyalty hasn\'t gone unnoticed. Think of this as a thank-you gift from the Kanka team.', 'title' => 'You\'ve earned this reward', ], ]; ================================================ FILE: lang/en/subscriptions/paypal-renew.php ================================================ [ 'permission' => 'Your subscription isn\'t set to expire in the next 14 days.', ], 'intro' => 'Your subscription expires on :date. Renewing before then will extend your access for one full year from that date, and allow you to continue enjoying your perks without being interrupted.', 'success' => 'Your subscription has been renewed successfully until :date.', ]; ================================================ FILE: lang/en/subscriptions/paypal.php ================================================ [ 'contact' => 'If this problem persists, contact us at :email.', 'failed' => 'PayPal failed to generate an invoice. Please try again.', 'incomplete' => 'PayPal payment incomplete. Please try again.', 'rejected' => 'PayPal generated an invalid invoice. Please try again.', ], ]; ================================================ FILE: lang/en/subscriptions/promos.php ================================================ [ 'inactive' => 'This promotion is no longer active.', 'invalid' => 'Unknown promotion.', 'only-new' => 'This promotion is only available to new subscribers.', ], ]; ================================================ FILE: lang/en/subscriptions/renew.php ================================================ [ 'renew' => 'Renew subscription', ], 'helper' => 'However, you can choose to renew your subscription to enjoy the benefits without interruptions.', 'success' => 'Your subscription has been renewed for one year.', 'title' => 'Subscription renewal', ]; ================================================ FILE: lang/en/subscriptions.php ================================================ [ 'failed' => 'Stripe was unabled to charge your payment method. Your subscription has consequently been deactivated.', ], ]; ================================================ FILE: lang/en/tags.php ================================================ [ 'actions' => [ 'add' => 'Add to tag', 'add_entity' => 'Add to entry', ], 'create' => [ 'attach_success' => '{1} Tagged :count entry.|[2,*] Tagged :count entries.', 'attach_success_entity' => 'Successfully updated tags for :name.', 'entity' => 'Add tags to :name', 'helper' => 'Tag one or several entries with :name', 'title' => 'Tag entries', ], ], 'create' => [ 'title' => 'New Tag', ], 'fields' => [ 'children' => 'Children', 'icon' => 'Icon', 'is_auto_applied' => 'Automatically apply to new entries', 'is_hidden' => 'Hidden from header and tooltip', ], 'helpers' => [ 'icon' => 'Use icons from :fontawesome or :rpgawesome. The icon will be shown instead of the tag name in lists.', 'no_children' => 'There are currently no entries tagged with this tag.', 'no_posts' => 'There are currently no articles tagged with this tag.', ], 'hints' => [ 'children' => 'This list contains all the entries that are assigned to this tag or the tag\'s children.', 'is_auto_applied' => 'Automatically apply this tag to newly created entries.', 'is_hidden' => 'Don\'t display this tag in an entry\'s header or tooltip.', 'tag' => 'This list contains all the tags are children of this tag or its children tags.', ], 'lists' => [ 'empty' => 'Use tags to group and filter entries across your world for easier navigation.', ], 'placeholders' => [ 'icon' => 'Try :example1 or :example2', 'type' => 'Lore, Wars, History, Religion, Vexillology', ], 'show' => [ 'tabs' => [ 'children' => 'Children', ], ], 'transfer' => [ 'entities' => [ 'helper' => 'Transfer entries tagged with :name to another tag.', 'title' => 'Transfer entries', ], 'fail' => 'Failed to transfer entries from :tag to :newTag', 'fail_post' => 'Failed to transfer articles from :tag to :newTag', 'posts' => [ 'helper' => 'Transfer articles tagged with :name to another tag.', 'title' => 'Transfer articles', ], 'success' => 'Successfully transferred entries from :tag to :newTag', 'success_post' => 'Successfully transferred articles from :tag to :newTag', 'transfer' => 'Transfer', ], ]; ================================================ FILE: lang/en/teams.php ================================================ [ 'lead' => 'Making worldbuilding fun and reliable', 'translations' => 'Translations', ], 'leads' => [ 'translators' => 'Kanka is translated to several languages thanks to these amazing contributors.', ], 'people'=> [ 'itzamna' => [ 'title' => 'Junior Developer', ], 'jay' => [ 'title' => 'Founder & Lead Developer', ], 'jon' => [ 'title' => 'Co-Founder & Business Manager', ], 'kaz' => [ 'title' => 'Bug Destroyer', ], 'laura' => [ 'title' => 'Social Media', ], ], ]; ================================================ FILE: lang/en/tiers.php ================================================ [ 'pay' => [ 'monthly' => 'Pay monthly', 'save' => 'Save 17% (2 months free)', 'yearly' => 'Pay yearly', ], 'subscribe' => [ 'choose' => 'Choose :tier', 'downgrade' => 'Downgrade to :tier', 'monthly' => ':tier monthly', 'upgrade' => 'Upgrade to :tier', 'yearly' => ':tier yearly', ], ], 'current' => 'Current subscription', 'features' => [ 'api_requests' => 'Up to :amount API requests per minute', 'boosters' => 'Campaign Boosters', 'discord' => 'Unique :discord role and channel', 'feature_influence' => 'Direct input into roadmap decisions', 'file_size' => ':size file uploads', 'import' => 'Campaign importer', 'modules' => ':count custom categories', 'nice_image' => 'Default thumbnail per category', 'no_ads' => 'Remove all ads', 'pagination' => 'Up to :amount elements visible per page', 'premium' => 'Each includes: :storage GiB storage, :modules custom categories', 'roadmap' => 'Upvote ideas in the roadmap', 'storage' => ':count GiB storage', ], 'helpers' => [ 'premium' => 'These bonuses apply to premium campaigns you unlock.', ], 'periods' => [ 'billed_monthly' => 'billed monthly', 'billed_yearly' => 'billed yearly', ], 'pricing' => ':currency :amount / month', 'ribbons' => [ 'best-value' => 'Recommended for GMs', 'current' => 'Current subscription', ], 'target' => [ 'elemental' => 'For worldbuilding pros managing multiple epic settings and expansive campaigns', 'owlbear' => 'Perfect for solo worldbuilders who want to supercharge their main campaign', 'wyvern' => 'Ideal for game masters running multiple adventures or collaborative storytellers', ], 'tiny' => 'tiny team', 'why' => 'Kanka is built by a :tiny of passionate worldbuilders. Subscriptions fund the people, servers, and time needed to keep improving the platform sustainably. There are no dark patterns, no investor pressure, and no infinite growth chasing. Development is steady and guided by our community.', ]; ================================================ FILE: lang/en/timelines/elements.php ================================================ [ 'copy_with_name' => 'Copy advanced mention with element name', 'success' => 'Advanced mention to element copied to the clipboard.', ], 'create' => [ 'success' => 'Element added to the timeline.', 'title' => 'New Timeline Element', ], 'delete' => [ 'success' => 'Element :name removed.', ], 'edit' => [ 'success' => 'Element updated.', 'title' => 'Edit Timeline Element', ], 'fields' => [ 'date' => 'Date', 'era' => 'Era', 'icon' => 'Icon', 'use_entity_entry' => 'Display the attached entry\'s entry below. This element\'s text will be displayed first if it is present.', 'use_event_date' => 'Use linked event\'s date.', ], 'helpers' => [ 'date' => 'If the element is linked to an event, display the event\'s date.', 'entity_is_private' => 'The element\'s entry is private.', 'icon' => 'Copy the CSS class of an icon from :fontawesome or :rpgawesome.', 'is_collapsed' => 'The element displays collapsed by default.', ], 'placeholders' => [ 'date' => 'e.g. March 42nd or 1332-1337', 'name' => 'Required if no entry selected', 'position' => 'Position in the list of elements for the era. Leave blank to add to the end.', ], ]; ================================================ FILE: lang/en/timelines/eras.php ================================================ [ 'add' => 'New era', ], 'bulks' => [ 'delete' => '{0} Removed :count era.|{1} Removed :count era.|[2,*] Removed :count eras.', ], 'create' => [ 'success' => 'Era :name created.', 'title' => 'New Era', ], 'delete' => [ 'success' => 'Era :name deleted.', ], 'edit' => [ 'success' => 'Era :name updated.', 'title' => 'Edit Era :name', ], 'fields' => [ 'abbreviation' => 'Abbreviation', 'end_year' => 'End Year', 'is_collapsed' => 'Collapsed', 'start_year' => 'Start Year', ], 'helpers' => [ 'eras' => 'The timeline needs to be created before eras can be added to it.', 'is_collapsed' => 'Era is collapsed (minimised) by default.', 'primary' => 'Separate your timeline into eras. A timeline needs at least one era to properly work.', ], 'index' => [ 'title' => 'Eras of :name', ], 'placeholders' => [ 'abbreviation' => 'AD, BC, BCE', 'end_year' => 'Year the era ends. Leave blank if this is the current era.', 'name' => 'Modern Era, Bronze Age, Galactic Wars', 'start_year' => 'Year the era starts. Leave blank if this is the first era.', ], ]; ================================================ FILE: lang/en/timelines.php ================================================ [ 'add_element' => 'Add element to :era', 'back' => 'Back to :name', 'save_order' => 'Save new order', ], 'create' => [ 'title' => 'New Timeline', ], 'fields' => [ 'copy_elements' => 'Copy Elements', 'copy_eras' => 'Copy Eras', 'eras' => 'Eras', 'reverse_order' => 'Reverse era order', ], 'helpers' => [ 'no_era_v2' => 'This timeline currently doesn\'t have any eras. Add one or several eras to it, after which you can add elements to the eras here.', 'reverse_order' => 'Enable to display eras in reverse chronological order (older era first)', ], 'lists' => [ 'empty' => 'Build a visual timeline to record major events and track how your world has evolved.', ], 'placeholders' => [ 'type' => 'Primary, World chronicle, Kingdom legacy', ], 'reorder' => [ 'empty' => 'Add eras and elements to the timeline to be able to reorder it.', 'success' => ':name successfully reordered.', 'title' => 'Reorder :name', ], 'show' => [ 'tabs' => [ 'reorder-elements' => 'Reorder elements', ], ], ]; ================================================ FILE: lang/en/tiptap.php ================================================ 'Share feedback', 'survey'=> 'Trying the new editor? :share (it takes just 2 minutes)', ]; ================================================ FILE: lang/en/tutorials/actions.php ================================================ 'Close', 'disable' => 'Disable tutorials', 'next' => 'Next', 'ok' => 'OK', ]; ================================================ FILE: lang/en/tutorials/characters.php ================================================ [ 'first' => 'This interface lists all the characters of your world. As you add more, they will be displayed here. Filters will become useful to find what you are looking for in the future, but right now there is nothing. Let\'s create your first character!', 'title' => 'Characters', ], 'character_2' => [ 'first' => 'Yoyo', 'title' => 'Creating a character', ], ]; ================================================ FILE: lang/en/tutorials/home.php ================================================ [ 'first' => 'The dashboard is your world\'s home page. It looks pretty empty right now, but don\'t fret.', 'second'=> 'Let\'s start simple by creating your first character. In the sidebar to the left, you can see the list of various categories of Kanka. Click on next and the characters sidebar link will close. Click on that.', 'title' => 'Dashboard', ], 'welcome' => [ 'first' => 'We\'ve created your first campaign for you. Getting started with a worldbuilding tool can be a overwhelming, so we\'ve create a small tutorial for you. This will guide you through creating your first entries and getting a basic overview of what is possible in Kanka.', 'second'=> 'If you are already experienced with Kanka, you can disable our tutorials all together.', 'title' => 'Welcome to Kanka, :user!', ], ]; ================================================ FILE: lang/en/users/profile.php ================================================ [ 'wordsmith' => 'A true wordsmith! Winner of a worldbuilding prompt.', ], 'fields' => [ 'achievements' => 'Achievements', 'banned' => 'This user has been banned', 'entities_created' => 'Entries created :help :count', 'member_since' => 'Member since :date', 'public_campaigns' => 'Public campaigns', 'subscriber_since' => 'Subscriber since :date', ], 'helpers' => [ 'entities_created' => 'This value is recalculated every day.', ], 'title' => ':name Profile', ]; ================================================ FILE: lang/en/validation.php ================================================ 'The :attribute must be accepted.', 'active_url' => 'The :attribute is not a valid URL.', 'after' => 'The :attribute must be a date after :date.', 'after_or_equal' => 'The :attribute must be a date after or equal to :date.', 'alpha' => 'The :attribute may only contain letters.', 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', 'alpha_num' => 'The :attribute may only contain letters and numbers.', 'array' => 'The :attribute must be an array.', 'attribute_unique' => 'The entitie\'s attribute names should be different', 'before' => 'The :attribute must be a date before :date.', 'before_or_equal' => 'The :attribute must be a date before or equal to :date.', 'between' => [ 'numeric' => 'The :attribute must be between :min and :max.', 'file' => 'The :attribute must be between :min and :max kilobytes.', 'string' => 'The :attribute must be between :min and :max characters.', 'array' => 'The :attribute must have between :min and :max items.', ], 'boolean' => 'The :attribute field must be true or false.', 'confirmed' => 'The :attribute confirmation does not match.', 'date' => 'The :attribute is not a valid date.', 'date_equals' => 'The :attribute must be a date equal to :date.', 'date_format' => 'The :attribute does not match the format :format.', 'different' => 'The :attribute and :other must be different.', 'digits' => 'The :attribute must be :digits digits.', 'digits_between' => 'The :attribute must be between :min and :max digits.', 'dimensions' => 'The :attribute has invalid image dimensions.', 'distinct' => 'The :attribute field has a duplicate value.', 'email' => 'The :attribute must be a valid email address.', 'exists' => 'The selected :attribute is invalid.', 'file' => 'The :attribute must be a file.', 'filled' => 'The :attribute field must have a value.', 'gt' => [ 'numeric' => 'The :attribute must be greater than :value.', 'file' => 'The :attribute must be greater than :value kilobytes.', 'string' => 'The :attribute must be greater than :value characters.', 'array' => 'The :attribute must have more than :value items.', ], 'gte' => [ 'numeric' => 'The :attribute must be greater than or equal :value.', 'file' => 'The :attribute must be greater than or equal :value kilobytes.', 'string' => 'The :attribute must be greater than or equal :value characters.', 'array' => 'The :attribute must have :value items or more.', ], 'image' => 'The :attribute must be an image.', 'in' => 'The selected :attribute is invalid.', 'in_array' => 'The :attribute field does not exist in :other.', 'integer' => 'The :attribute must be an integer.', 'ip' => 'The :attribute must be a valid IP address.', 'ipv4' => 'The :attribute must be a valid IPv4 address.', 'ipv6' => 'The :attribute must be a valid IPv6 address.', 'json' => 'The :attribute must be a valid JSON string.', 'lt' => [ 'numeric' => 'The :attribute must be less than :value.', 'file' => 'The :attribute must be less than :value kilobytes.', 'string' => 'The :attribute must be less than :value characters.', 'array' => 'The :attribute must have less than :value items.', ], 'lte' => [ 'numeric' => 'The :attribute must be less than or equal :value.', 'file' => 'The :attribute must be less than or equal :value kilobytes.', 'string' => 'The :attribute must be less than or equal :value characters.', 'array' => 'The :attribute must not have more than :value items.', ], 'max' => [ 'numeric' => 'The :attribute may not be greater than :max.', 'file' => 'The :attribute may not be greater than :max kilobytes.', 'string' => 'The :attribute may not be greater than :max characters.', 'array' => 'The :attribute may not have more than :max items.', ], 'mimes' => 'The :attribute must be a file of type: :values.', 'mimetypes' => 'The :attribute must be a file of type: :values.', 'min' => [ 'numeric' => 'The :attribute must be at least :min.', 'file' => 'The :attribute must be at least :min kilobytes.', 'string' => 'The :attribute must be at least :min characters.', 'array' => 'The :attribute must have at least :min items.', ], 'not_in' => 'The selected :attribute is invalid.', 'not_regex' => 'The :attribute format is invalid.', 'numeric' => 'The :attribute must be a number.', 'present' => 'The :attribute field must be present.', 'regex' => 'The :attribute format is invalid.', 'required' => 'The :attribute field is required.', 'required_if' => 'The :attribute field is required when :other is :value.', 'required_unless' => 'The :attribute field is required unless :other is in :values.', 'required_with' => 'The :attribute field is required when :values is present.', 'required_with_all' => 'The :attribute field is required when :values are present.', 'required_without' => 'The :attribute field is required when :values is not present.', 'required_without_all' => 'The :attribute field is required when none of :values are present.', 'same' => 'The :attribute and :other must match.', 'size' => [ 'numeric' => 'The :attribute must be :size.', 'file' => 'The :attribute must be :size kilobytes.', 'string' => 'The :attribute must be :size characters.', 'array' => 'The :attribute must contain :size items.', ], 'starts_with' => 'The :attribute must start with one of the following: :values', 'string' => 'The :attribute must be a string.', 'timezone' => 'The :attribute must be a valid zone.', 'unique' => 'The :attribute has already been taken.', 'uploaded' => 'The :attribute failed to upload.', 'url' => 'The :attribute format is invalid.', 'uuid' => 'The :attribute must be a valid UUID.', 'goodbye' => 'You must write :code to confirm the deletion of your account', 'delete_campaign' => 'You must write ":code" to confirm the deletion of the campaign', 'confirmation' => 'You must write ":code" to confirm this action.', 'forbidden_letter' => 'The :attribute cannot contain the letter ":letter."', //'entity_file' => 'Allowed extensions: :formats', 'fontawesome' => 'The icon must be the CSS class only, not the whole HTML. For example, use :example.', 'nested_loop' => 'Selecting :parent as the parent would cause a recursive loop. Please select another parent.', 'social_login' => 'Please log in using :provider.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap our attribute placeholder | with something more reader friendly such as "E-Mail Address" instead | of "email". This simply helps us make our message more expressive. | */ 'attributes' => [], ]; ================================================ FILE: lang/en/visibilities.php ================================================ [ 'admin' => 'Only members of the Admin role can view this element.', 'admin-self' => 'Only you and members of the Admin role can view this element.', 'all' => 'Everyone can view this element.', 'members' => 'Only members of the campaign can view this element.', 'self' => 'Only you can see this element.', ], 'picker' => [ 'admin' => 'Only visible to members of the :admin role.', 'admin-self' => 'Only you and members of the :admin role can see this.', 'all' => 'Anyone who can see :entity can see this.', 'failed' => 'Failed to update visibility.', 'member' => 'Only visible to campaign members. Useful for public campaigns.', 'self' => 'Only you can see this.', ], 'title' => 'Updating visibility', 'toast' => 'Successfully updated visibility.', 'tooltip' => 'Click to learn about the various visibility options and what they mean in our documentation.', ]; ================================================ FILE: lang/en/whiteboards/draw.php ================================================ [ 'add-circle' => 'Add circle', 'add-entity' => 'Add entry', 'add-image' => 'Add image', 'add-square' => 'Add square', 'add-text' => 'Add text', 'duplicate' => 'Duplicate selected', 'end-drawing' => 'End drawing', 'lock' => 'Lock', 'push-to-back' => 'Push to back', 'push-to-front' => 'Push to front', 'start-drawing' => 'Start drawing', 'unlock' => 'Unlock', ], 'entity-search' => [ 'placeholder' => 'Type an entry\'s name or alias', 'title' => 'Entry search', ], 'errors' => [ 'websockets' => [ 'disconnected' => 'The connection to the websocket was lost. Please try again.', 'error' => 'An error occurred while connecting to the websocket server.', 'unavailable' => 'The websocket server is unavailable. Please try again later.', ], ], 'fields' => [ 'color' => 'Colour', ], 'pen' => [ 'large-stroke' => 'Large stroke', 'thin-stroke' => 'Thin stroke', ], 'reset' => [ 'helper' => 'Are you sure you want to reset the whiteboard? This action cannot be undone.', 'title' => 'Reset Whiteboard', ], 'roles' => [ 'edit' => 'This user can edit the whiteboard', 'view' => 'This user can view the whiteboard', ], 'toast' => [ 'copy' => [ 'success' => 'Elements copied to clipboard.', ], 'paste' => [ 'error' => 'Something went wrong', ], ], ]; ================================================ FILE: lang/en/whiteboards.php ================================================ [ 'draw' => 'Draw', ], 'create' => [ 'title' => 'New Whiteboard', ], 'cta' => [ 'text' => 'To unlock whiteboards, a campaign needs to be made premium by a member at the :wyvern or :elemental tier.', 'title' => 'Whiteboards are a special premium feature', ], 'lists' => [ 'empty' => 'Use a whiteboard to visually organize ideas, relationships, or story structure.', ], 'placeholders' => [ 'type' => 'Idea, relationships, story structure', ], ]; ================================================ FILE: lang/en-US/bookmarks.php ================================================ [ 'customise' => 'Customize sidebar', ], ]; ================================================ FILE: lang/en-US/calendars/weather.php ================================================ [], 'placeholders' => [ 'colour' => 'Color', ], ]; ================================================ FILE: lang/en-US/campaigns/builder.php ================================================ 'Visually build a theme for the campaign with this interface. Scroll down to see how the changes would impact various elements of the campaign. When a color is selected, a "contrasting" color is automatically selected for coloring text. Learn more about theming in our :docs.', 'pitch' => 'Psst, we have a theme builder if all you want to do is change some of the colors of the campaign 😉', ]; ================================================ FILE: lang/en-US/campaigns.php ================================================ [], 'fields' => [], 'organisations' => [ 'create' => [ 'success' => 'Character added to organization.', 'title' => 'New Organization for :name', ], 'destroy' => [ 'success' => 'Character organization removed.', ], 'edit' => [ 'success' => 'Character organization updated.', 'title' => 'Update Organization for :name', ], ], 'placeholders' => [], 'show' => [], ]; ================================================ FILE: lang/en-US/colours.php ================================================ 'Gray', ]; ================================================ FILE: lang/en-US/crud.php ================================================ [ 'colour' => 'Color', ], 'placeholders' => [ 'organisation' => 'Choose an organization', ], ]; ================================================ FILE: lang/en-US/dashboard.php ================================================ [ 'customise' => 'Customize dashboard', ], ]; ================================================ FILE: lang/en-US/emails/welcome.php ================================================ [ 'unorganised' => 'Unorganized', ], ]; ================================================ FILE: lang/en-US/entities.php ================================================ [], 'organisation' => 'Organization', 'organisations' => 'Organizations', ]; ================================================ FILE: lang/en-US/front.php ================================================ [ 'layers' => [ 'title' => 'Characters, Families, Locations', ], ], 'home' => [], 'master' => [ 'description' => ':kanka is a community driven worldbuilding and tabletop RPG campaign management tool perfect for worldbuilders and game masters alike. We help you create and organise your campaigns and worlds with our @mentions system and a whole range of features such as calendars, interactive maps, timelines, organizations, families, and as many characters as you can come up with!', 'title' => 'Kanka', ], ]; ================================================ FILE: lang/en-US/helpers.php ================================================ [ 'type' => 'Weapon, Potion, Artifact', ], ]; ================================================ FILE: lang/en-US/locations.php ================================================ [ 'organisations' => 'View all organizations in this location and its children locations, or just those directly located here.', ], 'map' => [ 'points' => [ 'fields' => [ 'colour' => 'Color', ], ], ], 'organisations' => [ 'title' => 'Location :name Organizations', ], 'show' => [], ]; ================================================ FILE: lang/en-US/maps/markers.php ================================================ [ 'font_colour' => 'Icon Color', 'polygon_style' => [ 'stroke' => 'Stroke color', ], ], ]; ================================================ FILE: lang/en-US/organisations.php ================================================ [ 'title' => 'New Organization', ], 'destroy' => [], 'edit' => [], 'fields' => [], 'helpers' => [], 'index' => [], 'members' => [ 'destroy' => [ 'success' => 'Member removed from the organization.', ], 'helpers' => [ 'all_members' => 'All characters that are members of this organizations and it\'s sub-organizations.', 'members' => 'All characters that are members of this organization.', ], ], 'organisations' => [], 'placeholders' => [], 'quests' => [], 'show' => [], ]; ================================================ FILE: lang/en-US/quests.php ================================================ [], ]; ================================================ FILE: lang/en-US/sidebar.php ================================================ 'Organizations', ]; ================================================ FILE: lang/es/abilities.php ================================================ [], 'children' => [ 'actions' => [ 'attach' => 'Vincular a entidades', ], 'create' => [ 'attach_success' => '{1}Se ha vinculado la habilidad :name a :count entidad.|[2,*] Se ha vinculado la habilidad :name a :count entidades.', 'helper' => 'Vincular :name a una o varias entidades.', 'title' => 'Vincular entidades', ], 'description' => 'Entidades con esta habilidad', 'title' => 'Entidades de la habilidad :name', ], 'create' => [ 'title' => 'Nueva habilidad', ], 'destroy' => [], 'edit' => [], 'entities' => [], 'fields' => [ 'charges' => 'Usos', ], 'helpers' => [], 'index' => [], 'lists' => [ 'empty' => 'Agrega poderes, hechizos o talentos. Muchos creadores usan esto para modelar clases de D&D.', ], 'placeholders' => [ 'charges' => 'Cantidad de usos. Puedes hacer referencia a un atributo con {Nivel}*{CHA}', 'name' => 'Bola de fuego, Alerta, Puñalada trasera', 'type' => 'Hechizo, Proeza, Ataque', ], 'reorder' => [ 'parentless' => 'Sin padre', 'success' => 'Habilidades reordenadas exitosamente.', 'title' => 'Reordenar las habilidades', ], 'show' => [ 'tabs' => [ 'reorder' => 'Reordenar Habilidades', ], ], ]; ================================================ FILE: lang/es/account/email.php ================================================ [ 'update' => 'Actualizar correo electrónico', ], 'fields' => [ 'email' => 'Nueva dirección de correo electrónico.', ], 'helpers' => [ 'email' => 'Asegúrate de que esté bien escrito.', ], 'subtitle' => 'Cambia la dirección de correo electrónico asociada a tu cuenta.', 'title' => 'Actualiza tu dirección de correo electrónico', ]; ================================================ FILE: lang/es/account/password.php ================================================ [ 'update' => 'Actualizar contraseña', ], 'fields' => [ 'password' => 'Nueva contraseña', ], 'helpers' => [ 'password' => 'Usa un gestor de contraseñas para generar una contraseña segura.', 'password_confirmation' => '¡No te equivoques!', ], 'subtitle' => 'Cambia la contraseña de tu cuenta. Esto cerrará sesión en todos los demás dispositivos.', 'title' => 'Actualiza la contraseña de tu cuenta.', ]; ================================================ FILE: lang/es/account/social.php ================================================ 'Inicia sesión con :provider', 'subtitle' => 'Cambia de un inicio de sesión gestionado por :provider a uno gestionado por Kanka, donde inicias sesión con tu correo electrónico y contraseña.', 'title' => 'Cambiar a un inicio de sesión con Kanka', ]; ================================================ FILE: lang/es/assistance.php ================================================ [ 'campaign' => 'Campaña', ], 'opening' => 'Un miembro del equipo de Kanka te ha enviado a esta página con el propósito de ayudarte.', 'placeholders' => [ 'campaign' => 'Selecciona una campaña de la que seas administrador', ], 'select' => 'Selecciona una campaña de la que seas administrador en el menú desplegable que aparece a continuación para generar un token especial de uso único, que permitirá a un miembro del equipo de Kanka unirse temporalmente a la campaña como administrador.', 'success' => [ 'opening' => 'Tu token de asistencia se ha generado correctamente. El equipo de Kanka ha sido notificado y se unirá a tu campaña en breve para ayudarte. Normalmente nos pondremos en contacto contigo a través de :discord si necesitamos coordinar algo directamente.', 'secret' => 'Solo un miembro verificado del equipo Kanka puede utilizar este token, es inútil para cualquier otra persona, por lo que no hay necesidad de tratarlo como un secreto.', 'token' => 'Tu token de asistencia:', ], 'title' => 'Asistencia', ]; ================================================ FILE: lang/es/attribute_templates.php ================================================ [], 'create' => [ 'title' => 'Nueva Plantilla de Atributos', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'auto_apply' => 'Aplicar automáticamente', 'is_enabled' => 'Habilitado', ], 'hints' => [ 'automatic' => 'Atributos aplicados automáticamente desde la plantilla de atributos :link.', 'automatic_apply' => '{1} El siguiente atributo se aplicó automáticamente desde :link | [2,] Los :count siguientes atributos se aplicaron automáticamente desde :link.', 'entity_type' => 'Si se habilita, al crear una nueva entidad de este tipo se le añadirá esta plantilla de atributos automáticamente.', 'is_disabled' => 'Esta plantilla está deshabilitada.', 'is_enabled' => 'Habilita esta plantilla para usarla en la campaña.', 'parent_attribute_template' => 'Esta plantilla de atributos puede ser descendiente de otra plantilla de atributos. Al aplicar una plantilla, se aplicará con todos sus descendientes.', ], 'index' => [], 'lists' => [ 'empty' => 'Crea plantillas para reutilizar atributos comunes en múltiples entidades.', ], 'placeholders' => [ 'name' => 'Nombre de la plantilla de atributos', ], 'show' => [], ]; ================================================ FILE: lang/es/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Error', 'rendering' => 'Ha habido un error al renderizar el plugin del marketplace. Para más información, ponte en contacto con el creador del plugin.', ], ], 'helpers' => [], 'list' => [ 'sheets' => 'Hojas de personaje', ], 'pitch' => 'Encuentra y añade hojas de personaje del :marketplace a una :boosted-campaign.', ]; ================================================ FILE: lang/es/auth.php ================================================ [ 'permanent' => 'Has sido baneado permanentemente.', 'temporary' => '{1} Has sido baneado por :days dia.|[2,*] Has sido baneado por :days dias.', ], 'confirm' => [ 'confirm' => 'Confirmar', 'error' => 'Contraseña no válida, inténtalo de nuevo.', 'helper' => 'Por favor confirma tu contraseña antes de continuar.', 'title' => 'Confirmación de contraseña.', ], 'continue' => [ 'facebook' => 'Continuar con Facebook', 'google' => 'Continuar con Google', 'x' => 'Continuar con X', ], 'failed' => 'Los datos introducidos no coinciden con ningún usuario registrado.', 'helpers' => [ 'password' => 'Mostrar/ocultar contraseña', ], 'login' => [ 'fields' => [ '2fa' => 'Contraseña de un solo uso', 'email' => 'Email', 'password' => 'Contraseña', ], 'no-account' => '¿No tienes una cuenta?', 'or' => 'o bien', 'password_forgotten' => '¿Olvidaste tu contraseña?', 'sign-up' => 'Registrarse', 'submit' => 'Acceder', 'title' => 'Acceder', ], 'register' => [ 'already' => '¿Ya tienes una cuenta? :login', 'errors' => [ 'email_already_taken' => 'Ya existe una cuenta asociada a este correo electrónico.', 'general_error' => 'Ha ocurrido un error mientras se registraba la cuenta. Inténtalo de nuevo.', ], 'fields' => [ 'email' => 'Correo electrónico', 'name' => 'Usuario', 'password' => 'Contraseña', ], 'log-in' => 'Iniciar sesión', 'submit' => 'Registrarse', 'title' => 'Registrarse', 'tos' => 'Al registrar una cuenta, usted acepta nuestros :terms y :privacy.', ], 'reset' => [ 'fields' => [ 'email' => 'Dirección de correo electronico', 'password' => 'Contraseña', 'password_confirmation' => 'Confirma la contraseña', ], 'send' => 'Enviar enlace para restablecer la contraseña', 'submit' => 'Restablecer contraseña', 'title' => 'Restablecer contraseña', ], 'tfa' => [ 'helper' => 'La autenticación de dos factores está activada. Introduzca la contraseña de un solo uso (OTP) proporcionada por su aplicación de autenticación.', 'title' => 'Autenticación de dos factores', ], 'throttle' => 'Demasiados intentos de acceso. Por favor inténtelo en :seconds segundos.', 'x-twitter' => 'X antes conocido como Twitter', ]; ================================================ FILE: lang/es/billing/information.php ================================================ [ 'update' => 'Actualizar información', ], 'helper' => 'Tu dirección comercial, número de IVA, etc., pueden añadirse a todos tus recibos.', 'title' => 'Actualizar información de facturación', ]; ================================================ FILE: lang/es/billing/invoices.php ================================================ [ 'download' => 'Descargar PDF', ], 'description' => 'Mostrando facturas de los últimos 24 meses.', 'empty' => 'No se encontraron facturas', 'fields' => [ 'amount' => 'Importe', 'date' => 'Fecha', 'invoice' => 'Factura', 'status' => 'Estado', ], 'paypal' => 'Ten en cuenta que sólo los pagos realizados a través de Stripe y no a través de PayPal son visibles aquí.', 'status' => [ 'paid' => 'Pagado', 'pending' => 'Pendiente', ], 'title' => 'Historial de facturación', ]; ================================================ FILE: lang/es/billing/menu.php ================================================ 'Historial de facturación', 'overview' => 'Resumen', 'payment-method' => 'Método de pago', ]; ================================================ FILE: lang/es/billing/payment_methods.php ================================================ 'Método de pago', 'types' => [ 'card' => 'Tarjeta', ], ]; ================================================ FILE: lang/es/bookmarks.php ================================================ [ 'customise' => 'Personalizar la barra lateral', ], 'create' => [ 'title' => 'Nuevo acceso directo', ], 'destroy' => [], 'edit' => [ 'title' => 'Acceso directo :name', ], 'fields' => [ 'active' => 'Activo', 'dashboard' => 'Tablero', 'default_dashboard' => 'Cuadro de mandos por defecto', 'filters' => 'Filtros', 'menu' => 'Menú', 'position' => 'Posición', 'random_type' => 'Tipo de entidad aleatorio', 'selector' => 'Configuración del acceso directo', 'target' => 'Destino', ], 'helpers' => [ 'active' => 'Los accesos directos inactivos no aparecerán en la barra lateral.', 'css' => 'Agrega una clase CSS que se añadirá al enlace del acceso directo en la barra lateral.', 'dashboard' => 'Puedes hacer que un acceso directo lleve directamente a uno de los tableros personalizados de la campaña.', 'default_dashboard' => 'Enlace al panel de control predeterminado de la campaña. Es necesario seleccionar un panel de control personalizado.', 'entity' => 'Configura este acceso directo para acceder directamente a una entidad. El campo de :tab controla qué pestaña estará seleccionada. El campo de :menu controla qué subpágina de la entidad se abrirá.', 'position' => 'Usa este campo para controlar en qué orden ascendente aparecen los enlaces en el acceso directo.', 'random' => 'Usa este campo para tener un acceso directo a una entidad aleatoria. Puedes filtrar el enlace para que solo vaya a un tipo específico de entidad.', 'selector' => 'Configura adónde dirige este acceso directo cuando un usuario le hace clic en la barra lateral.', 'type' => 'Configura este acceso directo para ir directamente a una lista de entidades. Para filtrar los resultados, copia las partes de la URL de la lista filtrada a partir del símbolo :? en el campo de :filter.', ], 'index' => [], 'lists' => [ 'empty' => 'Guarda marcadores de tus entidades más usadas o listas filtradas para un acceso más rápido.', ], 'placeholders' => [ 'filters' => 'location_id=15&type=ciudad', 'menu' => 'Subpágina del menú (usa la última parte de la url)', 'tab' => 'Historia, Relaciones, Notas', ], 'random_no_entity' => 'No se ha encontrado ninguna entidad aleatoria.', 'random_types' => [ 'any' => 'Cualquier entidad', ], 'reorder' => [ 'success' => 'Enlaces reordenados.', 'title' => 'Reordenar los enlaces', ], 'show' => [], 'targets' => [ 'dashboard' => 'Uno de los paneles de la campaña', 'entity' => 'Una sola entidad', 'random' => 'Una entidad aleatoria', 'select' => 'Elige una opción', 'type' => 'Lista de entidades de un tipo/módulo específico', ], 'visibilities' => [ 'is_active' => 'Mostrar el acceso directo en la barra lateral', ], ]; ================================================ FILE: lang/es/bragi/backstory.php ================================================ 'Escribe una breve historia de fondo del personaje inspirada en esta información. Adapta el tono y los detalles a los sistemas de juego y géneros seleccionados. Puedes incluir elementos como la apariencia del personaje, su origen, creencias, relaciones, metas, defectos o experiencias notables — lo que mejor se ajuste al personaje.', 'setup' => [ 'gender' => 'Género: :gender', 'genres' => 'Géneros: :genres', 'name' => 'Nombre del personaje: :name', 'prompt' => 'Prompt: ":prompt"', 'pronouns' => 'Pronombres: :pronouns', 'systems' => 'Sistemas de juego: :systems', ], 'system' => 'Eres un narrador y escritor profesional de personajes para TTRPG. Creas historias de fondo inmersivas y emocionalmente resonantes para juegos de rol de mesa. Tu estilo se adapta al tono y la ambientación del juego. Normalmente escribes de 2 a 4 párrafos y hasta 400 palabras, entrelazando apariencia, historia, creencias, motivaciones y defectos.', ]; ================================================ FILE: lang/es/bragi.php ================================================ [ 'generate' => 'Generar', 'insert' => 'Usar', ], 'errors' => [ 'invalid-sub' => 'Para acceder a esta función, debes tener una suscripción Wyvern o Elemental.', 'out-of-tokens' => '¡Te has quedado sin tokens! Obtendrás más en :date.', ], 'here' => 'aquí', 'intro' => '¡Hola! Soy :name, una IA que está aquí para ayudarte a generar historias de origen para tus personajes en Kanka. Puedes aprender más sobre mí :here.', 'kankappy' => 'Es discípulo de Kankappy en secreto.', 'loading' => 'Por favor espera, estoy pensando mucho y puede tomarme hasta un minuto!', 'placeholders' => [ 'prompt' => 'Proporciona un mensaje que se transformará en una historia de origen.', ], 'token-limit' => 'Actualmente tienes :amount tokens. Cada vez que genero una historia de origen, se consume un token ¡Úsalos sabiamente!', ]; ================================================ FILE: lang/es/calendars/weather.php ================================================ [], 'create' => [ 'helper' => 'Agrega información meteorológica que aparecerá en el calendario.', 'success' => 'Clima añadido.', 'title' => 'Nuevo fenómeno climático', ], 'destroy' => [ 'success' => 'Clima eliminado.', ], 'edit' => [ 'success' => 'Clima actualizado.', 'title' => 'Actualizar clima', ], 'fields' => [ 'effect' => 'Efecto', 'name' => 'Nombre', 'precipitation' => 'Precipitación', 'temperature' => 'Temperatura', 'weather' => 'Clima', 'wind' => 'Viento', ], 'options' => [ 'weather' => [ 'bolt' => 'Tormentas', 'cloud' => 'Nublado', 'cloud-rain' => 'Lluvioso', 'cloud-showers-heavy' => 'Chubascos', 'cloud-sun' => 'Nublado y soleado', 'cloud-sun-rain' => 'Nubes, sol y lluvia', 'meteor' => 'Meteorito', 'smog' => 'Neblina', 'snowflake' => 'Nieve', 'sun' => 'Soleado', 'wind' => 'Ventoso', ], ], 'placeholders' => [ 'effect' => 'Fenómeno natural o mágico', 'name' => 'Texto opcional personalizado del clima', 'precipitation' => 'Cantidad de agua', 'temperature' => 'Máxima y mínima diaria', 'wind' => 'Velocidad del viento', ], ]; ================================================ FILE: lang/es/calendars.php ================================================ [ 'add_epoch' => 'Añadir época', 'add_intercalary' => 'Añadir días intercalares', 'add_month' => 'Añadir mes', 'add_moon' => 'Añadir luna', 'add_reminder' => 'Añadir recordatorio', 'add_season' => 'Añadir estación', 'add_weather' => 'Añadir fenómeno climático', 'add_week' => 'Añadir semana con nombre', 'add_weekday' => 'Añadir día de la semana', 'add_year' => 'Añadir año con nombre', 'set_today' => 'Poner como día actual', 'today' => 'Hoy', 'update_weather' => 'Actualizar clima', ], 'checkboxes' => [ 'is_recurring' => 'Ocurre cada año', ], 'create' => [ 'title' => 'Nuevo Calendario', ], 'destroy' => [], 'edit' => [ 'today' => 'Fecha del calendario actualizada.', ], 'event' => [ 'create' => [ 'success' => 'Evento creado en el calendario', 'title' => 'Añadir evento del calendario a :name', ], 'destroy' => 'Evento eliminado del calendario :name', 'edit' => [ 'success' => 'Evento del calendario actualizado', 'title' => 'Actualizar evento del calendario de :name', ], 'errors' => [ 'invalid_entity' => 'Selección de entidad inválida', ], 'helpers' => [ 'other_calendar' => 'Estás editando un recordatorio del calendario :calendar.', ], 'success' => 'Evento \':event\' añadido al calendario.', ], 'events' => [ 'bulks' => [ 'delete' => '{1} Se ha eliminado :count evento.|[2,*] Se han eliminado :count eventos.', 'patch' => '{1} Se ha actualizado :count evento.|[2,*] Se han actualizado :count eventos.', ], 'end' => '(fin)', 'filters' => [ 'show_after' => 'Mostrar hoy y después', 'show_all' => 'Mostrar todos', 'show_before' => 'Mostrar antes de hoy', ], 'start' => '(inicio)', ], 'fields' => [ 'comment' => 'Comentario', 'current_day' => 'Día actual', 'current_month' => 'Mes actual', 'current_year' => 'Año actual', 'date' => 'Fecha actual', 'day' => 'Día', 'default_layout' => 'Diseño predeterminado', 'format' => 'Formato', 'is_incrementing' => 'Fecha incremental', 'is_recurring' => 'Recurrente', 'leap_year' => 'Años bisiestos', 'leap_year_amount' => 'Añadir días', 'leap_year_month' => 'Mes', 'leap_year_offset' => 'Cada', 'leap_year_start' => 'Año bisiesto', 'length' => 'Duración del evento', 'length_days' => ':count day|:count days', 'month' => 'Mes', 'months' => 'Meses', 'moons' => 'Lunas', 'parameters' => 'Parámetros', 'recurring_until' => 'Recurrente hasta el año', 'reset' => 'Reinicio semanal', 'seasons' => 'Estaciones', 'show_birthdays' => 'Mostrar cumpleaños', 'skip_year_zero' => 'Saltar Año Cero', 'start_offset' => 'Retraso inicial', 'suffix' => 'Sufijo', 'week_names' => 'Nombres de las semanas', 'weekdays' => 'Días de la semana', 'year' => 'Año', ], 'helpers' => [ 'default_layout' => 'Seleccione qué diseño debe usar el calendario de forma predeterminada.', 'format' => 'Añadir formato de fecha personalizado para entidades del calendario.', 'month_type' => 'Los meses intercalares no usan los días de la semana, pero influyen en las lunas y las estaciones.', 'moon_offset' => 'De forma predeterminada, la primera luna llena aparece el primer día del año 0. Cambiar el desplazamiento alterará el cuando se muestra la primera luna llena. Este valor puede ser negativo (hasta la duración del primer mes) o positivo (hasta la duración del primer mes).', 'start_offset' => 'Por defecto, el calendario empieza en el primer día de la semana del año 0. En este campo puedes cambiar dónde se situará el primer día del calendario.', ], 'hints' => [ 'event_length' => 'Cuánto dura el evento. Un evento no puede durar más de dos meses.', 'is_incrementing' => 'Si está activado, se incrementará la fecha actual automáticamente a las 00:00h UTC.', 'leap_year' => 'Configure los años bisiestos para el calendario.', 'months' => 'Tu calendario debe tener al menos 2 meses.', 'moons' => 'Si añades lunas, aparecerán en el calendario cada luna llena y nueva. Si el período entre estas es mayor que 10 días, también se mostrarán los cuartos creciente y menguante.', 'parent_calendar' => 'Los calendarios incluyen los recordatorios y efectos climáticos de su calendario superior.', 'reset' => 'Empezar siempre el siguiente mes o año en el primer día de la semana.', 'seasons' => 'Crea estaciones en tu calendario estableciendo cuándo empieza cada una. Kanka se encargará del resto.', 'show_birthdays' => 'Muestra los cumpleaños anuales de los personajes que tienen un recordatorio de cumpleaños en este calendario hasta la fecha de su muerte.', 'skip_year_zero' => 'De forma predeterminada, el primer año del calendario es el año cero. Habilita esta opción para omitir el año cero.', 'weekdays' => 'Escribe los nombres de los días de la semana. Se requiere un mínimo de 2 días.', 'weeks' => 'Define los nombres para las semanas más importantes de tu calendario.', 'years' => 'Algunos años son tan importantes que tienen su propio nombre.', ], 'index' => [], 'layouts' => [ 'month' => 'Mes', 'monthly' => 'Mensual por defecto', 'year' => 'Año', 'yearly' => 'Anual por defecto', ], 'lists' => [ 'empty' => 'Crea un calendario para seguir fechas, festivales o eventos dentro del juego a lo largo del tiempo.', ], 'modals' => [ 'switcher' => [ 'title' => 'Conmutador de año', ], ], 'month_types' => [ 'intercalary' => 'Intercalar', 'standard' => 'Estándar', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Luna llena', 'fullmoon_name' => 'Luna :moon llena', 'month' => 'Mensual', 'newmoon' => 'Luna nueva', 'newmoon_name' => 'Luna :moon nueva', 'none' => 'Ninguna', 'unnamed_moon' => 'Luna :number', 'year' => 'Anual', ], ], 'resets' => [ '' => 'Ninguno', 'month' => 'Mensual', 'year' => 'Anual', ], ], 'panels' => [ 'intercalary' => 'Días intercalares', 'leap_year' => 'Año bisiesto', 'months' => 'Meses', 'weeks' => 'Semanas', 'years' => 'Años con nombre', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Días de duración', 'month' => 'Al final de qué mes', 'name' => 'Nombre de la intercalación', ], 'month' => [ 'alias' => 'Alias del mes', 'length'=> 'Número de días', 'name' => 'Nombre del mes', 'type' => 'Tipo', ], 'moon' => [ 'fullmoon' => 'Luna llena cada... días', 'name' => 'Nombre de la luna', 'offset' => 'Retraso de la primera luna llena', ], 'seasons' => [ 'day' => 'Día inicial', 'month' => 'Mes inicial', 'name' => 'Nombre de la estación', ], 'weeks' => [ 'name' => 'Nombre de la semana', 'number' => 'Número', ], 'year' => [ 'name' => 'Nombre del año', 'number' => 'Año', ], ], 'placeholders' => [ 'colour' => 'Color', 'comment' => 'Cumpleaños, Festival, Solsticio', 'date' => 'Fecha actual', 'leap_year_amount' => 'Número de días añadidos en un año bisiesto', 'leap_year_month' => 'Mes en que se añaden los días', 'leap_year_offset' => 'Cada cuántos años es un año bisiesto', 'leap_year_start' => 'Primer año que es un año bisiesto', 'length' => 'Días que dura el evento', 'months' => 'Número de meses en un año', 'recurring_until' => 'Último año recurrente (dejar vacío para que sea eterno)', 'seasons' => 'Número de estaciones', 'suffix' => 'Sufijo de la era actual (AC, BC)', 'type' => 'Tipo de calendario', 'weekdays' => 'Número de días de la semana', ], 'show' => [ 'missing_details' => 'Este calendario no se puede mostrar. Los calendarios necesitan un mínimo de 2 meses y 2 días semanales para renderizarse correctamente.', 'moon_1first_quarter' => ':moon cuarto creciente', 'moon_full' => ':moon luna llena', 'moon_last_quarter' => ':moon cuarto menguante', 'moon_new' => ':moon luna nueva', 'tabs' => [ 'events' => 'Eventos del calendario', 'weather' => 'Clima', ], ], 'sorters' => [ 'after' => 'De hoy en adelante', 'before'=> 'Hasta hoy', ], 'validators' => [ 'format' => 'El formato de la fecha es inválido.', 'moon_offset' => 'El desplazamiento de la primera luna llena de la luna no puede ser mayor que la duración del primer mes del calendario.', ], 'warnings' => [ 'event_length' => 'Los recordatorios que abarcan varios años sólo son visibles en los dos primeros años. Más información en nuestra :documentation.', ], ]; ================================================ FILE: lang/es/callouts.php ================================================ [ 'subscription' => 'Aprende más sobre las suscripciones', 'upgrade' => 'Mejorar a premium', ], 'booster' => [ 'actions' => [ 'boost' => 'Mejorar :campaign', 'superboost' => 'Supermejorar :campaign', ], 'learn-more' => 'Que', 'limitation' => '¿Qué son las mejoras?', 'limitations' => [ 'boosted' => 'Para acceder a esta función es necesario mejorar la campaña.', 'superboosted' => 'Para acceder a esta función es necesario supermejorar la campaña.', ], 'multiple' => 'Para acceder a estas funciones es necesario mejorar la campaña.', 'pitches' => [ 'element-class' => 'Dale una clase CSS personalizada a este elemento con una :boosted-campaign.', 'icon' => 'Desbloquea millones de iconos personalizados de FontAwesome con una :boosted-campaign.', ], 'titles' => [ 'boosted' => 'Función de mejora', 'superboosted' => 'Función de supermejora', ], ], 'premium' => [ 'learn-more' => '¿Qué son las campañas premium?', 'limitation' => 'Para acceder a esta función, es necesario activar las funciones Premium.', 'multiple' => 'Para acceder a estas funciones, es necesario habilitar las funciones premium para :campaign.', 'title' => 'Característica de campaña Premium', 'unlock' => 'Desbloquea funciones premium para :campaña', ], 'subscribe' => [ 'pitch-image' => 'Suscríbete para desbloquear la carga de archivos de tamaños de hasta :max MB.', 'share-booster' => 'Mejora :campaign para aumentar el tamaño de maximo de carga de archivos para todos los miembros de la campaña.', 'share-premium' => 'Aumenta el tamaño de subida de archivos para todos los miembros de la campaña con una campaña premium.', ], ]; ================================================ FILE: lang/es/campaigns/achievements.php ================================================ '¡Felicidades!', 'connections' => '{0} Ninguna conexión creada|{1} Una conexión creada|[2,*] :amount conexiones creadas', 'created' => '{0} No has creado :plural|{1} Has creado :amount :singular|[2,*] Has creado :amount :plural', 'dead' => '{0} Ningún asesinato misterioso|{1} Un asesinato misterioso|[2,*] :amount asesinatos misteriosos', 'goal' => 'Meta :number', 'goal_reached' => 'Logro desbloqueado', 'level' => 'nivel :number', 'markers' => '{0} Ningún marcador de mapa creado|{1} Un marcador de mapa creado|[2,*] :amount marcadores de mapa creados', 'painter' => '{0} Ningún tema creado|{1} Un tema creado|[2,*] :amount temas creados', 'pitch' => 'Los logros de campaña son una forma divertida de celebrar hitos en tu viaje de creación de mundos. Sigue el progreso, motiva a tus jugadores y muestra tus logros con una campaña premium.', 'plugins' => '{0} Ningún plugin instalado|{1} Un plugin instalado|[2,*] :amount plugins instalados', 'remaining' => [ 'generic' => 'Más y se desbloqueará el siguiente nivel.', ], 'spotlight' => [ 'active' => [ 'cta' => 'Ver destacado', ], 'private' => [ 'cta' => 'Revisar configuración pública', 'helper' => 'Haz pública tu campaña para que pueda ser seleccionada como campaña destacada.', ], 'public' => [ 'cta' => 'Aprende cómo funciona la campaña destacada', 'helper' => 'Las campañas seleccionadas aparecen como campañas destacadas en Kanka y en el blog.', ], ], 'spotlighted' => '{0} Aún no es campaña destacada |[1,*] Campaña destacada', 'tagged' => '{0} Ninguna entidad etiquetada|{1} Una entidad etiquetada|[2,*] :amount entidades etiquetadas', 'titles' => [ 'calendars' => 'Guardián del tiempo', 'characters' => 'Proveedor de nombres', 'connections' => 'Cupido', 'creatures' => 'Criador', 'dead' => 'Asesino', 'events' => 'Maestro del saber', 'families' => 'Planificación familiar', 'locations' => 'Constructor', 'markers' => 'Cartógrafo', 'organisations' => 'Fusiones y adquisiciones', 'plugins' => 'Conocedor de los plugins', 'quests' => 'Mente maestra', 'spotlighted' => 'Destacada', 'tags' => 'Bajo control', 'themes' => 'Pintor', ], 'tutorial' => 'Los logros registran acciones destacadas dentro de esta campaña, como crear entidades o usar funciones clave. Son solo informativos y se actualizan automáticamente a medida que exploras y construyes.', ]; ================================================ FILE: lang/es/campaigns/applications.php ================================================ [ 'accept' => 'Aceptar', 'reject' => 'Rechazar', ], 'apply' => [ 'apply' => 'Solicitar', 'help' => 'Esta campaña está abierta a nuevos miembros. Puedes solicitar unirte a ella mediante este formulario. Te notificaremos cuando los administradores de la campaña revisen tu solicitud.', 'remove_text' => 'tu solicitud', 'success' => [ 'apply' => 'Tu solicitud se ha guardado. Puedes cambiarla o cancelarla en cualquier momento. Te notificaremos cuando los administradores de la campaña la revisen.', 'remove'=> 'Se ha eliminado tu solicitud.', 'update'=> 'Se ha actualizado tu solicitud. Puedes cambiarla o cancelarla en cualquier momento. Te notificaremos cuando los administradores de la campaña la revisen.', ], 'title' => 'Unirse a :name', ], 'dashboard_widget' => [ 'add' => 'Agregar widget', 'has_widget' => 'El widget de unión está en el tablero.', 'has_widget_help' => 'El tablero de tu campaña muestra el widget de unión a jugadores potenciales.', 'no_widget' => 'Sin widget de unión en el tablero.', 'no_widget_help' => 'Tu campaña está abierta pero no lo muestra en el tablero. Agrega un widget para que los jugadores puedan descubrirla y solicitar unirse.', 'success' => 'Widget de unión agregado al tablero.', 'title' => 'Widget del tablero', ], 'errors' => [], 'experience' => [ 'intermediate' => 'Intermedio (Ha jugado algunas veces)', 'new' => 'Principiante (Primera vez)', 'veteran' => 'Veterano (Años de experiencia)', ], 'fields' => [ 'additional_notes' => 'Algo más', 'application' => 'Solicitud', 'availability_days' => 'Días disponibles', 'character_concept' => 'Concepto del personaje', 'experience_level' => 'Nivel de experiencia', 'external_link' => 'Hoja de personaje / Enlace', 'intro' => 'Introducción de la campaña', 'new_application' => 'Solicitud de nuevo jugador', 'player_count' => 'Número de jugadores', 'playstyle_tags' => 'Estilos de juego', 'pref_rp_combat' => 'Balance de enfoque', 'pref_tone' => 'Preferencia de tono', 'reason' => 'Motivo de aprobación / rechazo', 'schedule' => 'Horario', 'schedule-placeholder' => 'Todos los viernes a las 7 PM', 'time_end' => 'Hasta', 'time_start' => 'Desde', 'timezone' => 'Zona horaria', ], 'filters' => [ 'all' => 'Mostrar todo', 'approved' => 'Mostrar aprobados', 'pending' => 'Mostrar pendientes', 'rejected' => 'Mostrar rechazados', 'title' => 'Filtros', ], 'headers' => [ 'availability' => 'Disponibilidad y horario', 'preferences' => 'Preferencias de estilo de juego', ], 'helpers' => [ 'applications_closed' => 'Tu campaña es pública, pero los nuevos jugadores no pueden enviar solicitudes hasta que establezcas el estado en "Abierto".', 'availability_days' => 'Selecciona los días en que generalmente estás disponible para jugar.', 'experience_level' => '¿Qué tan familiarizado estás con este sistema de juego en particular?', 'external_link' => 'Enlace a D&D Beyond, Google Docs u otras hojas de personaje externas.', 'fill_setup' => 'Por favor, completa el formulario de configuración de campaña pública para poder abrir tu campaña al público.', 'filters_incomplete' => 'Tu campaña está abierta para solicitudes, pero aún no has terminado de configurar los filtros (como sistema, zona horaria o etiquetas). Completarlos facilitará mucho que los jugadores adecuados te encuentren.', 'modal' => 'Una campaña que está abierta a solicitudes y al público permite que los usuarios soliciten unirse a la campaña.', 'no_applications_title' => 'No se han encontrado aplicaciones', 'no_applications_v2' => 'Actualmente no hay solicitudes pendientes para unirse a la campaña, ¡pero puedes ayudar a los jugadores a encontrar su próxima gran aventura! La información detallada de la campaña y los filtros de búsqueda facilitan que los usuarios descubran e interesen en tu historia.', 'reason' => 'Si se proporciona, se notificará al solicitante con este motivo.', 'role' => 'Si se aprueba, el rol al que se añade el aplicante.', ], 'labels' => [ 'casual' => 'Casual / Informal', 'combat_focused' => 'Orientado al combate', 'rp_heavy' => 'Alto en rol', 'serious' => 'Serio / Inmersivo', ], 'open' => [ 'closed' => 'Campaña cerrada', 'open' => 'Campaña abierta', 'title' => 'Campaña abierta', ], 'placeholders' => [ 'additional_notes' => 'Comentarios adicionales o preguntas específicas.', 'character_concept' => 'Describe brevemente como quién quieres jugar, su trasfondo y cómo encaja en el mundo.', 'intro' => 'Una breve explicación de qué trata tu campaña, mostrada en la parte superior del formulario de solicitud.', 'note' => 'Escribe tu solicitud para unirte a la campaña', 'player_count' => '4-6 jugadores', 'reason' => 'Tus razones', ], 'public' => [ 'private' => 'La campaña es privada.', 'public' => 'La campaña es pública.', 'title' => 'Campaña pública', ], 'setup' => [ 'done' => 'Configuración de campaña pública completada.', 'prioritise' => 'Priorizar esta campaña', 'prioritise_conflict' => 'Solo puedes priorizar una campaña a la vez. Desactiva la priorización en :campaign primero.', 'prioritise_help' => 'Las campañas priorizadas aparecen en la parte superior de la lista de campañas públicas, antes que otras campañas abiertas.', 'prioritise_upgrade' => 'Esta función es exclusiva para suscriptores :link.', 'prioritised' => 'Campaña priorizada', 'setup' => 'Configura los ajustes de campaña pública para abrirla al público.', 'success' => 'Configuración de campaña guardada. Una vez que todos los campos estén completos, la campaña puede abrirse al público.', 'success_complete' => '¡La campaña puede abrirse al público!', 'title' => 'Configuración de campaña pública', 'tutorial' => 'Solo cuando todos estos campos estén completos se puede abrir la campaña al público.', ], 'statuses' => [], 'timezone' => 'Zona horaria e idioma', 'title' => 'Solicitudes de ingreso', 'toggle' => [ 'closed' => 'Cerrada a solicitudes', 'label' => 'Estado', 'open' => 'Abierta a solicitudes', 'success' => 'Actualización del estado de la solicitud para la campaña.', 'success_open' => 'La campaña es ahora pública y los usuarios pueden solicitar unirse.', 'title' => 'Estado de la solicitud', ], 'tutorial' => 'Las solicitudes de campaña permiten que las personas pidan acceso a esta campaña. Los solicitantes envían un formulario breve y los administradores de la campaña pueden revisar, aceptar o rechazar cada solicitud. Los usuarios aprobados se agregan a la campaña con el rol que asignes durante la revisión.', 'update' => [ 'approve' => 'Selecciona el rol que se asignará al usuario en tu campaña.', 'approved' => 'Solicitud aprobada.', 'reject' => 'Escribe un mensaje opcional al usuario explicando el motivo del rechazo.', 'rejected' => 'Solicitud rechazada', ], 'warnings' => [ 'applications_closed' => 'Las solicitudes están cerradas actualmente.', 'filters_incomplete' => 'Los filtros de la campaña están incompletos.', ], 'weekdays' => [ 'fri' => 'Vie', 'mon' => 'Lun', 'sat' => 'Sáb', 'sun' => 'Dom', 'thu' => 'Jue', 'tue' => 'Mar', 'wed' => 'Mié', ], ]; ================================================ FILE: lang/es/campaigns/builder.php ================================================ 'Construye visualmente un tema para la campaña con esta interfaz. Desplazate hacia abajo para ver cómo afectarían los cambios a varios elementos de la campaña. Cuando se selecciona un color, automáticamente se selecciona un color "de contraste" para colorear el texto. Más información sobre tematización en nuestra documentación :docs.', 'pitch' => 'Psst, si lo único que quieres es cambiar algunos de los colores de la campaña, tenemos un constructor de temas 😉', 'pitch-go' => 'Llévame al constructor de temas', 'reset' => 'Reinicio del estilo del constructor de temas.', 'success' => 'Estilo del constructor de temas guardado.', 'title' => 'Creador de temas', ]; ================================================ FILE: lang/es/campaigns/categories.php ================================================ [ 'permission-disabled' => 'Esta categoría está desactivada.', ], 'helpers' => [ 'aliases' => 'Agrega alias e identidades secretas a las entradas del mundo.', 'media' => 'Sube documentos multimedia (imágenes, PDFs, audio) y enlaces externos a las entradas.', ], 'tab' => 'Categorías', ]; ================================================ FILE: lang/es/campaigns/dashboard-header.php ================================================ [ 'success' => 'Cabecera del tablero actualizada.', 'title' => 'Actualizar cabecera del tablero', ], ]; ================================================ FILE: lang/es/campaigns/default-images.php ================================================ [ 'add' => 'Añadir nueva imagen por defecto', ], 'call-to-action' => 'Sube una miniatura personalizada para todos los personajes, ubicaciones u otras entidades de la campaña. Estas imágenes se muestran en varias listas.', 'create' => [ 'error' => 'Error al guardar las nuevas imágenes por defecto. ¿Has olvidado añadir un :type?', 'helper' => 'Sube una imagen que se usará como miniatura predeterminada para las entidades del módulo seleccionado.', 'success' => 'Imagen por defecto para :type creada.', 'title' => 'Nueva imagen por defecto', ], 'destroy' => [ 'success' => 'Imagen por defecto para :type eliminada.', ], 'empty' => 'Actualmente, ningún módulo tiene una miniatura predeterminada configurada.', 'helper' => 'Se usa para todas las entidades de este módulo que no tengan imagen.', 'index' => [], 'reset' => [ 'helper' => '¿Estás seguro de que deseas eliminar las imágenes predeterminadas de todos los módulos de la campaña?', 'success' => 'Las imágenes predeterminadas de todos los módulos se eliminaron correctamente.', 'title' => 'Restablecer imágenes predeterminadas', 'warning' => 'Esta acción es permanente y no se puede deshacer.', ], 'title' => 'Imágenes predeterminadas', 'tutorial' => 'Establece imágenes predeterminadas para las entidades sin imágenes personalizadas. Estas miniaturas aparecen de inmediato en toda la campaña y mantienen las listas visualmente consistentes.', ]; ================================================ FILE: lang/es/campaigns/defaults.php ================================================ [ 'character_personality_visibility' => 'Visibilidad predeterminada de la personalidad del personaje', 'connections' => 'Vista de conexiones de la entidad', 'connections_mode' => 'Estilo del mapa de conexiones', 'descendants' => 'Filtrado predeterminado de sublistas', 'entity_privacy' => 'Visibilidad de nuevas entidades', 'gallery_visibility' => 'Visibilidad predeterminada de imágenes de la galería', 'post_collapsed' => 'Diseño de nuevas publicaciones', 'private_mention_visibility' => 'Menciones privadas de entidades', 'related_visibility' => 'Visibilidad de contenido relacionado', ], 'helpers' => [ 'character_visibility' => 'Establece la visibilidad de los rasgos de personalidad al crear personajes.', 'connections' => 'Elige si las páginas de conexiones de entidades muestran un mapa visual o una lista de forma predeterminada.', 'connections_mode' => 'Establece el estilo de diseño predeterminado para los mapas de conexiones (disponible con premium).', 'descendants' => 'Al ver sublistas de entidades (como los personajes de una ubicación), muestra solo los descendientes directos o todos los descendientes.', 'display' => 'Establece las opciones de visualización predeterminadas para las páginas de entidades.', 'entity' => 'Controla qué visibilidad aplica Kanka automáticamente al nuevo contenido.', 'entity_privacy' => 'Establece la visibilidad de personajes, ubicaciones y otros elementos recién creados.', 'gallery_visibility' => 'Valor de visibilidad predeterminado al subir imágenes a la galería.', 'post_collapsed' => 'Al crear publicaciones en entidades, establece si la publicación aparece colapsada o expandida.', 'privacy' => 'Establece las visibilidades predeterminadas para el nuevo contenido. Estos ajustes se aplican al crear contenido nuevo y pueden modificarse en elementos individuales.', 'private_mention_visibility' => 'Cuando mencionas una entidad privada en contenido visible, controla si el nombre de la entidad se muestra u oculta.', 'related_visibility' => 'Controla la visibilidad de publicaciones, atributos y conexiones añadidos a las entidades.', ], 'sections' => [ 'display' => 'Valores predeterminados de visualización de entidades', 'entity' => 'Valores predeterminados de entidades', 'media' => 'Valores predeterminados de medios', 'mention' => 'Comportamiento de menciones', ], 'tutorial' => 'Agiliza la creación de contenido con valores predeterminados inteligentes. Elige configuraciones de visibilidad predeterminadas para entidades, publicaciones, imágenes y otros contenidos. Estas preferencias se aplicarán automáticamente al crear nuevo contenido, ahorrándote tiempo y manteniendo tu campaña organizada.', 'update' => [ 'success' => 'Valores predeterminados de la campaña actualizados.', ], 'values' => [ 'collapsed' => [ 'collapsed' => 'Colapsado', 'default' => 'Predeterminado', 'expanded' => 'Expandido', ], 'connections' => [ 'explorer' => 'Mapa de conexiones (premium)', 'list' => 'Interfaz de lista', ], 'descendants' => [ 'all' => 'Mostrar todos los descendientes de forma predeterminada', 'direct' => 'Mostrar descendientes directos de forma predeterminada', ], 'mentions' => [ 'private' => 'Ocultar nombre del objetivo', 'visible' => 'Mostrar nombre del objetivo', ], ], ]; ================================================ FILE: lang/es/campaigns/delete.php ================================================ 'respaldo', 'confirm' => 'Si estás seguro de que quieres eliminar :campaign de forma permanente, escribe :code en el campo de abajo.', 'confirm-button' => 'Eliminar :name permanentemente', 'helper' => 'Eliminar una campaña es una acción permanente que no puede revertirse. Esto eliminará todos los datos relacionados con la campaña de nuestros servidores, incluidas las imágenes y los recursos. Recomendamos hacer un :backup antes de continuar.', 'issue' => 'El siguiente problema debe solucionarse antes de poder eliminar la campaña.', 'members' => 'Todos los demás miembros deben ser expulsados de la campaña.', 'success' => 'Se ha eliminado a :name de forma permanente.', 'title' => 'Eliminación', ]; ================================================ FILE: lang/es/campaigns/export.php ================================================ [ 'download' => 'Descargar', 'export' => 'Exportar los datos de la campaña', ], 'confirm' => [ 'notification' => 'Los miembros con el rol de :admin serán notificados cuando la exportación esté lista para descargar.', 'title' => 'Confirmación de exportación', 'type' => 'Tipo de exportación', 'warning' => 'Estás a punto de exportar los datos de la campaña. Este proceso puede llevar mucho tiempo dependiendo del tamaño de la campaña. Puedes seguir utilizando Kanka mientras nuestros servidores generan la exportación.', ], 'errors' => [ 'limit' => 'La campaña ya se ha exportado una vez hoy. Vuelva a intentarlo mañana.', 'premium' => 'La exportación en Markdown es una función exclusiva de las campañas premium.', ], 'expired' => 'Enlace expirado', 'helpers' => [ 'json' => 'Para copias de seguridad y restauración - puede usarse para la importación de campaña.', 'markdown' => 'Para compartir y leer - formato legible para personas.', 'premium' => 'Disponible solo para campañas premium.', ], 'progress' => 'Progreso', 'size' => 'Tamaño', 'status' => [ 'failed' => 'Fallida', 'finished' => 'Terminada', 'running' => 'Ejecutándose', 'scheduled' => 'Programada', ], 'success' => 'Se está preparando la exportación de la campaña. Recibirás una notificación en Kanka cuando esté lista para descargar.', 'title' => 'Exportación de campaña', 'type' => 'Tipo', 'types' => [ 'json' => 'JSON', 'md' => 'Markdown', ], ]; ================================================ FILE: lang/es/campaigns/gallery.php ================================================ [ 'close' => 'Cerrar', 'file-link' => 'Enlace al archivo', 'focus_point' => 'Establecer punto de enfoque', 'image-link' => 'Enlace a la imagen', 'reset_focus' => 'Restablecer el punto de enfoque', 'save' => 'Guardar', 'upgrade' => 'Ampliar el espacio de almacenamiento', ], 'breadcrumb' => 'Galería', 'bulk' => [ 'destroy' => [ 'confirm' => '¿Estás seguro de que quieres eliminar permanentemente los elementos seleccionados? Esta acción no se puede deshacer.', 'success' => '{0}Ningún archivo eliminado.|{1}Un archivo eliminado.|{2,*}:count archivos eliminados.', ], ], 'cta' => 'Gestiona y reutiliza las imágenes a lo largo de la campaña.', 'destroy' => [ 'folder' => 'Carpeta :name eliminada.', 'success' => 'Imagen :name borrada.', ], 'errors' => [ 'max' => 'Por favor, sólo seleccione hasta :count archivos a la vez.', 'permissions' => 'A tus roles de campaña les falta el permiso :permission para poder subir imágenes a la galería de la campaña.', 'storage' => 'No hay espacio suficiente para almacenar las imágenes seleccionadas. Espacio de almacenamiento disponible: :available.', ], 'fields' => [ 'created_by' => 'Subida por', 'details' => 'Detalles', 'ext' => 'Ext', 'file_type' => 'Tipo de archivo', 'folder' => 'Carpeta', 'image_mentioned_in' => '{0} Esta imagen no se menciona en ninguna de las entidades de la campaña.|{1} Mencionada en una entrada/post.|[2,*] mencionada en :count entradas/posts.', 'image_used_in' => '{1}Se usa como la imagen de una entidad.|[2,*]Se usa como la imagen de :count entidades.', 'link' => 'Enlace', 'name' => 'Nombre', 'size' => 'Tamaño', 'unused' => 'No se utiliza en ninguna parte', 'used_in' => 'Utilizado en', ], 'focus' => [ 'locked' => 'Para fijar el punto de enfoque de una imagen se necesita una campaña premium.', 'removed' => 'Enfoque de la imagen eliminado.', 'updated' => 'Enfoque de la imagen actualizado.', ], 'new_folder' => [ 'title' => 'Nueva carpeta', ], 'no_folder' => 'Sin carpeta', 'pitch' => 'Sube imágenes a la galería de la campaña directamente desde el editor de texto.', 'placeholders' => [ 'search' => 'Buscar nombre de imagen...', ], 'storage' => [ 'of' => 'de', 'title' => 'Almacenamiento', ], 'title' => 'Galería de la campaña :campaign', 'update' => [ 'folder' => 'Carpeta modificada.', 'success' => 'Imagen modificada.', ], 'uploader' => [ 'add' => 'Añadir nueva', 'new_folder' => 'Nueva carpeta', 'or' => 'o', 'select_file' => 'Selecciona un archivo', 'well' => 'Suelta aquí el archivo a subir', ], ]; ================================================ FILE: lang/es/campaigns/import.php ================================================ [ 'import' => 'Subir la exportación', ], 'csv' => [ 'continue' => 'Continuar', 'fields_helper' => 'Selecciona una columna para asignar a cada uno de los campos completables de la entrada.', 'no_preview' => 'No hay datos de vista previa disponibles', 'preview' => 'Vista previa', 'select_module' => 'Selección de categoría', 'select_one' => 'Seleccionar uno', 'selected_tags' => 'Etiquetas seleccionadas', 'set_column' => 'Establecer columna', 'set_fields' => 'Establecer campos', 'submit' => 'Enviar importación CSV', 'traits' => 'Rasgos del personaje', 'traits_helper' => 'Puedes agregar rasgos a los personajes; el encabezado que selecciones se usará como el rasgo, mientras que el valor de la fila correspondiente también será el valor del rasgo.', 'type_helper' => 'Selecciona la categoría en la que deseas importar las nuevas entradas.', 'validation_error' => 'Al menos 1 columna debe estar completamente rellena', ], 'description_v2' => 'Importa entradas, artículos, propiedades, galerías y otros datos desde una exportación de campaña o nuevas entradas desde un archivo .CSV en esta campaña. La importación se ejecuta en segundo plano y puede tardar un tiempo. Tú y cualquier otro administrador serán notificados cuando finalice.', 'fields' => [ 'file_v2' => 'Archivo CSV o archivo ZIP de exportación', 'updated' => 'Última actualización', ], 'form' => 'Formulario de carga', 'limitation_v2' => 'Solo se aceptan archivos zip y csv. Máximo :size.', 'progress' => [ 'uploading' => 'Subiendo', ], 'status' => [ 'failed' => 'Fallida', 'finished' => 'Terminada', 'invalid' => 'Datos inválidos', 'processing' => 'Procesando', 'queued' => 'En cola', 'ready' => 'Listo para mapear', 'running' => 'Ejecutándose', 'validating' => 'Validando', ], 'title' => 'Importar', ]; ================================================ FILE: lang/es/campaigns/invites.php ================================================ [ 'helper' => 'Crea un enlace de invitación para enviar a tus jugadores, para que puedan unirse a la campaña.', ], ]; ================================================ FILE: lang/es/campaigns/limits.php ================================================ 'Límite alcanzado', ]; ================================================ FILE: lang/es/campaigns/logs.php ================================================ [ 'list' => 'Mantenemos un registro de todos los cambios principales realizados en la campaña durante :amount días. Estos registros no detallan cambios individuales en los valores, sino el estado general de la campaña.', 'nothing' => 'No hay registros para mostrar. Ten en cuenta que los registros solo se conservan durante :amount días.', 'title' => 'Sin registros', ], 'pitch' => 'Mantén un registro de los cambios generales realizados en la campaña durante un máximo de :amount días con una campaña premium.', 'premium' => [ 'helper' => 'Se necesitan funciones premium para ver registros con más de :amount días.', ], 'title' => 'Registro de auditoría', ]; ================================================ FILE: lang/es/campaigns/members.php ================================================ [ 'limited' => ':amount de :total miembros.', 'title' => 'Miembros disponibles', 'unlimited' => ':amount de miembros ilimitados.', ], 'roles' => [ 'admin' => 'No puedes añadir usuarios directamente al rol :admin desde aquí. Esto se hace desde la interfaz del rol :admin.', 'helper' => 'Añadir o eliminar roles del miembro :user.', 'success' => 'Roles actualizados correctamente para :user.', 'title' => 'Editar los roles del miembro', ], ]; ================================================ FILE: lang/es/campaigns/modules.php ================================================ [ 'create' => 'Crear módulo', 'customise' => 'Personalizar', ], 'create' => [ 'helper' => 'Crear un nuevo módulo personalizado para almacenar entidades que no encajan en los otros módulos.', 'success' => 'Se ha creado un nuevo módulo.', 'title' => 'Nuevo módulo', ], 'delete' => [ 'confirm' => 'Escribe :code si estás seguro de que deseas eliminar definitivamente el módulo personalizado :name.', 'confirm-button' => '{0} Eliminar :name permanentemente| {1} Eliminar :name y :count entidad permanentemente| [2,*] Eliminar :name y :count entidades permanentemente', 'entities' => '{1} Esto eliminará :count entidad permanentemente. | [2,*] Esto eliminará :count entidades permanentemente.', 'helper' => '¿Estás seguro de que quieres eliminar el módulo personalizado :name ? Esto también eliminará permanentemente todas las entidades, marcadores y widgets vinculados a este módulo.', 'success' => 'Módulo :name eliminado.', 'title' => 'Eliminación de módulos', ], 'errors' => [ 'disabled' => 'El módulo :name está desactivado. :fix', 'empty-custom' => 'Añade módulos personalizados para organizar datos que no encajan en los predeterminados.', 'limit' => 'Actualmente, las campañas están limitadas a :max módulos personalizados mientras perfeccionamos esta nueva función.', 'limit-title' => 'Límite de módulos personalizados alcanzado', 'subscription-limit' => 'La campaña ha alcanzado el número máximo de módulos personalizados disponibles. La persona que desbloquea las funciones premium puede suscribirse a un nivel superior para aumentar este límite.', ], 'fields' => [ 'icon' => 'Icono del módulo', 'image' => 'Imagen predeterminada', 'plural' => 'Nombre del módulo en plural', 'singular' => 'Nombre del módulo en singular', 'status' => 'Estado del módulo', 'update_name' => 'Renombrar el marcador del módulo con el nuevo nombre', ], 'helpers' => [ 'custom' => 'Este es un módulo personalizado.', 'icon' => 'El icono :fontawesome, por ejemplo :ejemplo.', 'plural' => 'El nombre en plural de las entidades del nuevo módulo. Por ejemplo, pociones', 'roles' => 'Selecciona los roles que tienen permiso para ver las entidades de este nuevo módulo. Esto puede cambiarse posteriormente en los permisos de rol.', 'singular' => 'El nombre singular de una entidad del nuevo módulo. Por ejemplo, poción', 'status' => 'Los módulos desactivados se ocultan de la navegación y los menús. No se elimina ningún dato.', 'tutorial' => 'Los módulos controlan qué funciones son visibles en la campaña. Activa los que uses y oculta el resto. Desactivar un módulo nunca elimina datos; solo lo quita de la navegación y de los menús de creación.', ], 'pitch' => 'Cambia el nombre y el icono asociado a este módulo para toda la campaña.', 'pitch-custom' => 'Crea módulos personalizados para almacenar entidades únicas.', 'pitch-title' => 'Desbloquear módulos personalizados', 'rename' => [ 'helper' => 'Cambia el nombre y el icono del módulo a lo largo de la campaña. Déjalo en blanco para usar el predeterminado de Kanka.', 'success' => 'Módulo personalizado.', 'title' => 'Personalizar el módulo :module', ], 'reset' => [ 'default' => 'Esto sólo restablecerá los módulos predeterminados, no los personalizados.', 'success' => 'Los módulos de la campaña se han restablecido.', 'title' => 'Restablecer los nombres e iconos personalizados de los módulos', 'warning' => '¿Estás seguro de que quieres restablecer los nombres e iconos originales de los módulos de campaña?', ], 'sections' => [ 'custom' => 'Módulos personalizados', 'default' => 'Módulos predeterminados', 'early-access' => 'Acceso anticipado', 'features' => 'Funciones', ], 'states' => [ 'disable' => 'Desactivar', 'disabled' => 'El módulo está desactivado', 'enable' => 'Activar', 'enabled' => 'El módulo está activado', ], 'status' => [ 'enabled' => 'Módulo activado', ], ]; ================================================ FILE: lang/es/campaigns/overview.php ================================================ [ 'title' => 'Seguidores', ], 'member' => [ 'title' => 'Membresía', ], 'premium' => [ 'enable' => 'Activar funciones premium', ], 'status' => [ 'title' => 'Visibilidad', ], ]; ================================================ FILE: lang/es/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'Desactivar plugins', 'enable' => 'Activar plugins', 'update' => 'Actualizar plugins', ], 'changelog' => 'Registro de cambios', 'disable' => 'Desactivar plugin', 'enable' => 'Activar plugin', 'find-plugins' => 'Buscar plugins', 'import' => 'Importar', 'update' => 'Actualizar plugin', 'update-to' => 'Actualizar a la versión :versión', 'update_available' => '¡Actualización disponible!', ], 'bulks' => [ 'delete' => '{1} :count plugin eliminado.|[2,*] :count plugins eliminados.', 'disable' => '{1} :count plugin desactivado.|[2,*] :count plugins desactivados.', 'enable' => '{1} :count plugin activado.|[2,*] :count plugins activados.', 'update' => '{1} :count plugin actualizado.|[2,*] :count plugins actualizados.', ], 'destroy' => [ 'success' => 'Se ha eliminado el plugin :plugin.', ], 'disabled' => [ 'success' => 'Se ha desactivado el plugin :plugin.', ], 'empty_list' => 'Esta campaña no contiene ningún plugin actualmente. Dirígete a la tienda para instalar alguno y vuelve para activarlo.', 'enabled' => [ 'success' => 'Se ha activado el plugin :plugin.', ], 'errors' => [ 'invalid_plugin' => 'Plugin no válido.', ], 'fields' => [ 'name' => 'Nombre del plugin', 'obsolete' => 'Este plugin ha sido marcado como obsoleto por el equipo de Kanka, lo que significa que ya no funciona como su creador pretendía originalmente.', 'status' => 'Estatus', 'type' => 'Tipo de plugin', ], 'import' => [ 'button' => 'Importar', 'created' => 'Se han creado las siguientes entidades:', 'fields' => [ 'only_new' => 'Solo nuevas entidades', 'private' => 'Entidades privadas', ], 'helper' => 'Se van a importar :count entidades del plugin :plugin. Si este plugin ya estaba importado, los cambios que hayas hecho a las entidades importadas podrían perderse.', 'no_new_entities' => 'No hay nuevas entidades que importar.', 'option_only_import' => 'Importa solo las entidades nuevas, omitiendo las previamente importadas.', 'option_private' => 'Importa todas las entidades como privadas.', 'success' => '{1} Se ha importado :count entidad del plugin :plugin.|[2,*] Se han importado :count entidades del plugin :plugin.', 'title' => 'Importar :plugin', 'updated' => 'Se han actualizado las siguientes entidades:', ], 'info' => [ 'description' => 'Mostrando las últimas actualizaciones para el plugin :plugin.', 'helper' => 'Cuando salga una nueva versión de un plugin, puedes actualizarla a la nueva versión.', 'installed' => 'Instalado', 'title' => 'Actualitzaciones del plugin :plugin', 'updates' => 'Actualizaciones', 'versions' => 'Versiones', ], 'pitch' => 'Instale y gestione plugins desde el :marketplace.', 'status' => [ 'always' => 'Este tipo de plugin siempre está activo a menos de que se elimine.', 'disabled' => 'Desactivado', 'enabled' => 'Activado', ], 'templates' => [ 'name' => ':name hecha por :user', ], 'title' => 'Plugins de la campaña :name', 'types' => [ 'attribute' => 'Plantilla de atributos', 'pack' => 'Pack de contenido', 'theme' => 'Tema', ], 'update' => [ 'success' => 'Se ha actualizado el plugin :plugin.', ], ]; ================================================ FILE: lang/es/campaigns/public.php ================================================ [ 'new' => 'Hazla pública para que la comunidad la descubra, o mantenla privada solo para miembros invitados.', 'permissions' => 'Hacer tu campaña pública no comparte automáticamente el contenido. Configura lo que los visitantes públicos pueden ver en los ajustes del rol :public.', ], 'title' => 'Cambia la visibilidad de la campaña', 'update' => [ 'private' => 'La campaña es ahora privada y sólo visible para los miembros de la misma.', 'public' => 'La campaña es ahora pública. Puede que tarde algún tiempo en aparecer en la página :public-campaigns.', 'unlisted' => 'La campaña ahora no está listada. Cualquiera con el enlace puede acceder, pero no aparecerá en la página de :public-campaigns.', ], ]; ================================================ FILE: lang/es/campaigns/recovery.php ================================================ [ 'recover' => 'Recuperar', 'recover_selected' => 'Recuperar seleccionados', ], 'error' => 'Ha habido un error tratando de recuperar entidades.', 'fields' => [ 'deleted' => 'Eliminadas', 'deleted_at' => 'Eliminado en :date por :user', ], 'name_link' => ':name se ha recuperado correctamente', 'order' => [ 'newest' => 'Ordenar por: Más recientes primero', 'newest_first' => 'Más recientes primero', 'oldest' => 'Ordenar por: Más antiguo primero', 'oldest_first' => 'Más antiguos primero', 'type' => 'Ordenar por: Tipo', 'type_order' => 'Tipo', ], 'premium' => 'La recuperación de elementos es una función de las campañas premium.', 'success_v2'=> '{1} Se recuperó un elemento. |[2,*] Se han recuperado :count elementos.', 'title' => 'Recuperación de entidades de :campaign', 'tutorial' => 'Visualiza y restaura elementos eliminados recientemente de la campaña. Las entidades, publicaciones y otros datos de apoyo pueden recuperarse durante :amount días antes de eliminarse de forma permanente. Al restaurar un elemento, regresa con todos sus datos intactos.', ]; ================================================ FILE: lang/es/campaigns/roles.php ================================================ [ 'status' => 'Estado: :status', ], 'create' => [ 'helper' => 'Crea un nuevo rol para la campaña.', ], 'overview' => [ 'limited' => ':amount de :total roles creados.', 'title' => 'Roles disponibles', 'unlimited' => ':amount de roles ilimitados creados.', ], 'permissions' => [ 'campaign-features' => 'Funciones de la campaña', 'content-modules' => 'Módulos de contenido', 'toggle' => [ 'action' => 'Cambiar todo', 'tooltip' => 'Cambiar el permiso :action para todos los módulos.', ], ], 'public' => [ 'helpers' => [ 'click' => 'Haz clic en cualquier módulo para cambiar el acceso público a todas las entidades que contiene.', 'intro' => 'Controla lo que los no miembros pueden ver en la campaña.', 'main' => 'Selecciona qué módulos son visibles para cualquiera que vea la campaña, tanto si ha iniciado sesión como si no. Esto incluye a visitantes públicos y a usuarios de Kanka que no son miembros de la campaña.', 'preview' => 'Vista previa como no miembro', ], ], 'show' => [ 'title' => 'permisos de :role - :campaign', ], 'toggle' => [ 'disabled' => 'Los miembros del rol :role ya no pueden :action :entities', 'enabled' => 'Los miembros del rol :role ahora pueden :action :entities', ], 'warnings' => [ 'adding-to-admin' => 'Los miembros del rol :name tienen acceso a todo en la campaña, y no pueden ser eliminados por otros miembros del rol. Después de :amount de minutos, sólo ellos pueden darse de baja del rol.', ], ]; ================================================ FILE: lang/es/campaigns/share.php ================================================ [ 'change_visibility' => 'Cambiar visibilidad', 'copy' => 'Copiar enlace', 'copy_public_link' => 'Copiar enlace público', 'make_public' => 'Hacer pública y copiar enlace', ], 'helpers' => [ 'private_explanation' => 'Solo los miembros pueden acceder a las campañas privadas.', 'public_explanation' => 'Esta campaña es pública. Cualquiera con el enlace puede verla.', 'unlisted_explanation' => 'Cualquiera con el enlace puede ver esta campaña, pero no aparece en directorios públicos.', ], 'labels' => [ 'member_link' => 'Solo los miembros pueden abrir esto', 'public_link' => 'Enlace público', ], 'status' => [ 'private' => 'Campaña privada', 'public' => 'Cualquiera con el enlace puede ver esta campaña', ], 'success' => [ 'copied_members' => 'Enlace solo para miembros copiado.', 'copied_public' => 'Enlace público copiado, cualquiera con el enlace puede verlo.', 'made_public' => 'La campaña es ahora pública.', ], 'title' => 'Compartir campaña', ]; ================================================ FILE: lang/es/campaigns/sidebar.php ================================================ [ 'reset' => 'Restablecer valores por defecto', ], 'call-to-action' => 'Personaliza el orden, los iconos y los nombres de los elementos de la barra lateral de la campaña.', 'helpers' => [ 'bookmarks' => 'Los marcadores no aparecen aquí porque cada uno tiene su propia configuración de :position que determina dónde aparece en la barra lateral.', 'image' => 'Añade una imagen para representar la campaña. Esta imagen se utilizará en la barra lateral y en la interfaz del selector de campañas. Puedes cambiarla en cualquier momento editando la campaña.', 'reordering'=> 'Reordena la barra lateral arrastrando y soltando los iconos del lado izquierdo.', ], 'image-success' => 'Se ha guardado la nueva imagen de la campaña. Esta imagen se puede volver a cambiar editando la campaña.', 'reset' => [ 'success' => 'Restablecimiento de la configuración de la barra lateral de la campaña.', 'title' => 'Restablecer la configuración de la barra lateral', 'warning' => '¿Estás seguro de que quieres restablecer los valores por defecto de la barra lateral de tu campaña?', ], 'success' => 'Configuración de la barra lateral de la campaña guardada.', 'title' => 'Configuración de la barra lateral de la campaña :campaign', 'tooltips' => [ 'image' => 'Cambiar esta imagen de fondo', ], ]; ================================================ FILE: lang/es/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Calendarios', 'title' => 'Guardián del Tiempo', ], 'murderer' => [ 'goal' => 'Personajes muertos', 'title' => 'Asesino', ], ], 'fields' => [ 'created' => 'Creado el', 'creator' => 'Creado por', 'entries' => 'Total de entradas', 'from-elements' => 'De los elementos', 'from-entities' => 'De las entidades', 'from-posts' => 'De las publicaciones', 'general' => 'General', 'words' => 'Palabras totales', ], 'targets' => [], 'title2' => 'Estadísticas', 'titles' => [ 'calendars' => 'Guardián del Tiempo nivel :level', 'characters'=> 'Nombrador nivel :level', 'dead' => 'Asesino nivel :level', 'families' => 'Planificación Familiar nivel :level', 'locations' => 'Constructor nivel :level', 'quests' => 'Mastermind nivel :level', 'races' => 'Criador nivel :level', ], 'tutorial' => 'Las estadísticas de la campaña muestran el recuento de entidades y la actividad reciente. Los datos se actualizan cada :amount horas. Úsalas para seguir el crecimiento y el uso a lo largo del tiempo.', ]; ================================================ FILE: lang/es/campaigns/styles.php ================================================ [ 'current' => 'Tema actual: :theme', 'disable' => 'Desactivar', 'enable' => 'Activar', 'new' => 'Nuevo estilo', ], 'bulks' => [ 'delete' => '{1} :count estilo eliminados.|[2,*] :count estilos eliminados .', 'disable' => '{1} :count estilo desactivado.|[2,*] :count estilos desactivados.', 'enable' => '{1} :count estilo activado.|[2,*] :count estilos activados.', ], 'create' => [ 'success' => 'Se ha creado el nuevo estilo.', 'title' => 'Nuevo estilo', ], 'delete' => [ 'success' => 'Se ha eliminado el estilo :name.', ], 'errors' => [ 'max_content' => 'La regla CSS no puede tener más de :amount caracteres.', 'max_reached' => 'Número máximo de estilos (:max) alcanzado.', ], 'fields' => [ 'content' => 'Regla CSS', 'is_enabled' => 'Habilitado', 'length' => 'Longitud', 'modified' => 'Modificado', 'name' => 'Nombre', 'order' => 'Orden', ], 'helpers' => [ 'here' => 'en nuestro blog', 'is_enabled' => 'Habilita este tema en todas las páginas.', 'main' => 'Puedes crear estilos CSS personalizados para tu campaña mejorada. Estos estilos se cargan después de los temas del marketplace que hayas habilitado para la campaña. Puedes saber más sobre cómo aplicar estilos a tu campaña :here.', 'tutorial' => 'Controla el estilo visual de la campaña. Elige colores, preferencias de diseño y otras opciones de presentación. Estos cambios afectan solo a esta campaña y pueden actualizarse en cualquier momento.', ], 'pitch' => 'Crea estilos CSS personalizados para cambiar por completo el aspecto de la campaña.', 'placeholders' => [ 'name' => 'Nombre del estilo', ], 'reorder' => [ 'save' => 'Guardar nuevo orden', 'success' => '{1} :count estilos reordenados.|[2,*] :count estilos reordenados.', 'title' => 'Reordenar estilos', ], 'theme' => [ 'none' => 'Utilizar las preferencias del usuario', 'override' => 'Sobrescribir tema', 'success' => 'Tema de campaña actualizado.', 'title' => 'Actualizar el tema de la campaña', ], 'title' => 'Personalizar la campaña', 'toggle' => [ 'disable' => 'Estilo deshabilitado correctamente.', 'enable' => 'Estilo habilitado correctamente.', ], 'update' => [ 'success' => 'Se ha actualizado el estilo :name.', 'title' => 'Actualizar estilo', ], ]; ================================================ FILE: lang/es/campaigns/vanity.php ================================================ '¡El nombre :vanity está disponible!', 'forever' => 'Una vez establecido, no se puede cambiar. :docs', 'helper-v2' => 'Dale a la campaña una dirección web personalizada y fácil de recordar. En lugar de :default, la campaña podría aparecer como: :example', 'rule' => 'El campo :field necesita al menos un carácter alfabético.', 'rule2' => 'El campo :field no permite el siguiente carácter: /.', 'set' => 'La URL de vanidad de la campaña se ha establecido permanentemente como :vanity.', ]; ================================================ FILE: lang/es/campaigns/visibilities.php ================================================ [ 'public' => 'Listada en campañas públicas', 'unlisted' => 'Oculta de las campañas públicas', ], 'helpers' => [ 'premium' => 'Para habilitar esta configuración, las funciones premium deben estar desbloqueadas para esta campaña.', 'private' => 'Solo los miembros invitados pueden acceder', 'public' => 'Cualquiera con un enlace puede ver', ], 'titles' => [ 'private' => 'Privada', 'public' => 'Pública', 'unlisted' => 'Pública (no listada)', ], ]; ================================================ FILE: lang/es/campaigns/webhooks.php ================================================ [ 'action' => 'Cambiar estado', 'add' => 'Crear webhook', 'bulks' => [ 'delete_success' => '{1} Se ha eliminado :count webhook.|[2,*] Se han eliminado :count webhooks.', 'disable' => 'Desactivar', 'disable_success' => '{1} Se ha desactivado :count webhook.|[2,*] Se han desactivado :count webhooks.', 'enable' => 'Activar', 'enable_success' => '{1} Se ha activado :count webhook.|[2,*] Se han activado :count webhooks.', ], 'test' => 'Webhook de prueba', 'update' => 'Actualizar webhook', ], 'create' => [ 'success' => 'El webhook se ha creado con éxito', 'title' => 'Añadir nuevo webhook', ], 'destroy' => [ 'success' => 'Webhook eliminado correctamente.', ], 'edit' => [ 'success' => 'Webhook actualizado correctamente', 'title' => 'Actualizar webhook', ], 'error' => [ 'pitch' => 'Desbloquea funciones premium para acceder a los webhooks.', ], 'fields' => [ 'enabled' => 'Habilitado', 'event' => 'Evento', 'events' => [ 'deleted' => 'Entidad eliminada', 'edited' => 'Entidad editada', 'new' => 'Entidad nueva', ], 'message' => 'Mensaje', 'private_entities' => [ 'helper' => 'No activar el webhook al actualizar entidades privadas.', 'skip' => 'Omitir entidades privadas', ], 'type' => 'Tipo', 'types' => [ 'custom' => 'Mensaje', 'payload' => 'Payload', ], 'url' => 'Url', ], 'helper' => [ 'active' => 'Si el webhook está activo', 'message' => 'Añade un mensaje personalizado con soporte para asignaciones', 'status' => 'Cambiar el estado activo del webhook', 'tutorial' => 'Usa webhooks para enviar actualizaciones en tiempo real de la campaña a herramientas externas. Los eventos se activan automáticamente cuando se crean, actualizan o eliminan entidades. Puedes añadir varios webhooks y probarlos desde esta página.', ], 'placeholders' => [ 'message' => '{who} ha hecho cambios en {name}, revísalos en {url}.', 'url' => 'URL del webhook', ], 'test' => [ 'success' => 'Solicitud de prueba enviada', ], 'title' => 'Webhooks', 'toggle' => [ 'disable' => 'Webhook deshabilitado correctamente.', 'enable' => 'Webhook habilitado correctamente.', ], ]; ================================================ FILE: lang/es/campaigns.php ================================================ [], 'create' => [ 'success' => 'Campaña creada.', 'title' => 'Nueva campaña', ], 'destroy' => [], 'edit' => [ 'success' => 'Campaña actualizada.', ], 'entity_note_visibility' => [], 'entity_personality_visibilities' => [ 'private' => 'La personalidad de los personajes nuevos es privada por defecto.', ], 'entity_visibilities' => [ 'private' => 'Nuevas entidades privadas por defecto', ], 'errors' => [ 'access' => 'No tienes acceso a esta campaña.', 'premium' => 'Esta función sólo está disponible para las campañas premium.', 'unknown_id' => 'Campaña desconocida.', ], 'export' => [], 'fields' => [ 'boosted' => 'Mejorada por', 'entity_count' => 'Número de entidades', 'entry' => 'Descripción de la campaña', 'followers' => 'Seguidores', 'genre' => 'Género(s)', 'header_image' => 'Imagen de cabecera', 'image' => 'Imagen', 'locale' => 'Idioma', 'name' => 'Nombre', 'open' => 'Inscripciones abiertas', 'premium' => 'Premium desbloqueado por :name', 'public' => 'Visibilidad de la campaña', 'public_campaign_filters' => 'Filtros de las campañas públicas', 'superboosted' => 'Supermejorada por', 'system' => 'Sistema', 'theme' => 'Tema', 'vanity' => 'URL personalizada', ], 'following' => 'Siguiendo', 'helpers' => [ 'boosted' => 'Algunas características están desbloqueadas porque esta campaña está mejorada. Para saber más sobre esto, echa un vistazo en la página de :settings.', 'css' => 'Escribe tu propio CSS para las páginas de tu campaña. Ten en cuenta que abusar de esta herramienta puede llevar a la eliminación de tu CSS personalizado. Incumplimientos repetidos o graves pueden llevar a la eliminación de tu campaña.', 'dashboard' => 'Personaliza la forma en que el widget se muestra en el tablero rellenando los campos siguientes.', 'excerpt' => 'El extracto de la campaña se mostrará en el tablero principal. Escribe unas pocas líneas para introducir tu mundo. Si lo dejas en blanco, se mostrarán los primeros 1.000 caracteres de la descripción de la campaña.', 'header_image' => 'La imagen que se muestra como fondo en el widget de la cabecera del tablero.', 'hide_history' => 'Habilita esta opción para esconder el historial de entidades a los miembros no administradores.', 'hide_members' => 'Habilita esta opción para esconder la lista de miembros de la campaña a los no administradores.', 'locale' => 'El idioma en que está escrita tu campaña. Esto se usa para generar contenido y agrupar campañas públicas.', 'name' => 'Tu campaña/mundo puede tener cualquier nombre, siempre y cuando contenga al menos 4 letras o números.', 'no_entry' => '¡Parece que la campaña aún no tiene una descripción! Arreglemos eso.', 'premium' => 'Algunas funciones están disponibles porque las funciones premium de esta campaña están desbloqueadas. Más información en la página de :settings.', 'public_campaign_filters' => 'Facilita que otros encuentren tu campaña entre las demás proporcionando la siguiente información.', 'public_no_visibility' => '¡Ojo! Tu campaña es pública, pero el rol público no tiene acceso a nada. :fix.', 'system' => 'Si tu campaña es visible públicamente, el sistema se mostrará en la página de :link.', 'systems' => 'Para evitar líos, algunos elementos de Kanka solo están disponibles en sistemas RPG específicos (por ejemplo, el bloque de stats de monstruo de D&D 5e). Si eliges un sistema soportado, se activarán dichos elementos.', 'theme' => 'Establece un tema único para la campaña, anulando las preferencias de los usuarios.', 'view_public' => 'Para ver tu campaña como lo haría un visitante público, abre un :link en una ventana de incógnito.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Copiar el enlace al portapapeles', 'link' => 'Nuevo enlace', ], 'create' => [ 'buttons' => [ 'create' => 'Crear invitación', ], 'success_link' => 'Link creado. :link', 'title' => 'Invita a alguien a tu campaña', ], 'destroy' => [ 'success' => 'Invitación eliminada.', ], 'error' => [ 'inactive_token' => 'Este identificador ya se ha usado o la campaña ya no existe.', 'invalid_token' => 'El identificador ya no es válido.', 'join' => 'Inicia sesión o registra una nueva cuenta para unirte a :campaign.', ], 'fields' => [ 'created' => 'Enviado', 'role' => 'Rol', 'token' => 'Token', 'type' => 'Tipo', 'usage' => 'Número máximo de usos', ], 'helpers' => [ 'role' => 'Los usuarios tienen que unirse antes de que puedan ser ascendidos al rol de administrador.', 'usage' => 'Cuántas veces se puede utilizar el enlace de invitación antes de que quede inactivo.', ], 'unlimited_validity' => 'Ilimitado', 'usages' => [ 'five' => '5 usos', 'no_limit' => 'Ilimitado', 'once' => '1 uso', 'ten' => '10 usos', ], ], 'leave' => [ 'action' => 'Salir de la campaña', 'confirm' => '¿Seguro que quieres abandonar la campaña :name? No tendrás acceso a ella, a no ser que un administrador te invite de nuevo.', 'confirm-button' => 'Sí, abandonar la campaña', 'error' => 'No puedes abandonar la campaña.', 'fix' => 'Ir a los miembros de la campaña', 'no-admin-left' => 'No es posible salir de la campaña porque hacerlo la dejaría sin administradores. Agrega otro miembro al rol de administrador primero.', 'success' => 'Has abandonado la campaña.', 'title' => 'Saliendo de la campaña', ], 'members' => [ 'actions' => [ 'remove' => 'Quitar de la campaña', 'switch' => 'Ver como', 'switch-back' => 'Volver a mi usuario', 'switch-entity' => 'Ver como', ], 'fields' => [ 'banned' => 'El usuario está baneado', 'joined' => 'Inscrito', 'last_login' => 'Última conexión', 'name' => 'Usuario', 'role' => 'Rol', 'roles' => 'Roles', ], 'helpers' => [ 'switch' => 'Ver como este usuario', ], 'impersonating' => [ 'message' => 'Estás viendo la campaña como otro usuario. Algunas funcionalidades están deshabilitadas, pero el resto actúa exactamente como el usuario lo vería. Para volver a tu usuario, usa el botón "Volver a mi usuario" cerca del botón de Cerrar Sesión.', 'title' => 'Estás viendo como :name', ], 'invite' => [ 'description' => 'Puedes invitar a tus amigos a unirse a la campaña mediante un enlace de invitación. Una vez acepten la invitación, se añadirán con su rol correspondiente. También puedes enviarles la invitación por correo electrónico, siempre y cuando no sea una dirección de Hotmail, ya que siempre rechazan los mails de Kanka.', 'more' => 'Puedes añadir más roles desde la :link.', 'title' => 'Invitaciones', ], 'removal' => 'Estás eliminando a ":member" de la campaña.', 'roles' => [ 'member' => 'Miembro', 'owner' => 'Administrador', 'player' => 'Jugador', 'public' => 'Público', 'viewer' => 'Invitado', ], 'switch_back_success' => 'Has vuelto a tu usuario.', ], 'mentions' => [], 'modules' => [], 'open_campaign' => [], 'options' => [], 'overview' => [ 'entity-count' => '{0} Sin entidades|{1} :amount entidad|[2,*] :amount entidades', 'follower-count' => '{0} Sin seguidores|{1} :amount seguidor|[2,*] :amount seguidores', ], 'panels' => [ 'dashboard' => 'Tablero', 'privacy' => 'Valores predeterminados de privacidad', 'setup' => 'Configuración', 'sharing' => 'Compartir', 'systems' => 'Sistemas', 'ui' => 'Interfaz', ], 'placeholders' => [ 'locale' => 'Código de idioma', 'name' => 'El nombre de tu campaña', 'system' => 'D&D 5e, Pathfinder, Fate...', ], 'privacy' => [ 'hidden' => 'Oculta', 'private' => 'Privada', 'visible' => 'Visible', ], 'public' => [ 'helpers' => [ 'introduction' => 'Las campañas son privadas por defecto, pero se pueden hacer públicas. De esta forma todo el mundo podrá acceder a ellas y aparecerán en la página de :public-campaigns si tienen entidades visibles para el rol :public-role. Una campaña pública es visible para todos, pero para que su contenido se pueda ver, hay que configurar los permisos del rol :public-role.', ], ], 'roles' => [ 'actions' => [ 'add' => 'Añadir un rol', 'duplicate' => 'Duplicar rol', 'permissions' => 'Configurar permisos', 'rename' => 'Renombrar rol', 'save' => 'Guardar rol', ], 'admin_role' => 'rol de administrador', 'bulks' => [ 'delete' => '{1} :count rol eliminado.|[2,*] :count roles eliminados.', 'edit' => '{1} :count rol actualizado.|[2,*] :count roles actualizados.', ], 'create' => [ 'success' => 'Rol creado.', 'title' => 'Crear un nuevo rol en :name', ], 'destroy' => [ 'success' => 'Rol eliminado.', ], 'edit' => [ 'success' => 'Rol actualizado.', 'title' => 'Editar rol :name', ], 'fields' => [ 'copy_permissions' => 'Copiar permisos', 'name' => 'Nombre', 'permissions' => 'Permisos', 'type' => 'Tipo', 'users' => 'Usuarios', ], 'helper' => [ '1' => 'Una campaña puede tener tantos roles como se quiera. El rol "Administrador" tiene acceso automáticamente a todo dentro de una campaña, pero cada uno de los demás roles puede tener permisos específicos en diferentes tipos de entidades (personajes, lugares, etc).', '2' => 'Las entidades pueden tener permisos más afinados mediante la pestaña "Permisos" de una entidad. Esta pestaña aparece cuando tu campaña tiene varios roles o miembros.', '3' => 'Se puede usar un sistema de "exclusión", donde los roles tienen acceso a todas las entidades, y usar la casilla de "Privado" en las entidades que se quieran ocultar. O bien, pueden darse pocos permisos a los roles, y configurar cada entidad para que sea visible individualmente.', '4' => 'Las campañas mejoradas pueden tener una cantidad ilimitada de roles.', 'permissions_helper' => 'Duplicar todos los permisos del rol, tanto en módulos como en entidades.', ], 'hints' => [ 'campaign_not_public' => 'El rol "Público" tiene permisos pero la campaña es privada. Puedes ajustar esto en la pestaña Compartir al editar la campaña.', 'empty_role' => 'El rol aún no tiene miembros.', 'role_admin' => 'Los miembros del rol :name automáticamente pueden acceder a todas las entidades y características de la campaña.', 'role_permissions' => 'Habilitar el rol ":name" para que pueda hacer las siguientes acciones en todas las entidades.', ], 'members' => 'Miembros', 'modals' => [ 'details' => [ 'campaign' => 'Los permisos de la campaña permiten lo siguiente.', 'entities' => 'A continuación se muestra un resumen de los permisos de este rol.', 'more' => 'Para más detalles, mira nuestro tutorial en Youtube.', 'title' => 'Detalles de permisos', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Crear', 'articles' => 'Artículos', 'dashboard' => 'Tablero', 'delete' => 'Eliminar', 'edit' => 'Editar', 'gallery' => [ 'browse' => 'Navegar', 'manage' => 'Control absoluto', 'upload' => 'Subir', ], 'manage' => 'Configurar', 'members' => 'Miembros', 'permission' => 'Administrar permisos', 'read' => 'Ver', 'toggle' => 'Cambiar para todos', ], 'helpers' => [ 'add' => 'Permite crear entidades de este tipo. Podrán ver y editar las entidades que creen aunque no tengan el permiso de ver o editar.', 'articles' => 'Permite agregar, editar y eliminar artículos incluso si el miembro no puede editar la entrada.', 'dashboard' => 'Permite editar los tableros y sus widgets.', 'delete' => 'Permite eliminar todas las entidades de este tipo.', 'edit' => 'Permite editar todas las entidades de este tipo.', 'gallery' => [ 'browse' => 'Permite ver la galería y establecer la imagen de una entidad desde la galería.', 'manage' => 'Permite todo en la galería como un administrador puede, incluyendo la edición y eliminación de imágenes.', 'upload' => 'Permite subir imágenes a la galería. Sólo verá las imágenes que han subido si no se combina con el permiso de navegación.', ], 'manage' => 'Permite editar la campaña como un administrador, excepto eliminar la campaña.', 'members' => 'Permite invitar nuevos miembros a la campaña.', 'not_public' => 'La campaña no es pública. Se pueden establecer permisos para el rol público, pero serán ignorados. Edita la campaña para hacerla pública.', 'permission' => 'Permite configurar los permisos de las entidades de este tipo que puedan editar.', 'read' => 'Permite visualizar todas las entidades de este tipo que no sean privadas.', ], ], 'placeholders' => [ 'name' => 'Nombre del rol', ], 'title' => 'Roles de la campaña :name', 'types' => [ 'owner' => 'Administrador', 'public' => 'Público', 'standard' => 'Estándar', ], 'users' => [ 'actions' => [ 'add' => 'Añadir miembro', 'remove' => ':user del rol :role', 'remove_user' => 'Quitar usuario del rol', ], 'create' => [ 'success' => 'Usuario añadido al rol.', 'title' => 'Añadir un miembro al rol :name', ], 'destroy' => [ 'success' => 'Usuario eliminado del rol.', ], 'errors' => [ 'cant_kick_admins' => 'Para evitar abusos, no es posible eliminar a otros miembros del rol de :admin de la campaña. En caso de problemas, contáctanos en :discord o en :email.', 'needs_more_roles' => 'Debes agregarte a otro rol en la campaña antes de poder eliminarte del rol de :admin.', ], 'fields' => [ 'name' => 'Nombre', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Habilitar', ], 'boosted' => 'Esta función está en beta y actualmente solo está disponible para las :boosted.', 'deprecated' => [ 'help' => 'Este módulo está obsoleto, lo que significa que ya no recibe mantenimiento y que no se prueba con cada nueva actualización. Usa este módulo con el conocimiento de que eventualmente se eliminará de Kanka.', 'title' => 'Obsoleto', ], 'disabled' => 'El módulo :module está deshabilitado.', 'enabled' => 'El módulo :module está habilitado.', 'errors' => [ 'module-disabled' => 'El módulo solicitado está actualmente deshabilitado en la configuración de la campaña. :fix.', ], 'helpers' => [ 'abilities' => 'Crea habilidades, proezas, hechizos o poderes y asígnalos a entidades.', 'assets' => 'Sube archivos, establece enlaces y define alias para entidades individuales.', 'bookmarks' => 'Crea marcadores de entidades o listas filtradas que aparezcan en la barra lateral.', 'calendars' => 'El sitio para definir los calendarios de tu mundo.', 'characters' => 'Las personas que viven en tu mundo.', 'conversations' => 'Conversaciones ficticias entre personajes o entre usuarios de la campaña.', 'creatures' => 'Crea las criaturas, animales y monstruos de tu mundo con el módulo de criaturas.', 'dice_rolls' => 'Una manera de manejar las tiradas de dados para aquellos que usan Kanka para campañas de rol.', 'entity_attributes' => 'Lleva un control de los atributos de las entidades de la campaña, por ejemplo, HP o VELOCIDAD.', 'events' => 'Celebraciones, festivales, desastres, cumpleaños, guerras...', 'families' => 'Clanes o familias, sus relaciones y sus miembros.', 'inventories' => 'Gestiona el inventario de tus entidades.', 'items' => 'Armas, vehículos, reliquias, pociones...', 'journals' => 'Observaciones escritas por los personajes, o preparación de la sesión del máster.', 'locations' => 'Planetas, planos, continentes, ríos, estados, asentamientos, templos, tabernas...', 'maps' => 'Sube mapas con diferentes capas y marcadores que señalen a otras entidades de la campaña.', 'notes' => 'Tradiciones, religiones, historia, magia, razas...', 'organisations' => 'Sectas, unidades militares, facciones, gremios...', 'quests' => 'Para llevar un seguimiento de varias misiones con personajes y localizaciones.', 'races' => 'Si tu campaña tiene más de una raza, de esta forma no las perderás de vista.', 'tags' => 'Cada entidad puede tener varias etiquetas. Éstas pueden pertenecer a otras etiquetas, y las entradas pueden filtrarse por etiqueta.', 'timelines' => 'Representa la historia de tu mundo con líneas de tiempo.', 'whiteboards' => 'Dibuja y escribe en pizarras para planificar visualmente tu mundo y tus objetivos.', ], ], 'sharing' => [ 'filters' => 'Las campañas públicas son visibles en la página :public-campaigns. Rellenar estos campos facilita que las personas descubran la campaña.', 'language' => 'El idioma en el que está escrito el contenido de la campaña.', 'system' => 'Si juegas un TTRPG, el sistema usado para jugar en la campaña.', ], 'show' => [ 'actions' => [ 'edit' => 'Editar campaña', ], 'tabs' => [ 'achievements' => 'Logros', 'customisation' => 'Personalización', 'danger' => 'Peligro', 'data' => 'Datos', 'default-images' => 'Imágenes por defecto', 'defaults' => 'Valores predeterminados', 'deletion' => 'Eliminación', 'export' => 'Exportar', 'import' => 'Importar', 'logs' => 'Registros', 'management' => 'Gestión', 'members' => 'Miembros', 'plugins' => 'Plugins', 'recovery' => 'Recuperación', 'roles' => 'Roles', 'sidebar' => 'Configuración de la barra lateral', 'stats' => 'Estadísticas', 'styles' => 'Personalización', 'webhooks' => 'Webhooks', ], 'title' => 'Campaña :name', ], 'status' => [ 'free' => 'Funciones Premium desactivadas.', 'legacy' => [ 'title' => 'Funciones mejoradas (antiguas)', ], 'premium' => 'Funciones premium desbloqueadas por :name.', 'title' => 'Funciones Premium', ], 'superboosted' => [], 'themes' => [ 'none' => 'Ninguno (el valor predeterminado es configuraciónes de usuario)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Solo visible para los administradores de campaña', 'visible' => 'Visible para los miembros', ], 'fields' => [ 'entity_history' => 'Registros históricos de la entidad', 'member_list' => 'Lista de miembros de la campaña', ], 'helpers' => [ 'entity-history' => 'Controla quién puede ver los cambios realizados recientemente en cada entidad de la campaña.', 'member-list' => 'Controla quién puede ver a los miembros de la campaña.', 'theme' => 'Muestra la campaña en el tema del usuario u oblíguela mostrarse en uno de los siguientes temas.', ], 'members' => [ 'hidden' => 'Solo visible para administradores de campaña', 'visible' => 'Visible para miembros', ], ], 'visibilities' => [ 'private' => 'Privada', 'public' => 'Pública', 'unlisted' => 'Público (no listado)', ], 'warning' => [], ]; ================================================ FILE: lang/es/characters.php ================================================ [ 'add_appearance' => 'Añadir apariencia', 'add_personality' => 'Añadir personalidad', ], 'conversations' => [], 'create' => [ 'title' => 'Nuevo Personaje', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'families' => [ 'helper' => 'Reordena y controla qué familias de :name son visibles o están ocultas para los usuarios que no son administradores.', 'reorder' => [ 'success' => 'Las familias del personaje se han actualizado correctamente.', ], 'title2' => 'Gestionar familias', ], 'fields' => [ 'age' => 'Edad', 'is_appearance_pinned' => 'Apariencia fijada', 'is_dead' => 'Muerto', 'is_personality_pinned' => 'Personalidad fijada', 'is_personality_visible' => 'Personalidad visible', 'life' => 'Biografía', 'physical' => 'Apariencia', 'pronouns' => 'Pronombres', 'sex' => 'Género', 'title' => 'Título', 'traits' => 'Características', ], 'helpers' => [ 'age' => 'Puedes vincular esta entidad con un calendario de la campaña para calcular automáticamente su edad. :more', ], 'hints' => [ 'is_appearance_pinned' => 'Si está seleccionado, la apariencia del personaje aparecerá bajo la entrada principal de la página.', 'is_dead' => 'Este personaje está muerto', 'is_personality_visible' => 'Se puede ocultar la sección de personalidad a los usuarios no administradores.', 'personality_not_visible' => 'Los rasgos de personalidad de este personaje actualmente solo son visibles para los administradores.', 'personality_visible' => 'Los rasgos de personalidad de este personaje son visibles para todos.', ], 'index' => [], 'items' => [], 'journals' => [], 'labels' => [ 'appearance' => [ 'entry' => 'Descripción de la apariencia', 'name' => 'Nombre de la apariencia', ], 'personality' => [ 'entry' => 'Descripción del rasgo de personalidad', 'name' => 'Nombre del rasgo de personalidad', ], ], 'lists' => [ 'empty' => 'Crea tu primer héroe, villano o acompañante para dar vida a tu mundo.', ], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => 'Personaje añadido a la organización.', 'title' => 'Nueva organización para :name', ], 'destroy' => [ 'success' => 'Personaje quitado de la organización.', ], 'edit' => [ 'success' => 'Organización del personaje actualizada.', 'title' => 'Actualizar organización de :name', ], 'fields' => [ 'role' => 'Rol', ], ], 'placeholders' => [ 'age' => 'Edad', 'appearance_entry' => 'Descripción', 'appearance_name' => 'Cabello, ojos, piel, altura...', 'name' => 'Nombre del personaje', 'personality_entry' => 'Detalles', 'personality_name' => 'Objetivos, manías, miedos, lazos...', 'physical' => 'Físico', 'pronouns' => 'Él, ella, elle', 'sex' => 'Género', 'title' => 'Título', 'traits' => 'Características', 'type' => 'PNJ, Personaje Jugador, divinidad...', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Misiones que el personaje ha promovido.', 'quest_member' => 'Misiones de las que el personaje es miembro.', ], ], 'races' => [ 'helper' => 'Reordena y controla qué razas de :name son visibles u ocultas para los usuarios que no son administradores.', 'reorder' => [ 'success' => 'Razas del personaje actualizadas correctamente', ], 'title2' => 'Gestionar razas', ], 'sections' => [ 'appearance' => 'Apariencia', 'personality' => 'Personalidad', ], 'show' => [], 'warnings' => [ 'personality_hidden' => 'No puedes editar los rasgos de personalidad de este personaje.', ], ]; ================================================ FILE: lang/es/colours.php ================================================ 'Cian', 'black' => 'Negro', 'blue' => 'Azul', 'brown' => 'Marrón', 'green' => 'Verde', 'grey' => 'Gris', 'light-blue' => 'Azul claro', 'maroon' => 'Granate', 'navy' => 'Azul marino', 'none' => 'Ninguno', 'orange' => 'Naranja', 'pink' => 'Rosa', 'purple' => 'Lila', 'red' => 'Rojo', 'teal' => 'Turquesa', 'white' => 'Blanco', 'yellow' => 'Amarillo', ]; ================================================ FILE: lang/es/concept.php ================================================ 'campaña mejorada', 'premium-campaign' => 'campaña premium', 'premium-campaign-count' => '{0} Campañas no Premium |{1} 1 Campaña Premium |[2,*] :count Campañas Premium', 'premium-campaigns' => 'campañas premium', 'premium-feature' => 'Función Premium', 'superboosted-campaign' => 'campaña supermejorada', ]; ================================================ FILE: lang/es/confirm/editing.php ================================================ 'Regresar', 'description' => 'Parece que otra persona está editando esta página. ¿Quieres regresar o ignorar esta advertencia, con el riesgo de perder datos?', 'ignore' => 'Editar de todos modos', 'members' => 'Miembros editando esta página:', 'title' => 'Advertencia', 'user' => ':user desde :since', ]; ================================================ FILE: lang/es/confirm.php ================================================ [ 'bulk' => '¿Estás seguro de que deseas eliminar los elementos seleccionados?', 'helper' => '¿Estás seguro de que deseas eliminar :name?', 'recoverable' => 'Esta acción se puede deshacer hasta :day días después con una :premium-campaign.', 'title' => 'Eliminar elemento', ], ]; ================================================ FILE: lang/es/connections/web.php ================================================ [ 'add' => 'Conectar existente', 'back' => 'Volver a conexiones', 'download' => 'Descargar PNG', 'download-pdf' => 'Descargar PDF', 'download-png' => 'Descargar imagen (.png)', 'reset-layout' => 'Restablecer diseño', 'view' => 'Ver red', 'zoom-fit' => 'Zoom para ajustar', ], 'cta' => [ 'text' => 'Esta es una vista previa de la red completa de conexiones de la campaña. Las campañas gratuitas pueden explorar hasta :amount nodos. Las campañas premium desbloquean el mapa completo con todas las conexiones y navegación total. Actualiza para ver toda la estructura de tu mundo de una sola vez.', 'title' => 'Vista previa limitada a :amount conexiones', ], 'title' => 'Red de conexiones', ]; ================================================ FILE: lang/es/conversations.php ================================================ [ 'title' => 'Nueva Conversación', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_closed' => 'Cerrada', 'messages' => 'Mensajes', 'participants' => 'Participantes', ], 'hints' => [ 'empty' => 'No hay participantes en esta conversación.', 'participants' => 'Añade participantes a la conversación mediante el icono :icon arriba a la derecha.', ], 'index' => [], 'lists' => [ 'empty' => 'Registra diálogos, cartas o intercambios entre personajes y facciones.', ], 'messages' => [ 'destroy' => [ 'success' => 'Mensaje eliminado.', ], 'is_updated' => 'Actualizado', 'load_previous' => 'Cargar mensajes previos', 'placeholders' => [ 'message' => 'Tu mensaje', ], ], 'participants' => [ 'create' => [ 'success' => 'El participante :entity se ha añadido a la conversación.', ], 'destroy' => [ 'success' => 'El participante :entity se ha eliminado de la conversación.', ], 'helper' => 'Agregar y eliminar participantes de :name.', 'modal' => 'Participantes', 'title' => 'Participantes de :name', ], 'placeholders' => [ 'name' => 'Nombre de la conversación', 'type' => 'Dentro del juego, Preparación, Argumento...', ], 'show' => [ 'is_closed' => 'La conversación se ha cerrado.', ], 'tabs' => [ 'participants' => 'Participantes', ], 'targets' => [ 'characters' => 'Personajes', 'members' => 'Miembros', ], ]; ================================================ FILE: lang/es/cookieconsent.php ================================================ 'Permitir cookies', 'dismiss' => 'Descartar', 'header' => 'Consentimiento de uso de cookies', 'link' => 'Más información', 'message' => 'Kanka utiliza cookies para que tengas la mejor experiencia en nuestro sitio web.', 'policy' => 'Política de cookies', 'reject' => 'Declinar', ]; ================================================ FILE: lang/es/creatures.php ================================================ [ 'title' => 'Nueva criatura', ], 'fields' => [ 'is_dead' => 'Muerto', 'is_extinct' => 'Extinta', ], 'helpers' => [], 'hints' => [ 'is_dead' => 'Esta criatura está muerta.', 'is_extinct' => 'Esta criatura está extinguida.', ], 'lists' => [ 'empty' => 'Añade bestias, monstruos o seres míticos que tus héroes puedan enfrentar o entablar amistad.', ], 'placeholders' => [ 'type' => 'Herbívoro, Acuático, Mítico', ], ]; ================================================ FILE: lang/es/crud.php ================================================ [ 'actions' => 'Acciones', 'apply' => 'Aplicar', 'back' => 'Atrás', 'change' => 'Cambiar', 'close' => 'Cerrar', 'confirm' => 'Confirmar', 'copy' => 'Copiar', 'copy_mention' => 'Copiar mención [ ]', 'copy_to_campaign' => 'Copiar a campaña', 'disable' => 'Desactivar', 'enable' => 'Activar', 'explore_view' => 'Vista anidada', 'export' => 'Exportar', 'find_out_more' => 'Saber más', 'go_to' => 'Ir a :name', 'help' => 'Ayuda', 'json-export' => 'Exportar (JSON)', 'markdown-export' => 'Exportar (Markdown)', 'move' => 'Mover', 'new' => 'Nuevo', 'new_child' => 'Nuevo hijo', 'new_post' => 'Nuevo artículo', 'next' => 'Siguiente', 'open' => 'Abrir', 'print' => 'Imprimir', 'reorder' => 'Reordenar', 'reset' => 'Restablecer', 'save-changes' => 'Guardar cambios', 'share' => 'Compartir', 'transform' => 'Transformar', ], 'add' => 'Añadir', 'alerts' => [ 'copy_attribute' => 'Se ha copiado la mención del atributo al portapapeles.', 'copy_invite' => 'El enlace de invitación a la campaña se ha copiado en el portapapeles.', 'copy_mention' => 'La mención avanzada de la entidad se ha copiado al portapapeles.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Editar y etiquetar en lote', 'kits' => 'Aplicar kit de propiedades', 'permissions' => 'Cambiar permisos', ], 'age' => [ 'helper' => 'Puedes usar + y - antes del número para modificar la edad.', ], 'buttons' => [ 'label' => 'Para la selección', ], 'edit' => [ 'locations' => 'Acción para las ubicaciones', 'tagging' => 'Acción para las etiquetas', 'tags' => [ 'add' => 'Añadir', 'remove' => 'Eliminar', ], 'title' => 'Edición múltiple', ], 'errors' => [ 'admin' => 'Solamente los administradores de la campaña pueden cambiar el estatus privado de las entidades.', 'general' => 'Ha habido un error al procesar la acción. Vuelve a intentarlo o contáctanos si el problema persiste. Mensaje de error: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Ignorar', ], 'helpers' => [ 'override' => 'Si está seleccionado, los permisos de las entidades seleccionadas serán ignorados y usarán estos ajustes en su lugar. Si no está seleccionado, estos permisos se añadirán a los existentes.', ], 'title' => 'Cambiar permisos a varias entidades', ], ], 'bulk_templates' => [ 'bulk_title' => 'Aplicar una plantilla a varias entidades', ], 'cancel' => 'Cancelar', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Copiar entidades a otra campaña', 'panel' => 'Copiar', 'title' => 'Copiar ":name" a otra campaña', ], 'create' => 'Crear', 'datagrid' => [ 'empty' => 'Aún no hay nada que mostrar.', ], 'delete_modal' => [ 'callout' => '¡Pssst!', 'confirm' => 'Confirmar eliminación', 'permanent' => 'Esta acción es permanente.', 'recoverable' => 'Las entidades pueden recuperarse durante un máximo de :día días con una campaña :boosted.', 'title' => 'Eliminar', ], 'destroy_many' => [], 'dynamic' => [ 'permission' => 'No tienes los permisos adecuados para crear una entidad del módulo :module.', 'unknown' => 'Entidad inválida del módulo :module.', ], 'edit' => 'Editar', 'errors' => [ 'boosted_campaigns' => 'Esta funcionalidad solo está disponible para las :boosted.', 'unavailable_feature' => 'Funcionalidad no disponible', ], 'events' => [], 'fields' => [ 'archived' => 'Archivado', 'calendar_date' => 'Fecha del calendario', 'category' => 'Categoría', 'child' => 'Hijo', 'closed' => 'Cerrado', 'colour' => 'Color', 'copy_abilities' => 'Copiar habilidades', 'copy_inventory' => 'Copiar inventario', 'copy_links' => 'Copiar notas de entidad', 'copy_permissions' => 'Copiar permisos (esto anulará los valores que hayas configurado en la pestaña de permisos)', 'copy_posts' => 'Copiar publicaciones (esto incluye los permisos de cada publicación)', 'copy_reminders' => 'Copy Reminders', 'creator' => 'Creador', 'date_range' => 'Rango de fechas', 'excerpt' => 'Extracto', 'has_entity_files' => 'Tiene archivos', 'has_entry' => 'Tiene entrada', 'has_image' => 'Tiene imagen', 'has_posts' => 'Tiene artículos', 'header_image' => 'Imagen de cabecera', 'image' => 'Imagen', 'is_closed' => 'La conversación se cerrará y no aceptará más mensajes nuevos.', 'is_private' => 'Privado', 'is_private_v3' => 'Muestra esto solo a miembros del rol :admin-role. Esto anula cualquier otro permiso.', 'is_star' => 'Fijada', 'locations' => ':first en :second', 'name' => 'Nombre', 'names' => 'Nombres', 'parent' => 'Padre', 'position' => 'Posición', 'replace_mentions' => 'Sustituir las menciones de atributos de la entrada por las de la nueva entidad.', 'template' => 'Plantilla', 'tooltip' => 'Descripción emergente', 'type' => 'Tipo', 'visibility' => 'Visibilidad', 'word-count' => 'Número de palabras: :number', ], 'files' => [ 'errors' => [ 'max' => 'Has alcanzado el número máximo (:max) de archivos para esta entidad.', 'max_size' => 'La campaña ha alcanzado la capacidad de almacenamiento de archivos máxima.', 'no_files' => 'No hay archivos.', ], 'hints' => [ 'limit' => 'Cada entidad puede tener un máximo de :max archivos.', 'limitations' => 'Formatos soportados: :formats. Tamaño máximo de archivo: :size', ], ], 'filter' => 'Filtrar', 'filters' => [ 'all' => 'Mostrar todos los descendientes', 'clear' => 'Quitar filtros', 'copy_helper' => 'Usa los filtros copiados en el portapapeles para filtrar los widgets del tablero y los accesos directos.', 'copy_to_clipboard' => 'Copiar filtros', 'direct' => 'Filtrar solo los descendientes directos', 'filtered' => 'Mostrando :count de :total :entity.', 'lists' => [ 'desktop' => [ 'all' => 'Mostrar todos los descendientes (:count)', 'filtered' => 'Mostrar los descendientes directos (:count)', ], 'paginated' => 'Alterna entre hijos directos y todos los niveles de descendientes. Los resultados se muestran paginados para un mejor rendimiento.', ], 'mobile' => [ 'clear' => 'Quitar', 'copy' => 'Portapapeles', ], 'options' => [ 'any' => 'Cualquiera', 'children' => 'Coincide con éste o sus descendientes', 'exclude' => 'Excluir', 'hide' => 'Ocultar', 'include' => 'Incluir', 'none' => 'Nada', 'show' => 'Mostrar', ], 'show' => 'Mostrar filtros', 'sorting' => [ 'asc' => 'Ascendiente por :field', 'desc' => 'Descendiente por :field', 'helper' => 'Controla en qué orden aparecen los resultados.', ], 'title' => 'Filtros', ], 'fix-this-issue' => 'Arreglar este problema', 'forms' => [ 'actions' => [ 'calendar' => 'Añadir fecha de calendario', ], 'copy_options' => 'Opciones de copia', ], 'helpers' => [ 'copy_options' => 'Copia los siguientes elementos relacionados del origen a la nueva entidad.', 'linking' => 'Vinculando con otras entidades', 'parent' => 'Selecciona un padre del que la entidad será hijo.', 'per-page' => 'Resultados por página', ], 'hidden' => 'Oculto', 'hints' => [ 'calendar_date' => 'Las fechas de calendario hacen que sea más fácil filtrar las listas, y también fijan los eventos al calendario seleccionado.', 'image_dimension' => 'Dimensiones recomendadas: :dimension píxeles.', 'image_limitations' => 'Formatos soportados: :formats. Tamaño máximo del archivo: :size.', 'image_recommendation' => 'Dimensiones recomendadas: :width por :height px.', 'is_star' => 'Los elementos fijados aparecerán en el menú principal de la entidad.', 'kit' => 'El kit de propiedades seleccionado se aplicará al guardar la entrada.', 'tooltip' => 'Reemplaza la descripción emergente automática con el siguiente contenido.', ], 'history' => [ 'created_clean' => 'Creado por :name el :date', 'created_date_clean' => 'Creado el :date', 'unknown' => 'Desconocido', 'updated_clean' => 'Última modificación por :name el :date', 'updated_date_clean' => 'Última modificación el :date', 'view' => 'Historial de cambios de la entidad', ], 'image' => [ 'error' => 'No hemos podido obtener la imagen. Puede que la página web no nos permita descargarla (típico de Squarespace o DeviantArt), o que el enlace ya no sea válido. Asegúrate también de que la imagen no supera los :size.', ], 'is_private' => 'Esta entidad es privada y solo pueden verla los administradores.', 'keyboard-shortcut' => 'Atajo de teclado :code', 'move' => [], 'navigation' => [ 'cancel' => 'cancelar', 'or_cancel' => 'o :cancel', 'skip_to_content' => 'Saltar navegación', ], 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Permitir', 'deny' => 'Denegar', 'ignore' => 'Ignorar', 'remove' => 'Quitar', ], 'bulk_entity' => [ 'allow' => 'Permitir', 'deny' => 'Denegar', 'inherit' => 'Heredar', ], 'delete' => 'Eliminar', 'edit' => 'Editar', 'private' => 'Hacer privado', 'toggle' => 'Cambiar', 'view' => 'Ver', ], 'fields' => [ 'member' => 'Miembro', 'role' => 'Rol', ], 'helpers' => [ 'setup' => 'Desde aquí puedes afinar cómo los diferentes roles y usuarios pueden interactuar con esta entidad. :allow les permitirá hacer dicha acción; :deny se la denegará, y :inherit usará el permiso que ya tenga el rol o usuario. Un usuario con una acción puesta en :allow podrá hacerla, aunque su rol esté en :deny.', ], 'success' => 'Permisos guardados.', 'title' => 'Permisos', 'too_many_members' => 'Esta campaña tiene demasiados miembros (>10) para poder mostrarlos todos aquí. Usa el botón de permisos en la vista de entidad para controlar los permisos detalladamente.', ], 'placeholders' => [ 'calendar' => 'Escoge un calendario', 'entry' => 'Utiliza @ seguido de tres letras para mencionar otras entidades de la campaña.', 'fallback' => 'Elija :módulo', 'gallery_image' => 'Elige una imagen de la galería de la campaña', 'image_url' => 'Puedes subir una imagen desde una URL', 'journal' => 'Elige un diario', 'location' => 'Elige una localización', 'multiple' => 'Elije uno o varios', 'name' => 'Nombre de la entidad', 'organisation' => 'Elige una organización', 'parent' => 'Elija un padre', 'tag' => 'Elige una etiqueta', 'template' => 'Elige una plantilla', 'timeline' => 'Elige una línea de tiempo', 'type' => 'Tipo de la entidad', 'user' => 'Elija un usuario', ], 'relations' => [], 'remove' => 'Eliminar', 'reorder' => [ 'empty' => 'No hay elementos que reordenar.', ], 'save' => 'Guardar', 'save_and_close' => 'Guardar y cerrar', 'save_and_copy' => 'Guardar y copiar', 'save_and_new' => 'Guardar y crear nuevo', 'save_and_update' => 'Guardar y seguir editando', 'save_and_view' => 'Guardar y ver', 'search' => 'Buscar', 'select' => 'Seleccionar', 'tabs' => [ 'abilities' => 'Habilidades', 'inventory' => 'Inventario', 'mentions' => 'Menciones', 'overview' => 'Resumen', 'permissions' => 'Permisos', 'premium' => 'Premium', 'profile' => 'Perfil', 'reminders' => 'Recordatorios', ], 'timestamps' => [ 'edited' => 'Editado hace :ago', ], 'titles' => [ 'editing' => 'Editando :name', 'new' => 'Nuevo :module', ], 'tooltips' => [], 'update' => 'Actualizar', 'users' => [ 'unknown' => 'Desconocido', ], 'view' => 'Ver', 'visibilities' => [ 'admin' => 'Admin', 'admin-self' => 'Yo + Admin', 'all' => 'Todos', 'members' => 'Miembros', 'self' => 'Solo yo', ], ]; ================================================ FILE: lang/es/dashboard.php ================================================ [ 'customise' => 'Personalizar el tablero', 'follow' => 'Seguir', 'join' => 'Unirse', 'unfollow' => 'Dejar de seguir', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Editar', 'new' => 'Nuevo tablero', ], 'create' => [ 'helper' => 'Crea un nuevo panel para :name y asigna qué roles pueden verlo o tenerlo como su panel predeterminado.', 'success' => 'Tablero :name creado.', 'title' => 'Nuevo tablero de campaña', ], 'custom' => [ 'text' => 'Estás editando el tablero :name de la campaña.', ], 'default' => [ 'text' => 'Estás editando el tablero por defecto de la campaña.', 'title' => 'Tablero por defecto', ], 'delete' => [ 'success' => 'Tablero :name eliminado.', ], 'fields' => [ 'copy_widgets' => 'Copiar widgets', 'name' => 'Nombre del tablero', 'visibility' => 'Visibilidad', ], 'helpers' => [ 'copy_widgets' => 'Duplica los widgets del tablero :name a este.', ], 'pitch' => 'Crea múltiples cuadros de mando con permisos personalizados para cada función de la campaña.', 'placeholders' => [ 'name' => 'Nombre del tablero', ], 'update' => [ 'success' => 'Tablero :name actualizado.', 'title' => 'Actualizar el tablero de campaña :name', ], 'visibility' => [ 'default' => 'Por defecto', 'none' => 'Ninguna', 'visible' => 'Visible', ], ], 'helpers' => [ 'follow' => 'Si sigues una campaña, esta aparecerá en el menú de cambio de campaña (arriba a la derecha) bajo tus campañas.', 'join' => 'Esta campaña está abierta a nuevos miembros. Haz clic en unirse para enviar una solicitud.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Añadir widget', 'back_to_dashboard' => 'Volver al tablero', 'edit' => 'Editar widget', 'new' => 'Nuevo widget :type', ], 'reorder' => [ 'helper' => 'Arrástrame para moverme', 'success' => 'Widgets reordenados.', ], 'title' => 'Configurar el tablero de campaña', 'tutorial' => [ 'blog' => 'nuestro tutorial', 'text' => '¿Necesitas ayuda para configurar el tablero de tu campaña? Lee :blog para obtener ayuda e inspiración.', ], ], 'title' => 'Tablero de', 'welcome' => [], 'widgets' => [ 'advanced_options_boosted' => 'Habilita más opciones como mostrar pines con una :boosted_campaign.', 'calendar' => [ 'actions' => [ 'next' => 'Cambiar fecha al día siguiente', 'previous' => 'Cambiar fecha al día anterior', ], 'previous_events' => 'Anterior', 'upcoming_events' => 'Próximo', ], 'campaign' => [ 'helper' => 'Este widget muestra el encabezado de la campaña. Siempre se muestra en el tablero por defecto.', ], 'create' => [ 'helper' => 'Selecciona un tipo de widget para añadir al panel :name.', 'helper-default' => 'Selecciona un tipo de widget para añadir al panel predeterminado.', 'success' => 'Widget añadido al tablero.', 'title' => 'Nuevo widget', ], 'delete' => [ 'success' => 'Widget eliminado del tablero.', ], 'fields' => [ 'class' => 'Clase CSS', 'dashboard' => 'Tablero', 'name' => 'Nombre personalizado del widget', 'optional-entity' => 'Link a la entidad', 'order' => 'Orden', 'size' => 'Tamaño', 'width' => 'Anchura', ], 'helpers' => [ 'class' => 'Define una clase CSS personalizada para este widget.', 'filters' => 'Haz clic para conocer las opciones de filtro disponibles.', ], 'orders' => [ 'name_asc' => 'Ascendente por nombre', 'name_desc' => 'Descendiente por nombre', 'oldest' => 'Modificación más antigua', 'recent' => 'Recientemente modificadas', ], 'preview' => [ 'displays' => [ 'expand' => 'Entrada expandible', 'full' => 'Entrada completa', ], 'fields' => [ 'display' => 'Mostrar', ], ], 'random' => [ 'helpers' => [ 'name' => 'Puedes referenciar el nombre de la entidad aleatoria con {name}', ], 'type' => [ 'all' => 'Todos', ], ], 'recent' => [ 'advanced_filter' => 'Filtro avanzado', 'advanced_filters' => [ 'mentionless' => 'Sin menciones (entidades que no mencionan a otras)', 'unmentioned' => 'No mencionada (entidades que no son mencionadas por otras)', ], 'all-entities' => 'Todas las entidades', 'entity-header' => 'Usar la cabecera de la entidad como imagen', 'filters' => 'Filtros', 'help' => 'Solo muestra la previsualización de la última entidad actualizada.', 'helpers' => [ 'entity-header' => 'Si la entidad tiene una imagen de cabecera (funcionalidad de campañas mejoradas), puedes habilitar que este widget use dicha imagen en lugar de la imagen de la entidad.', 'show_attributes' => 'Mostrar los atributos bajo la entrada', 'show_members' => 'Si la entidad es una familia u organización, muestra sus miembros bajo la entrada.', 'show_relations' => 'Muestra las relaciones fijadas bajo la entrada.', ], 'show_attributes' => 'Mostrar atributos', 'show_members' => 'Mostrar miembros', 'show_relations' => 'Mostrar relaciones fijadas', 'singular' => 'Singular', 'tags' => 'Filtra la lista de las entidades recientemente modificadas con etiquetas específicas.', 'title' => 'Modificado recientemente', ], 'tabs' => [ 'advanced' => 'Avanzado', 'setup' => 'Configuración', ], 'unmentioned' => [ 'title' => 'Entidades no mencionadas', ], 'update' => [ 'success' => 'Widget modificado.', ], 'widths' => [ '0' => 'Auto', '12'=> 'Completa (100%)', '3' => 'Cuarto (25%)', '4' => 'Tercio (33%)', '6' => 'Mitad (50%)', '8' => 'Dos tercios (66%)', '9' => 'Tres cuartos (75%)', ], ], ]; ================================================ FILE: lang/es/dashboards/onboarding.php ================================================ [ 'continue' => 'Empieza a construir', 'skip' => 'Omitir por ahora', ], 'families' => [ 'varren' => [ 'title' => 'Casa Varren', ], ], 'fields' => [ 'name' => 'Nombre del mundo', ], 'intro' => 'Tu mundo está listo. Hazlo tuyo antes de sumergirte en él.', 'placeholders' => [ 'name' => 'Puedes cambiar esto en cualquier momento.', ], 'quests' => [ 'crown' => [ 'title' => 'Recupera la corona fragmentada', ], ], 'roles' => [ 'co-writer' => 'Coautor', 'contributor' => 'Colaborador', ], 'selection' => [ 'campaign' => 'Campaña de RPG de mesa', 'campaign-description' => 'Gestiona tu campaña, personajes y sesiones para tu grupo de TTRPG.', 'helper' => '(Tu elección personalizará el contenido de demostración y el diseño de tu panel).', 'intro' => '¿Qué estás creando?', 'story' => 'Novela o universo narrativo', 'story-description' => 'Desarrolla los personajes, escenarios y la línea temporal de tu historia mientras escribes.', 'title' => 'Tipo de mundo', 'worldbuilding' => 'Proyecto de creación de mundos', 'worldbuilding-description' => 'Construye y organiza la historia, los lugares y las personas de tu propio mundo.', ], 'splash' => 'Bienvenido, :name 👋 Tu mundo está listo.', 'success' => '¡Bienvenido! Hemos personalizado tu mundo para un inicio rápido.', 'title' => 'Haz este mundo tuyo', 'ttrpg' => [ 'tags' => [ 'npcs' => 'NPCs', ], ], 'widgets' => [ 'active-quests' => 'Misiones activas', ], ]; ================================================ FILE: lang/es/dashboards/setup.php ================================================ [ 'add' => 'Añadir widget', ], 'sections' => [ 'switch' => 'Cambiar a...', ], 'tooltips' => [ 'add' => 'Haz clic para añadir un nuevo widget al panel.', 'switch' => 'Haz clic para cambiar a otro panel.', ], ]; ================================================ FILE: lang/es/dashboards/widgets/calendar.php ================================================ 'Muestra los próximos recordatorios.', 'name' => 'Calendario', ]; ================================================ FILE: lang/es/dashboards/widgets/campaign.php ================================================ 'Muestra una vista previa de la campaña.', 'name' => 'Campaña', ]; ================================================ FILE: lang/es/dashboards/widgets/header.php ================================================ 'Muestra un encabezado de texto.', 'name' => 'Encabezado', ]; ================================================ FILE: lang/es/dashboards/widgets/help.php ================================================ 'Muestra enlaces de ayuda y de la comunidad.', 'name' => 'Ayuda y Comunidad', 'title' => 'Ayuda y Comunidad', ]; ================================================ FILE: lang/es/dashboards/widgets/join.php ================================================ '¡Solicitar unirse!', 'description' => 'Muestra el botón de solicitud para unirse a la campaña junto con la introducción de la campaña.', 'name' => 'Se buscan jugadores', 'register' => 'Regístrate para solicitar', 'title' => 'Se buscan jugadores', 'update' => 'Actualizar tu solicitud', ]; ================================================ FILE: lang/es/dashboards/widgets/onboarding.php ================================================ 'Muestra una lista de tareas para empezar con la creación de mundos.', 'name' => 'Primeros pasos', 'tasks' => [ 'campaign' => [ 'name' => 'Tu primer mundo está listo.', ], 'character' => [ 'helper' => 'Añade a alguien que exista en tu mundo.', 'name' => 'Crea tu primer personaje.', ], 'invite' => [ 'helper' => 'Añade personas a tu campaña con roles.', 'name' => 'Invita a un amigo o coautor.', ], 'location' => [ 'helper' => 'Da contexto a tu mundo con una ubicación.', 'name' => 'Crea tu primera ubicación.', ], 'rename' => [ 'helper' => 'Establece un nombre adecuado para tu campaña.', 'name' => 'Renombra tu mundo.', ], 'widgets' => [ 'helper' => 'Añade o reorganiza widgets.', 'name' => 'Personaliza tu panel.', ], ], ]; ================================================ FILE: lang/es/dashboards/widgets/preview.php ================================================ 'Muestra una entidad específica.', 'name' => 'Entidad seleccionada', ]; ================================================ FILE: lang/es/dashboards/widgets/random.php ================================================ 'Muestra una entidad aleatoria de la campaña.', 'name' => 'Entidad aleatoria', ]; ================================================ FILE: lang/es/dashboards/widgets/recent.php ================================================ 'Muestra una lista de entidades modificadas recientemente.', 'name' => 'Entidades modificadas recientemente', ]; ================================================ FILE: lang/es/dashboards/widgets/welcome.php ================================================ 'Muestra un mensaje de bienvenida con consejos.', 'endings' => [], 'focus' => [ 'text' => '¡Aquí, soy yo!', 'title' => 'Hey', ], 'intros' => [ '1' => '¡:user, saluda a tu nuevo hogar para la construcción de mundos! Hemos creado tu primera campaña e incluido dos :characters y :locations de ejemplo. Estos también están visibles aquí en el panel de control de la campaña.', '2' => 'Para empezar, haz clic en el botón grande :new-entity (o pulsa :letter en tu teclado), y haz clic en :characters para crear tu primer personaje. Así de fácil. Puedes encontrar todos tus personajes, ubicaciones y otras :entities en la barra lateral de la izquierda de la página.', '3' => 'Éstos son nuestros 5 mejores trucos para utilizar Kanka', ], 'name' => 'Bienvenida', 'title' => '¡Bienvenid@ a :kanka! 🎉', 'tricks' => [ '1' => 'Cuando escribas descripciones, no reescribas los nombres de los elementos de la campaña. En su lugar, escribe :code y tres letras para :mention otras entidades de la campaña. Estas menciones se actualizarán automáticamente cuando cambies los nombres.', '2' => 'Para editar el nombre, el tema o la imagen de la campaña, haz clic en :world en la barra lateral y, a continuación, en el botón :edit .', '3' => 'Escribe información secreta sobre las entidades como :posts en lugar de en el campo de texto principal.', '4' => 'Invita a tus amigos a la campaña haciendo clic en :world y despues, en :members. Desde ahí, puedes crear enlaces de invitación.', '5' => 'Puedes eliminar este mensaje de bienvenida y mostrar otra información en esta página (llamada panel de control). Navega hacia abajo y haz clic en el botón :button.', 'mention' => 'mención', ], ]; ================================================ FILE: lang/es/datagrids.php ================================================ [ 'back_to' => 'Volver a :name', ], 'modes' => [ 'flatten' => 'Cambiar a un diseño plano', 'grid' => 'Cambiar a la vista de cuadrícula', 'nested' => 'Cambiar a un diseño anidado', 'table' => 'Cambiar a la vista de tabla', ], 'tooltips' => [ 'nested' => 'Esta entidad tiene hijos. Haga clic en la imagen para verlos.', ], ]; ================================================ FILE: lang/es/datetime.php ================================================ 'día', 'days' => 'dias', 'elapsed_ago' => 'hace :duration', 'hour' => 'hora', 'hours' => 'horas', 'just_now' => 'ahora mismo', 'minute' => 'minuto', 'minutes' => 'minutos', 'month' => 'mes', 'months' => 'meses', 'second' => 'segundo', 'seconds' => 'segundos', 'week' => 'semana', 'weeks' => 'semanas', 'year' => 'año', 'years' => 'años', ]; ================================================ FILE: lang/es/default.php ================================================ 'Titulo de página', ]; ================================================ FILE: lang/es/dice_roll_results.php ================================================ [ 'title' => 'Resultados de las tiradas de dados', ], ]; ================================================ FILE: lang/es/dice_rolls.php ================================================ [ 'title' => 'Nueva tirada de dados', ], 'destroy' => [ 'dice_roll' => 'Tirada de dados eliminada.', ], 'edit' => [], 'fields' => [ 'created_at' => 'Tirada en', 'parameters' => 'Parámetros', 'results' => 'Resultados', 'rolls' => 'Tiradas', ], 'hints' => [ 'parameters' => '¿Qué opciones de dados hay?', ], 'index' => [ 'actions' => [ 'results' => 'Resultados', ], ], 'lists' => [ 'empty' => 'Crea y guarda tiradas para la campaña y lleva un registro de los resultados directamente en Kanka.', ], 'placeholders' => [ 'name' => 'Nombre de la tirada de dados', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Tirada', ], 'error' => 'Tirada de dados fallida. No se pueden analizar los parámetros.', 'fields' => [ 'creator' => 'Creador', 'date' => 'Fecha', 'result' => 'Resultado', ], 'hint' => 'Todas las tiradas de la plantilla han sido completadas.', 'success' => 'Dados tirados.', ], 'show' => [ 'tabs' => [ 'results' => 'Resultados', ], ], ]; ================================================ FILE: lang/es/emails/activity/email.php ================================================ 'El correo electrónico de tu cuenta Kanka ha cambiado a :email.', 'title' => 'Correo electrónico cambiado', ]; ================================================ FILE: lang/es/emails/activity/password.php ================================================ 'La contraseña de tu cuenta en Kanka ha sido modificada.', 'help' => 'Si no fuiste tú, ponte en contacto con nosotros en :email.', 'title' => 'Contraseña modificada', ]; ================================================ FILE: lang/es/emails/purge/first.php ================================================ 'Si estás utilizando regularmente tu cuenta, no te preocupes, sólo estamos eliminando cuentas y campañas que no están en uso activo.', 'help' => '¿Necesitas ayuda para usar Kanka? Únete a nuestro :discord o ponte en contacto con nosotros en :email', 'intro_account' => 'Nos ponemos en contacto contigo para informarte de que tu cuenta será eliminada en :amount días, ya que no la has utilizado en los últimos :duration meses.', 'intro_campaigns' => 'Nos ponemos en contacto contigo para informarte de que tu cuenta y las siguientes campañas serán eliminadas en :amount días, ya que no la has utilizado en los últimos :duration meses.', 'keep' => 'Si deseas mantener tu cuenta activa, conéctate dentro de los próximos :amount días.', 'title' => 'Tu cuenta en Kanka será eliminada en :amount días', 'warning' => [ 'account' => 'Una vez hecho esto, todos los datos asociados a tu cuenta, bajo :email, serán borrados permanentemente.', 'campaigns' => 'Una vez hecho esto, todos los datos asociados a tu cuenta, bajo email :email, así como las siguientes campañas serán eliminados permanentemente.', ], ]; ================================================ FILE: lang/es/emails/purge/second.php ================================================ 'Esto es un último recordatorio de que la cuenta Kanka bajo el email :email será eliminada en :amount días ya que no la has usado en los últimos :duration meses.', 'title' => 'Última advertencia: Tu cuenta en Kanka será eliminada en :amount días', ]; ================================================ FILE: lang/es/emails/subscriptions/expiring.php ================================================ 'actualiza los datos de tu tarjeta', 'primary' => 'Esto es un aviso automático de que su :brand **** :last está a punto de caducar.', 'title' => 'Tarjeta por caducar para su suscripción', 'valid' => 'Si deseas mantener tu suscripción, por favor :action.', ]; ================================================ FILE: lang/es/emails/subscriptions/upcoming.php ================================================ 'Si no deseas renovar tu suscripción, accede a tu cuenta Kanka y :link.', 'closing' => 'Sinceramente,', 'dear' => 'Estimado :name', 'link' => 'cancelar tu suscripción', 'notice' => 'Aviso importante sobre las renovaciones:', 'primary' => 'Esto es un recordatorio automático de que vamos a realizar un cargo automático en tu :marca **** :último en :fecha, por tu suscripción a Kanka.', 'title' => 'Pago anual de tu suscripción a Kanka', 'valid' => 'Por favor, asegúrate de que tu tarjeta de crédito será válida en la fecha de pago.', ]; ================================================ FILE: lang/es/emails/subscriptions/validation.php ================================================ 'Validación del correo electrónico de tu cuenta en Kanka.', ]; ================================================ FILE: lang/es/emails/validation.php ================================================ 'Validación fallida, por favor inténtalo nuevamente.', 'modal' => 'Para suscribirse es necesario disponer de una dirección de correo electrónico validada. Por favor, revisa tu bandeja de entrada para encontrar el enlace para confirmar tu correo electrónico antes de continuar con el proceso de suscripción.', 'success' => 'Correo electrónico validado con éxito.', ]; ================================================ FILE: lang/es/emails/welcome/2024.php ================================================ 'Feliz creación de mundos y gracias por participar en este viaje con nosotros,', 'header' => '¡Bienvenido al mejor lugar para crear tu campaña, :name!', 'lead_1' => 'Kanka es fruto de la pasión de jugadores de juegos de rol que querían adoptar un enfoque sencillo y colaborativo para la creación de mundos, sin sacrificar ninguna funcionalidad.', 'lead_2' => 'El resultado de todo esto es Kanka. Estamos aquí para ayudarte a organizar tu campaña y dejar que te centres en dar vida a tu mundo.', 'ps' => 'PD: Si quieres ponerte en contacto con nosotros, puedes encontrarnos en :discord, o en :email.', 'what_1' => 'Para ayudarte a empezar, hemos creado tu primera campaña e incluido dos personajes y lugares de ejemplo. Si lo prefieres, puedes :start.', 'what_2' => 'También deberías consultar estos recursos:', 'what_3' => 'Nuestro :kb si tienes preguntas básicas sobre las funciones de Kanka y tu cuenta.', 'what_4' => 'Los :doc lo explican todo con más detalle.', 'what_5' => 'Y por último, :campaigns muestran lo que otros han hecho con Kanka.', 'what_new' => 'empezar una nueva', 'what_now' => '¿Y ahora qué?', 'why' => 'Has recibido este correo electrónico porque te has registrado en nuestro sitio web.', ]; ================================================ FILE: lang/es/emails/welcome.php ================================================ [ 'basics' => [ 'text_1' => 'Con una herramienta tan extensa como Kanka, puede ser difícil saber por dónde empezar o qué hacer. Nuestro :kb cubre las preguntas más básicas que puedas tener, y para más ayuda, puedes dirigirte a nuestra :doc.', 'title' => 'Lo esencial', ], 'chat' => [ 'text_1' => 'Nos encanta escuchar a nuestros usuarios. Somos más activos en :discord, donde encontrarás a muchos de nuestros usuarios dedicados, un equipo de introducción, así como a los fundadores de Kanka, que pueden responder a cualquier pregunta que puedas tener. También puedes enviarnos un correo electrónico a :email.', 'title' => '¿Quieres chatear?', ], 'intro' => [ 'header' => '¡Bienvenido a la mejor comunidad de worldbuilding, :name!', 'link' => '¡Ve a tu mundo!', 'text_1' => '¡Saluda a tu nuevo hogar de worldbuilding, :name! La comunidad está en nuestro ADN, y estamos encantados de que te unas a nosotros. Kanka es una creación de jugadores apasionados por los juegos de rol que creen en una forma simplificada y comunitaria de abordar la construcción de mundos, sin comprometer las características.', 'text_2' => 'Hemos creado tu primera campaña e incluido dos personajes y localizaciones de ejemplo para ayudarte a empezar.', ], 'preview' => '¡Forma parte de la mejor comunidad de worldbuilding, :name!', ], 'header' => '¡Te damos la bienvenida a Kanka, :name!', 'header_sub' => '¡Enhorabuena! Has completado el primer paso en la creación de tu mundo en :kanka.', 'pricing' => 'tarifas', 'section_1' => '¿Qué hago ahora?', 'section_11' => 'Crea tu mundo,', 'section_2' => 'El recurso más importante es :discord, donde encontrarás a muchos de nuestros dedicados usuarios, un equipo de bienvenida y al fundador de Kanka, que pueden contestar cualquier pregunta que tengas.', 'section_4' => 'Nuestro :youtube tiene vídeos que cubren los básicos de Kanka. Aunque no todos los temas están explicados aún, añadimos nuevos vídeos regularmente.', 'section_4_v2' => 'Nuestra :knowledge-base cubre las preguntas más básicas que puedas tener, para una ayuda más completa, puedes dirigirte a nuestra :documentation.', 'section_6' => 'Contacta con nosotros', 'section_7' => 'Si no encuentras respuesta a tus preguntas, o simplemente quieres ponerte en contacto, puedes encontrarnos en :facebook o enviarnos un correo a :email. Somos un pequeño equipo de 2 amigos, pero nos aseguramos de que todos los mails tengan respuesta, así que no lo dudes!', 'section_8' => 'Y por último', 'section_9_v2' => 'Nos hemos asegurado de que todas las funcionalidades principales de Kanka sean gratuitas, y siempre las mantendremos así. No obstante, si quieres apoyar este proyecto, puedes convertirte en suscriptor y ganar así acceso a funcionalidades adicionales, además de nuestra eterna gratitud. Infórmate más en la página de :pricing.', 'social_account' => 'Si tienes problemas para iniciar sesión, ten en cuenta que estás usando un login de :provider. Puedes cambiarlo en los ajustes de tu cuenta.', 'title' => 'Por dónde empezar en Kanka', ]; ================================================ FILE: lang/es/entities/abilities.php ================================================ [ 'add' => 'Añadir habilidad', 'reset' => 'Restablecer usos de habilidad', 'sync' => 'Añadir desde razas', ], 'charges' => [ 'left' => ':amount restante', ], 'create' => [ 'helper' => 'Vincula una o varias habilidades a :name.', 'success' => 'Habilidad :ability añadida a :entity.', 'success_multiple' => 'Habilidades :abilities añadidas a :entity.', 'title' => 'Añadir habilidad a :name', ], 'fields' => [ 'note' => 'Nota', 'position' => 'Posición', ], 'groups' => [ 'unorganised' => 'Sin organizar', ], 'helpers' => [ 'note' => 'Puedes referenciar entidades mediante las menciones avanzadas (por ejemplo, :code) y atributos de la entidad (por ejemplo, :attr) en este campo.', 'recharge' => 'Restablece todos las cargas de las habilidades que se han utilizado.', 'sync' => 'Importa habilidades que estén definidas en las razas del personaje.', ], 'import' => [ 'errors' => [ 'no_race' => 'El personaje no tiene raza.', 'not_character' => 'La entidad no es un personaje.', ], 'helper' => 'Adjunta habilidades de las siguientes razas a las que pertenece :name:', 'no_abilities' => 'Actualmente no hay habilidades para importar de las razas a las que pertenece :name.', 'race_abilities' => '{1} :name (:count habilidad) |[2,*] :name (:count habilidades)', 'success' => '{1} Se han importado :count habilidades.|[2,*] Se han importado :count habilidades.', ], 'recharge' => [ 'success' => 'Se han restablecido todos las cargas.', ], 'reorder' => [ 'parentless' => 'Sin padre', 'success' => 'Habilidades reordenadas con éxito', ], 'show' => [ 'helper' => 'Adjunta habilidades a esta entidad. Puedes modificar su visibilidad o eliminarlas más adelante. Las habilidades pertenecientes al mismo grupo se agrupan por tipos.', 'reorder' => 'Reordenar', 'title' => 'Habilidades de :name', ], 'types' => [ 'unorganised' => 'Las habilidades se agrupan por su campo de origen, y en su defecto se encuentran aquí.', ], 'update' => [ 'success' => 'Habilidad de la entidad :ability actualizada.', 'title' => 'Habilidad de :name', ], ]; ================================================ FILE: lang/es/entities/actions.php ================================================ [ 'set' => 'Establecer como arquetipo', 'toggle' => 'Estado de plantilla alternado.', 'unset' => 'Quitar como plantilla', ], 'archive' => [ 'success' => ':name ha sido archivado.', 'title' => 'Archivar', ], 'convert' => 'Convertir módulo', 'copy-campaign' => 'Copiar a campaña', 'json-export' => 'Exportar a JSON', 'markdown-export' => 'Exportar a Markdown', 'templates' => [], 'tooltips' => [ 'edit' => 'Editar esta entidad', ], 'transfer' => 'Transferir a campaña', 'unarchive' => [ 'success' => ':name ya no está archivado.', 'title' => 'Desarchivar', ], ]; ================================================ FILE: lang/es/entities/aliases.php ================================================ [ 'add' => 'Añadir un alias', ], 'create' => [ 'helper' => 'Crea un alias para :name, que permitirá encontrarle en la búsqueda global y mediante menciones :code.', 'success' => 'Alias :name añadido a :entity.', 'title' => 'Añadir un alias a :name', ], 'destroy' => [ 'success' => 'Alias :name eliminado.', ], 'fields' => [ 'name' => 'Nombre', ], 'helpers' => [ 'primary' => 'Añade uno o más alias a una entidad para encontrarla mediante la búsqueda global (en la barra superior) o las menciones.', ], 'limit' => 'La campaña ha alcanzado el límite de alias disponibles. Para obtener alias ilimitados, desbloquea las funciones premium.', 'pitch' => 'Crea alias para esta entidad que te permitan encontrarla fácilmente a través de la búsqueda y de menciones.', 'placeholders' => [ 'name' => 'Nuevo alias', ], 'unboosted' => [], 'update' => [ 'success' => 'Alias :name de :entity actualizado.', 'title' => 'Actualizar alias de :name', ], ]; ================================================ FILE: lang/es/entities/assets.php ================================================ [ 'alias' => 'Alias', 'file' => 'Archivo', 'link' => 'Enlace', ], 'copy_alias' => [ 'success' => 'Mención del alias copiada en el portapapeles.', 'title' => 'Haz clic para copiar la mención del alias al portapapeles.', ], 'show' => [ 'title' => 'Archivos de :name', ], ]; ================================================ FILE: lang/es/entities/attributes.php ================================================ [ 'apply_kit' => 'Aplicar un kit de propiedades', 'load' => 'Cargar', 'manage' => 'Administrar', 'more' => 'Más opciones', 'remove_all' => 'Eliminar todos', 'save_and_edit' => 'Aplicar y editar', 'save_and_story'=> 'Aplicar y ver', 'show_hidden' => 'Mostrar atributos ocultos', 'toggle_privacy'=> 'Privado/Público', ], 'errors' => [ 'api' => 'Datos no válidos', 'loop' => '¡Hay un bucle infinito en el cálculo de este atributo!', 'no_attribute_selected' => 'Selecciona uno o varios atributos primero.', 'too_many_v2' => 'Se ha alcanzado el máximo de campos (:count/:max). Elimina algunos atributos primero antes de poder añadir más.', ], 'fields' => [ 'community_templates' => 'Plantillas de la comunidad', 'is_private' => 'Atributos privados', 'is_star' => 'Fijado', 'preferences' => 'Preferencias', 'property' => 'Propiedad', 'value' => 'Valor', ], 'filters' => [ 'name' => 'Nombre del atributo', 'value' => 'Valor del atributo', ], 'helpers' => [ 'delete_all' => '¿Seguro que quieres eliminar todos los atributos de esta entidad?', 'is_private' => 'Sólo permite ver los atributos de esta entidad a los miembros del rol :admin-role.', 'setup' => 'Puedes representar elementos como los PV o la inteligencia de un personaje mediante los atributos. Puedes añadirlos manualmente desde el botón de :manage, o aplicarlos desde una plantilla de atributos.', ], 'hints' => [], 'index' => [ 'success' => 'Atributos de :entity actualizados.', 'title' => 'Atributos de :name', ], 'labels' => [ 'checkbox' => 'Nombre de la casilla', 'name' => 'Nombre del atributo', 'section' => 'Nombre de la sección', 'value' => 'Valor del atributo', ], 'live' => [ 'success' => 'Atributo :attribute actualizado.', 'title' => 'Actualizando :attribute', ], 'placeholders' => [ 'attribute' => 'Número de conquistas, Iniciativa, Población...', 'block' => 'Nombre del bloque', 'checkbox' => 'Nombre de la casilla', 'icon' => [ 'class' => 'Clase de FontAwesome o RPG Awesome: fas fa-users', 'name' => 'Nombre del icono', ], 'number' => 'Valor numérico', 'random' => [ 'name' => 'Nombre del atributo', 'value' => '1-100 o una lista de valores separados por comas', ], 'section' => 'Nombre de la sección', 'value' => 'Valor del atributo', ], 'ranges' => [ 'text' => 'Opciones disponibles: :options', ], 'sections' => [ 'unorganised' => 'Sin organizar', ], 'show' => [ 'hidden' => 'Atributos ocultos', 'title' => 'Atributos de :name', ], 'template' => [ 'load' => [ 'success' => 'Plantilla cargada', 'title' => 'Cargar desde plantilla', ], 'pitch' => 'Carga atributos desde una plantilla de atributos o desde plugins instalados desde :plugin.', 'success' => 'Plantilla de atributos :name aplicada a :entity', 'title' => 'Aplicar plantilla de atributos a :name', ], 'title' => 'Atributos', 'toasts' => [ 'bulk_deleted' => 'Atributos eliminados', 'bulk_privacy' => 'Privacidad de atributos cambiada', 'lock' => 'Atributo bloqueado', 'pin' => 'Atributo fijado', 'unlock' => 'Atributo desbloqueado', 'unpin' => 'Atributo no fijado', ], 'tutorials' => [], 'types' => [ 'attribute' => 'Atributo', 'block' => 'Bloque', 'checkbox' => 'Casilla', 'icon' => 'Icono', 'kits' => 'Kits', 'number' => 'Número', 'random' => 'Aleatorio', 'section' => 'Sección', 'text' => 'Texto multilínea', ], 'update' => [ 'success' => 'Atributos de :entity actualizados.', ], 'visibility' => [ 'entry' => 'El atributo se muestra en el menú de la entidad.', 'private' => 'Atributo visible solo para miembros con el rol "Admin".', 'public' => 'Atributo visible por todos los miembros.', 'tab' => 'El atributo se muestra solo en la pestaña de Atributos.', ], ]; ================================================ FILE: lang/es/entities/children.php ================================================ 'Hijos', ]; ================================================ FILE: lang/es/entities/events.php ================================================ [ 'helper' => 'Crea un recordatorio para vincular :name a un calendario.', ], 'fields' => [ 'type' => 'Tipo de evento', ], 'helpers' => [ 'characters' => 'Al seleccionar fecha de nacimiento o de fallecimiento, se calculará automáticamente la edad de este personaje. :more', 'founding' => 'Al establecer el tipo como :type se calculará automáticamente la antigüedad de la entidad desde su fundación.', ], 'show' => [ 'actions' => [ 'add' => 'Añadir recordatorio', ], 'title' => 'Recordatorios de :name', ], 'types' => [ 'birth' => 'Nacimiento', 'birthday' => 'Cumpleaños', 'death' => 'Muerte', 'founded' => 'Fundación', 'primary' => 'Primario', ], 'years-ago' => '{1} :count año atrás|[2,*] :count años atrás', ]; ================================================ FILE: lang/es/entities/files.php ================================================ [ 'max' => [ 'helper' => 'No puedes adjuntar más archivos a menos que elimines uno existente.', 'limit' => 'Esta entidad ha alcanzado su límite de archivos.', ], 'upgrade' => [ 'limit' => 'Has alcanzado el límite de :limit archivos para esta entidad.', 'premium' => 'Actualiza a una campaña premium para adjuntar archivos ilimitados y desbloquear aún más flexibilidad creativa.', 'upgrade' => 'Actualiza a una campaña premium para adjuntar hasta :limit archivos y desbloquear aún más flexibilidad creativa.', ], ], 'create' => [ 'helper' => 'Agrega un archivo a :name. El archivo contará para el límite de almacenamiento de la galería.', 'success_plural' => '{1} Archivo :name añadido.|[2,*] :count archivos añadidos.', 'title' => 'Nuevo archivo para :entity', ], 'destroy' => [ 'success' => 'Archivo :file eliminado.', ], 'fields' => [ 'file' => 'Archivo', 'files' => 'Archivos', 'name' => 'Nombre del archivo', ], 'max' => [ 'title' => 'Límite alcanzado', ], 'update' => [ 'success' => 'Archivo :file actualizado.', 'title' => 'Actualizar archivo', ], ]; ================================================ FILE: lang/es/entities/image.php ================================================ [ 'change_focus' => 'Cambiar encuadre', 'change_visibility' => 'Cambiar visibilidad', 'copy_url' => 'Copiar URL de imagen', 'copy_url_success' => 'URL de imagen copiada al portapapeles.', 'replace_image' => 'Reemplazar imagen', 'save-replace' => 'Reemplazar imagen', 'save_focus' => 'Guardar encuadre', 'view' => 'Ver imagen', ], 'call-to-action' => 'Haz clic en la imagen de la entidad para establecer su punto de enfoque en lugar de utilizar la suposición automatica.', 'focus' => [ 'breadcrumb' => 'Encuadre de la imagen', 'helper' => 'Haz clic en la imagen para definir el encuadre. Haz clic en el encuadre para eliminarlo.', 'panel_title' => 'Encuadre de la imagen', 'success' => 'Encuadre de la imagen actualizado.', 'title' => 'Encuadre de la imagen de :name', 'unboosted' => 'Definir el encuadre de una imagen es una funcionalidad reservada para las :boosted-campaigns.', 'warning' => 'El punto de enfoque de las imágenes de la :gallery es compartido por todas las entidades que utilizan esa misma imagen.', ], 'gallery_permissions' => [ 'admin' => 'Esta imagen de la galería sólo es visible para los miembros del rol :admin de la campaña.', 'adminself' => 'Esta imagen de la galería sólo es visible para :creator y los miembros del rol :admin de la campaña.', 'member' => 'Esta imagen de la galería sólo es visible para los miembros de la campaña.', 'self' => 'Esta imagen de la galería sólo es visible para ti.', ], 'replace' => [ 'breadcrumb' => 'Reemplazar imagen', 'panel_title' => 'Reemplazar la imagen de la entidad', 'success' => 'Imagen reemplazada.', 'title' => 'Reemplazar la imagen de :name', ], 'visibility' => [ 'helper' => 'Cambia la visibilidad de la imagen de la galería, controlando quién puede verla.', 'updated' => 'Visibilidad de la imagen actualizada.', ], ]; ================================================ FILE: lang/es/entities/inventories.php ================================================ [ 'copy_from_entity' => 'Copiar desde otra entidad', 'copy_inventory' => 'Copiar inventario', 'generate' => 'Generar', 'multiple' => 'Añadir elementos', ], 'copy' => [ 'helper' => 'Copiar todo el inventario de una entidad a :name.', ], 'create' => [ 'helper' => 'Agrega un objeto al inventario de :name. Opcionalmente, puede estar vinculado a un objeto existente de la campaña.', 'success' => 'Objeto :item añadido a :name', 'success_bulk' => '{0} No se ha añadido ningún objeto a :entity.|{1} Se ha añadido :count objeto a :entity.|[2,*] Se han añadido :count objetos a :entity.', 'title' => 'Añade un objeto a :name', ], 'default_position' => 'Sin organizar', 'destroy' => [ 'success' => 'Objeto :item eliminado de :entity.', 'success_position' => 'Elementos en :position eliminados de :entity.', ], 'fields' => [ 'amount' => 'Cantidad', 'copy_entity_entry_v2' => 'Utilizar entrada del objeto', 'description' => 'Observaciones', 'is_equipped' => 'Equipado', 'item_amount' => 'Número de objetos', 'match_all' => 'Coincidir todas las etiquetas', 'name' => 'Nombre', 'position' => 'Localización', 'qty' => 'Cantidad', 'replace' => 'Reemplazar inventario', ], 'generate' => [ 'helper' => 'Generar un inventario para :name basado en los objetos existentes en la campaña.', 'title' => 'Generar inventario', ], 'helpers' => [ 'amount' => 'Número de objetos', 'copy_entity_entry_v2' => 'Mostrar la entrada del objeto en lugar de la descripción personalizada.', 'description' => 'Añadir una descripción personalizada al objeto', 'is_equipped' => 'Marca estos objetos como equipados.', 'name' => 'Da nombre al objeto. Se requiere un nombre si no se selecciona ningún objeto', 'replace' => 'Reemplaza el inventario actual por el generado.', ], 'placeholders' => [ 'amount' => 'Cualquier cantidad', 'description' => 'Usado, dañado, roto', 'name' => 'Requerido si no se selecciona ningún objeto', 'position' => 'Equipado, Mochila, Almacenamiento, Banco...', ], 'show' => [ 'helper' => 'Las entidades pueden tener objetos asociados a ellas, creando así un inventario.', 'title' => 'Inventario de :name', 'unsorted' => 'Sin clasificar', ], 'togglers' => [ 'hide' => [ 'price' => 'Ocultar precio', 'quantity' => 'Ocultar cantidad', 'size' => 'Ocultar tamaño', 'weight' => 'Ocultar peso', ], 'show' => [ 'price' => 'Mostrar precio', 'quantity' => 'Mostrar cantidad', 'size' => 'Mostrar tamaño', 'weight' => 'Mostrar peso', ], ], 'tooltips' => [ 'equipped' => 'Este objeto está equipado', ], 'tutorials' => [ 'all' => 'Lleva un registro de lo que :name posee, almacena u ofrece añadiendo objetos a este inventario.', ], 'update' => [ 'success' => 'Objeto :item actualizado en :entity.', 'title' => 'Actualizar un objeto de :name', ], ]; ================================================ FILE: lang/es/entities/links.php ================================================ [ 'add' => 'Añadir un enlace', ], 'call-to-action' => 'Añade enlaces a recursos externos en esta entidad, como a DnDBeyond, y se mostrarán directamente en la vista general de la entidad.', 'create' => [ 'helper' => 'Agrega un enlace externo a :name, por ejemplo, a su página de DnDBeyond.', 'success' => 'Enlace :name añadido a :entity.', 'title' => 'Añadir un enlace a :name', ], 'destroy' => [ 'success' => 'Enlace :name eliminado de :entity.', ], 'fields' => [ 'icon' => 'Icono', 'name' => 'Nombre', 'position' => 'Posición', 'url' => 'URL', ], 'go' => [ 'actions' => [ 'confirm' => 'Estoy segur@', 'trust' => 'No me preguntes otra vez', ], 'description' => 'Este enlace le llevará a :link. ¿Estás segur@ de que deseas ir allí?', 'title' => 'Saliendo de Kanka', ], 'helpers' => [ 'icon' => 'Se puede personalizar el icono mostrado en el enlace con cualquiera de los iconos gratuitos de :fontawesome. Si se deja en blanco, se usará el icono por defecto.', 'parent' => 'Mostrar este enlace rápido después de un elemento de la barra lateral, en lugar de en la sección de enlaces rápidos de la barra lateral.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Las campañas mejoradas pueden añadir enlaces en las entidades que dirigen a webs externas.', 'title' => 'Enlaces de :name', ], 'unboosted' => [], 'update' => [ 'success' => 'Enlace :name actualizado.', 'title' => 'Actualizar enlace de :name', ], ]; ================================================ FILE: lang/es/entities/logs.php ================================================ [ 'create' => 'Crear', 'create_post' => 'Entrada creada ":post"', 'delete' => 'Eliminar', 'delete_post' => 'Entrada eliminada', 'reorder_post' => 'Entradas reordenadas', 'restore' => 'Restaurar', 'reveal' => 'Mostrar detalles', 'update' => 'Actualizar', 'update_post' => 'Entrada ":post" actualizada', 'view' => 'Ver cambios', ], 'call-to-action' => 'Registros de cambios completos de hasta :amount días en campañas premium.', 'fields' => [ 'action' => 'Acción', 'date' => 'Fecha', ], 'filters' => [ 'keywords' => 'Palabras clave', ], 'impersonated' => 'Hecho pasar por :name', 'none' => 'Ninguno', 'show' => [ 'title' => 'Historial de :name', ], 'tooltips' => [ 'post' => 'Ir a la publicación', ], ]; ================================================ FILE: lang/es/entities/map-points.php ================================================ 'Esta entidad aparece en los siguientes mapas.', 'title' => 'Puntos de mapa de :name', ]; ================================================ FILE: lang/es/entities/mentions.php ================================================ [ 'element' => 'Elemento', 'type' => 'Tipo', ], 'helper' => 'La lista siguiente contiene todas las entidades que mencionan esta entidad en el campo de "Presentación".', 'mentioned_in_v2' => 'Esta entidad se menciona en :count entidades, artículos o campañas. :more', 'see_more' => 'Ver más detalles', 'show' => [ 'title' => 'Menciones de :name', ], 'title' => 'Entidad mencionada', ]; ================================================ FILE: lang/es/entities/move.php ================================================ [ 'copy' => 'Copiar', 'transfer' => 'Transferir', ], 'errors' => [ 'permission' => 'No tienes permiso para crear entidades de este tipo en la campaña objetivo.', 'permission_update' => 'No tienes permiso para mover esta entidad.', 'same_campaign' => 'Tienes que seleccionar otra campaña adonde mover la entidad.', 'unknown_campaign' => 'Campaña desconocida.', ], 'fields' => [ 'campaign' => 'Campaña objetivo', 'copy' => 'Hacer una copia', 'select_one' => 'Seleccionar campaña', ], 'helpers' => [ 'copy' => 'Crea una copia de la entidad en la campaña destino.', ], 'panel' => [ 'description' => 'Selecciona una campaña adonde quieras mover o copiar esta entidad.', 'description_bulk_copy' => 'Selecciona una campaña adonde quieras copiar las entidades seleccionadas.', 'title' => 'Mover o copiar una entidad a otra campaña', ], 'success' => 'Entidad :name movida.', 'success_copy' => 'Entidad :name copiada.', 'title' => 'Mover :name', 'warnings' => [ 'custom' => 'Esta entidad no es de un módulo por defecto, sino de un tipo de entidad personalizada ":module". Se creará como una entidad Nota en la campaña de destino.', ], ]; ================================================ FILE: lang/es/entities/notes.php ================================================ [ 'add' => 'Nuevo post', 'add_role' => 'Añadir rol', 'add_user' => 'Añadir usuario', ], 'collapsed' => [ 'closed' => 'El post se reduce sólo a la cabecera', 'open' => 'El post está expandido', ], 'copy_mention' => [ 'copy' => 'Copiar mención avanzada', 'copy_with_name' => 'Copiar mención avanzada con el nombre del post', 'success' => 'Se ha copiado la mención avanzada de este post.', ], 'create' => [ 'success' => 'Se ha añadido el post ":name" a :entity.', ], 'destroy' => [ 'success' => 'Se ha eliminado el post ":name" de :entity.', ], 'edit' => [ 'success' => 'Se ha actualizado el post ":name" de :entity.', ], 'fields' => [ 'creator' => 'Creador', 'display' => 'Mostrar', 'name' => 'Nombre', 'position' => 'Posición', ], 'footer' => [ 'created' => 'Creado por :user el :date', 'updated' => 'Actualizad por :user el :date', ], 'hint' => 'Aquí puedes añadir toda aquella información que no acaba de encajar en los campos por defecto de la entidad, o que quieres mantener en privado.', 'hints' => [ 'reorder' => 'Puedes reordenar los posts de una entidad haciendo clic en el icono de :icono en la cabecera de la entidad.', ], 'index' => [], 'move' => [ 'copy' => 'Conserva una copia del post en la entidad actual.', 'copy_success' => 'Post :name movido a :entity exitosamente.', 'copy_title' => 'Guarda una copia', 'description' => 'Selecciona una entidad a la que mover este mensaje', 'entity' => 'Entidad objetivo', 'move_success' => 'Post :name movido a :entity con éxito.', ], 'placeholders' => [ 'name' => 'Nombre del post, observación...', ], 'show' => [ 'advanced' => 'Permisos avanzados', 'title' => 'Post :name de :entity', ], 'states' => [ 'collapsed' => 'Colapsado', 'expanded' => 'Expandido', ], ]; ================================================ FILE: lang/es/entities/permissions.php ================================================ [ 'text' => 'Esta entidad está configurada como privada. Aún se pueden definir permisos personalizados, pero mientras la entidad sea privada, éstos serán ignorados y sólo los miembros del rol :admin de la campaña podrán ver la entidad.', 'warning' => 'Advertencia', ], 'quick' => [ 'empty-permissions' => 'Ningún rol o usuario fuera de los administradores de campaña tiene acceso a esta entidad.', 'manage' => 'Administrar permisos', 'screen-reader' => 'Abrir la configuración de privacidad', 'success' => [ 'private' => ':entity ahora está oculta.', 'public' => ':entity es ahora visible.', ], 'title' => 'Permisos', 'viewable-by' => 'Visible por', ], 'toggle' => [ 'label' => 'Privacidad de la entidad', 'private' => [ 'description' => 'Visible solo para los miembros con el rol :admin.', 'title' => 'Privado', ], 'public' => [ 'description' => 'Visible para los siguientes roles y miembros.', 'title' => 'Público', ], ], ]; ================================================ FILE: lang/es/entities/pins.php ================================================ 'Enlaces', 'title' => 'Chinchetas', ]; ================================================ FILE: lang/es/entities/profile.php ================================================ [ 'edit_profile' => 'Editar perfil', ], 'aliases' => 'Alias', 'history' => 'Historial', 'show' => [ 'tab_name' => 'Perfil', 'title' => 'Perfil de :name', ], ]; ================================================ FILE: lang/es/entities/quests.php ================================================ 'La entidad forma parte de las siguientes misiones.', 'title' => 'Misiones de :name', ]; ================================================ FILE: lang/es/entities/relations.php ================================================ [ 'mode-map' => 'Explorador de relaciones', 'mode-table' => 'Tabla de relaciones y conexiones', ], 'bulk' => [ 'delete' => '{1} Se ha eliminado :count relación.|[2,*] Se han eliminado :count relaciones.', 'fields' => [ 'delete_mirrored' => 'Borrar duplicado', 'unmirror' => 'Desenlazar duplicado', 'update_mirrored' => 'Actualizar duplicado', ], 'helpers' => [ 'delete_mirrored' => 'Elimina también las conexiones duplicadas.', 'unmirror' => 'Desenlazar conexiones duplicadas.', 'update_mirrored' => 'Actualizar las conexiones duplicadas.', ], 'success' => [ 'editing' => '{1} Se ha actualizado :count relación.|[2,*] Se han actualizado :count relaciones.', 'editing_partial' => '{1} Se ha eliminado :count/:total relación.|[2,*] Se han eliminado :count/:total relaciones.', ], ], 'call-to-action' => 'Explore visualmente las conexiones de esta entidad y cómo se relaciona con el resto de la campaña.', 'connections' => [ 'map_point' => 'Punto de mapa', 'mention' => 'Mención', 'quest_element' => 'Elemento de una misión', 'timeline_element' => 'Elemento de una línea de tiempo', ], 'create' => [ 'helper' => 'Crea una conexión entre :name y una o varias entidades.', 'new_title' => 'Nueva relación', 'success_bulk' => '{1} Se ha añadido :count conexión a :entity.|[2,*] Se han añadido :count conexiones a :entity.', ], 'delete_mirrored' => [ 'helper' => 'Esta conexión se refleja en la entidad de destino. Seleccione esta opción para eliminar también la conexión duplicada.', 'option' => 'Borrar conexión duplicada', ], 'destroy' => [ 'mirrored' => 'Esto también eliminará la conexión duplicada permanentemente.', 'success' => 'Relación :target eliminada de :entity.', ], 'fields' => [ 'attitude' => 'Actitud', 'is_pinned' => 'Fijado', 'link' => 'Enlace recíproco', 'mirror_relation' => 'Rol recíproco', 'owner' => 'Fuente', 'role' => 'Rol', 'target' => 'Objetivo', 'targets' => 'Entidades objetivo', 'two_way' => 'Reflejar relación', 'unmirror' => 'Desenlaza esta conexión.', ], 'filters' => [ 'connection' => 'Relación de conexión', 'name' => 'Objetivo de la conexión', ], 'helper' => 'Crea relaciones entre entidades y configura su actitud y visibilidad. Las relaciones también se pueden fijar al menú de la entidad.', 'helpers' => [ 'description' => 'Detalla la naturaleza de la conexión entre las dos entidades.', 'link' => 'Crear una relación coincidente en los objetivos.', 'mirror_relation' => 'Cómo ve el objetivo esta entrada (dejar en blanco para copiar lo anterior).', 'no_relations' => 'Esta entidad no tiene actualmente ninguna conexión con otras entidades de la campaña.', ], 'hints' => [ 'attitude' => 'Aquí se puede definir opcionalmente el orden en el que las relaciones aparecen por defecto de forma descendiente.', 'two_way' => 'Al reflejar una relación, ésta se copiará en el objetivo seleccionado. Sin embargo, si editas una, la otra no se verá afectada.', ], 'index' => [ 'title' => 'Relaciones', ], 'linked' => [ 'break' => 'Romper enlace', 'helper' => 'Esta relación está sincronizada con :link', 'label' => 'Relación vinculada', 'unmirror-helper' => 'Convertir esto en una relación independiente no eliminará nada.', ], 'options' => [ 'mentions' => 'Relaciones + relacionadas + menciones', 'only_relations' => 'Sólo conexión directa', 'related' => 'Relaciones + relacionadas', 'relations' => 'Relaciones', 'show' => 'Mostrar', ], 'panels' => [ 'related' => 'Eliminar', ], 'placeholders' => [ 'attitude' => 'Desde -100 hasta 100, siendo 100 muy positiva.', 'role' => 'Rival, Mejor amigo, Hermano', ], 'show' => [ 'title' => 'Relaciones de :name', ], 'types' => [ 'family_member' => 'Familiar', 'organisation_member' => 'Miembro de organización', ], 'update' => [ 'success' => 'Relación :target de :entity actualizada.', 'title' => 'Actualizar relaciones de :name', ], ]; ================================================ FILE: lang/es/entities/reminders.php ================================================ [ 'add' => 'Vincular a un calendario', 'remove' => 'Eliminar fecha del calendario', ], 'helpers' => [ 'pitch' => 'Deja atrás el calendario del mundo real y vincula esta entidad a un calendario de tu mundo para mantenerte inmerso en tu línea de tiempo ficticia.', ], ]; ================================================ FILE: lang/es/entities/share.php ================================================ [ 'copy' => 'Copiar enlace', 'make_public' => 'Hacer pública la campaña', ], 'fields' => [ 'campaign_access' => 'Configuración de campaña', 'visibility_mode' => 'Ajustar visibilidad', ], 'helpers' => [ 'campaign_access' => 'Para compartir esto con el público, primero debes hacer pública la campaña.', 'entity_permissions_warning' => 'Hacer pública esta campaña permite que cualquiera la vea. Las entradas marcadas como privadas permanecen ocultas.', 'hidden_explanation' => 'La campaña es pública, pero esta entrada está actualmente oculta para los no miembros.', 'hidden_unlisted_explanation' => 'La campaña no está listada; solo las personas con el enlace pueden encontrarla.', 'member-link' => 'Compartir esto solo con miembros', 'private_explanation' => 'Solo los miembros pueden acceder a esta entrada.', 'public_explanation' => 'Tanto la campaña como esta entrada son públicas. Cualquiera con el enlace puede verlas.', 'unlisted_explanation' => 'La campaña no está listada y esta entrada es visible. Cualquiera con el enlace puede verla.', ], 'labels' => [ 'member_link' => 'Enlace solo para miembros', 'public_link' => 'Enlace público', 'share_link' => 'Enlace para compartir', ], 'options' => [ 'keep_private' => 'Mantener campaña privada', 'make_all_public' => 'Mostrar todo :module a no miembros', 'make_campaign_public' => 'Hacer campaña pública', 'make_entity_public' => 'Mostrar :name a no miembros', ], 'status' => [ 'hidden' => 'No visible para no miembros', 'private' => 'Esta campaña es privada', 'public' => 'Visible para no miembros', 'unlisted' => 'Visible para cualquiera con el enlace', ], 'success' => [ 'copied' => '¡Enlace copiado al portapapeles!', 'copied_members' => 'Enlace solo para miembros copiado.', 'copied_public' => 'Enlace público copiado, cualquiera con el enlace puede ver la entrada.', 'updated' => 'Configuración de visibilidad actualizada exitosamente.', ], 'title' => 'Compartir entrada', ]; ================================================ FILE: lang/es/entities/story.php ================================================ [ 'collapse_all' => 'Colapsar todo', 'expand_all' => 'Expandir todo', 'load_more' => 'Cargar más', 'login_for_more' => 'Inicia sesión para ver más entradas', ], 'reorder' => [ 'helper' => 'Arrastra y suelta las publicaciones para reordenarlas en la página de resumen de la entidad.', 'icon_tooltip' => 'Reordenar notas', 'panel_title' => 'Reordenar notas', 'save' => 'Guardar nuevo orden', 'success' => 'Notas reordenadas.', ], 'update' => [ 'title' => 'Actualizar entrada de :entity', ], 'warning' => [], ]; ================================================ FILE: lang/es/entities/tags.php ================================================ [ 'helper' => 'Agrega o elimina etiquetas en :name.', 'title' => 'Agrega o elimina etiquetas', ], ]; ================================================ FILE: lang/es/entities/timelines.php ================================================ 'Las líneas de tiempo que tienen elementos vinculados a esta entidad se muestran a continuación.', 'show' => [ 'title' => 'Líneas de tiempo de :name', ], ]; ================================================ FILE: lang/es/entities/tooltips.php ================================================ 'HTML compatible: texto (:text), estructura (:layout), listas/tablas e imágenes.', 'helper' => 'Sobrescribe el texto de vista previa generado automáticamente que se muestra al pasar el cursor sobre los enlaces a esta entidad.', 'label' => 'Resumen', 'placeholder' => 'Describe brevemente esta entidad en una o dos frases.', 'premium' => 'Desbloquea la posibilidad de personalizar el resumen al pasar el cursor con una :boosted-campaign.', ]; ================================================ FILE: lang/es/entities/transform.php ================================================ [ 'convert' => 'Convertir a módulo', ], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Tipo de entidad desconocido o no válido.', ], 'success' => '{1} Se ha transformado :count entidad al nuevo tipo: :type.|[2,*] Se han transformado :count entidades al nuevo tipo: :type.', ], 'confirm' => [ 'checkbox' => 'Entiendo que al transformar :entity a otro módulo, se perderán los siguientes elementos:', 'label' => 'Confirmar pérdida de datos', ], 'documentation' => 'Documentación: Conversión de módulos de entidades', 'fields' => [ 'current' => 'Módulo actual', 'select_one' => 'Elige una', 'target' => 'Nuevo tipo de entidad', ], 'panel' => [ 'bulk_description' => 'Cambia el tipo de entidad a múltiples entidades. Ten en cuenta que algunos datos podrían perderse debido a que hay diferentes campos en otras entidades.', 'bulk_title' => 'Transformar entidades en lota', 'title' => 'Transformar entidad', 'warning' => 'Algunos datos pueden no transferirse si el nuevo módulo utiliza campos diferentes.', ], 'success' => 'Entidad :name transformada.', 'title' => 'Transformar :name', ]; ================================================ FILE: lang/es/entities.php ================================================ 'Habilidades', 'ability' => 'Habilidad', 'article' => 'Artículo', 'articles' => 'Artículos', 'attribute_template' => 'Plantilla de atributo', 'attribute_templates' => 'Plantillas de atributos', 'bookmark' => 'Marcar como favorito', 'bookmarks' => 'Marcadores', 'calendar' => 'Calendario', 'calendars' => 'Calendarios', 'campaign' => 'Campaña', 'campaigns' => 'Campañas', 'character' => 'Personaje', 'characters' => 'Personajes', 'conversation' => 'Conversación', 'conversations' => 'Conversaciones', 'creator' => [ 'actions' => [ 'create' => 'Crear :type', 'full' => 'Ir al formulario completo', 'more' => 'Añadir más detalles', ], 'back' => 'Volver a la selección', 'bulk_names' => 'Añadir un nombre por línea', 'duplicate' => 'Ya existen otras entidades de este tipo con el mismo nombre.', 'helper_v2' => 'Crea rápidamente los cimientos de una nueva entidad sin interrumpir tu flujo actual.', 'missing_v2' => 'En esta interfaz sólo están disponibles los módulos que están activados y que tienes permiso para crear. :learn-more.', 'modes' => [ 'bulk' => 'Añadir en bloque', 'default' => 'Añadir rápidamente', ], 'name' => [ 'new' => 'Nuevo nombre', 'remove' => 'Eliminar', ], 'success_multiple' => '{1} Nueva entidad :link creada.|[2,*] Nuevas entidades :link creadas.', 'success_multiple_posts' => '{1} Nueva entrada :link creada.|[2,*] Nuevas entradas :link creadas.', 'title' => 'Nueva entidad', 'titles' => [ 'everything' => 'Todo', 'quick-access' => 'Acceso rápido', ], 'tooltip' => 'Crear una nueva entidad sin abandonar la página actual', 'tooltips' => [ 'create' => 'Crea la entidad y vuelve a la pantalla de selección de entidades', 'create_more' => 'Crea la entidad y empieza a crear otra del mismo tipo', 'edit' => 'Crea la entidad y empieza a editarla', ], ], 'creature' => 'Criatura', 'creatures' => 'Criaturas', 'dice_roll' => 'Tirada de dados', 'dice_rolls' => 'Tiradas de dados', 'entries' => 'Entradas', 'entry' => 'Entrada', 'event' => 'Evento', 'events' => 'Eventos', 'families' => 'Familias', 'family' => 'Familia', 'inventories' => 'Inventarios', 'item' => 'Objeto', 'items' => 'Objetos', 'journal' => 'Diario', 'journals' => 'Diarios', 'location' => 'Lugar', 'locations' => 'Lugares', 'map' => 'Mapa', 'maps' => 'Mapas', 'media' => 'Multimedia', 'new' => [], 'note' => 'Nota', 'notes' => 'Notas', 'organisation' => 'Organización', 'organisations' => 'Organizaciones', 'properties' => 'Propiedades', 'quest' => 'Misión', 'quest_element' => 'Elemento de la misión', 'quests' => 'Misiones', 'race' => 'Raza', 'races' => 'Razas', 'relation' => 'Relación', 'relations' => 'Relaciones', 'reminders' => 'Recordatorios', 'tag' => 'Etiqueta', 'tags' => 'Etiquetas', 'templates' => 'Plantillas', 'timeline' => 'Línea de tiempo', 'timeline_element' => 'Elemento de línea de tiempo', 'timelines' => 'Líneas de tiempo', 'whiteboard' => 'Pizarra', 'whiteboards' => 'Pizarras', ]; ================================================ FILE: lang/es/entries/archetypes.php ================================================ [ 'how' => 'Cómo definir arquetipos', ], 'success' => [ 'set' => ':name establecido como arquetipo.', 'unset' => ':name ya no está establecido como arquetipo.', ], ]; ================================================ FILE: lang/es/entries/bulk.php ================================================ [ 'copy_to_campaign' => '{1} :count entrada copiada a :campaign.|[2,*] :count entradas copiadas a :campaign.', 'delete' => '{1} Se eliminó :count entrada.|[2,*] Se eliminaron :count entradas.', 'editing' => '{1} :count entrada actualizada.|[2,*] :count entradas actualizadas.', 'editing_partial' => '{1} :count/:total entrada actualizada.|[2,*] :count/:total entradas actualizadas.', 'permissions' => '{1} Permisos cambiados para :count entrada.|[2,*] Permisos cambiados para :count entradas.', 'private' => '{1} :count entrada es ahora privada.|[2,*] :count entradas son ahora privadas.', 'public' => '{1} :count entrada es ahora visible.|[2,*] :count entradas son ahora visibles.', 'templates' => '{1} Se aplicó un kit a :count entrada.|[2,*] Se aplicó un kit a :count entradas.', ], ]; ================================================ FILE: lang/es/entries/fields.php ================================================ [ 'placeholder' => 'Nombre de la entrada', ], 'type' => [ 'placeholder' => 'Tipo de la entrada', ], ]; ================================================ FILE: lang/es/entries/tabs.php ================================================ 'Alias', 'identity' => 'Identidad', 'media' => 'Multimedia', 'properties' => 'Propiedades', 'relations' => 'Relaciones', ]; ================================================ FILE: lang/es/errors.php ================================================ [ 'body' => '¡Parece que no tienes permiso para acceder a esta página!', 'title' => 'Permiso denegado', ], '403-form' => [ 'help' => 'Puede que tu sesión haya caducado. Intenta volver a iniciar sesión en otra ventana antes de guardar.', ], '404' => [ 'body' => 'Lo sentimos, la página que estás buscando no se encuentra.', 'title' => 'Página no encontrada', ], '500' => [ 'body' => [ '1' => 'Ups, parece que algo ha ido mal.', '2' => 'Nos ha llegado un informe con este error, pero a veces nos ayuda saber un poco más sobre lo que estabas haciendo.', ], 'title' => 'Error', ], '503' => [ 'body' => [ '1' => 'Kanka está en mantenimiento ahora mismo. ¡Suele ser porque hay una actualización en camino!', '2' => 'Disculpa las molestias. Todo volverá a la normalidad en solo unos minutos.', ], 'json' => 'Kanka está actualmente en mantenimiento, por favor inténtalo de nuevo en unos minutos.', 'title' => 'Mantenimiento', ], '503-form' => [], 'back-to-campaigns' => 'Volver a una de tus campañas', 'footer' => 'Si necesitas más asistencia, contáctanos en hello@kanka.io o en :discord', 'log-in' => 'Acceder a tu cuenta podría revelarte lo que estás buscando.', 'post_layout' => 'Diseño de post no válido.', 'private-campaign' => [ 'auth' => [ 'helper' => 'No tienes acceso a esta campaña.', ], 'guest' => [ 'helper' => 'La campaña a la que intentas acceder es privada y no has iniciado sesión.', 'login' => 'Iniciar sesión podría permitirte acceder a los contenidos.', ], 'title' => 'Campaña privada', ], ]; ================================================ FILE: lang/es/events.php ================================================ [ 'title' => 'Nuevo evento', ], 'destroy' => [], 'edit' => [], 'events' => [ 'helper' => 'Aquí se muestran los eventos que tienen esta entidad como evento padre.', ], 'fields' => [ 'date' => 'Fecha', ], 'helpers' => [ 'date' => 'Este campo puede contener cualquier cosa y no está vinculado a los calendarios de la campaña. Para vincular este evento con un calendario, añádelo desde la pestaña de recordatorios o desde el mismo calendario.', ], 'index' => [], 'lists' => [ 'empty' => 'Añade momentos importantes como batallas, coronaciones o descubrimientos a la historia de tu mundo.', ], 'placeholders' => [ 'date' => 'Fecha del evento', 'type' => 'Ceremonia, festival, catástrofe, batalla, nacimiento...', ], 'show' => [], 'tabs' => [ 'calendars' => 'Entradas del calendario', ], ]; ================================================ FILE: lang/es/export.php ================================================ 'Contenido', 'hidden_campaign' => 'Campaña Oculta', 'index' => 'Índice de Entidades', ]; ================================================ FILE: lang/es/families/trees.php ================================================ [ 'clear' => 'Borrar todo', 'first' => 'Añadir un fundador', 'founder' => 'Añadir un nuevo fundador', 'rename-relation' => 'Renombrar relación', 'reset' => 'Descartar cambios', 'save' => 'Guardar', ], 'modal' => [ 'first-title' => 'Selecciona una entidad', 'helper' => 'Sustituir la entidad por otra de la campaña', 'relation' => 'Relación', 'title' => 'Reemplazar entidad', ], 'modals' => [ 'clear' => [ 'confirm' => '¿Estás seguro de que quieres reinicializar todos los datos del árbol genealógico?', ], 'entity' => [ 'add' => [ 'founder' => 'Fundador/a', 'member' => 'Miembro', 'success' => 'Entidad añadida.', 'title' => 'Añadir una entidad', ], 'child' => [ 'success' => 'Descendencia añadida.', 'title' => 'Añadir descendencia', ], 'edit' => [ 'helper' => 'Selecciona esta opción si la relación es desconocida. Se puede añadir un personaje más tarde.', 'success' => 'Entidad actualizada.', 'title' => 'Actualizar una entidad', ], 'founder' => [ 'title' => 'Añadir un nuevo fundador', ], 'remove' => [ 'confirm' => '¿Estás seguro de que quieres eliminar esta entidad del árbol genealógico?', 'success' => 'Entidad eliminada.', ], ], 'relations' => [ 'add' => [ 'success' => 'Relación añadida.', 'title' => 'Añadir una relación', ], 'edit' => [ 'success' => 'Relación actualizada.', 'title' => 'Actualizar una relación', ], 'unknown' => 'Desconocido', ], 'reset' => [ 'confirm' => '¿Estás seguro de que quieres descartar los cambios realizados en el árbol genealógico?', ], ], 'pitch' => 'Crea un árbol genealógico detallado para las familias de la campaña.', 'success' => [ 'cleared' => 'Árbol genealógico borrado.', 'reseted' => 'El árbol genealógico se ha restablecido.', 'saved' => 'Árbol genealógico guardado.', ], 'title' => 'Árbol genealógico :name', 'unknown' => 'no establecido', ]; ================================================ FILE: lang/es/families.php ================================================ [ 'title' => 'Nueva familia', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Esta familia está extinta.', 'members' => 'Aquí se muestran los miembros de la familia. Se puede añadir un personaje a una familia en el menú de edición de dicho personaje, usando el desplegable "Familia".', ], 'index' => [], 'lists' => [ 'empty' => 'Haz seguimiento de linajes, clanes o casas nobles que conectan a tus personajes.', ], 'members' => [ 'create' => [ 'helper' => 'Añadir uno o mas miembros a :name.', 'success' => '{0} No se ha añadido ningún miembro. |{1} Se ha añadido 1 miembro. |[2,*] Se han añadido :count miembros.', 'title' => 'Nuevos miembros', ], ], 'placeholders' => [ 'name' => 'Nombre de la familia', 'type' => 'Real, noble, extinguida...', ], 'show' => [ 'tabs' => [ 'tree' => 'Árbol genealógico', ], ], ]; ================================================ FILE: lang/es/faq.php ================================================ [ 'account_settings' => 'Ajustes de la cuenta', 'answer' => 'Para eliminar tu cuenta, dirígete a la página de :account y baja hasta la sección de eliminación de la cuenta. Esto eliminará tu cuenta y todas las campañas de las que seas el único miembro.', 'question' => '¿Cómo puedo eliminar mi cuenta?', ], 'app_backup' => [ 'answer' => 'Realizamos dos copias de seguridad al día para evitar cualquier pérdida de datos. Nuestras propias campañas están en el servidor, así que no queremos correr ningún riesgo!', 'question' => '¿Cada cuánto tiempo se hace una copia de seguridad de los datos de Kanka?', ], 'attribute-templates' => [ 'answer' => <<<'TEXT' La mejor forma de explicártelo es con un ejemplo. Imagina que tu mundo tiene montones de localizaciones, y en muchas de ellas quieres acordarte de crear un atributo personalizado de “Población”, “Clima”, “Nivel de criminalidad”... Podrías incluirlos manualmente en cada localización, pero puede ser un proceso tedioso y a veces se te puede olvidar el “Nivel de criminalidad”. Aquí es donde las plantillas de atributos resultan útiles. Puedes crear una plantilla de atributos con aquellos atributos (población, clima, nivel de criminalidad, etc.) y aplicar después esa plantilla a tus localizaciones. De este modo, se aplicarán los atributos de la plantilla a las localizaciones, y todo lo que tendrás que hacer es cambiar los valores sin tener que acordarte de los atributos! TEXT , 'question' => '¿Qué son las “Plantillas de atributos”?', ], 'backup' => [ 'answer' => 'Una vez al día, puedes exportar todos los datos de tu campaña en un archivo ZIP. En la app, haz clic en "Campaña", en el menú de la izquierda, y dale a "Exportar". No podrás subir este archivo a Kanka, sino que está pensado para que estés tranquilo si no vas a usar más la app.', 'question' => '¿Cómo puedo hacer una copia de seguridad o exportar mi campaña?', ], 'bugs' => [ 'answer' => 'Simplemente únete a nuestro :discord e informa del error en el canal #errors-and-bugs.', 'question' => '¿Cómo puedo informar de un error?', ], 'campaign-sync' => [ 'answer' => 'Kanka no tiene esta funcionalidad. Sin embargo, si quieres tener varios grupos en el mismo mundo, puedes usar la misma campaña y separar a los grupos mediante etiquetas, misiones y permisos.', 'question' => '¿Puedo sincronizar entidades entre varias campañas?', ], 'conversations' => [], 'custom' => [ 'answer' => 'Kanka viene con un conjunto de entidades predefinidas que interactúan entre ellas. Permitir tipos de entidad personalizados requeriría volver a reconstruir la app desde cero y le quitaría su propósito como herramienta de creación. Además, la flexibilidad de Kanka permite que puedas representar cualquier tipo de entidad con las Etiquetas.', 'question' => '¿Puedo crear tipos de entidad personalizados?', ], 'delete-campaign' => [ 'answer' => 'Ve al tablero de campaña y haz clic en "Campaña" en el menú de la izquierda. Si eres el administrador de la campaña, te aparecerá el botón de "Eliminar". Ten en cuenta que eliminar una campaña es una acción permanente que eliminará todos los datos almacenados en nuestros servidores, incluyendo las imágenes.', 'question' => '¿Cómo puedo eliminar una campaña?', ], 'discord' => [ 'answer' => 'Para vincular tu cuenta de Kanka con :discord, primero has de hacer clic sobre tu avatar en la esquina superior derecha de la página e ir al Perfil. Desde ahí, navega hasta la subpágina de :apps y haz clic en Conectar.', 'question' => '¿Cómo vinculo mi cuenta de Kanka con Discord?', ], 'early-access' => [ 'answer' => 'El acceso anticipado es nuestra manera de recompensar a nuestros increíbles suscriptores, dándoles un periodo exclusivo de 30 días en el que pueden probar las últimas novedades antes que el resto.', 'question' => '¿Qué es el acceso anticipado?', ], 'entity-notes' => [ 'answer' => 'Todas las entidades tienen una pestaña de "Notas", que son pequeños fragmentos de texto que se pueden configurar para que solo sean visibles para ti (genial para los co-másters), solo para administradores o visibles para todos. También puedes dar permiso a tus jugadores para crear y editar estas notas sin darles acceso también a editar la entidad completa.', 'question' => '¿Como gestiona Kanka la información oculta?', ], 'fields' => [ 'answer' => 'Respuesta', 'category' => 'Categoría', 'locale' => 'Locale', 'order' => 'Order', 'question' => 'Pregunta', ], 'free' => [ 'answer' => <<<'TEXT' Además de votar sobre la dirección que tomará Kanka, al apoyarnos obtendrás un aumento en el tamaño de los archivos que puedes subir, añadiremos tu nombre en el muro de la fama, recibirás bonitos iconos predefinidos, podrás votar qué funciones se priorizan y mucho más! TEXT , 'question' => '¿La app seguirá siendo gratis?', ], 'gods-and-religions' => [ 'answer' => 'Recomendamos que crees los dioses como Personajes y las religiones como Organizaciones. Para encontrar a tus deidades rápidamente, puedes etiquetarlas con la Etiqueta o el tipo apropiados.', 'question' => '¿Dónde puedo crear dioses y religiones?', ], 'help' => [ 'answer' => 'Antes de nada, ¡gracias por ofrecerte! Siempre estamos interesados en aceptar ayuda con las traducciones, probar nuevas funciones, o ayudar a nuevos usuarios. También nos encanta cuando la gente promociona Kanka para que llegue a nuevos usuarios en lugares que nunca habíamos pensado. Tu mejor curso de acción es unirte a nosotros en el :discord, donde hay un canal dedicado a ofrecer ayuda. ¡También amamos a nuestros patrones en :patreon, si quieres apoyarnos y acceder a algunos beneficios!', 'question' => '¡Quiero ayudar! ¿Qué puedo hacer?', ], 'map' => [ 'answer' => 'Cada localización puede contener un mapa (png, jpg o svg) al que se pueden añadir puntos, personalizando su tamaño, forma, icono y color; y que enlacen a otras entidades o que simplemente sean etiquetas.', 'question' => '¿Puedo subir mapas a Kanka?', ], 'mobile' => [ 'answer' => 'Actualmente no hay ninguna app móvil para Kanka, pero la web funciona perfectamente en un dispositivo móvil. La única limitación es que la herramienta de menciones no funciona en el editor de textos :( Si el soporte de Patreon lo permite, espero poder pagar a alguien para que haga una app móvil algún día, pero no va a ocurrir en un futuro próximo.', 'question' => '¿Hay una app móvil? ¿Hay alguna planeada?', ], 'monsters' => [ 'answer' => 'Recomendamos usar el módulo de Razas para etnias, especies, monstruos y cualquier ser viviente que no sea un personaje.', 'question' => '¿Dónde se crean los monstruos?', ], 'multiworld' => [ 'answer' => '¡No hace falta! Puedes crear tantas “campañas” como quieras en la aplicación, y hacer que cada una represente mundos, escenarios o lo que quieras. Una vez tengas varias campañas, puedes cambiar fácilmente entre ellas.', 'question' => 'Estoy construyendo varios mundos en escenarios diferentes. ¿Necesito una cuenta diferente para cada mundo?', ], 'nested' => [ 'answer' => 'Si prefieres ver tus entidades en vista anidada por defecto, puedes hacerlo desde las opciones de Diseño, dentro de tu Perfil. Allí puedes seleccionar la opción "Vista anidada por defecto". Esto solo afectará a tu cuenta.', 'question' => '¿Puedo configurar las listas para que aparezcan anidadas por defecto?', ], 'organise_play' => [], 'permissions' => [ 'answer' => '¡Por supuesto, para eso hemos creado Kanka! Puedes invitar a todos tus jugadores a tus campañas, y darles roles y permisos. Hemos construido el sistema para que sea extremadamente flexible (con opción de incluir o de excluir) para cubrir las máximas necesidades y situaciones posibles.', 'question' => 'Quiero usar Kanka para construir mi mundo de rol, pero quiero que mis jugadores tengan acceso a algunas de las entidades y editar sus personajes. ¿Es posible?', ], 'plans' => [ 'answer' => <<<'TEXT' Los planes a largo plazo para Kanka incluyen construir del todo esta herramienta versátil de worldbuilding y gestión de campañas RPG, manteniéndonos agnósticos (sin un sistema concreto de RPG). La comunidad puede añadir contenido específico mediante las Plantillas de la Comunidad. Un objetivo más ambicioso es llegar a integrar Kanka con otras plataformas, como las de rol virtual. Por otro lado, muchos proyectos hobby acaban quemando y el creador los abandona. El Patreon está precisamente para ayudarme a reducir mis horas de trabajo y dedicar más tiempo a Kanka sin sacrificar la seguridad financiera de mi familia, además de cubrir los costes del servidor. Además, el proyecto es "open source" y la comunidad lo puede continuar en caso de que algo me ocurriera. Todos los datos de las campañas se pueden exportar una vez al día, en caso de que te preocupe perder todo tu contenido. TEXT , 'question' => '¿Qué planes hay a largo plazo? ¿Qué pasa si Ilestis se aburre de trabajar en Kanka?', ], 'public-campaigns' => [ 'answer' => 'Puedes ojear las :public-campaigns para ver cómo los demás usan Kanka en sus campañas.', 'question' => '¿Cómo usan Kanka otras personas?', ], 'renaming-modules' => [ 'answer' => 'Aunque esto sería fácil de hacer en inglés y otros idiomas que no usan géneros, cambiar el nombre de los módulos rompería la corrección gramatical y la experiencia de usuario para la mayoría de idiomas de Kanka.', 'question' => '¿Puedo renombrar los módulos? Por ejemplo, Clanes en vez de Familias, o Facciones en lugar de Organizaciones.', ], 'sections' => [ 'community' => 'Comunidad', 'general' => 'General', 'other' => 'Otros', 'permissions' => 'Permisos', 'pricing' => 'Tarifas', 'worldbuilding' => 'Creación de mundos', ], 'show' => [ 'return' => 'Volver a las FAQ', 'timestamp' => 'Última actualización el :date', 'title' => 'FAQ :name', ], 'unboost' => [ 'answer' => 'Al dejar de mejorar una campaña, no se elimina ningún dato, sino que se esconde. Si vuelves a mejorar la campaña, toda la información y funcionalidades volverán a estar disponibles con la misma configuración de antes.', 'question' => '¿Qué pasa si dejo de mejorar una campaña?', ], 'user-switch' => [ 'answer' => 'Manejar los permisos puede ser complicado, sobre todo en campañas grandes. Como administrador de campaña, puedes navegar por la página de miembros y hacer clic en el botón de "Ver como" junto a cada miembro. Así, podrás navegar por la campaña y verla como ellos lo harán. Esta es la manera más fácil de comprobar los permisos de tu campaña.', 'question' => 'Los permisos de mi campaña ya están configurados, ¿cómo puedo comprobarlos?', ], 'visibility' => [ 'answer' => 'Solo las personas que invites a tu campaña pueden verla e interactuar con ella. Tus datos son privados y siempre están bajo tu control. Por otro lado, puedes configurar tu campaña como pública para que la vean los usuarios no registrados.', 'question' => '¿Quién puede ver mi mundo?', ], ]; ================================================ FILE: lang/es/fields.php ================================================ [ 'label' => 'Descripción', ], 'entry' => [ 'label' => 'Entrada', ], 'gallery' => [ 'placeholder' => 'Elige una imagen de la galería', ], 'gallery-header' => [ 'description' => 'Si la entidad no tiene imagen de cabecera, muestra una imagen de la galería.', ], 'gallery-image' => [ 'description' => 'Si la entidad no tiene imagen, muestra una imagen de la galería.', ], 'header-image' => [ 'boosted-description' => 'Muestra una imagen de fondo en la cabecera de la entidad con una :boosted-campaign.', 'description' => 'Muestra una imagen de fondo en la cabecera de la entidad. Para obtener mejores resultados, usa una imagen muy grande.', 'title' => 'Imagen de cabecera', ], 'tooltip' => [], ]; ================================================ FILE: lang/es/filters.php ================================================ [ 'bookmark' => 'Añadir a favoritos', ], 'alerts' => [ 'copy' => 'Los filtros se han copiado en tu portapapeles.', ], 'bookmark' => [ 'helper' => 'Crea un nuevo marcador para esta vista usando los filtros actuales.', 'name' => ':module (filtrado)', 'premium' => 'Para añadir más marcadores es necesario activar las funciones premium de la campaña.', 'success' => 'Marcador creado.', ], 'helpers' => [ 'guest' => 'Inicia sesión en tu cuenta para filtrar los resultados.', 'icon' => 'Asigna a este marcador un ícono especial :fontawesome, por ejemplo :example.', 'icon-premium' => 'Asigna a este marcador un ícono especial :fontawesome, como :example, con un :premium.', ], ]; ================================================ FILE: lang/es/footer.php ================================================ 'Acerca de nosotros', 'blog' => 'Blog', 'boosters' => 'Potenciadores', 'community' => 'Comunidad', 'company' => 'Empresa', 'contact' => 'Contacto', 'copyright' => 'Copyright :copy :year Owlchester SNC', 'documentation' => 'Documentación', 'features' => 'Características', 'kb' => 'Base de conocimientos', 'language-switcher' => [ 'other' => 'Otros idiomas', 'title' => 'Selecciona tu idioma', ], 'made' => 'Hecho con ❤️ en Ginebra, Suiza', 'newsletter' => 'Boletín de noticias', 'platform' => 'Plataforma', 'plugins' => 'Biblioteca de plugins', 'premium' => 'Campañas Premium', 'press-kit' => 'Kit de prensa', 'pricing' => 'Precios', 'privacy' => 'Privacidad', 'public-campaigns' => 'Campañas públicas', 'resources' => 'Recursos', 'roadmap' => 'Hoja de ruta', 'security' => 'Seguridad', 'server-time' => 'Esta es la hora de nuestro servidor (:server)', 'showcase' => 'Destacados', 'status' => 'Estado del servicio', 'terms' => 'Términos', 'thanks' => 'Sólo posible gracias a nuestros suscriptores.', 'translator_call' => 'Kanka está traducido a otros lenguajes gracias a nuestra increíble comunidad. Si quieres ayudar a traducir Kanka a tu idioma, contáctanos en :discord!', 'whats-new' => 'Novedades', ]; ================================================ FILE: lang/es/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Votaciones comunitarias', ]; ================================================ FILE: lang/es/front/hall-of-fame.php ================================================ 'Salón de la fama', ]; ================================================ FILE: lang/es/front/kb.php ================================================ [], 'show' => [], 'title' => 'Base de conocimientos', ]; ================================================ FILE: lang/es/front/newsletter.php ================================================ [ 'learn_more' => 'Saber más', 'subscribe' => 'Suscribirse', ], 'fields' => [ 'firstname' => 'Nombre', 'lastname' => 'Apellidos', 'notifications' => 'Notificaciones', ], 'groups' => [ 'all' => 'Recibe actualizaciones ocasionales sobre nuevas funciones, votaciones de la comunidad, promociones y eventos.', 'newsletter' => 'Newsletter', ], 'headline' => 'Suscríbete a una (o a todas) nuestras newsletters para estar al día de las novedades de Kanka.', 'title' => 'Correos de novedades', ]; ================================================ FILE: lang/es/front.php ================================================ [], 'actions' => [], 'campaigns' => [ 'public' => [ 'filters' => [ 'is-premium' => '¡Esta es una campaña premium!', ], ], ], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => '¡Entendido!', 'link' => 'Saber más', 'message' => 'Esta página web usa cookies para asegurarse de que obtienes la mejor experiencia.', ], 'faq' => [], 'featured_campaigns' => [], 'features' => [ 'api' => [ 'link' => 'Documentación API', ], 'patreon' => [ 'api_calls' => 'Llamadas a la API aumentadas (90)', 'boosts' => 'Mejoras de campaña', 'default_image' => 'Imágenes bonitas por defecto para las entidades', 'discord' => 'Canal privado de Discord', 'free' => 'Gratis', 'hall_of_fame' => 'Nombre en el :link', 'impact' => 'Influencia en futuras características', 'monthly_vote' => 'Participación en la votación mensual de características', 'pagination' => 'Aumento en los resultados por página (100)', 'upload_limit' => 'Aumento del tamaño máximo de subida de archivos (8mb)', 'upload_limit_map' => 'Aumento del tamaño máximo de subida de mapas (10mb)', ], ], 'first_block' => [], 'footer' => [], 'goodbye' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Kanka es una herramienta de gestión de campañas de rol y creación de mundos, que facilita la organización y planificación de tus campañas RPG.', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Tablero', 'login' => 'Iniciar sesión', 'register' => 'Registrarse', 'register_free' => 'Regístrate gratis', ], 'meta' => [ 'description' => 'Kanka es un administrador flexible de campañas de rol online.', 'title' => 'Kanka - Gestiona partidas de rol y crea mundos online', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Gratis', 'month' => 'Mes', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Worldbuilding, Creación de mundos, RPG, Rol, Juego de rol, Gestión de campaña de rol', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/es/gallery.php ================================================ [ 'gallery' => 'De la galería', 'url' => 'Subir una imagen desde una URL', ], 'browse' => [ 'layouts' => [ 'large' => 'Previsualizaciones grandes', 'small' => 'Previsualizaciones pequeñas', ], 'search' => [ 'placeholder' => 'Buscar una imagen en la galería', ], 'title' => 'Galería', 'unauthorized' => 'Ninguno de tus roles tiene el permiso "navegar por la galería".', ], 'cta' => [ 'action' => 'Desbloquear más espacio de almacenamiento', 'helper' => 'Desbloquea hasta :size GB de espacio de almacenamiento con una campaña :premium.', 'title' => 'Almacenamiento lleno', ], 'delete' => [ 'success' => '[0] Se han eliminado 0 elementos|[1] Se ha eliminado un elemento|{2,*} Se han eliminado :count elementos', ], 'download' => [ 'errors' => [ 'copy_failed' => 'Nuestros servidores no han podido descargar la imagen.', 'gallery_full_free' => 'El espacio de almacenamiento de la galería está lleno. Activa las funciones premium para obtener más espacio de almacenamiento.', 'gallery_full_premium' => 'El espacio de almacenamiento de la galería está lleno. Elimina los archivos que no utilices.', 'invalid_format' => 'El archivo no tiene un formato válido.', 'too_big' => 'El archivo es demasiado grande (:size MB vs :max MB)', 'unauthorized' => 'Ninguno de tus roles tiene el permiso "subir a galería".', ], ], 'file' => [ 'saved' => 'Guardado', ], 'filters' => [ 'only_unused' => 'Mostrar sólo los archivos no utilizados', 'sort' => 'Ordenar por', ], 'move' => [ 'success' => '[0] Se han movido 0 elementos|[1] Se ha movido un elemento|{2,*} Se han movido :count elementos', ], 'update' => [ 'home' => 'Carpeta de inicio', 'success' => '[0] Se han atualizado 0 elementos|[1] Se ha actualizado un elemento|{2,*} Se han actualizado :count elementos', ], ]; ================================================ FILE: lang/es/general.php ================================================ 'Deseleccionar todo', 'documentation' => 'Aprende más sobre esta función en nuestra documentación.', 'done' => 'Hecho', 'learn-more' => 'Aprender más', 'no' => 'No', 'required' => 'Requerido', 'select_all' => 'Seleccionar todo', 'success' => [ 'created' => 'Se ha creado :name.', 'deleted' => 'Se ha eliminado :name.', 'deleted-cancel' => 'Se ha eliminado :name. :cancel.', 'updated' => 'Se ha actualizado :name.', ], 'tutorial' => 'Ver tutorial', 'yes' => 'Si', ]; ================================================ FILE: lang/es/genres.php ================================================ 'Historia alternativa', 'cyberpunk' => 'Cyberpunk', 'fantasy' => 'Fantasía', 'historical' => 'Histórico', 'many_worlds' => 'Múltiples mundos', 'modern' => 'Moderno', 'occult' => 'Ocultismo', 'post_apocalyptic' => 'Post-apocalíptico', 'pulp' => 'Pulp', 'science_fantasy' => 'Fantasía científica', 'science_fiction' => 'Ciencia ficción', 'space_opera' => 'Ópera espacial', 'steampunk' => 'Steampunk', 'superhero' => 'Superhéroes', 'urban_fantasy' => 'Fantasía urbana', 'western' => 'Western', ]; ================================================ FILE: lang/es/header.php ================================================ [ 'title' => 'Noticias sobre Kanka', ], 'notifications' => [ 'dismiss' => 'Descartar', 'no-unread' => 'No hay notificaciones sin leer', 'read_all' => 'Leer todas', ], 'qq' => [ 'tooltip' => 'Crear una entidad o una publicación', ], 'toggle_navigation' => 'Activar menú', 'user' => [ 'settings' => 'Ajustes', 'sign-out' => 'Cerrar sesión', 'upgrade' => 'Mejorar', 'your-profile' => 'Tu perfil', ], ]; ================================================ FILE: lang/es/helpers.php ================================================ [], 'api-filters' => [ 'description' => 'Están disponibles los siguientes filtros para el endpoint :name de la API.', 'title' => 'Filtros de la API', ], 'attributes' => [ 'link' => 'Opciones de atributos', ], 'calendar-widget' => [ 'info' => '¿Por qué se muestran estos recordatorios?', 'title' => 'Widget de calendario', ], 'dice' => [], 'entity_templates' => [], 'filters' => [ 'title' => 'Cómo usar los filtros', ], 'link' => [ 'description' => 'Puedes enlazar fácilmente otras entidades usando los siguientes atajos.', ], 'map' => [], 'pins' => [], 'public' => 'Mira el vídeo tutorial en Youtube acerca de las campañas públicas.', 'troubleshooting' => [ 'description' => 'Un miembro del equipo de Kanka te ha enviado a esta página. Selecciona una campaña del desplegable para generar un token y así poderte unir temporalmente a la campaña como administrador.', 'errors' => [ 'token_exists' => 'Ya existe un token para :campaign.', ], 'save_btn' => 'Generar token', 'select_campaign' => 'Seleccionar una campaña', 'subtitle' => '¡Ayuda, por favor!', 'success' => 'Copia el siguiente token y envíalo a alguien del equipo de Kanka.', 'title' => 'Resolución de problemas', ], 'widget-filters' => [ 'description' => 'Puedes filtrar las entidades mostradas en el widget de recientemente modificadas mediante sus campos y valores. Por ejemplo, puedes usar :example para filtrar por personajes muertos de tipo NPC.', 'link' => 'filtros de widget', 'title' => 'Filtrar los widgets del tablero', ], ]; ================================================ FILE: lang/es/history.php ================================================ [ 'show-old' => 'Cambios', ], 'cta' => 'Mostrar un registro de todos los cambios recientes en la campaña.', 'empty' => 'Sin valor', 'fields' => [ 'action' => 'Acción', 'category' => 'Categoría', 'details' => 'Detalles', 'when' => 'Cuándo', 'who' => 'Quién', ], 'filters' => [ 'all-actions' => 'Todas las acciones', 'all-users' => 'Todos los miembros', 'no-results' => 'No hay resultados que mostrar. Prueba con otros filtros o vuelve después de hacer cambios en las entidades de la campaña.', ], 'helpers' => [ 'base' => 'Esta interfaz contiene los cambios recientes en las entidades de la campaña durante un máximo de :amount meses, mostrando primero los cambios más recientes.', 'changes' => 'Los siguientes campos tenían estos valores anteriormente.', ], 'log' => [ 'create' => ':user ha creado :entity', 'create_post' => ':user creó el post ":post" en :entidad', 'delete' => ':user eliminó :entity', 'delete_post' => ':user ha eliminado un post en :entity', 'reorder_post' => ':user reordenó los posts de :entity', 'restore' => ':user restauró :entity', 'update' => ':user actualizó :entity', 'update_post' => ':user actualizó el post ":post" en :entity', 'update_tree' => ':user actualizó el árbol genealógico de :entity', ], 'title' => 'Historial', 'unknown' => [ 'entity' => 'una entidad desconocida', ], ]; ================================================ FILE: lang/es/items.php ================================================ [ 'title' => 'Nuevo objeto', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_equipped' => 'Equipado', 'price' => 'Precio', 'size' => 'Tamaño', 'weight' => 'Peso', ], 'helpers' => [], 'hints' => [], 'index' => [], 'inventories' => [], 'lists' => [ 'empty' => 'Añade armas, artefactos u objetos de importancia a tu mundo.', ], 'placeholders' => [ 'price' => 'Precio del objeto', 'size' => 'Tamaño, peso, dimensiones', 'type' => 'Arma, Poción, Artefacto...', 'weight'=> 'Peso del objeto', ], 'quests' => [], 'show' => [ 'tabs' => [ 'inventories' => 'Inventarios', ], ], ]; ================================================ FILE: lang/es/journals.php ================================================ [ 'title' => 'Nuevo diario', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Autor', 'date' => 'Fecha', ], 'helpers' => [], 'index' => [], 'journals' => [], 'lists' => [ 'empty' => 'Crea entradas de diario para registrar aventuras, pensamientos de los personajes o resúmenes y preparación de sesiones.', ], 'placeholders' => [ 'author' => 'Quién ha escrito el diario', 'date' => 'Fecha del diario', 'type' => 'Sesión, Borrador...', ], 'show' => [], ]; ================================================ FILE: lang/es/languages.php ================================================ [ 'ca' => 'Catalán', 'cs' => 'Checo', 'de' => 'Alemán', 'el' => 'Griego', 'en' => 'Inglés', 'en-US' => 'Inglés (EEUU)', 'es' => 'Español', 'fr' => 'Francés', 'gl' => 'Gallego', 'he' => 'Hebreo', 'hr' => 'Croata', 'hu' => 'Húngaro', 'it' => 'Italiano', 'nb' => 'Noruego (Bokmal)', 'nl' => 'Holandés', 'pl' => 'Polaco', 'pt-BR' => 'Portugués (Brasil)', 'ru' => 'Ruso', 'sk' => 'Eslovaco', 'tr' => 'Turco', ], 'header'=> 'Idiomas', ]; ================================================ FILE: lang/es/lists.php ================================================ [ 'learn' => 'Aprende sobre este módulo', 'public'=> 'Ver cómo lo hacen otros', ], 'empty' => [ 'title' => 'Aún no hay :plural.', ], ]; ================================================ FILE: lang/es/locations.php ================================================ [], 'create' => [ 'title' => 'Nuevo lugar', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [ 'is_destroyed' => 'Destruido', ], 'helpers' => [ 'characters' => 'Muestra todos los personajes en este lugar y sus lugares anidados, o solo los que están aquí.', ], 'hints' => [ 'is_destroyed' => 'Esta ubicación está destruida.', ], 'index' => [], 'items' => [], 'journals' => [], 'lists' => [ 'empty' => 'Añade tu primera ciudad, taberna o ruina oculta para dar base a tu mundo.', ], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'type' => 'Ciudad, Reino, Ruinas', ], 'quests' => [], 'show' => [], ]; ================================================ FILE: lang/es/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Entrar al modo de edición', 'exit-edit-mode' => 'Salir del modo de edición', 'finish-drawing' => 'Terminar de dibujar el polígono', ], 'notifications' => [ 'start-drawing' => 'Haz clic en el mapa para empezar a dibujar un polígono', ], 'toggle' => 'Abrir/cerrar todos los grupos', ]; ================================================ FILE: lang/es/maps/groups.php ================================================ [ 'add' => 'Añadir nuevo grupo', ], 'bulks' => [ 'delete' => '{1} Se ha eliminado :count grupo.|[2,*] Se han eliminado :count grupos.', 'patch' => '{1} Se ha actualizado :count grupo.|[2,*] Se han actualizado :count grupos.', ], 'create' => [ 'helper' => 'Agrega un nuevo grupo a :name. Luego, los marcadores pueden asignarse a este grupo.', 'success' => 'Grupo :name creado.', 'title' => 'Nuevo grupo', ], 'delete' => [ 'success' => 'Grupo :name eliminado.', ], 'edit' => [ 'success' => 'Grupo :name actualizado.', 'title' => 'Editar grupo :name', ], 'fields' => [ 'is_shown' => 'Mostrar marcadores del grupo', 'parent' => 'Grupo padre', 'position' => 'Posición', ], 'helper' => [ 'amount_v3' => 'Los marcadores pueden agruparse utilizando grupos de mapas. Al explorar un mapa, se puede hacer clic en cada grupo para mostrar u ocultar rápidamente todos los marcadores que contiene.', ], 'hints' => [ 'is_shown' => 'Si está seleccionado, los marcadores del grupo se mostrarán en el mapa por defecto.', ], 'index' => [ 'title' => 'Grupos de :name', ], 'pitch' => [ 'max' => [ 'helper' => 'No puedes agregar más grupos a menos que elimines uno existente.', 'limit' => 'Este mapa ha alcanzado su límite de grupos.', ], 'upgrade' => [ 'limit' => 'Has alcanzado el límite de :limit grupos para este mapa.', 'upgrade' => 'Actualiza a una campaña premium para agregar hasta :limit grupos y desbloquear aún más flexibilidad creativa.', ], ], 'placeholders' => [ 'name' => 'Tiendas, tesoros, PNJs...', 'position' => 'Campo opcional para indicar el orden en el que aparecen los grupos.', 'position_list' => 'Después de :name', ], 'reorder' => [ 'save' => 'Guardar nuevo orden', 'success' => '{1} Se ha reordenado :count grupo.|[2,*] Se han reordenado :count grupos.', 'title' => 'Reordenar grupos', ], ]; ================================================ FILE: lang/es/maps/layers.php ================================================ [ 'add' => 'Añadir nueva capa', ], 'base' => 'Capa base', 'bulks' => [ 'delete' => '{1} Se ha eliminado :count capa.|[2,*] Se han eliminado :count capas.', 'patch' => '{1} Se ha actualizado :count capa.|[2,*] Se han actualizado :count capas.', ], 'create' => [ 'success' => 'Capa :name creada.', 'title' => 'Nueva capa', ], 'delete' => [ 'success' => 'Capa :name eliminada.', ], 'edit' => [ 'success' => 'Capa :name actualizada.', 'title' => 'Editar capa :name', ], 'fields' => [ 'position' => 'Posición', 'type' => 'Tipo de capa', ], 'helper' => [ 'amount_v2' => 'Cargue capas en un mapa para cambiar la imagen de fondo que se muestra debajo de los marcadores, o como superposiciones sobre el mapa pero debajo de los marcadores.', 'is_real' => 'Las capas no están disponibles con OpenStretMaps.', ], 'index' => [ 'title' => 'Capas de :name', ], 'pitch' => [ 'max' => [ 'helper' => 'No puedes agregar más capas a menos que elimines una existente.', 'limit' => 'Este mapa ha alcanzado su límite de capas.', ], 'upgrade' => [ 'limit' => 'Has alcanzado el límite de :limit capas para este mapa.', 'upgrade' => 'Actualiza a una campaña premium para agregar hasta :limit capas y desbloquear aún más flexibilidad creativa.', ], ], 'placeholders' => [ 'name' => 'Subterráneo, nivel 2, naufragio...', 'position' => 'Campo opcional para definir en qué orden se apilan las capas.', 'position_list' => 'Después de :name', ], 'reorder' => [ 'save' => 'Guardar nuevo orden', 'success' => '{1} Se ha reordenado :count capa.|[2,*] Se han reordenado :count capas.', 'title' => 'Reordenar capas', ], 'short_types' => [ 'overlay' => 'Superposición', 'overlay_shown' => 'Superposición (mostrar automáticamente)', 'standard' => 'Estándar', ], 'types' => [ 'overlay' => 'Superposición (se muestra sobre la capa activa)', 'overlay_shown' => 'Superposición mostrada por defecto', 'standard' => 'Capa estándar (cambia entre capas)', ], ]; ================================================ FILE: lang/es/maps/markers.php ================================================ [ 'entry' => 'Escribir una entrada personalizada para este marcador.', 'remove' => 'Eliminar marcador', 'reset-polygon' => 'Restablecer posiciones', 'save_and_explore' => 'Guardar y explorar', 'start-drawing' => 'Empezar a dibujar', 'update' => 'Editar marcador', ], 'bulks' => [ 'delete' => '{1} Se ha eliminado :count marcador.|[2,*] Se han eliminado :count marcadores.', 'patch' => '{1} Se ha actualizado :count marcador.|[2,*] Se han actualizado :count marcadores.', ], 'circle_sizes' => [ 'custom' => 'Personalizado', 'huge' => 'Enorme', 'large' => 'Grande', 'small' => 'Pequeño', 'standard' => 'Estándar', 'tiny' => 'Diminuto', ], 'create' => [ 'success' => 'Marcador :name creado.', 'title' => 'Nuevo marcador', ], 'delete' => [ 'success' => 'Marcador :name eliminado.', ], 'details' => [ 'from-entity' => 'De entidad', ], 'edit' => [ 'success' => 'Marcador :name actualizado.', 'title' => 'Editar marcador :name', ], 'fields' => [ 'bg_colour' => 'Color del fondo', 'circle_radius' => 'Radio circular', 'copy_elements' => 'Copiar elementos', 'custom_icon' => 'Icono personalizado', 'custom_shape' => 'Forma personalizada', 'font_colour' => 'Color del icono', 'group' => 'Grupo de marcadores', 'icon' => 'Ícono', 'is_draggable' => 'Arrastrable', 'latitude' => 'Latitud', 'longitude' => 'Longitud', 'opacity' => 'Opacidad', 'pin_size' => 'Tamaño del marcador', 'polygon_style' => [ 'stroke' => 'Color del trazo', 'stroke-opacity' => 'Opacidad del trazo', 'stroke-width' => 'Grosor del trazo', ], 'popupless' => 'Información emergente', 'size' => 'Tamaño', ], 'helpers' => [ 'base' => 'Añade marcadores al mapa haciendo clic en cualquier lugar.', 'copy_elements' => 'Copiar grupos, capas y marcadores.', 'copy_elements_to_campaign' => 'Copiar grupos, capas y marcadores de los mapas. Los marcadores vinculados a una entidad se convertirán en marcadores normales.', 'css' => 'Define una clase CSS personalizada añadida al marcador.', 'custom_icon_v2' => 'Utiliza iconos de :fontawesome, :rpgawesome o un icono SVG personalizado. Descubre cómo en la :docs.', 'custom_radius' => 'Selecciona la opción de tamaño personalizado en el desplegable para definir un tamaño.', 'draggable' => 'Actívalo para poder mover el marcador en el modo de exploración.', 'is_popupless' => 'Desactiva la aparición del tooltip del marcador al pasar el ratón por encima.', 'label' => 'Las etiquetas se muestran como un bloque de texto en el mapa. El contenido será el nombre del marcador.', 'polygon' => [ 'edit' => 'Haz clic en el mapa para añadir esa posición a las coordenadas del polígono.', ], ], 'hints' => [ 'entry' => 'Edita el marcador para escribir una entrada personalizada sobre él.', ], 'icons' => [ 'custom' => 'Personalizado', 'entity' => 'Entidad', 'exclamation' => 'Exclamación', 'marker' => 'Marcador', 'question' => 'Interrogación', ], 'index' => [ 'title' => 'Marcadores de :name', ], 'pitches' => [ 'poly' => 'Dibuje formas poligonales personalizadas para representar fronteras y otras formas irregulares.', ], 'placeholders' => [ 'custom_icon' => 'Prueba con :example1 o :example2', 'custom_shape' => '100, 100 200, 240 340, 110', 'name' => 'Nombre del marcador', ], 'presets' => [ 'helper' => 'Haz clic en un preajuste para cargarlo o crea uno nuevo.', ], 'shapes' => [ '0' => 'Círculo', '1' => 'Cuadrado', '2' => 'Triángulo', '3' => 'Personalizada', ], 'sizes' => [ '0' => 'Minúsculo', '1' => 'Estándar', '2' => 'Pequeño', '3' => 'Grande', '4' => 'Enorme', ], 'tabs' => [ 'circle' => 'Círculo', 'label' => 'Etiqueta', 'marker' => 'Marcador', 'polygon' => 'Polígono', 'preset' => 'Preestablecido', ], ]; ================================================ FILE: lang/es/maps.php ================================================ [ 'back' => 'Volver a :name', 'edit' => 'Editar mapa', 'explore' => 'Explorar', ], 'create' => [ 'title' => 'Nuevo mapa', ], 'destroy' => [], 'edit' => [], 'errors' => [ 'chunking' => [ 'error' => 'Se ha producido un error al fragmentar el mapa. Ponte en contacto con el equipo en :discord para obtener ayuda.', 'running' => [ 'edit' => 'El mapa no se puede editar mientras se esté fragmentando.', 'explore' => 'El mapa no puede visualizarse mientras se está fragmentando.', 'time' => 'Esto puede llevar de varios minutos a varias horas, dependiendo del tamaño del mapa.', ], ], 'dashboard' => [ 'missing' => 'Este mapa necesita una imagen para que se pueda mostrar en el tablero.', ], 'explore' => [ 'missing' => 'Por favor, añade una imagen al mapa para poder explorarlo.', ], ], 'fields' => [ 'center_marker' => 'Marcador', 'center_x' => 'Posicionamiento (longitud) por defecto', 'center_y' => 'Posicionamiento (latitud) por defecto', 'centering' => 'Centrar', 'distance_measure' => 'Medida de la distancia', 'distance_name' => 'Etiqueta de unidad de distancia', 'grid' => 'Cuadrícula', 'has_clustering' => 'Marcadores de agrupamiento', 'initial_zoom' => 'Zoom inicial', 'is_real' => 'Usar OpenStreetMaps', 'max_zoom' => 'Zoom máximo', 'min_zoom' => 'Zoom mínimo', 'tabs' => [ 'coordinates' => 'Coordenadas', 'marker' => 'Marcador', ], ], 'helpers' => [ 'center' => 'Cambia estos valores para controlar en qué área está focalizado el mapa. Si lo dejas en blanco, se mostrará el centro del mapa por defecto.', 'centering' => 'Si un marcador está centrado, tendrá prioridad sobre las coordenadas por defecto.', 'chunked_zoom' => 'Agrupa automáticamente los marcadores cuando están próximos entre sí.', 'distance_measure' => 'Al darle al mapa una medida de distancia, se habilitará la herramienta de medidas en el modo de exploración.', 'distance_measure_2' => 'Para que 100 píxeles midan 1 kilómetro, introduce un valor de 0.0041.', 'grid' => 'Define un tamaño para la cuadrícula que se mostrará en el modo de exploración.', 'has_clustering' => 'Agrupa automáticamente los marcadores cuando están próximos entre sí.', 'initial_zoom' => 'El nivel inicial de zoom en el que se carga el mapa. El valor por defecto es :default, mientras que el valor máximo permitido es :max, y el mínimo, :min.', 'is_real' => 'Selecciona esta opción si quieres usar un mapa del mundo real en lugar de la imagen. Esta opción deshabilita las capas.', 'max_zoom' => 'El máximo que se puede acercar un mapa. El valor por defecto es :default, mientras que el valor máximo permitido es :max.', 'min_zoom' => 'El máximo que se puede alejar un mapa. El valor por defecto es :default, mientras que el valor máximo permitido es :min.', 'missing_image' => 'Guarda el mapa con una imagen antes de añadir capas y marcadores.', ], 'index' => [], 'lists' => [ 'empty' => 'Sube un mapa para visualizar las ubicaciones y mostrar la geografía de tu mundo.', ], 'maps' => [], 'panels' => [ 'groups' => 'Grupos', 'layers' => 'Capas', 'legend' => 'Leyenda', 'markers' => 'Marcadores', 'settings' => 'Configuración', ], 'placeholders' => [ 'center_marker' => 'Dejar en blanco para cargar el mapa en el centro', 'center_x' => 'Dejar en blanco para cargar el mapa en el centro', 'center_y' => 'Dejar en blanco para cargar el mapa en el centro', 'distance_name' => 'Km, millas, pies, hamburguesas', 'grid' => 'Distancia en píxeles entre los elementos de la cuadrícula. Déjalo en blanco para esconder la cuadrícula.', 'name' => 'Nombre del mapa', 'type' => 'Mazmorra, ciudad, galaxia...', ], 'show' => [ 'tabs' => [ 'maps' => 'Mapas', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'El mapa se está fragmentando. Este proceso puede durar de varios minutos a horas.', ], ], ]; ================================================ FILE: lang/es/misc.php ================================================ [ 'member' => 'Hazte miembro.', 'remove_v5' => 'Kanka ha sido creado por nosotros dos solos. Apoya nuestra misión y disfruta de una experiencia sin anuncios por menos del precio de un café especial.', ], ]; ================================================ FILE: lang/es/notes.php ================================================ [ 'title' => 'Nueva nota', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'Subnotas', ], 'helpers' => [], 'hints' => [], 'index' => [], 'lists' => [ 'empty' => 'Almacena ideas, referencias, reglas o información que no encaje en ningún otro lugar.', ], 'placeholders' => [ 'note' => 'Elige una nota superior', 'type' => 'Religión, Raza, Sistema politico...', ], 'show' => [], ]; ================================================ FILE: lang/es/notifications.php ================================================ [ 'discord' => [ 'invalid' => 'Tu token de Discord ha expirado. Por favor, vuelve a sincronizar tu cuenta de Discord con Kanka.', ], ], 'campaign' => [ 'application' => [ 'approved' => 'Tu solicitud para la campaña :campaign ha sido aprobada.', 'approved_message' => 'Tu inscripción a la campaña :campaign ha sido aprobada. Mensaje proporcionado: :reason', 'new' => 'Nueva solicitud para :campaign.', 'rejected' => 'Tu solicitud para la campaña :campaign ha sido rechazada. Motivo:', 'rejected_no_message' => 'Su inscripción a la campaña :campaign ha sido rechazada.', ], 'asset_export' => 'Está disponible una exportación de la campaña. El enlace estará disponible durante :time minutos.', 'boost' => [ 'add' => ':user está mejorando la campaña :campaign.', 'remove' => ':user ya no está mejorando la campaña :campaign.', 'superboost' => ':user está supermejorando la campaña :campaign.', ], 'created' => 'Has creado :campaña.', 'deleted' => 'Se ha eliminado la campaña :campaign.', 'export' => 'Ya se ha exportado tu campaña. El enlace estará disponible durante :time minutos.', 'export_error' => 'Ha ocurrido un error mientras se exportaba tu campaña. Por favor, contáctanos si el error persiste.', 'hidden' => 'La campaña :campaign está ahora oculta de la página de campañas públicas.', 'import' => [ 'csv_ready' => 'La importación CSV para :campaign está lista.', 'csv_success' => 'Se importaron exitosamente :count entidades mediante importación CSV a :campaign.', 'failed' => 'La importación de la campaña :campaign ha fallado.', 'success' => 'La campaña :campaign ha terminado de importarse.', ], 'join' => ':user se ha unido a la campaña :campaign.', 'leave' => ':user ha abandonado la campaña :campaign.', 'new_owner' => 'Has sido designado administrador de :campaign.', 'plugin' => [ 'deleted' => 'El plugin :plugin se ha eliminado del marketplace y de tu campaña :campaign.', ], 'premium' => [ 'add' => 'Se han desbloqueado funciones premium para la campaña :campaign por :user.', 'remove' => ':user ya no desbloquea funciones premium para la campaña :campaign.', ], 'removed-image' => 'La imagen o cabecera de :entity se ha eliminado debido a un reclamo de derechos de autor.', 'role' => [ 'add' => 'Te han asignado el rol :role en la campaña :campaign.', 'remove' => 'Has sido eliminado del rol :role en la campaña :campaign.', ], 'troubleshooting' => [ 'joined' => ':user, miembro del equipo de Kanka, se ha unido a la campaña :campaign.', ], ], 'clear' => [ 'action' => 'Borrar todas', 'success' => 'Se han eliminado las notificaciones.', 'title' => 'Borrar notificaciones', ], 'features' => [ 'approved' => 'Tu idea :feature ha sido aprobada.', 'finished' => '¡Tu idea :feature ya está disponible en Kanka!', 'rejected' => 'Tu idea :feature ha sido rechazada, motivo: :reason.', ], 'header' => 'Tienes :count notificaciones', 'index' => [ 'title' => 'Notificaciones', ], 'map' => [ 'chunked' => 'El mapa :name ha terminado de fragmentarse y ya puede utilizarse.', ], 'no_notifications' => 'No tienes ninguna notificación.', 'plugins' => [ 'comments' => [ 'new_comment' => ':user ha dejado un nuevo comentario en el plugin :plugin.', 'new_reply' => ':user ha respondido a tu comentario en :plugin.', ], ], 'subscriptions' => [ 'charge_fail' => 'Ha habido un error procesando tu pago. Espera un momento mientras volvemos a intentarlo. Si no se producen cambios, contacta con nosotros.', 'deleted' => 'Tu suscripción a Kanka se ha cancelado tras demasiados intentos fallidos de hacer el cobro en tu tarjeta. Dirígete a la configuración de tu suscripción e intenta actualizar tus datos de pago.', 'ended' => 'Tu suscripción a Kanka ha finalizado. Se han eliminado tus mejoras de campaña y tus roles de Discord. ¡Esperamos volver a verte pronto!', 'failed' => 'Tu suscripción a Kanka se ha cancelado tras demasiados intentos de cargar el cobro en tu tarjeta. Dirígete a los ajustes de suscripción e intenta actualizar tus detalles de pago.', 'started' => 'Tu suscripción a Kanka ha empezado.', 'trial' => 'Tu prueba gratuita de Kanka ha finalizado. ¡Esperamos que la hayas disfrutado y te volvamos a ver pronto!', ], 'unread' => 'Nueva notificación', ]; ================================================ FILE: lang/es/onboarding/attributes.php ================================================ 'Los atributos te permiten añadir pequeñas piezas de información reutilizables a :name, como edad, puntos de vida, rango de facción o cualquier estadística personalizada que lleves. Son ideales para datos que quieras consultar, ordenar o usar como plantilla en varias entidades.', 'title' => 'Almacena datos estructurados para esta entidad', ]; ================================================ FILE: lang/es/onboarding/characters.php ================================================ 'Con eso es suficiente para empezar.', 'text' => 'Concéntrate en lo básico: nombre, una breve descripción y uno o dos rasgos definitorios. Más adelante puedes añadir detalles como conexiones, atributos y retratos.', 'title' => 'Crea tu primer personaje', ]; ================================================ FILE: lang/es/onboarding/locations.php ================================================ 'Mantenlo simple por ahora.', 'text' => 'Empieza poco a poco. Nombra el lugar y añade una frase sobre lo que lo hace importante. Puedes ampliarlo con mapas, habitantes y ubicaciones anidadas cuando el mundo tome forma.', 'title' => 'Crea tu primera ubicación', ]; ================================================ FILE: lang/es/onboarding/posts.php ================================================ 'Las publicaciones te permiten separar el texto público de las notas privadas, los secretos del GM y la información de apoyo. Crea tantas publicaciones como necesites y controla quién puede ver cada una.', 'title' => 'Usa publicaciones para secretos y detalles adicionales', ]; ================================================ FILE: lang/es/onboarding/reminders.php ================================================ 'Crea recordatorios para fechas límite, fechas dentro de la historia, aniversarios o cualquier cosa relacionada con :name que no quieras olvidar. Los recordatorios que añadas aquí se mostrarán en esta página y en cualquier fecha del calendario a la que los vincules.', 'title' => 'Registra aquí los detalles sensibles al tiempo', ]; ================================================ FILE: lang/es/onboarding/tags.php ================================================ 'NPCs', ]; ================================================ FILE: lang/es/organisations.php ================================================ [ 'title' => 'Nueva organización', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_defunct' => 'Extinta', 'members' => 'Miembros', ], 'helpers' => [], 'hints' => [ 'is_defunct' => 'Esta organización ha desaparecido.', ], 'index' => [], 'lists' => [ 'empty' => 'Crea gremios, facciones o sociedades secretas para dar forma a la estructura de poder de tu mundo.', ], 'members' => [ 'actions' => [ 'add_multiple' => 'Agregar miembros', ], 'create' => [ 'helper' => 'Agregar uno o varios miembros a :name.', 'success_multiple' => '{1} Se agregó :count miembro a :name.|[2,*] Se agregaron :count miembros a :name.', ], 'destroy' => [ 'success' => 'Miembro borrado de la organización.', ], 'edit' => [ 'helper' => 'Cambia el estado de la membresía de :name.', 'title' => 'Actualizar miembro de :name', ], 'fields' => [ 'parent' => 'Superior', 'pinned' => 'Fijada', 'role' => 'Rol', 'status' => 'Estatus de miembro', ], 'helpers' => [ 'all_members' => 'Todos los personajes que son miembros de la organización y de los descendientes de esta.', 'members' => 'Todos los personajes que pertenecen a esta organización.', 'pinned' => 'Elige esta opción si el miembro debe mostrarse en la sección fijada de las entidades asociadas a esta.', ], 'pinned' => [ 'both' => 'Ambos', 'none' => 'Ninguno', ], 'placeholders' => [ 'parent' => 'Quién es el superior de este miembro', 'role' => 'Líder, Miembro, Maestro de Espías, Septón Supremo...', ], 'status' => [ 'active' => 'Miembro activo', 'inactive' => 'Miembro inactivo', 'unknown' => 'Estatus desconocido', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'Culto, banda, Rebelión, Gremio...', ], 'quests' => [], 'show' => [], ]; ================================================ FILE: lang/es/pagination.php ================================================ '« Anterior', 'next' => 'Siguiente »', ]; ================================================ FILE: lang/es/partials.php ================================================ [ 'description' => 'Han habido problemas con lo que has introducido.', 'title' => '¡Ups!', ], ]; ================================================ FILE: lang/es/passwords.php ================================================ 'La contraseña debe de tener al menos seis caracteres y que sea la misma al confirmarla.', 'reset' => '¡Contraseña restablecida!', 'sent' => 'Te hemos enviado un enlace para restablecer la contraseña.', 'token' => 'El identificador del restablecimiento de contraseña es invalido.', 'user' => 'No podemos encontrar un usuario con ese correo electrónico.', ]; ================================================ FILE: lang/es/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/es/permissions.php ================================================ [ 'delete' => 'Permiso para eliminar este elemento', 'edit' => 'Permiso para editar este elemento', 'view' => 'Permiso para ver este elemento', ], 'members' => [ 'inherited' => ':member ya puede hacerlo por formar parte del rol :role.', ], 'roles' => [ 'inherited' => 'El rol :role ya puede hacer esto en todo el módulo de :module.', ], ]; ================================================ FILE: lang/es/pins.php ================================================ 'Más información sobre los pines en nuestra documentación.', 'options' => [ 'no' => 'Sin ancla', 'yes' => 'Anclado a la página general de la entidad', ], ]; ================================================ FILE: lang/es/playstyles.php ================================================ 'Casual / Amigable para invitados', 'character-focused' => 'Centrado en personajes', 'combat-focused' => 'Centrado en combate', 'episodic-one-shot-friendly' => 'Episódico / Apto para one-shots', 'exploration-focused' => 'Centrado en exploración', 'linear-gm-led' => 'Lineal / Dirigido por el DM', 'long-term-campaign' => 'Campaña de largo plazo', 'narrative-first' => 'La narrativa primero', 'roleplay-heavy' => 'Alto en roleplay', 'roleplay-light' => 'Bajo en roleplay', 'rules-light' => 'Reglas ligeras', 'sandbox-player-driven' => 'Sandbox / Impulsado por jugadores', 'serious-immersive' => 'Serio / Inmersivo', 'story-driven' => 'Impulsado por la historia', 'tactical-crunchy' => 'Táctico / Complejo mecánicamente', ]; ================================================ FILE: lang/es/post_layouts.php ================================================ 'Organizaciones del personaje', 'connection_map' => 'Mapa de conexiones', 'helper' => 'Este post está configurado para mostrar la subpágina :subpage de la entidad.', 'location_characters' => 'Personajes de la ubicación', 'location_events' => 'Eventos de la ubicación', 'location_quests' => 'Misiones de la ubicación', 'pitch' => [ 'custom' => 'Muestra contenido de las subpáginas de esta entidad directamente en la vista general usando diseños de publicaciones. Por ejemplo, mostrar el inventario de :entity.', 'title' => 'Diseños de publicaciones avanzados', ], 'premium' => 'Algunas opciones de diseño están deshabilitadas porque requieren una campaña premium.', 'quest_elements' => 'Elementos de la misión', ]; ================================================ FILE: lang/es/posts/templates.php ================================================ [ 'set' => 'Establecer como plantilla reutilizable', 'unset' => 'Quitar como plantilla reutilizable', ], 'helper' => 'Los siguientes artículos han sido definidos como plantillas que pueden reutilizarse.', 'tab' => 'Cargar desde plantillas', ]; ================================================ FILE: lang/es/posts.php ================================================ [ 'title' => 'Nuevo post', ], 'fields' => [ 'description' => 'Descripción', 'layout' => 'Diseño de publicación', 'name' => 'Nombre', ], 'helpers' => [ 'new' => 'Añadir un nuevo post a esta entidad.', 'visibility' => 'Cambia la visibilidad del post :name.', ], 'move' => [ 'copy' => [ 'helper' => 'Mantén una copia del post en :name.', ], 'helper' => 'Mueve o copia el post :name a una entidad diferente.', 'title' => 'Mover post', ], 'permissions' => [ 'actions' => [ 'members' => 'Agregar miembros', 'roles' => 'Agregar roles', ], 'helpers' => [ 'members' => 'Agrega uno o varios miembros para que tengan permisos especiales en este post.', 'roles' => 'Agrega uno o varios roles para que tengan permisos especiales en este post.', ], ], 'placeholders' => [ 'name' => 'Nombre del post', ], 'position' => [ 'dont_change' => 'No cambiar', 'first' => 'Primero', 'last' => 'Último', ], 'remove' => [ 'title' => 'Eliminar publicación', ], 'visibility' => [ 'helper' => 'Cambia la visibilidad del post :name.', 'title' => 'Visibilidad del post', ], ]; ================================================ FILE: lang/es/presets.php ================================================ [ 'create' => 'Crear un nuevo predeterminado', ], 'create' => [ 'success' => 'Predeterminado :name creado.', 'title' => 'Nuevo predeterminado', ], 'destroy' => [ 'success' => 'Predeterminado :name eliminado.', ], 'edit' => [ 'success' => 'Predeterminado :name modificado.', 'title' => 'Editar predeterminado :name', ], 'fields' => [ 'name' => 'Nombre del predeterminado', ], 'lists' => [ 'empty' => 'Actualmente no hay predeterminados disponibles en la campaña.', ], 'placeholders' => [ 'name' => 'El nombre del predeterminado', ], ]; ================================================ FILE: lang/es/profiles.php ================================================ [ 'success' => 'Avatar actualizado.', ], 'campaign_switcher_order_by' => [], 'edit' => [ 'success' => 'Perfil actualizado', ], 'editors' => [], 'fields' => [ 'avatar' => 'Avatar', 'bio' => 'Bio', 'email' => 'Correo Electronico', 'hide_subscription' => 'Ocultar mi nombre del :hall_of_fame.', 'last_login_share' => 'Compartir la última vez que estuve en línea con otros miembros de la campaña.', 'link' => 'Enlace social', 'login_sharing' => 'Compartir la última conexión', 'name' => 'Nombre', 'new_password' => 'Contraseña nueva', 'new_password_confirmation' => 'Confirmar nueva contraseña', 'newsletter' => 'Me gustaría recibir noticias de la web por correo electrónico.', 'password' => 'Contraseña actual', 'profile-name' => 'Nombre del perfil', 'pronouns' => 'Pronombres', 'settings' => 'Ajustes', 'subscription_hiding' => 'Ocultar suscripción', 'theme' => 'Tema', ], 'helpers' => [ 'link' => 'Cambia la forma en que aparece un enlace a tu perfil social en tu :profile y en el :marketplace. Si se deja en blanco, no se mostrará ningún enlace.', 'profile-name' => 'Cambia la forma en que aparece tu nombre en tu :profile y en el :marketplace. Si se deja en blanco, se utilizará el nombre de tu cuenta en su lugar.', 'pronouns' => 'Cambia la forma en que aparecen tus pronombres en tu :profile y en el :marketplace. Si se deja en blanco, no se mostrarán pronombres.', ], 'link' => [ 'button' => 'Perfil social de :name', ], 'newsletter' => [ 'helpers' => [ 'header' => 'Suscríbete a las newsletters para saber lo que está ocurriendo en Kanka.', ], 'options' => [ 'monthly' => 'Newsletter de Kanka', ], 'title' => 'Newsletters', 'updated' => 'Actualización de las preferencias del boletín.', ], 'password' => [ 'success' => 'Contraseña actualizada', ], 'placeholders' => [ 'bio' => 'Una breve biografía tuya en tu perfil público.', 'email' => 'Tu correo electrónico', 'name' => 'Tu nombre de usuario', 'new_password' => 'Tu nueva contraseña', 'new_password_confirmation' => 'Confirma tu nueva contraseña', 'password' => 'Escribe tu contraseña actual para aplicar los cambios', ], 'sections' => [ 'dangerzone' => 'Zona de peligro', 'delete' => [ 'confirm' => 'Eliminar mi cuenta ahora', 'delete' => 'Eliminar cuenta', 'goodbye' => 'En caso afirmativo, escribe :code en la casilla de abajo.', 'helper' => 'Eliminar tu cuenta también eliminara cualquier campaña de la que seas el único miembro. Esta acción es permanente y no se puede deshacer.', 'subscribed' => 'Por favor, cancele su :subscription antes de poder eliminar su cuenta.', 'title' => 'Elimina tu cuenta', 'warning' => 'Al eliminar tu cuenta todos tus datos serán borrados. ¿Estás seguro?', ], 'password' => [ 'title' => 'Cambia tu contraseña', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'La biografía es visible en su :link.', 'profile' => 'perfil público', ], 'success' => 'Ajustes cambiados.', ], 'theme' => [ 'success' => 'Tema cambiado.', 'themes' => [ 'dark' => 'Oscuro', 'default' => 'Por defecto', 'future' => 'Futuro', 'midnight' => 'Azul medianoche', ], ], 'title' => 'Actualizar perfil', 'workflows' => [ 'created' => 'Ir a la entidad recién creada', 'default' => 'Lista de entidades', ], ]; ================================================ FILE: lang/es/quests.php ================================================ [ 'title' => 'Nueva misión', ], 'destroy' => [], 'edit' => [], 'elements' => [ 'create' => [ 'success' => 'Se ha añadido la entidad :entity a la misión.', 'title' => 'Nuevo elemento para :name', ], 'destroy' => [ 'success' => 'Se ha quitado :entidad de la misión.', ], 'edit' => [ 'success' => 'Se ha actualizado :entity en la misión.', 'title' => 'Actualizar elemento de la misión :name', ], 'fields' => [ 'entity_or_name' => 'Selecciona una entidad de la campaña o asigna un nombre a este elemento.', ], ], 'fields' => [ 'copy_elements' => 'Copiar elementos vinculados a la misión', 'date' => 'Fecha', 'element_role' => 'Rol', 'instigator' => 'Instigador', 'is_completed' => 'Completada', 'location' => 'Lugar de inicio', 'role' => 'Rol', ], 'helpers' => [ 'is_completed' => 'Selecciona esto si la misión ya se ha completado.', ], 'hints' => [], 'index' => [], 'lists' => [ 'empty' => 'Crea misiones para registrar objetivos, tramas o motivaciones de los personajes.', ], 'placeholders' => [ 'date' => 'Fecha real de la misión', 'entity' => 'Nombre de un elemento de la misión', 'location' => 'Lugar de inicio de la misión', 'role' => 'El papel que juega la entidad en la misión', 'type' => 'Historia Principal, Arco de Personaje, Misión Secundaria...', ], 'show' => [ 'actions' => [ 'add_element' => 'Añadir elemento', ], 'tabs' => [ 'elements' => 'Elementos', ], ], ]; ================================================ FILE: lang/es/races.php ================================================ [], 'create' => [ 'title' => 'Nueva raza', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'members' => 'Miembros', ], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Esta raza está extinta.', ], 'index' => [], 'lists' => [ 'empty' => 'Define las especies, culturas o pueblos que habitan tu mundo.', ], 'members' => [ 'create' => [ 'helper' => 'Agrega uno o varios personajes a :name.', 'submit' => 'Añadir miembros', 'success' => '{0} No se ha añadido ningún miembro. |{1} Se ha añadido 1 miembro. |[2,*] :se han añadido un número de miembros.', 'title' => 'Nuevos miembros', ], ], 'placeholders' => [ 'type' => 'Humano, Elfo, Troll...', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/es/redirects.php ================================================ 'Sesión expirada. Inicia sesión de nuevo.', 'unknown_entity' => 'Perdona, no sabemos qué es \':entity\'.', ]; ================================================ FILE: lang/es/referrals.php ================================================ [ 'copy' => 'Copiar', ], 'benefits' => 'Construyan mundos juntos.', 'fields' => [ 'link' => 'Tu enlace de referido:', ], 'stats' => [ 'badge' => 'Insignia: Constructor de Mundos :level', 'empty' => 'Aún nadie. Comparte tu enlace para comenzar.', 'invited' => 'Has invitado a:', 'subscribers' => 'Suscriptores referidos: :amount', 'users' => '[1] un usuario|{2,*} :amount usuarios', ], 'title' => 'Invita a tus amigos a Kanka', 'toasts' => [ 'copied' => 'Enlace copiado al portapapeles', ], ]; ================================================ FILE: lang/es/releases.php ================================================ [ 'event' => 'Evento', 'livestream' => 'Transmisión en directo', 'other' => 'Otros', 'release' => 'Lanzamiento', 'vote' => 'Votación comunitaria', ], 'index' => [ 'description' => 'Ultimas novedades de kanka.io', 'title' => 'Novedades', ], 'post' => [ 'footer' => 'Por :name el :date', ], 'show' => [ 'return' => 'Volver a novedades', 'title' => 'Novedad :name', ], ]; ================================================ FILE: lang/es/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Mundo de Tinieblas', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5ª edición', ], ]; ================================================ FILE: lang/es/search/fulltext.php ================================================ 'Búsqueda de entidades, entradas, atributos y más para el término :term.', 'title' => 'Búsqueda de texto completo', ]; ================================================ FILE: lang/es/search.php ================================================ 'Buscar en todas partes', 'lookup' => [ 'empty' => 'No hay resultados', 'hint' => 'Escriba al menos 3 letras para buscar entidades en la campaña.', 'keyboard' => 'pulse :k para buscar, :esc para descartar', 'recents' => 'Recientes', 'results' => 'Resultados', ], 'no_results' => 'Sin resultados.', 'placeholder' => 'BUSCAR', 'placeholders' => [ 'entry' => 'Buscar una entrada', ], 'preview' => [ 'links' => 'Enlaces', 'no-connections' => 'No hay relaciones ancladas que mostrar', ], 'title' => 'Buscar', ]; ================================================ FILE: lang/es/seo.php ================================================ 'Panel de :campaign', 'entity-list' => 'Explora el :module de :campaign', ]; ================================================ FILE: lang/es/settings/account.php ================================================ 'Controla tu correo electrónico, contraseña, seguridad y otros ajustes de la cuenta.', 'title' => 'Información de la cuenta', ]; ================================================ FILE: lang/es/settings/api.php ================================================ [ 'title' => 'Aplicaciones autorizadas', ], 'clients' => [ 'empty' => 'No has creado ningún cliente OAuth.', 'form' => [ 'name' => 'Nombre del cliente', 'name_helper' => 'Algo que tus usuarios reconozcan y en lo que confíen.', 'name_placeholder' => 'Nombre del cliente', 'redirect' => 'URL de redirección', 'redirect_helper' => 'La URL de callback de autorización de tu aplicación.', 'redirect_placeholder' => 'http://mi-super-app.com/callback', ], 'new' => 'Crear nuevo cliente', 'title' => 'Clientes OAuth', 'update'=> 'Actualizar cliente', ], 'fields' => [ 'client' => 'ID del cliente', 'client_name' => 'Nombre del cliente', 'scopes' => 'Alcances', 'secret' => 'Secreto', 'token_name' => 'Nombre del token', ], 'new' => [ 'copy' => 'Token copiado al portapapeles.', 'title' => 'Tu nuevo token de acceso personal:', ], 'revoke' => 'Revocar', 'revoke-confirm' => 'Confirmar revocación', 'tokens' => [ 'empty' => 'No has creado ningún token de acceso personal.', 'form' => [ 'name' => 'Nombre del token', 'name_placeholder' => 'Nombra el token', ], 'new' => 'Crear nuevo token', 'title' => 'Tokens de acceso personal', ], ]; ================================================ FILE: lang/es/settings/appearance.php ================================================ [ 'learn-more' => 'Para más información sobre esta configuración, consulta nuestra documentación.', 'save' => 'Guardar ajustes', ], 'campaign-switcher' => [ 'alphabetical' => 'Orden alfabético (A-Z)', 'date_created' => 'Fecha de creación (la más antigua primero)', 'date_joined' => 'Fecha de incorporación ( la más antigua primero)', 'r_alphabetical' => 'Orden alfabético (Z-A)', 'r_date_created' => 'Fecha de creación (la más reciente primero)', 'r_date_joined' => 'Fecha de incorporación (la más reciente primero)', ], 'dismissible' => [ 'main' => 'Controla el aspecto de Kanka. Ten en cuenta que las campañas pueden anular algunos de estos ajustes.', ], 'editors' => [ 'default' => 'Por defecto (:name)', 'helpers' => [ 'feedback' => 'Ayúdanos a mejorarlo dando tu opinión (2 min)', 'legacy' => 'El editor de texto heredado (TinyMCE) no admite menciones en dispositivos móviles, galerías de campaña u otras funciones avanzadas.', 'tiptap' => 'Este es nuestro nuevo editor de texto experimental que se está trabajando activamente y se actualiza con regularidad. Aún no contiene todas las funciones a las que podrías estar acostumbrado.', ], 'legacy' => 'Legado (:name)', 'tiptap' => 'Experimental 2026', ], 'explore' => [ 'grid' => 'Cuadrícula (por defecto)', 'table' => 'Tabla', ], 'fields' => [ 'campaign-order' => 'Orden de la lista de campañas', 'date-format' => 'Formato de fechas', 'editor' => 'Editor de texto', 'entity-explore' => 'Lista de entidades', 'mentions' => 'Menciones al editar', 'new-entity-workflow' => 'Nuevo flujo de trabajo de entidades', 'pagination' => 'Resultados por página', 'theme' => 'Tema preferido', ], 'helpers' => [ 'advanced-mentions' => 'Al editar textos, controla si las menciones se muestran como el nombre de la entidad o como la mención avanzada.', 'campaign-order' => 'Cambia el orden en el que aparecen las campañas en el selector de campañas.', 'date-format' => 'Cuando esté disponible, controla el formato en el que se mostrarán las fechas del mundo real.', 'editors' => 'Cambia entre editores de texto al crear o editar campos de texto largos.', 'entity-explore' => 'Controla la forma en que se muestran las listas de entidades en las campañas.', 'new-entity-workflow' => 'Controla la interfaz a la que se te dirige tras crear una nueva entidad.', 'overridable' => 'Cada campaña puede anular esta preferencia.', 'pagination' => 'Para las listas que abarcan varias páginas, define cuántas son visibles en cada página.', 'theme' => 'Elige el aspecto que Kanka tendrá para ti.', ], 'mentions' => [ 'advanced' => 'Mostrar menciones avanzadas :code', 'default' => 'Menciones como el nombre de la entidad', ], 'nested' => [], 'success' => 'Opciones de apariencia guardadas.', 'values' => [ 'pagination' => ':amount resultados por página', 'pagination-sub' => ':amount (disponible para suscriptores)', ], ]; ================================================ FILE: lang/es/settings/boosters.php ================================================ [ 'boost_name' => 'Mejora :name', ], 'available' => ':amount/:total potenciadores disponibles', 'benefits' => [ 'boosted' => 'Al mejorar una campaña con :one potenciador se desbloqueará el acceso al :marketplace, opciones de temas, un mayor tamaño de archivos para todos los miembros, recuperación de entidades eliminadas, y :more.', 'more' => 'más funciones sorprendentes', 'superboosted' => 'Al potenciar una campaña con :amount potenciadores se desbloquearán todas las ventajas de una campaña potenciada, así como una galería de la campaña, los registros completos de los cambios realizados en las entidades y :more.', ], 'boost' => [ 'actions' => [ 'confirm' => '¡Mejórala!', 'remove' => 'Deja de potenciar :campaign', 'subscribe' => 'Suscríbete a Kanka', 'upgrade' => 'Actualiza tu suscripción', ], 'confirm' => '¡Qué emoción! Estás a punto de potenciar :campaign. Esto asignará uno (:cost) de tus potenciadores de campaña disponibles.', 'duration' => 'Los potenciadores asignados permanecen asignados hasta que los elimines manualmente o cuando tu suscripción finalice.', 'errors' => [ 'boosted' => 'Oh oh, ¡Parece que :campaign ya está impulsada!', 'out-of-boosters' => '¡Oh, no! No tienes suficientes potenciadores disponibles. Tienes :available y necesitas :cost. Deja de impulsar otras campañas, o :upgrade.', ], 'pitch' => 'Hazte suscriptor para desbloquear potenciadores de campaña.', 'success' => 'La campaña :campaign ahora está potenciada. ¡Disfruta de todas las nuevas e increíbles funciones!', 'title' => 'Potenciar :campaign', 'upgrade' => 'actualiza tu suscripción', ], 'campaign' => [ 'boosted' => 'Potenciado por :user desde :time', 'premium' => 'Premium gracias a :user desde :time', 'standard' => 'Estándar', 'superboosted' => 'Superpotenciado por :user desde :time', 'unboosted' => 'Sin potenciar', ], 'intro' => [ 'anyone' => 'No estás limitado a impulsar campañas que hayas creado. Puedes impulsar cualquier campaña en la que participes o que puedas ver. Esto incluye campañas en las que seas jugador o :public que disfrutes.', 'data' => 'Cuando una campaña deja de estar potenciada, se elimina el acceso a las funciones potenciadas. Sin embargo, no se elimina nada del contenido, por lo que al volver a impulsar la campaña en el futuro se recupera el acceso a este.', 'first' => 'Las funciones avanzadas se desbloquean asignando tus potenciadores para potenciar o superpotenciar una campaña. La cantidad de potenciadores que tienes viene determinada por tu :subscription. Este número está a tu disposición en todo momento mientras seas suscriptor. Al potenciar una campaña le asignarás uno de tus potenciadores, mientras que al superpotenciar una campaña le asignarás tres.', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Recupera una entidad previamente eliminada durante un máximo de :amount días', 'customisable' => 'Personalización total del aspecto de una campaña', 'icons' => 'Acceso a miles de hermosos iconos para mapas y líneas de tiempo', 'title' => 'Las campañas potenciadas obtienen', 'upload' => 'Mayor capacidad de carga de archivos para todos los miembros de la campaña', ], 'description' => 'Asigna potenciadores a las campañas y ayuda a desbloquear funciones increíbles para todos los participantes. ¿No te impresionan las campañas potenciadas? ¡Tenemos campañas superpotenciadas para ti!', 'more' => 'Consulta la lista completa de ventajas en nuestra página :boosters.', 'title' => 'Lleva una campaña al siguiente nivel con personalización y ventajas para todos sus miembros', ], 'ready' => [ 'available' => 'Tus potenciadores de campaña disponibles.', 'pricing' => 'Todos nuestros niveles de suscripción incluyen al menos un potenciador de campaña y comienzan a partir de :amount al mes.', 'pricing-amount' => ':currency:amount', 'title' => 'Potenciar una campaña', ], 'superboost'=> [ 'actions' => [ 'confirm' => '¡Superpotenciala!', 'instead' => '¡Superpotenciala por :count!', 'remove' => 'Dejar de superpotenciar :campaign', ], 'confirm' => '¡Qué emocionante! Estás a punto de superpotenciar :campaign. Esto asignará tres (:cost) de tus potenciadores de campaña disponibles.', 'errors' => [ 'boosted' => 'Oh oh, ¡Parece que :campaign ya está superpotenciada!', ], 'success' => 'La campaña :campaign está ahora superpotenciada. ¡Disfruta de todas las nuevas e increíbles funciones!', 'title' => 'Superpotenciar :campaign', 'upgrade' => '¿Listo para la experiencia Kanka definitiva? Superpotenciar :campaign asignará :cost potenciadores de campaña adicionales.', ], 'title' => 'Potenciadores de campaña', 'unboost' => [ 'confirm' => 'Sí, estoy segur@', 'status' => [ 'boosting' => 'potenciando', 'superboosting' => 'superpotenciando', ], 'success' => 'La campaña :campaign ya no está potenciada y tus potenciadores vuelven a estar disponibles.', 'title' => 'De-potenciando una campaña', 'warning' => '¿Estás seguro de que quieres parar de :action :campaign? Esto liberará los potenciadores asignados y ocultará todo el contenido y las características relacionadas con las mejoras hasta que la campaña sea potenciada de nuevo.', ], ]; ================================================ FILE: lang/es/settings/premium.php ================================================ [ 'remove' => 'Remover premium', 'unlock' => 'Convertirse en Premium', ], 'create' => [ 'actions' => [ 'confirm' => '¡Convertirse en premium!', ], 'confirm' => '¡Qué emocionante! Estás a punto de desbloquear funciones premium para :campaign. Esto utilizará una de tus campañas premium disponibles.', 'duration' => 'Las campañas Premium permanecen así hasta que lo retires manualmente, o cuando finalice tu suscripción.', 'success' => 'La campaña :campaign ahora es premium. ¡Disfruta de todas las nuevas e increíbles funciones!', ], 'exceptions' => [ 'already' => 'Ya se han desbloqueado las funciones Premium para esta campaña.', 'out-of-stock' => 'No tienes suficientes campañas premium disponibles para activar esta campaña. Elimina el estado premium de otra campaña o :upgrade.', ], 'pitch' => [ 'description' => 'Hazte premium en las campañas y ayuda a desbloquear funciones increíbles para todos los participantes.', 'title' => 'Las campañas premium obtienen', ], 'ready' => [ 'available' => 'Sus campañas premium disponibles.', 'pricing' => 'Todos nuestros niveles de suscripción incluyen al menos una campaña premium y comienzan desde :amount al mes.', 'pricing-amount' => ':currency:amount', 'title' => 'Vuélvete premium', ], 'remove' => [ 'confirm' => 'Sí, estoy seguro', 'cooldown' => 'Las características premium de :campaign pueden ser removidas después de :date.', 'success' => 'Se han removido las características premium de la campaña :campaign. Ahora puedes desbloquear funciones premium en otra campaña.', 'title' => 'Removiendo características premium', 'warning' => '¿Estás seguro de que quieres eliminar las características premium de :campaign? Esto te permitirá desbloquear otra campaña y ocultará todo el contenido y las características premium relacionadas con las características hasta que se vuelva a activar el estado premium de la campaña.', ], ]; ================================================ FILE: lang/es/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Deshabilitar la autenticación de dos factores', 'disable-confirm' => 'Haz clic de nuevo para confirmar', 'finish' => 'Finalizar la configuración e iniciar sesión', ], 'activation_helper' => 'Para terminar de configurar la autenticación de dos factores de tu cuenta, sigue estas instrucciones.', 'disable' => [ 'helper' => 'Si quieres desactivar la autenticación de dos factores, haz clic en el botón de abajo. Ten en cuenta que esto dejará tu cuenta vulnerable a cualquiera que conozca tus datos de acceso.', 'title' => 'Deshabilitar la autenticación de dos factores', ], 'enable_instructions' => 'Para iniciar el proceso de activación, genera tu código QR de autenticación y escanéalo en la aplicación Google Authenticator (:ios, :android) u otra aplicación de autenticación similar.', 'enabled' => 'La autenticación de dos factores está actualmente activada en su cuenta.', 'error_enable' => 'Código inválido, inténtalo de nuevo', 'fields' => [ 'otp' => 'Introduce la contraseña de un solo uso (OTP) proporcionada por la aplicación de autenticación.', 'qrcode' => 'Escanea el siguiente código QR con tu aplicación de autenticación para generar una contraseña de un solo uso (OTP).', ], 'generate_qr' => 'Generar código QR', 'helper' => 'La autenticación de dos factores (2FA) aumenta la seguridad del acceso al requerir dos métodos (también denominados factores) para verificar su identidad en cada inicio de sesión.', 'learn_more' => 'Más información sobre la autenticación de dos factores.', 'social' => 'La autenticación de dos factores de Kanka sólo está habilitada para los usuarios que inician sesión utilizando su correo electrónico y contraseña. Cambia tu método de inicio de sesión en la configuración de tu cuenta antes de poder activar esta opción.', 'success_disable' => 'Autenticación de dos factores desactivada correctamente.', 'success_enable' => 'La autenticación de dos factores se ha activado correctamente. Por favor, inicia sesión de nuevo para finalizar la configuración.', 'success_key' => 'Tu código QR seguro se ha generado correctamente. Por favor, completa la configuración para activar la autenticación de dos factores.', 'title' => 'Autenticación de dos factores', ], 'actions' => [ 'social' => 'Cambiar a inicio de sesión en Kanka', 'update_email' => 'Actualizar email', 'update_password' => 'Actualizar contraseña', ], 'email' => 'Cambiar email', 'email_success' => 'Email actualizado.', 'password' => 'Cambiar contraseña', 'password_success' => 'Contraseña actualizada.', 'social' => [ 'error' => 'Ya estás utilizando el inicio de sesión de Kanka con esta cuenta.', 'helper' => 'Tu cuenta está vinculada con :provider. Puedes dejar de usarla y cambiar al inicio de sesión estándar de Kanka escribiendo una contraseña.', 'success' => 'Tu cuenta ahora usa el inicio de sesión de Kanka.', 'title' => 'De social a Kanka', ], 'title' => 'Cuenta', ], 'api' => [ 'helper' => 'Bienvenido a las APIs de Kanka. Genera un Token de Acceso Personal para usar en tus llamadas a la API para obtener información sobre las campañas a las que perteneces.', 'link' => 'Leer la documentación de la API', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Conectar', 'remove' => 'Eliminar', ], 'benefits' => 'Kanka ofrece algunas integraciones con servicios de terceros. Hay más integraciones planeadas para el futuro.', 'discord' => [ 'confirm' => '¿Estás seguro de que quieres desconectar tu cuenta de Discord? Esto eliminará todos los roles con los que hayas estado sincronizado.', 'errors' => [ 'add' => 'Ha ocurrido un error tratando de vincular tu cuenta de Discord con Kanka. Por favor, inténtalo de nuevo.', ], 'success' => [ 'add' => 'Se ha vinculado tu cuenta de Discord.', 'remove' => 'Se ha desvinculado tu cuenta de Discord.', ], 'text' => 'Accede a los roles de suscripción automáticamente.', 'unlock' => 'Desbloquear roles de Discord', ], 'title' => 'Integración de aplicaciones', ], 'billing' => [ 'placeholder' => 'Si necesitas añadir información adicional de contacto o fiscal a tus recibos (dirección comercial, número de IVA, etc.), introdúcela a continuación y aparecerá en todos tus recibos.', 'save' => 'Guardar los datos de facturación', 'title' => 'Datos de facturación', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'La campaña :name ya está mejorada.', 'exhausted_boosts' => 'Te has quedado sin mejoras. Elimina tu mejora de una campaña antes de dársela a otra.', 'exhausted_superboosts' => 'Te has quedado sin mejoras. Necesitas 3 mejoras para supermejorar una campaña.', ], ], 'countries' => [ 'austria' => 'Austria', 'belgium' => 'Bégica', 'france' => 'Francia', 'germany' => 'Alemania', 'italy' => 'Italia', 'netherlands' => 'Holanda', 'spain' => 'España', ], 'invoices' => [], 'layout' => [ 'title' => 'Diseño', ], 'marketplace' => [], 'menu' => [ 'account' => 'Cuenta', 'api' => 'API', 'appearance' => 'Apariencia', 'apps' => 'Aplicaciones', 'boosters' => 'Potenciadores', 'notifications' => 'Notificaciones', 'other' => 'Otros', 'patreon' => 'Patreon', 'payment_options' => 'Opciones de pago', 'personal_settings' => 'Ajustes personales', 'premium' => 'Campañas Premium', 'profile' => 'Perfil', 'settings' => 'Configuración', 'subscription' => 'Suscripción', 'subscription_status' => 'Estado de la suscripción', ], 'patreon' => [ 'deprecated' => 'Funcionalidad obosleta. Si deseas apoyar a Kanka, puedes hacerlo mediante una :subscription. La vinculación con Patreon aún sigue activa para nuestros Patrons que vincularon sus cuentas antes de la mudanza de Patreon.', 'pledge' => 'Pledge :name', 'remove' => [ 'button' => 'Desvincular mi cuenta de Patreon', 'success' => 'Tu cuenta de Patreon se ha desvinculado.', 'text' => 'Desvincular tu cuenta de Patreon de Kanka eliminará tus bonus, tu nombre en el salón de la fama, tus mejoras y otras funcionalidades vinculadas. Sin embargo, tu contenido mejorado no se perderá: si vuelves a suscribirte, volverás a tener acceso a esos datos, incluyendo la posibilidad de volver a mejorar dicha campaña.', 'title' => 'Desvincular mi cuenta de Patreon de Kanka', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Actualizar perfil', ], 'avatar' => 'Foto de perfil', 'success' => 'Perfil actualizado.', 'title' => 'Perfil personal', ], 'referrals' => [ 'title' => 'Referidos', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Cancelar suscripción', 'subscribe' => 'Suscribirse', 'update_currency' => 'Guardar moneda preferida', ], 'billing' => [ 'helper' => 'Tu información de pago se procesa y se guarda de forma segura mediante :stripe. Este método de pago se usará para todas tus suscripciones.', 'saved' => 'Método de pago guardado', ], 'cancel' => [ 'grace' => [ 'text' => 'Tu suscripción ya está programada para finalizar en :date, después de lo cual tus campañas premium volverán a ser campañas estándar y se desactivarán otros beneficios relacionados con el apoyo a Kanka.', 'title' => 'Período de gracia', ], 'options' => [ 'competitor' => 'Cambio a un competidor', 'financial' => 'La suscripción es demasiado cara', 'missing_features' => 'Faltan características', 'not_for' => 'La suscripción no es para mí', 'not_playing' => 'Ya no juego o la campaña está en pausa', 'not_using' => 'Actualmente no utilizo Kanka', 'other' => 'Otro', 'testing' => 'Solo probando Kanka', ], 'text' => '¡Lamentamos verte marchar! Al cancelar tu suscripción, esta seguirá activa hasta el nuevo ciclo de facturación, tras lo cual perderás tus mejoras de campaña y otros beneficios relacionados. No tengas miedo de informarnos sobre cómo podemos mejorar o qué te ha llevado a tomar esta decisión.', 'title' => 'Cancelar suscripción', ], 'cancelled' => 'Se ha cancelado tu suscripción. Puedes renovarla una vez el período de la suscripción actual termine.', 'change' => [ 'text' => [ 'downgrade_monthly' => 'Estás cambiando al plan :tier por :downgrade; después se te facturará mensualmente por :amount.', 'downgrade_yearly' => 'Estás cambiando al plan :tier por :downgrade; después se te facturará anualmente por :amount.', 'monthly' => 'Estás suscribiéndote al nivel :tier, que cuesta :amount mensuales.', 'upgrade_monthly' => 'Estás actualizando al nivel :tier por :upgrade, a partir de ahora se facturará mensualmente por :amount.', 'upgrade_paypal' => 'Estás actualizando al nivel :tier por :upgrade hasta :date.', 'upgrade_yearly' => 'Estás actualizando al nivel :tier por :upgrade, a partir de ahora, se facturará anualmente por :amount.', 'yearly' => 'Estás suscribiéndote al nivel :tier, que cuesta :amount anuales.', ], 'title' => 'Cambiar nivel de suscripción', ], 'coupon' => [ 'check' => 'Ver código promocional', 'invalid' => 'Código promocional no válido.', 'label' => 'Código promocional', 'percent_off' => '¡Tendrás un descuento del :percent% en tu primera suscripción anual!', ], 'currencies' => [ 'brl' => 'BRL', 'eur' => 'Euros', 'usd' => 'Dólares estadounidenses', ], 'currency' => [ 'title' => 'Cambia la moneda de facturación', ], 'errors' => [ 'callback' => 'Nuestro proveedor de pagos nos ha informado de un error. Por favor, vuelve a intentarlo o infórmanos si el problema persiste.', 'failed' => 'Estamos experimentando problemas con nuestro sistema de facturación. Por favor, ponte en contacto con nosotros en :email para obtener ayuda.', 'subscribed' => 'No se ha podido procesar tu suscripción. Stripe nos ha dado este mensaje:', ], 'fields' => [ 'active_since' => 'Activa desde', 'active_until' => 'Activa hasta', 'billing' => 'Cobro', 'currency' => 'Moneda de cobro', 'payment_method' => 'Método de pago', 'plan' => 'Plan actual', 'reason' => 'Razón', 'reset' => 'Restablecer los datos de facturación', 'reset_billing' => 'Entiendo que al cambiar de divisa se perderá mi historial de facturación y tendré que volver a introducir mi método de pago.', ], 'helpers' => [ 'alternatives' => 'Paga por tu suscripción usando :method. Este método de pago no se renovará automáticamente al final de tu suscripción. :method solo está disponible en euros.', 'alternatives-2' => 'Paga tu suscripción utilizando :method. Este pago es único y no se renueva automáticamente al final de la suscripción.', 'alternatives_warning' => 'No se puede mejorar la suscripción usando este método. Por favor, crea una nueva suscripción cuando la actual termine.', 'alternatives_yearly' => 'Debido a las restricciones de los pagos recurrentes, :method solo está disponible para las suscripciones anuales.', 'currency_block' => 'No es posible cambiar de divisa mientras tengas una suscripción a Kanka activa, puedes cambiar de divisa una vez que finalice tu suscripción actual.', 'currency_reset' => 'Al cambiar la divisa elegida, se borrará tu historial de facturación y tendrás que volver a introducir un método de pago.', 'paypal_v3' => 'Paga tu suscripción anual de forma segura con PayPal.', 'stripe' => 'Tu información de facturación se procesa y almacena de forma segura a través de :stripe.', ], 'manage_subscription' => 'Gestionar suscripción', 'payment_method' => [ 'actions' => [ 'add' => 'Añadir', 'add_new' => 'Añadir nuevo método de pago', 'change' => 'Cambiar método de pago', 'save' => 'Guardar método de pago', 'show_alternatives' => 'Métodos de pago alternativos', ], 'add_one' => 'Aún no tienes ningún método de pago guardado.', 'alternatives' => 'Puedes suscribirte usando estos métodos de pago alternativos. Esto hará un solo cobro en tu cuenta y no se renovará automáticamente cada mes.', 'card' => 'Tarjeta', 'card_name' => 'Nombre en la tarjeta', 'country' => 'País de residencia', 'ending' => 'Termina en', 'helper' => 'Se usará esta tarjeta para todas tus suscripciones.', 'new_card' => 'Añadir nuevo método de pago', 'saved' => ':brand que termina en :last4', ], 'periods' => [ 'monthly' => 'Mensual', 'yearly' => 'Anual', ], 'placeholders' => [ 'downgrade_reason' => 'Si lo deseas, indícanos el motivo de la reducción de categoría de tu suscripción.', 'reason' => 'Opcionalmente, puedes contarnos por qué ya no apoyas a Kanka. ¿Faltaba algo? ¿Cambió tu situación financiera?', ], 'plans' => [ 'cost_monthly' => ':amount :currency mensuales', 'cost_yearly' => ':amount :currency anuales', ], 'sub_status' => 'Información sobre la suscripción', 'subscription' => [ 'actions' => [ 'cancel' => 'Cancelar suscripción', 'downgrading' => 'Contáctanos para bajar de nivel', 'rollback' => 'Cambiar a Kobold', 'subscribe' => 'Cambiar a :tier al mes', 'subscribe_annual' => 'Cambiar a :tier anualmente', ], ], 'success' => [ 'alternative' => 'Se ha registrado tu pago. Recibirás una notificación en cuanto terminemos de procesarlo y se active tu suscripción.', 'callback' => 'Tu suscripción ha tenido éxito. Tu cuenta será actualizada en cuanto nuestro proveedor de pagos nos informe del cambio (puede llevar algunos minutos).', 'currency' => 'Se ha actualizado tu moneda preferida.', 'subscribed' => 'Tu suscripción ha tenido éxito. ¡No te olvides de suscribirte a la newsletter de votaciones comunitarias para enterarte cuando se abra una votación! Puedes cambiar tu configuración de newsletters en tu perfil.', ], 'tiers' => 'Niveles de suscripción', 'trial_period' => 'Las suscripciones anuales tienen un período de cancelación de 14 días. Contáctanos en :email si quieres cancelar tu suscripción anual y recuperar el dinero.', 'upgrade_downgrade' => [ 'button' => 'Información acerca de subir o bajar de nivel', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Tus bonus permanecen activos hasta el final del período de facturación.', 'boosts' => 'Lo mismo ocurre con tus campañas mejoradas. Las funcionalidades mejoradas se vuelven invisibles pero no se eliminan cuando dejas de mejorar la campaña.', 'kobold' => 'Para cancelar la suscripción, cambia al nivel de Kobold.', 'premium' => 'Lo mismo ocurre con tus campañas premium. Las funciones premium se vuelven invisibles, pero no se eliminan cuando una campaña deja de ser premium.', ], 'title' => 'Cancelar tu suscripción', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Tu nivel actual estará activo hasta el final de tu ciclo de pago actual, tras el cual se bajará tu suscripción al nuevo nivel.', ], 'provide_reason' => 'Si puedes, por favor, comparte con nosotros por qué estás bajando de categoría tu suscripción.', 'title' => 'Bajar de nivel', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Se cobrará en tu método de pago inmediatamente y tendrás acceso al nuevo nivel.', 'prorate' => 'Al subir de nivel de Owlbear a Elemental, solo se te cobrará la diferencia entre los dos niveles.', ], 'title' => 'Subir de nivel', ], ], 'warnings' => [ 'incomplete' => 'No hemos podido hacer el cobro en tu tarjeta de crédito. Por favor, actualiza la información de la tarjeta y volveremos a intentarlo en los próximos días. Si vuelve a fallar, tu suscripción será cancelada.', 'patreon' => 'Tu cuenta se encuentra vinculada con Patreon. Desvincúlala en la configuración de :patreon antes de cambiarla por una suscripción de Kanka.', ], ], ]; ================================================ FILE: lang/es/sidebar.php ================================================ [ 'count' => 'Miembro de :member', 'created_campaigns' => 'Tus campañas', 'follow_more' => 'Encontrar campañas', 'followed_campaigns'=> 'Campañas seguidas', 'new_campaign' => 'Nueva campaña', 'public_campaigns' => 'Campañas públicas', 'reorder' => 'Reordenar', 'updated' => 'Actualizada', ], 'dashboard' => 'Tablero', 'entity-creator' => 'Creador rápido', 'gallery' => 'Galería', 'game' => 'Juego', 'other' => 'Otros', 'recent' => 'Cambios recientes', 'relations' => 'Relaciones', 'settings' => 'Configuración', 'time' => 'Tiempo', 'world' => 'Mundo', ]; ================================================ FILE: lang/es/spotlights.php ================================================ [ 'actions' => [ 'retract' => 'Retirar solicitud', ], 'description' => 'Tu solicitud ha sido enviada y ahora está en revisión. Recibirás una notificación cuando sea aprobada o rechazada.', 'title' => 'Solicitud enviada', ], 'apply' => [ 'errors' => [ 'empty' => 'La pregunta :field necesita más contenido', ], ], 'approved' => [ 'description' => '¡Felicidades! Tu solicitud ha sido aprobada y ahora aparece en la página de :spotlight.', 'title' => 'Solicitud aprobada', ], 'faq' => [ 'finisher' => 'Enviar una solicitud no garantiza la selección. Leemos cada solicitud, pero no podemos destacar todas.', 'how' => [ 'a' => [ 'end' => 'No se basa en número de seguidores. No en popularidad. No en estado de membresía.', 'lead' => 'Seleccionamos de 1 a 3 campañas por mes.', 'req1' => 'Identidad y temas claros', 'req2' => 'Construcción de mundo bien desarrollada', 'req3' => 'Historias o enfoques interesantes', 'requirements' => 'La selección es editorial, no competitiva. Buscamos:', ], 'q' => '¿Cómo se seleccionan las campañas?', ], 'reapply' => [ 'a' => 'Sí. Si tu campaña no es seleccionada, puedes volver a postularte más adelante, especialmente si tu mundo ha evolucionado.', 'q' => '¿Puedo postularme más de una vez?', ], 'selected' => [ 'a' => [ 'end' => 'Se te notificará antes de la publicación.', 'lead' => 'Si es seleccionada:', 'req1' => 'Tu campaña recibe el logro de Campaña Destacada', 'req2' => 'Publicamos un artículo en el :blog y en la :showcase', 'req3' => 'Podríamos editar ligeramente tus respuestas para mayor claridad', ], 'q' => '¿Qué sucede si mi campaña es seleccionada?', ], 'what' => [ 'a' => 'El Spotlight resalta campañas excepcionales creadas con Kanka. Las campañas seleccionadas aparecen en los destacados de Kanka y en una breve entrevista publicada en el blog.', 'q' => '¿Qué es el Spotlight?', ], 'who' => [ 'a' => [ 'end' => 'Sin tamaño mínimo. Sin restricción de sistema.', 'lead' => 'Cualquier campaña pública en Kanka puede postularse', 'req1' => 'Ser públicamente accesible', 'req2' => 'Mostrar uso activo (contenido, historial o jugadores)', 'req3' => 'Representar el tipo de mundos de los que otros puedan aprender', 'requirements' => 'Tu campaña debe:', ], 'q' => '¿Quién puede postularse?', ], ], 'form' => [ 'actions' => [ 'apply' => 'Enviar solicitud', 'retract' => 'Retirar solicitud', 'save' => 'Guardar borrador', ], 'draft' => 'Este es un borrador de tu solicitud. Puedes guardarlo y volver más tarde.', 'not-public' => 'Esta campaña no es públicamente visible y no puede postularse al Spotlight.', 'preset' => 'Cuéntanos un poco sobre :campaign y por qué crees que merece ser destacada. Puedes guardar y volver a estas preguntas más tarde.', 'required' => 'Este campo es obligatorio.', 'title' => 'Formulario de solicitud para destacados', ], 'overview' => [ 'cta' => 'Postularse a destacados con :name', 'not-public' => ':name no es una campaña públicamente visible.', 'showcase' => 'Ver Destacados', ], 'placeholders' => [ 'inspiration' => 'Libros, juegos, historia, música, vibras', 'kanka' => 'Cuéntanos por qué Kanka terminó siendo la herramienta adecuada para tu mundo', 'proud' => 'Puede ser el lore, los jugadores, la longevidad, el estado actual', 'stories' => 'Tragedia, heroísmo, política, familia elegida, caos absoluto', 'time' => '¿Meses, años, décadas, a lo largo de múltiples generaciones?', 'world' => 'Temas, emociones, conflictos (el gancho)', ], 'questions' => [ 'inspiration' => '¿Qué inspira este mundo?', 'kanka' => '¿Por qué diriges partidas en Kanka?', 'proud' => '¿De qué estás más orgulloso?', 'share' => 'Permitir al equipo de Kanka usar tu respuesta en su material de marketing.', 'stories' => '¿Qué tipo de historias surgen en la mesa?', 'time' => '¿Cuánto tiempo llevas construyendo este mundo?', 'world' => '¿De qué trata realmente este mundo?', ], 'rejected' => [ 'description' => 'Tu solicitud ha sido rechazada. Por favor, inténtalo nuevamente más adelante.', 'title' => 'Solicitud rechazada', ], 'retract' => [ 'success' => 'Tu solicitud ha sido retirada correctamente. Ahora puedes editarla nuevamente.', ], 'rules' => 'Seleccionamos de 1 a 3 campañas cada mes para destacarlas en la :showcase de Kanka. La selección no está garantizada. Las campañas destacadas reciben un logro permanente y una entrevista publicada.', 'started' => 'Para comenzar, selecciona una de tus campañas.', 'title' => 'Postularse a Destacados', ]; ================================================ FILE: lang/es/starter.php ================================================ [ 'name' => 'Mundo de :user', ], 'character1' => [ 'age' => '[20s/30s/40s]', 'background' => [ 'cur' => 'Actualmente [ocupación/rol]', 'loc' => 'Creció en [ciudad natal/región]', 'seeking' => 'Buscando [objetivo/motivación]', 'title' => 'Trasfondo', ], 'description' => [ 'intro' => '[Una breve introducción a tu personaje: quién es, de dónde viene y qué quiere.]', 'template' => 'Este es un personaje de plantilla que puedes personalizar. Reemplaza los detalles de ejemplo a continuación con la información de tu propio personaje. Siempre puedes agregar más campos después.', 'tip' => 'Consejo: Comienza solo con un nombre y una descripción de una oración. Puedes ampliar los detalles conforme se desarrolla tu mundo.', ], 'name' => '[Nombre de tu personaje]', 'personality' => [ 'trait1' => [ 'name' => 'Rasgo 1', 'value' => '[Valiente/Cauteloso/Ambicioso]', ], 'trait2' => [ 'name' => 'Rasgo 2', 'value' => '[Leal/Independiente/Astuto]', ], 'trait3' => [ 'name' => 'Rasgo 3', 'value' => '[Optimista/Cínico/Pragmático]', ], ], 'physical' => [ 'build' => [ 'name' => 'Complexión', 'value' => '[Delgado/Promedio/Musculoso]', ], 'features' => [ 'name' => 'Rasgos notables', 'value' => '[Cicatrices, tatuajes, ropa distintiva]', ], ], ], 'character2' => [ 'description' => [ 'first' => 'Un personaje secundario que ayuda o viaja con :mention. Personaliza estos detalles para que se adapten a tu historia.', 'second'=> 'Consejo: Los personajes secundarios no necesitan tantos detalles como los protagonistas. Enfócate en lo que los hace útiles o interesantes para tu historia.', ], 'name' => '[Nombre del personaje aliado]', 'relation' => '[Amigo/Mentor/Rival]', 'skills' => [ 'first' => '[Habilidad 1: Combate/Magia/Sanación/Artesanía]', 'second'=> '[Habilidad 2: Social/Conocimiento/Técnica]', 'third' => '[Habilidad 3: Talento único o especialidad]', 'title' => 'Habilidades y capacidades', ], ], 'city' => [ 'description' => 'El corazón palpitante del reino, donde comerciantes, nobles y gente común se mezclan en bulliciosos mercados y grandes plazas. Los viejos muros de la ciudad aún se mantienen en pie, aunque la ciudad hace tiempo que creció más allá de ellos.', 'districts' => [ 'first' => 'Barrio noble: Casas señoriales y jardines', 'fourth'=> 'Zona portuaria: Puerto fluvial, almacenes', 'second'=> 'Distrito del mercado: Comercio, artesanía, tabernas', 'third' => 'Ciudad vieja: Ciudad amurallada original', 'title' => 'Distritos', ], 'locations' => [ 'first' => 'El Palacio Real (centro del Barrio Noble)', 'second'=> 'El Gran Bazar (Distrito del mercado)', 'third' => 'Posada La Espada Oxidada (punto de encuentro popular de aventureros)', 'title' => 'Lugares notables', ], 'name' => '[Nombre de tu ciudad capital]', 'type' => 'Capital', ], 'item1' => [], 'kingdom' => [ 'description' => 'Un reino próspero conocido por sus fértiles tierras de cultivo y bosques ancestrales. La familia real ha gobernado durante tres generaciones, manteniendo la paz mediante la diplomacia y el comercio.', 'features' => [ 'capital' => [ 'name' => 'Capital', ], 'exp' => [ 'name' => 'Exportación principal', 'value' => 'Grano, madera', ], 'gov' => [ 'name' => 'Gobierno', 'value' => 'Monarquía hereditaria', ], 'pop' => [ 'name' => 'Población', 'value' => '~50\'000', ], 'title' => 'Características notables', ], 'name' => '[Nombre de tu reino]', 'recent' => [ 'first' => 'Mayor actividad de bandidos en los caminos del este', 'second'=> 'Cosecha fallida en las provincias del sur', 'title' => 'Eventos recientes', ], 'type' => 'Reino', ], 'kingdom1' => [], 'kingdom2' => [], 'name' => ':name (ejemplo)', 'note1' => [], ]; ================================================ FILE: lang/es/subscription.php ================================================ [ 'main' => 'Suscríbete a Kanka para subir más imágenes, disfrutar de una experiencia sin anuncios, :boosters y mucho :more. Utilizamos :stripe para gestionar toda la facturación, sin que la información de la tarjeta de crédito se almacene o transite por nuestros servidores.', 'more' => 'más características sorprendentes', ], 'errors' => [ 'grace' => 'Tu suscripción actual finaliza en :date, a partir de entonces podrás volver a suscribirte.', 'invalid_card_country' => [ 'brl' => 'Lo sentimos, pero actualmente sólo aceptamos pagos en BRL para clientes con tarjetas de crédito brasileñas. Si crees que se trata de un error, ponte en contacto con nosotros en :email.', ], 'invalid_currency' => 'Anteriormente tenías una suscripción en :old, lo que te impide tener una nueva suscripción en :new. Por favor, cambia tu divisa a :old, o contacta con nosotros en :email si deseas cambiar de divisa.', ], ]; ================================================ FILE: lang/es/subscriptions/cancellation.php ================================================ '¡Lamentamos verte partir! Tu suscripción seguirá activa hasta :date, después de lo cual tus campañas premium volverán al modo estándar y estos beneficios se desactivarán.', 'loss' => [ 'ads' => [ 'title' => 'Experiencia sin anuncios para ti y tus jugadores', ], 'discord' => [ 'title' => 'Rol ":role" en nuestra comunidad de Discord', ], 'downgrade' => 'Puedes degradar tu suscripción en lugar de cancelarla para conservar la mayoría de tus increíbles beneficios.', 'premium' => [ 'players' => '{1}:count jugador perderá acceso a funciones premium |[2,*]:count jugadores perderán acceso a funciones premium', 'plugins' => '{1}Acceso a :count plugin instalado |[2,*]Acceso a :count plugins instalados', 'storage' => 'Tu :current', 'title' => '{0}Estado premium en ":campaign"|{1}Estado premium en ":campaign" y :count otra campaña|[2,*]Estado premium en ":campaign" y :count otras campañas', ], 'roadmap' => 'Mejoramos Kanka constantemente con ideas enviadas por los usuarios a través de nuestro :roadmap; quizá esa función que falta esté a la vuelta de la esquina.', 'title' => 'Antes de cancelar, esto es lo que perderás:', ], 'pause' => [ 'button' => 'Pausar suscripción por 1–3 meses', 'helper' => 'Mantén todo. Sin cargos. Reanuda en cualquier momento.', ], ]; ================================================ FILE: lang/es/subscriptions/cancelled.php ================================================ [ 'adfree' => 'Una experiencia sin anuncios', 'discord' => 'Roles de :discord solo para suscriptores', 'helper' => 'Tu suscripción permanecerá activa hasta :date. Hasta entonces, seguirás disfrutando de todos los beneficios de la suscripción, incluyendo:', 'limit' => 'Límites de carga de archivos aumentados', 'more' => '¡Y mucho más!', 'premium' => 'Campañas premium y sus funciones', 'title' => 'Tus beneficios siguen activos', ], 'change' => [ 'action' => 'Renueva tu suscripción ahora', 'helper' => '¡Nos encantaría tenerte de vuelta! Puedes renovar tu suscripción en cualquier momento para continuar justo donde lo dejaste.', 'title' => '¿Cambiaste de opinión?', ], 'contact' => [ 'feedback' => 'Si hay algo que podríamos haber hecho mejor, nos encantaría saberlo:', 'helper' => 'Aunque no tengas una suscripción, sigues siendo parte de la comunidad de Kanka. Sigue creando tus mundos y siéntete libre de reconectarte cuando sea el momento adecuado.', 'send' => 'Contáctanos con tus comentarios', 'title' => 'Mantente en contacto', ], 'next' => [ 'data' => 'Tus datos estarán seguros, nada se elimina y puedes renovar tu suscripción en cualquier momento.', 'discord' => 'Ya no tendrás acceso a las funciones y canales exclusivos para suscriptores.', 'helper' => 'Una vez que termine tu suscripción:', 'premium' => 'Las campañas con funciones premium activadas perderán su estado premium.', 'title' => '¿Qué sucede después de eso?', ], 'seo_title' => 'Lamentamos que te vayas', 'subtitle' => 'Gracias por haber sido suscriptor, tu apoyo ha significado mucho para nosotros. Esto es lo que puedes esperar ahora:', 'title' => 'Lamentamos verte partir, :name', ]; ================================================ FILE: lang/es/subscriptions/confirm.php ================================================ [ 'pay' => 'Paga :currency:amount ahora', 'paypal' => 'Paga :currency:amount con PayPal', 'subscribe' => 'Suscribirse por :currency:amount', ], 'helpers' => [ 'auto-renew' => [ 'monthly' => 'Tu suscripción se renueva automáticamente cada mes. La próxima fecha de facturación es :date.', 'none' => 'Pagar con PayPal es un pago único y no se renueva automáticamente. Puedes volver a suscribirte cuando tu suscripción termine después de :date.', 'yearly' => 'Tu suscripción se renueva automáticamente cada 12 meses. La próxima fecha de facturación es :date.', ], 'paypal' => 'Serás redirigido a PayPal para completar esta transacción.', 'refund' => 'Ofrecemos una política de reembolso sin preguntas durante 14 días en todas las suscripciones anuales. Simplemente envíanos un correo a :email para iniciar el proceso de reembolso.', 'tiny' => 'Gracias por apoyar a un pequeño equipo de apasionados creadores de mundos.', ], 'title' => 'Suscripción de :name', ]; ================================================ FILE: lang/es/subscriptions/faq.php ================================================ [ 'answer' => '¡Claro! Encontrarás un botón para cancelar justo en esta página si estás suscrito actualmente. Todos los beneficios de la suscripción permanecerán activos hasta el final de tu período de facturación. Ten en cuenta que las suscripciones por PayPal terminan automáticamente al concluir, ya que no soportan renovación automática.', 'question' => '¿Puedo cancelar mi suscripción en cualquier momento?', ], 'cost' => [ 'answer' => 'Kanka ofrece tres niveles de suscripción nombrados como monstruos de D&D: Owlbear, Wyvern y Elemental. Los precios varían según tu moneda preferida (USD, EUR o BRL). Además, disfrutarás de dos meses gratis al elegir una suscripción anual en lugar de facturación mensual.', 'question' => '¿Cuánto cuesta una suscripción?', ], 'data' => [ 'answer' => 'Puedes estar tranquilo, nunca eliminamos tus datos cuando una suscripción termina. Las campañas premium simplemente vuelven a la funcionalidad estándar, con las funciones premium temporalmente deshabilitadas. Cuando vuelves a suscribirte, todas tus configuraciones y datos premium se restauran de inmediato tal como los dejaste.', 'question' => '¿Qué pasa con mis datos si cancelo mi suscripción?', ], 'discount' => [ 'answer' => '¡Sí! Recompensamos a nuestros suscriptores anuales con dos meses gratis en comparación con la facturación mensual. Esta es nuestra forma de agradecerte por tu compromiso a largo plazo con Kanka.', 'question' => '¿Hay descuentos para las suscripciones anuales?', ], 'downgrade' => [ 'answer' => 'Puedes cambiar tu nivel de suscripción en cualquier momento. Al subir de nivel, solo te cobraremos la diferencia entre tu plan actual y el nuevo por el resto de tu período de facturación. Al bajar de nivel, la nueva tarifa más baja se aplicará en tu próxima fecha de renovación, sin interrupción de tus beneficios actuales.', 'question' => '¿Cómo puedo mejorar o degradar mi suscripción?', ], 'fail' => [ 'answer' => 'Si un pago no se procesa, te notificaremos por correo electrónico de inmediato e intentaremos cobrar tu tarjeta hasta tres veces más automáticamente. Si estos intentos no tienen éxito, tu suscripción se pausará. Puedes resolver esto fácilmente actualizando tu información de :billing con un método de pago válido.', 'question' => '¿Qué pasa si mi pago falla?', ], 'help' => [ 'answer' => 'Tu suscripción paga por nuestro tiempo, nuestros servidores y la libertad de mantener Kanka sostenible sin perseguir el crecimiento a cualquier costo. Nos permite corregir errores más rápido, desarrollar funciones en las que realmente creemos y mantenernos atentos a la comunidad en lugar de a los inversores. En pocas palabras: mantiene a Kanka vivo y mejorando.', 'question' => '¿Cómo ayuda mi suscripción a Kanka?', ], 'methods' => [ 'answer' => 'Aceptamos pagos con tarjeta de crédito y PayPal en USD, EUR y BRL. La seguridad de tu pago es importante para nosotros; todo el procesamiento de tarjetas de crédito se realiza de forma segura a través de nuestro proveedor de pagos de confianza, :stripe.', 'question' => '¿Qué métodos de pago aceptan?', ], 'refund' => [ 'answer' => '¡Sí! Ofrecemos una política de reembolso del 100% sin preguntas durante 14 días para todas las suscripciones anuales. Simplemente envíanos un correo a :email solicitando tu reembolso, y nos encargaremos de todo.', 'question' => '¿Ofrecen reembolsos?', ], 'renewal' => [ 'answer' => 'Sí, para suscripciones con tarjeta de crédito, renovaremos automáticamente tu plan al mismo precio cuando termine tu período de facturación. Las suscripciones por PayPal son la excepción; requieren renovación manual porque PayPal no admite la renovación automática para nuestro servicio.', 'question' => '¿Se me cobrará automáticamente cuando se renueve mi suscripción?', ], 'security' => [ 'answer' => 'Tu seguridad financiera es nuestra prioridad. Trabajamos con :stripe, un procesador de pagos que cumple con PCI y mantiene los más altos estándares en seguridad. Todos los datos sensibles de pago son gestionados y almacenados por Stripe bajo protocolos compatibles con el GDPR, no en nuestros servidores.', 'question' => '¿Qué tan segura está mi información de pago?', ], 'sharing' => [ 'answer' => '¡Por supuesto! Tu suscripción te permite activar campañas premium que benefician a todos los involucrados. Todos los miembros de la campaña disfrutan de las funciones premium dentro de esa campaña, sin importar su estado personal de suscripción, haciendo de Kanka la opción perfecta para la creación colaborativa de mundos.', 'question' => '¿Puedo compartir mi cuenta o suscripción con otros?', ], 'title' => 'Preguntas frecuentes', 'trial' => [ 'answer' => 'Aunque no ofrecemos una prueba tradicional, la versión gratuita de Kanka brinda potentes herramientas para la creación de mundos y la gestión de campañas para que comiences. Cuando estés listo para más, la suscripción desbloquea funciones premium como límites aumentados para subir imágenes, una experiencia sin anuncios, roles exclusivos en Discord y mejoras adicionales para tu kit de creación de mundos.', 'question' => '¿Hay una prueba gratuita disponible?', ], 'update' => [ 'answer' => 'Actualizar tus datos de facturación es sencillo, solo visita tu página de :billing en la configuración de tu cuenta. Allí puedes modificar los métodos de pago, actualizar la información de la tarjeta o cambiar las direcciones de facturación según sea necesario.', 'question' => '¿Cómo actualizo mi información de facturación?', ], 'why' => [ 'answer' => 'Kanka es creado y mantenido por un pequeño equipo independiente. Las suscripciones son lo que nos permite trabajar en él a largo plazo, mejorarlo de forma constante y mantenerlo libre de prácticas engañosas. No estás pagando a una corporación; estás financiando directamente a las personas que diseñan, desarrollan y dan soporte a la plataforma.', 'question' => '¿Por qué Kanka cobra suscripciones?', ], ]; ================================================ FILE: lang/es/subscriptions/finish.php ================================================ [ 'action' => 'Conecta tu cuenta de Discord', 'enjoy' => 'Dirígete a Discord para disfrutar de tus nuevos beneficios', 'helper' => 'Como suscriptor, desbloqueas roles exclusivos y canales privados en nuestra comunidad de Discord, pero primero, necesitas conectar tu cuenta de Discord.', 'title' => 'Obtén tus beneficios de Discord', ], 'header' => '¡Te has suscrito con éxito, bienvenido a la sociedad secreta de los creadores de mundos!', 'help' => [ 'contact-us' => 'contáctanos', 'helper' => 'Si tienes alguna pregunta o comentario, :contact-us o visita nuestros :docs. Estamos aquí para hacer que tu experiencia de creación de mundos sea mágica.', 'title' => '¿Necesitas ayuda?', ], 'next' => 'Tu apoyo nos ayuda a crecer y a seguir mejorando Kanka para todos. Aquí tienes lo que puedes hacer a continuación para desbloquear todos los beneficios de tu suscripción:', 'premium' => [ 'action' => 'Desbloquear', 'helper' => 'Desbloquea funciones premium como módulos personalizados, acceso a los :plugins, ¡y mucho más!', 'title' => 'Activa las funciones premium en una campaña', ], 'roadmap' => [ 'action' => 'Consulta la hoja de ruta y sugiere nuevas funciones', 'helper' => 'Estamos construyendo Kanka para ti. ¡Consulta la hoja de ruta pública y sugiere o vota por las funciones que te gustaría ver!', 'title' => 'Ayuda a moldear el futuro de Kanka', ], 'title' => '¡Gracias por apoyar a Kanka!', ]; ================================================ FILE: lang/es/subscriptions/free-trial.php ================================================ [ 'accept' => 'Inicia tu prueba gratuita', 'magic' => 'No se necesita tarjeta. Es magia pura.', ], 'final' => [ 'magic' => 'Sin compromisos, sin trucos. Solo más magia para tu campaña.', 'title' => 'Comenzar mi prueba de 15 días ahora', ], 'header' => 'Hemos visto tu dedicación en los reinos de Kanka. Para honrar tu viaje, te otorgamos una :what. No se necesitan monedas, gemas, ni tarjeta.', 'included' => [ 'title' => 'Qué incluye', 'upsell'=> [ 'action' => 'Ver todos los niveles de suscripción', 'pitch' => 'Esto es solo el nivel :tier. ¿Listo para desbloquear aún más?', ], ], 'pitch' => [ 'title' => 'Disfruta de una prueba gratuita de 15 días a cuenta nuestra. Desbloquea todas las funciones premium y descubre lo que te estabas perdiendo.', ], 'started' => [ 'header' => 'Has iniciado tu prueba gratuita con éxito, ¡bienvenido al círculo interior de los creadores de mundos!', 'title' => '¡Bienvenido a tu prueba gratuita!', ], 'tease' => [ 'helper' => '¿Quieres desbloquear cargas de archivos más grandes y más espacios premium para campañas? Visita la página :subscription para ver lo que más te espera.', 'title' => 'Pero quieres más', ], 'title' => '¡Una nueva misión te espera, aventurero!', 'what' => 'Prueba gratuita de :amount días del nivel :tier', 'why' => [ 'helper' => 'Has creado, explorado y hecho crecer tu campaña. Tu lealtad no ha pasado desapercibida. Piensa en esto como un regalo de agradecimiento de parte del equipo de Kanka.', 'title' => 'Te has ganado esta recompensa', ], ]; ================================================ FILE: lang/es/subscriptions/paypal.php ================================================ [ 'contact' => 'Si este problema persiste, contáctanos en :email.', 'failed' => 'PayPal no pudo generar una factura. Por favor, inténtalo de nuevo.', 'incomplete' => 'El pago de PayPal está incompleto. Por favor, inténtalo de nuevo.', 'rejected' => 'PayPal generó una factura no válida. Por favor, inténtalo de nuevo.', ], ]; ================================================ FILE: lang/es/subscriptions/promos.php ================================================ [ 'inactive' => 'Esta promoción ya no está activa.', 'invalid' => 'Promoción desconocida.', 'only-new' => 'Esta promoción sólo está disponible para nuevos suscriptores.', ], ]; ================================================ FILE: lang/es/subscriptions/renew.php ================================================ [ 'renew' => 'Renovar suscripción', ], 'helper' => 'Sin embargo, puedes optar por renovar tu suscripción para disfrutar de los beneficios sin interrupciones.', 'title' => 'Renovación de la suscripción', ]; ================================================ FILE: lang/es/subscriptions.php ================================================ [ 'failed' => 'Stripe no ha podido cargar tu método de pago. En consecuencia, tu suscripción se ha desactivado.', ], ]; ================================================ FILE: lang/es/tags.php ================================================ [ 'actions' => [ 'add' => 'Añadir etiqueta nueva', 'add_entity' => 'Añadir a entidad', ], 'create' => [ 'attach_success' => '{1} Se ha añadido :count entidad a la etiqueta :name.|[2,*] Se han añadido :count entidades a la etiqueta :name.', 'attach_success_entity' => 'Etiquetas actualizadas con éxito para :name.', 'entity' => 'Añadir etiquetas a :name', 'helper' => 'Etiquetar una o varias entidades con :name', 'title' => 'Etiquetar entidades', ], ], 'create' => [ 'title' => 'Nueva etiqueta', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Entidades anidadas', 'is_auto_applied' => 'Aplicar automáticamente a nuevas entidades', 'is_hidden' => 'Oculto en la cabecera y en el tooltip', ], 'helpers' => [ 'no_children' => 'Actualmente no hay entidades etiquetadas con esta etiqueta.', 'no_posts' => 'Actualmente no hay entradas etiquetadas con esta etiqueta.', ], 'hints' => [ 'children' => 'Aquí se muestran todas las entidades que pertenecen directamente a esta etiqueta y a todas las etiquetas anidadas.', 'is_auto_applied' => 'Aplicar automáticamente esta etiqueta a las entidades recién creadas.', 'is_hidden' => 'No mostrar esta etiqueta en la cabecera o tooltip de una entidad.', 'tag' => 'A continuación se muestran todas las etiquetas que están directamente bajo esta etiqueta.', ], 'index' => [], 'lists' => [ 'empty' => 'Usa etiquetas para agrupar y filtrar entidades en todo tu mundo para facilitar la navegación.', ], 'placeholders' => [ 'type' => 'Tradiciones, guerras, historia, religión...', ], 'show' => [ 'tabs' => [ 'children' => 'Entidades anidadas', ], ], 'tags' => [], 'transfer' => [ 'entities' => [ 'helper' => 'Transferir entidades etiquetadas con :name a otra etiqueta.', 'title' => 'Transferir entidades', ], 'fail' => 'Fallo al transferir entidades de :tag a :newTag', 'fail_post' => 'Error al transferir entradas de :tag a :newTag', 'posts' => [ 'helper' => 'Transferir posts etiquetados con :name a otra etiqueta.', 'title' => 'Transferir publicaciones', ], 'success' => 'Entidades de :tag transferidas con éxito a :newTag', 'success_post' => 'Se han transferido correctamente las entradas de :tag a :newTag', 'transfer' => 'Transferir', ], ]; ================================================ FILE: lang/es/teams.php ================================================ [ 'lead' => 'Hacer que la construcción de mundos sea divertida y fiable', 'translations' => 'Traducciones', ], 'leads' => [ 'translators' => 'Kanka está traducido a varios idiomas gracias a estos increíbles colaboradores.', ], 'patreon' => [], 'people' => [ 'itzamna' => [ 'title' => 'Desarrollador junior', ], 'jay' => [ 'title' => 'Fundador y desarrollador principal', ], 'jon' => [ 'title' => 'Cofundador y gerente', ], 'kaz' => [ 'title' => 'Destructor de bichos', ], 'laura' => [ 'title' => 'Medios sociales', ], ], ]; ================================================ FILE: lang/es/tiers.php ================================================ [ 'pay' => [ 'monthly' => 'Pagar mensualmente', 'save' => 'ahorra 2 meses', 'yearly' => 'Pagar anualmente', ], 'subscribe' => [ 'choose' => 'Elegir :tier', 'downgrade' => 'Cambiar al plan :tier', 'monthly' => ':tier mensual', 'upgrade' => 'Mejorar al plan :tier', 'yearly' => ':tier anual', ], ], 'current' => 'Tu suscripción actual', 'features' => [ 'api_requests' => ':amount solicitudes API / minuto', 'boosters' => 'Mejoras de campaña', 'discord' => 'Roles de Discord', 'feature_influence' => 'Influencia sobre nuevas funcionalidades', 'file_size' => 'Subir archivos de hasta :size', 'import' => 'Importador de campaña', 'modules' => ':count módulos personalizados', 'nice_image' => 'Imágenes por defecto en las entidades', 'no_ads' => 'Sin anuncios', 'pagination' => 'Máximo de hasta :amount resultados en una página', 'premium' => 'Cada uno incluye: :storage GB de almacenamiento, :modules módulos personalizados', 'roadmap' => 'Vota sobre las ideas en la hoja de ruta', 'storage' => ':count GB de almacenamiento', ], 'helpers' => [ 'premium' => 'Estos beneficios se aplican a las campañas premium que desbloquees.', ], 'periods' => [ 'billed_monthly' => 'facturado mensualmente', 'billed_yearly' => 'facturado anualmente', ], 'pricing' => ':amount / mes :currency', 'ribbons' => [ 'best-value' => 'Mejor relación calidad-precio', 'current' => 'Suscripción actual', ], 'target' => [ 'elemental' => 'Para profesionales del worldbuilding que gestionan múltiples escenarios épicos y campañas expansivas.', 'owlbear' => 'Perfecto para creadores de mundos en solitario que desean potenciar su campaña principal.', 'wyvern' => 'Ideal para directores de juego que dirigen múltiples aventuras o narradores colaborativos.', ], 'tiny' => 'pequeño equipo', 'toggle' => [], 'why' => 'Kanka está creado por un :tiny de apasionados creadores de mundos. Las suscripciones financian a las personas, los servidores y el tiempo necesarios para seguir mejorando la plataforma de forma sostenible. No hay prácticas engañosas, ni presión de inversores, ni una búsqueda infinita de crecimiento. El desarrollo es constante y está guiado por nuestra comunidad.', ]; ================================================ FILE: lang/es/timelines/elements.php ================================================ [ 'copy_with_name' => 'Copiar mención avanzada con el nombre del elemento', 'success' => 'Mención avanzada al elemento copiado en el portapapeles.', ], 'create' => [ 'success' => 'Elemento añadido a la línea de tiempo.', 'title' => 'Nuevo elemento cronológico', ], 'delete' => [ 'success' => 'Elemento ":name" eliminado.', ], 'edit' => [ 'success' => 'Elemento actualizado.', 'title' => 'Editar elemento cronológico', ], 'fields' => [ 'date' => 'Fecha', 'era' => 'Era', 'icon' => 'Icono', 'use_entity_entry' => 'Muestra la información de la entidad adjunta a continuación. El texto de este elemento se mostrará primero si está presente.', 'use_event_date' => 'Utiliza la fecha del evento vinculado.', ], 'helpers' => [ 'date' => 'Si el elemento está vinculado a una entidad evento, muestra la fecha del evento.', 'entity_is_private' => 'La entidad de este elemento es privada.', 'icon' => 'Copia el HTML de un icono de :fontawesome o :rpgawesome.', 'is_collapsed' => 'El elemento se muestra colapsado por defecto.', ], 'placeholders' => [ 'date' => '22 de marzo, 1332-1337...', 'name' => 'Requerido si no hay ninguna entidad seleccionada', 'position' => 'Posición en la lista de elementos de la era. Déjalo en blanco para añadirlo al final.', ], ]; ================================================ FILE: lang/es/timelines/eras.php ================================================ [ 'add' => 'Añadir nueva era', ], 'bulks' => [ 'delete' => '{0} :count eras eliminadas.|{1} :count era eliminada.|[2,*] :count eras eliminadas.', ], 'create' => [ 'success' => 'Era :name creada.', 'title' => 'Nueva era', ], 'delete' => [ 'success' => 'Era :name eliminada.', ], 'edit' => [ 'success' => 'Era :name actualizada.', 'title' => 'Editar era :name', ], 'fields' => [ 'abbreviation' => 'Abreviatura', 'end_year' => 'Año final', 'is_collapsed' => 'Colapsada', 'start_year' => 'Año inicial', ], 'helpers' => [ 'eras' => 'Hay que crear la línea de tiempo para poder añadirle eras.', 'is_collapsed' => 'La era estará colapsada (minimizada) por defecto.', 'primary' => 'Separa tu línea de tiempo en eras. Una línea de tiempo necesita al menos una era para funcionar correctamente.', ], 'index' => [ 'title' => 'Eras de :name', ], 'placeholders' => [ 'abbreviation' => 'a.C., d.C., BCE...', 'end_year' => 'Año en que termina la era. Déjalo en blanco si esta es la era actual.', 'name' => 'Era moderna, Edad del bronce, Guerras galácticas...', 'start_year' => 'Año en que la era comienza. Déjalo en blanco si esta es la primera era.', ], 'reorder' => [], ]; ================================================ FILE: lang/es/timelines.php ================================================ [ 'add_element' => 'Añadir elemento a la era :era', 'back' => 'Volver a :name', 'save_order' => 'Guardar orden nuevo', ], 'create' => [ 'title' => 'Nueva línea de tiempo', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_elements' => 'Copiar elementos', 'copy_eras' => 'Copiar eras', 'eras' => 'Eras', 'reverse_order' => 'Era en orden inverso', ], 'helpers' => [ 'no_era_v2' => 'Actualmente, esta línea de tiempo no tiene ninguna era. Añádele una o varias eras, tras lo cual podrás añadir elementos a las eras aquí.', 'reverse_order' => 'Habilitar para mostrar las eras en orden cronológico inverso (la era más antigua primero)', ], 'index' => [], 'lists' => [ 'empty' => 'Construye una línea de tiempo visual para registrar eventos importantes y seguir cómo ha evolucionado tu mundo.', ], 'placeholders' => [ 'type' => 'Primaria, Crónica del mundo, Legado del reino...', ], 'reorder' => [ 'empty' => 'Añade eras y elementos a la línea de tiempo para poder reordenarla.', 'success' => ':name reordenado con éxito.', 'title' => 'Reordenar :name', ], 'show' => [ 'tabs' => [ 'reorder-elements' => 'Reordenar elementos', ], ], 'timelines' => [], ]; ================================================ FILE: lang/es/tiptap.php ================================================ 'Comparte tu opinión', 'survey'=> '¿Probando el nuevo editor? :share (solo toma 2 minutos)', ]; ================================================ FILE: lang/es/users/profile.php ================================================ [ 'wordsmith' => 'Un verdadero artífice de la palabra. Ganador de un concurso de worldbuilding.', ], 'fields' => [ 'achievements' => 'Logros', 'banned' => 'Este usuario ha sido baneado', 'entities_created' => 'Entidades creadas :help :count', 'member_since' => 'Miembro desde :date', 'public_campaigns' => 'Campañas públicas', 'subscriber_since' => 'Suscriptor desde :date', ], 'helpers' => [ 'entities_created' => 'Este valor se recalcula cada día.', ], 'title' => 'Perfil de :name', ]; ================================================ FILE: lang/es/validation.php ================================================ ':attribute debe ser aceptado.', 'active_url' => ':attribute no es una URL válida.', 'after' => ':attribute debe ser una fecha posterior a :date.', 'after_or_equal' => ':attribute debe ser una fecha posterior o igual a :date.', 'alpha' => ':attribute sólo debe contener letras.', 'alpha_dash' => ':attribute sólo debe contener letras, números y guiones.', 'alpha_num' => ':attribute sólo debe contener letras y números.', 'array' => ':attribute debe ser un conjunto.', 'before' => ':attribute debe ser una fecha anterior a :date.', 'before_or_equal' => ':attribute debe ser una fecha anterior o igual a :date.', 'between' => [ 'numeric' => ':attribute tiene que estar entre :min - :max.', 'file' => ':attribute debe pesar entre :min - :max kilobytes.', 'string' => ':attribute tiene que tener entre :min - :max caracteres.', 'array' => ':attribute tiene que tener entre :min - :max ítems.', ], 'boolean' => 'El campo :attribute debe tener un valor verdadero o falso.', 'confirmed' => 'La confirmación de :attribute no coincide.', 'date' => ':attribute no es una fecha válida.', 'date_equals' => ':attribute debe ser una fecha igual a :date.', 'date_format' => ':attribute no corresponde al formato :format.', 'different' => ':attribute y :other deben ser diferentes.', 'digits' => ':attribute debe tener :digits dígitos.', 'digits_between' => ':attribute debe tener entre :min y :max dígitos.', 'dimensions' => 'Las dimensiones de la imagen :attribute no son válidas.', 'distinct' => 'El campo :attribute contiene un valor duplicado.', 'email' => ':attribute no es un correo válido', 'exists' => ':attribute es inválido.', 'file' => 'El campo :attribute debe ser un archivo.', 'filled' => 'El campo :attribute es obligatorio.', 'gt' => [ 'numeric' => 'El campo :attribute debe ser mayor que :value.', 'file' => 'El campo :attribute debe tener más de :value kilobytes.', 'string' => 'El campo :attribute debe tener más de :value caracteres.', 'array' => 'El campo :attribute debe tener más de :value elementos.', ], 'gte' => [ 'numeric' => 'El campo :attribute debe ser como mínimo :value.', 'file' => 'El campo :attribute debe tener como mínimo :value kilobytes.', 'string' => 'El campo :attribute debe tener como mínimo :value caracteres.', 'array' => 'El campo :attribute debe tener como mínimo :value elementos.', ], 'image' => ':attribute debe ser una imagen.', 'in' => ':attribute es inválido.', 'in_array' => 'El campo :attribute no existe en :other.', 'integer' => ':attribute debe ser un número entero.', 'ip' => ':attribute debe ser una dirección IP válida.', 'ipv4' => ':attribute debe ser un dirección IPv4 válida', 'ipv6' => ':attribute debe ser un dirección IPv6 válida.', 'json' => 'El campo :attribute debe tener una cadena JSON válida.', 'lt' => [ 'numeric' => 'El campo :attribute debe ser menor que :value.', 'file' => 'El campo :attribute debe tener menos de :value kilobytes.', 'string' => 'El campo :attribute debe tener menos de :value caracteres.', 'array' => 'El campo :attribute debe tener menos de :value elementos.', ], 'lte' => [ 'numeric' => 'El campo :attribute debe ser como máximo :value.', 'file' => 'El campo :attribute debe tener como máximo :value kilobytes.', 'string' => 'El campo :attribute debe tener como máximo :value caracteres.', 'array' => 'El campo :attribute debe tener como máximo :value elementos.', ], 'max' => [ 'numeric' => ':attribute no debe ser mayor a :max.', 'file' => ':attribute no debe ser mayor que :max kilobytes.', 'string' => ':attribute no debe ser mayor que :max caracteres.', 'array' => ':attribute no debe tener más de :max elementos.', ], 'mimes' => ':attribute debe ser un archivo con formato: :values.', 'mimetypes' => ':attribute debe ser un archivo con formato: :values.', 'min' => [ 'numeric' => 'El tamaño de :attribute debe ser de al menos :min.', 'file' => 'El tamaño de :attribute debe ser de al menos :min kilobytes.', 'string' => ':attribute debe contener al menos :min caracteres.', 'array' => ':attribute debe tener al menos :min elementos.', ], 'not_in' => ':attribute es inválido.', 'not_regex' => 'El formato del campo :attribute no es válido.', 'numeric' => ':attribute debe ser numérico.', 'present' => 'El campo :attribute debe estar presente.', 'regex' => 'El formato de :attribute es inválido.', 'required' => 'El campo :attribute es obligatorio.', 'required_if' => 'El campo :attribute es obligatorio cuando :other es :value.', 'required_unless' => 'El campo :attribute es obligatorio a menos que :other esté en :values.', 'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.', 'required_with_all' => 'El campo :attribute es obligatorio cuando :values está presente.', 'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.', 'required_without_all' => 'El campo :attribute es obligatorio cuando ninguno de :values estén presentes.', 'same' => ':attribute y :other deben coincidir.', 'size' => [ 'numeric' => 'El tamaño de :attribute debe ser :size.', 'file' => 'El tamaño de :attribute debe ser :size kilobytes.', 'string' => ':attribute debe contener :size caracteres.', 'array' => ':attribute debe contener :size elementos.', ], 'starts_with' => 'El campo :attribute debe comenzar con uno de los siguientes valores: :values', 'string' => 'El campo :attribute debe ser una cadena de caracteres.', 'timezone' => 'El :attribute debe ser una zona válida.', 'unique' => 'El campo :attribute ya ha sido registrado.', 'uploaded' => 'Subir :attribute ha fallado.', 'url' => 'El formato :attribute es inválido.', 'uuid' => 'El campo :attribute debe ser un UUID válido.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'password' => [ 'min' => 'La :attribute debe contener más de :min caracteres', ], 'email' => [ 'unique' => 'El :attribute ya ha sido registrado.', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ 'name' => 'nombre', 'username' => 'usuario', 'email' => 'correo electrónico', 'first_name' => 'nombre', 'last_name' => 'apellido', 'password' => 'contraseña', 'password_confirmation' => 'confirmación de la contraseña', 'city' => 'ciudad', 'country' => 'país', 'address' => 'dirección', 'phone' => 'teléfono', 'mobile' => 'móvil', 'age' => 'edad', 'sex' => 'sexo', 'gender' => 'género', 'year' => 'año', 'month' => 'mes', 'day' => 'día', 'hour' => 'hora', 'minute' => 'minuto', 'second' => 'segundo', 'title' => 'título', 'content' => 'contenido', 'body' => 'contenido', 'description' => 'descripción', 'excerpt' => 'extracto', 'date' => 'fecha', 'time' => 'hora', 'subject' => 'asunto', 'message' => 'mensaje', ], ]; ================================================ FILE: lang/es/visibilities.php ================================================ [ 'admin' => 'Sólo los administradores de la campaña pueden ver este elemento.', 'admin-self' => 'Sólo tú y los administradores de la campaña podéis ver este elemento.', 'all' => 'Todos pueden ver este elemento.', 'members' => 'Sólo los miembros de la campaña pueden ver este elemento.', 'self' => 'Sólo tú puedes ver este elemento.', ], 'title' => 'Actualizando visibilidad', 'toast' => 'Visibilidad actualizada con éxito.', 'tooltip' => 'Haz clic para obtener información sobre las distintas opciones de visibilidad.', ]; ================================================ FILE: lang/es/whiteboards/draw.php ================================================ [ 'add-circle' => 'Añadir círculo', 'add-entity' => 'Añadir entidad', 'add-image' => 'Añadir imagen', 'add-square' => 'Añadir cuadrado', 'add-text' => 'Añadir texto', 'duplicate' => 'Duplicar selección', 'end-drawing' => 'Finalizar dibujo', 'lock' => 'Bloquear', 'push-to-back' => 'Enviar al fondo', 'push-to-front' => 'Traer al frente', 'start-drawing' => 'Iniciar dibujo', 'unlock' => 'Desbloquear', ], 'entity-search' => [ 'placeholder' => 'Escribe el nombre o alias de una entidad', 'title' => 'Búsqueda de entidades', ], 'errors' => [ 'websockets' => [ 'disconnected' => 'Se perdió la conexión con el websocket. Por favor, inténtalo de nuevo.', 'error' => 'Ocurrió un error al conectarse al servidor de websocket.', 'unavailable' => 'El servidor de websocket no está disponible. Por favor, inténtalo más tarde.', ], ], 'fields' => [ 'color' => 'Color', ], 'pen' => [ 'large-stroke' => 'Trazo grueso', 'thin-stroke' => 'Trazo fino', ], 'reset' => [ 'helper' => '¿Estás seguro de que deseas restablecer el tablero? Esta acción no se puede deshacer.', 'title' => 'Restablecer tablero', ], 'roles' => [ 'edit' => 'Este usuario puede editar el tablero', 'view' => 'Este usuario puede ver el tablero', ], 'toast' => [ 'copy' => [ 'success' => 'Elementos copiados al portapapeles.', ], 'paste' => [ 'error' => 'Algo salió mal', ], ], ]; ================================================ FILE: lang/es/whiteboards.php ================================================ [ 'draw' => 'Dibujar', ], 'create' => [ 'title' => 'Nuevo tablero', ], 'cta' => [ 'text' => 'Para desbloquear los tableros, un miembro con el nivel :wyvern o :elemental debe convertir la campaña en premium.', 'title' => 'Los tableros son una función premium especial', ], 'lists' => [ 'empty' => 'Usa un tablero para organizar visualmente ideas, relaciones o la estructura de la historia.', ], 'placeholders' => [ 'type' => 'Ideas, relaciones, estructura de la historia', ], 'update' => [], ]; ================================================ FILE: lang/fr/abilities.php ================================================ [ 'actions' => [ 'attach' => 'Ajouter des entrées', ], 'create' => [ 'attach_success' => '{1} Le pouvoir :name ajouté à :count entrée.|[2,*] Le pouvoir :name ajouté à :count entrées.', 'helper' => 'Attacher :name à une ou plusieurs entrées.', 'title' => 'Attacher des entrées', ], 'description' => 'Entrées ayant le pouvoir', 'title' => 'Entrées du pouvoir :name', ], 'create' => [ 'title' => 'Nouveau pouvoir', ], 'fields' => [ 'charges' => 'Charges', ], 'lists' => [ 'empty' => 'Ajoute des pouvoirs, des sorts ou des talents. De nombreux créateurs utilisent cette fonctionnalité pour modéliser les classes de D&D.', ], 'placeholders' => [ 'charges' => 'Nombre d\'utilisation. Les propriétés peuvent être référencées avec {Level}*{CHA}', 'name' => 'Jet de feu, Alert, Résistance', 'type' => 'Sort, Compétence, Attaque', ], 'reorder' => [ 'parentless' => 'Aucun parent', 'success' => 'Pouvoirs réordonnés.', 'title' => 'Réorganiser les pouvoirs', ], 'show' => [ 'tabs' => [ 'reorder' => 'Réorganiser les pouvoirs', ], ], ]; ================================================ FILE: lang/fr/account/email.php ================================================ [ 'update' => 'Mettre à jour', ], 'fields' => [ 'email' => 'Nouvelle adresse email', ], 'helpers' => [ 'email' => 'Assures-toi que c\'est écrit correctement.', ], 'subtitle' => 'Changes l\'adresse email associé à ton compte.', 'title' => 'Mettre à jour ton adresse email', ]; ================================================ FILE: lang/fr/account/password.php ================================================ [ 'update' => 'Modifier le mot de passe', ], 'fields' => [ 'password' => 'Nouveau mot de passe', ], 'helpers' => [ 'password' => 'Utilises un gestionnaire de mots de passe pour créer un mot de passe fort.', 'password_confirmation' => 'Ne te plantes pas!', ], 'subtitle' => 'Changes ton mot de passe. Ceci te déconnectera de toutes tes autres machines.', 'title' => 'Modifier ton mot de passe', ]; ================================================ FILE: lang/fr/account/social.php ================================================ 'Connection avec :provider', 'subtitle' => 'Passes d\'un login géré par :provider à un login géré par Kanka, où tu te connectes avec ton email et votre mot de passe.', 'title' => 'Changer à un login Kanka', ]; ================================================ FILE: lang/fr/articles.php ================================================ [ 'move' => 'Déplacer vers une entrée', ], 'helpers' => [ 'permissions' => 'Ces permissions peuvent remplacer le paramètre :visibility de l\'article.', ], 'tabs' => [ 'layout' => 'Mise en page', 'main' => 'Principal', 'permissions' => 'Permissions spéciales', ], ]; ================================================ FILE: lang/fr/assistance.php ================================================ [ 'campaign' => 'Campagne', ], 'opening' => 'Un membre de l\'équipe de Kanka t\'a envoyé sur cette page afin de t\'apporter de l\'aide.', 'placeholders' => [ 'campaign' => 'Sélectionne une campagne dont tu es admin', ], 'select' => 'Sélectionne une campagne dont tu es admin dans la liste ci-dessous pour générer un jeton spécial à usage unique. Cela permettra à un membre de l\'équipe de Kanka de rejoindre temporairement ta campagne en tant qu\'admin.', 'success' => [ 'opening' => 'Ton jeton d\'assistance a été généré avec succès. L\'équipe de Kanka a été notifiée et rejoindra ta campagne sous peu pour t\'aider. On te contactera généralement via :discord si on a besoin de se coordonner directement.', 'secret' => 'Seul un membre vérifié de l\'équipe de Kanka peut utiliser ce jeton. Il est inutile pour toute autre personne, donc pas besoin de le garder secret.', 'token' => 'Ton jeton d\'assistance :', ], 'title' => 'Assistance', ]; ================================================ FILE: lang/fr/attribute_templates.php ================================================ [ 'entity_type' => [ 'unset' => 'Réinitialiser', ], ], 'create' => [ 'title' => 'Créer un nouveau kit de propriétés', ], 'fields' => [ 'auto_apply' => 'Auto-appliquer', 'is_enabled' => 'Activé', ], 'hints' => [ 'automatic' => 'Propriétés automatiquement appliqués depuis le kit :link.', 'automatic_apply' => '1} La propriété suivante a été automatiquement appliquée pour :link | [2,] Les :count propriétées suivantes ont été automatiquement appliquées pour :link.', 'entity_type' => 'Si défini, lors de la création d\'une nouvelle entrée de cette catégorie, ce kit ainsi que ses parents seront automatiquement appliqués.', 'is_disabled' => 'Ce kit est désactivé.', 'is_enabled' => 'Activer ce kit pour l\'utiliser.', 'parent_attribute_template' => 'Ce kit peut être l\'enfant d\'un autre kit. Lorsqu\'un kit est appliqué, celui-ci ainsi que tous ses descendants seront aussi appliqués.', ], 'lists' => [ 'empty' => 'Créé des kits pour réutiliser des propriétés communes à plusieurs entrées.', ], 'placeholders' => [ 'name' => 'Nom du kit de propriétés', ], 'show' => [], ]; ================================================ FILE: lang/fr/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Erreur', 'rendering' => 'Une erreur est survenue lors de l\'affichage du plugin. Prière de contacter le ou la créateur/rice du plugin.', ], ], 'list' => [ 'sheets' => 'Feuilles de personnages', ], 'pitch' => 'Trouves et ajoutes des fiches de personnage sur le :marketplace dans une :boosted-campaign.', ]; ================================================ FILE: lang/fr/auth.php ================================================ [ 'permanent' => 'Ton compte a été désactivé.', 'temporary' => '{1} Ton compte a été suspendu pour :days jour.|[2,*] Ton compte a été suspendu pour :days jours.', ], 'confirm' => [ 'confirm' => 'Confirmer', 'error' => 'Mot de passe invalid, merci de ressayer.', 'helper' => 'Confirmer le mot de passe avant de pouvoir continuer.', 'title' => 'Confirmation du mot de passe.', ], 'continue' => [ 'facebook' => 'Continuer avec Facebook', 'google' => 'Continuer avec Google', 'x' => 'Continuer avec X', ], 'failed' => 'Ces informations ne correspondent pas avec nos données.', 'helpers' => [ 'password' => 'Afficher / Cacher le mot de passe', ], 'login' => [ 'fields' => [ '2fa' => 'Code à usage unique', 'email' => 'Adresse Email', 'password' => 'Mot de passe', ], 'no-account' => 'Sans compte?', 'or' => 'OU', 'password_forgotten' => 'Mot de passe oublié?', 'sign-up' => 'S\'inscrire', 'submit' => 'Se connecter', 'title' => 'Login', ], 'register' => [ 'already' => 'Tu as déjà un compte? :login', 'errors' => [ 'email_already_taken' => 'Un compte avec cette adresse email est déjà enregistré.', 'general_error' => 'Une erreur est survenue lors de la création du compte. Merci de ressayer.', ], 'fields' => [ 'email' => 'Adresse email', 'name' => 'Votre nom d\'utilisateur', 'password' => 'Mot de passe', ], 'log-in' => 'Connectes-toi', 'submit' => 'S\'inscrire', 'title' => 'Inscription', 'tos' => 'En enregistrant un compte, tu acceptes nos :terms et :privacy.', ], 'reset' => [ 'fields' => [ 'email' => 'Adresse email', 'password' => 'Mot de passe', 'password_confirmation' => 'Confirmation du mot de passe', ], 'send' => 'M\'envoyer un mail de réinitialisation de mot de passe', 'submit' => 'Enregistrer', 'title' => 'Réinitialisation du mot de passe', ], 'tfa' => [ 'helper' => 'L\'authentification à deux facteurs est activée. Prière d\'indiquer le code à usage unique fourni par l\'application d\'authentification.', 'title' => 'Authentification à deux facteurs', ], 'throttle' => 'Trop d\'essais. Réessaies dans :seconds secondes.', 'x-twitter' => 'X anciennement connu comme Twitter', ]; ================================================ FILE: lang/fr/billing/information.php ================================================ [ 'update' => 'Modifier l\'info', ], 'helper' => 'Ton adresse professionnelle, ton numéro de TVA, etc. peuvent être ajoutés à tous tes reçus.', 'title' => 'Modifier l\'information de facturation', ]; ================================================ FILE: lang/fr/billing/invoices.php ================================================ [ 'download' => 'Télécharger le PDF', ], 'description' => 'Affichage des factures des derniers 24 mois.', 'empty' => 'Aucune facture trouvée.', 'fields' => [ 'amount' => 'Somme', 'date' => 'Date', 'invoice' => 'Facture', 'status' => 'Status', ], 'paypal' => 'Seuls les paiements effectués par Stripe, et non par PayPal, sont visibles ici.', 'status' => [ 'paid' => 'Payée', 'pending' => 'En attente', ], 'title' => 'Historique de facturation', ]; ================================================ FILE: lang/fr/billing/menu.php ================================================ 'Historique de facturation', 'overview' => 'Aperçu', 'payment-method' => 'Méthode de paiement', ]; ================================================ FILE: lang/fr/billing/payment_methods.php ================================================ 'Méthode de paiement', 'types' => [ 'card' => 'Carte de crédit/débit', ], ]; ================================================ FILE: lang/fr/bookmarks.php ================================================ [ 'customise' => 'Personnaliser la navigation', ], 'create' => [ 'title' => 'Nouveau favori', ], 'edit' => [ 'title' => 'Favori :name', ], 'fields' => [ 'active' => 'Actif', 'dashboard' => 'Tableau de bord', 'default_dashboard' => 'Tableau de bord par défaut', 'filters' => 'Filtres', 'menu' => 'Menu', 'position' => 'Position', 'random_type' => 'Catégorie aléatoire', 'selector' => 'Configuration du favori', 'target' => 'Cible', ], 'helpers' => [ 'active' => 'Les favoris inactifs ne s\'affichent pas dans la navigation.', 'css' => 'Ajoutes une classe CSS qui sera ajoutée au lien du bookmark dans la navigation.', 'dashboard' => 'Mettre en place le favori pour aller à un tableau de bord.', 'default_dashboard' => 'Favori vers le tableau de bord par défaut de la campagne. Un tableau de bord personnalisé doit encore être sélectionné.', 'entity' => 'Mettre en place le favori pour aller directement sur une entrée. Le champ :tab contrôle quel onglet est ouvert. Le champ :menu contrôle quel sous-menu est affiché.', 'position' => 'Ce champ contrôle dans quel ordre les favoris apparaissent.', 'random' => 'Utilises ce champ pour avoir le favori qui pointe vers une entrée aléatoire. La catégorie d\'entrée peut être filtré.', 'selector' => 'Configurer vers quel catégorie l\'utilisateur ira en cliquant sur le favori dans le menu de navigation.', 'type' => 'Définir ce favori pour aller directement sur une liste d\'entrée. Pour filtrer les résultats, il faut copier l\'url de la page filtrée après le :? de l\'url dans le champs :filter.', ], 'lists' => [ 'empty' => 'Enregistre des favoris dans tes entrées les plus utilisées ou tes listes filtrées pour un accès plus rapide.', ], 'placeholders' => [ 'filters' => 'location_id=15&type=city', 'menu' => 'Sous-page (dernière partie de l\'url)', 'tab' => 'entry, relations, notes, map', ], 'random_no_entity' => 'Aucune entrée au hasard n\'a été trouvée.', 'random_types' => [ 'any' => 'Toutes les entrées', ], 'reorder' => [ 'success' => 'Favoris réorganisés.', 'title' => 'Réorganiser les favoris', ], 'targets' => [ 'dashboard' => 'Un des tableaux de bord de la campagne', 'entity' => 'Une seule entrée', 'random' => 'Une entrée au hasard', 'select' => 'Choisir une option', 'type' => 'Liste des entrées d\'une categérie spécifique', ], 'visibilities' => [ 'is_active' => 'Afficher le favori dans la navigation', ], ]; ================================================ FILE: lang/fr/bragi/backstory.php ================================================ 'Écris une courte histoire de fond inspirée de ces informations. Adapte ton ton et tes détails aux systèmes de jeu et aux genres sélectionnés. Tu peux inclure des éléments comme l\'apparence du personnage, ses origines, ses croyances, ses relations, ses objectifs, ses failles ou des événements marquants — selon ce qui convient le mieux au personnage.', 'setup' => [ 'gender' => 'Genre: :gender', 'genres' => 'Genres: :genres', 'name' => 'Nom du personnage: :name', 'prompt' => 'Prompt: ":prompt"', 'pronouns' => 'Pronoms: :pronouns', 'systems' => 'Systèmes de jeu: :systems', ], 'system' => 'Tu es un conteur professionnel de JDR et un créateur de personnages. Tu écris des histoires de fond immersives et émouvantes pour des personnages de jeux de rôle. Ton style s\'adapte au ton et à l\'univers du jeu. Tu rédiges généralement 2 à 4 paragraphes pour un total de 400 mots maximum, en intégrant l\'apparence, l\'histoire, les croyances, les motivations et les failles du personnage.', ]; ================================================ FILE: lang/fr/bragi.php ================================================ [ 'generate' => 'Générer', 'insert' => 'Utiliser', ], 'errors' => [ 'invalid-sub' => 'Cette fonctionnalité est réservée aux abonnés de type Wyvern ou Elemental.', 'out-of-tokens' => 'Tu n\'as plus de jeton! Ils seront de retour le :date.', ], 'here' => 'ici', 'intro' => 'Salut! Moi c\'est :name, une intelligence artificielle développée pour t\'aider à générer des histoires pour tes personnages dans Kanka. Tu peux en savoir plus sur moi :here.', 'kankappy' => 'Cette personne est secrètement un/e disciple de Kankappy.', 'loading' => 'Donne moi un petit moment, je réfléchis à fond et ça peut me prendre jusqu\'à une minute!', 'placeholders' => [ 'prompt' => 'Fourni moi un peu d\'information pour que je génère une histoire.', ], 'token-limit' => 'Tu détiens actuellement :amount jetons. Un jeton est consommé lors de chaque génération d\'histoire, du coup utilise-les à bon escient!', ]; ================================================ FILE: lang/fr/calendars/weather.php ================================================ [ 'helper' => 'Ajouter des informations météo à apparaître sur le calendrier.', 'success' => 'Météo ajoutée.', 'title' => 'Nouvel effet météorologique', ], 'destroy' => [ 'success' => 'Météo supprimée.', ], 'edit' => [ 'success' => 'Météo modifiée.', 'title' => 'Modifier la météo', ], 'fields' => [ 'effect' => 'Effet', 'name' => 'Nom', 'precipitation' => 'Précipitation', 'temperature' => 'Température', 'weather' => 'Météo', 'wind' => 'Vent', ], 'options' => [ 'weather' => [ 'bolt' => 'Orage', 'cloud' => 'Nuageux', 'cloud-rain' => 'Pluie', 'cloud-showers-heavy' => 'Forte Pluie', 'cloud-sun' => 'Nuage et soleil', 'cloud-sun-rain' => 'Nuage, soleil et pluie', 'meteor' => 'Météorite', 'smog' => 'Brouillard', 'snowflake' => 'Neige', 'sun' => 'Ensoleillé', 'wind' => 'Venteux', ], ], 'placeholders' => [ 'effect' => 'Effet magique ou naturel', 'name' => 'Text optionnel pour l\'effet météorologique', 'precipitation' => 'Quantité de pluie', 'temperature' => 'Quotidien haut et bas', 'wind' => 'Force du vent', ], ]; ================================================ FILE: lang/fr/calendars.php ================================================ [ 'add_epoch' => 'Ajouter une époque', 'add_intercalary' => 'Ajouter des jours intercalaires', 'add_month' => 'Ajouter un mois', 'add_moon' => 'Ajouter une lune', 'add_reminder' => 'Ajouter un rappel', 'add_season' => 'Ajouter une saison', 'add_weather' => 'Effet météo', 'add_week' => 'Ajouter un nom de semaine', 'add_weekday' => 'Ajouter un jour de semaine', 'add_year' => 'Ajouter un nom d\'année', 'set_today' => 'Définir en tant que jour actuel', 'today' => 'Aujourd\'hui', 'update_weather' => 'Modifier la météo', ], 'checkboxes' => [ 'is_recurring' => 'A lieu chaque année', ], 'create' => [ 'title' => 'Nouveau Calendrier', ], 'edit' => [ 'today' => 'Date du calendrier mise à jour.', ], 'event' => [ 'create' => [ 'success' => 'Rappel ajouté.', 'title' => 'Ajouter un rappel', ], 'destroy' => 'Rappel retiré du calendrier \':name\'.', 'edit' => [ 'success' => 'Rappel modifié.', 'title' => 'Modifier un rappel pour :name', ], 'errors' => [ 'invalid_entity' => 'Choix invalide d\'entrée.', ], 'helpers' => [ 'other_calendar' => 'Modification d\'un rappel du calendrier :calendar.', ], 'success' => 'Rappel \':event\' ajouté au calendrier.', ], 'events' => [ 'bulks' => [ 'delete' => '{1} Supprimé :count rappel. |[2,*] Supprimé :count rappels.', 'patch' => '{1} Modifié :count rappel.|[2,*] Modifié :count rappels.', ], 'end' => '(fin)', 'filters' => [ 'show_after' => 'Afficher aujourd\'hui et après', 'show_all' => 'Afficher tout', 'show_before' => 'Afficher avant aujourd\'hui', ], 'start' => '(début)', ], 'fields' => [ 'comment' => 'Commentaire', 'current_day' => 'Jour Actuel', 'current_month' => 'Mois actuel', 'current_year' => 'Année actuelle', 'date' => 'Date actuelle', 'day' => 'Jour', 'default_layout' => 'Mise en page par défaut', 'format' => 'Format', 'is_incrementing' => 'Incrément de date', 'is_recurring' => 'Répétition', 'leap_year' => 'Années bissextiles', 'leap_year_amount' => 'Jours à ajouter', 'leap_year_month' => 'Mois', 'leap_year_offset' => 'Chaque', 'leap_year_start' => 'Année bissextile', 'length' => 'Jours', 'length_days' => ':count jour|:count jours', 'month' => 'Mois', 'months' => 'Mois', 'moons' => 'Lunes', 'parameters' => 'Paramètres', 'recurring_until' => 'Répétition jusqu\'à l\'année', 'reset' => 'Réinitialisation de semaine', 'seasons' => 'Saisons', 'show_birthdays' => 'Afficher les anniversaires', 'skip_year_zero' => 'Ignorer l\'année zéro', 'start_offset' => 'Décalage de début', 'suffix' => 'Suffix', 'week_names' => 'Nom de semaine', 'weekdays' => 'Jours de la semaine', 'year' => 'Année', ], 'helpers' => [ 'default_layout' => 'Choix de la mise en page par défaut du calendrier.', 'format' => 'Ajouter un format d\'affichage personnalisé pour les entrées du calendrier.', 'month_type' => 'Les mois intercalaires n\'utilisent pas les jours de la semaine, mais ont quand-même une influence sur les lunes et saisons.', 'moon_offset' => 'Par défaut, la première pleine lune apparait lors du premier jour de l\'année 0. Modifier ce champ permet de définir quand la première pleine lune apparaitra. Cette valeur peut être négative (jusqu\'à la durée du premier mois) ou positive (jusqu\'à la durée du premier mois).', 'start_offset' => 'Un calendrier commence par défaut le premier jour de la première semaine de l\'année 0. Modifier ce champ permet d\'influencer quand le premier jour tombe.', ], 'hints' => [ 'event_length' => 'La durée d\'un rappel. Un rappel ne peux pas durer plus de 2 mois.', 'is_incrementing' => 'Un calendrier avec cette option vera son jour actuel automatiquement avancer chaque jour à 00:00 UTC.', 'leap_year' => 'Définir les années bissextiles du calendrier.', 'months' => 'Le calendrier doit avoir au moins 2 mois.', 'moons' => 'Chaque lune sera affichée dans le calendrier lors de la pleine lune.', 'parent_calendar' => 'Définir un calendrier parent inclura les rappels et la météo du celui-ci.', 'reset' => 'Toujours commencer le début du mois sur le premier jour de la semaine.', 'seasons' => 'Les saisons seront affichées dans le calendrier lorsqu\'elles commencent.', 'show_birthdays' => 'Afficher les anniversaires chaque année pour les personnages qui ont une date de naissance définie.', 'skip_year_zero' => 'Par défaut, la première année du calendrier est l\'année zéro. Activer cette option pour ignorer l\'année zéro.', 'weekdays' => 'Un calendrier doit posséder au moins 2 jours dans la semaine.', 'weeks' => 'Les semaines importantes du calendrier peuvent avoir un nom.', 'years' => 'Certaines années sont tellement importantes qu\'elles ont leur propre nom.', ], 'layouts' => [ 'month' => 'Mois', 'monthly' => 'Mensuel par défaut', 'year' => 'Année', 'yearly' => 'Annuel par défaut', ], 'lists' => [ 'empty' => 'Créé un calendrier pour suivre les dates, les festivals ou les événements dans le jeu au fil du temps.', ], 'modals' => [ 'switcher' => [ 'title' => 'Changement d\'année', ], ], 'month_types' => [ 'intercalary' => 'Intercalaire', 'standard' => 'Standard', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Pleine lune', 'fullmoon_name' => ':moon pleine lune', 'month' => 'Chaque mois', 'newmoon' => 'Nouvelle lune', 'newmoon_name' => ':moon nouvelle lune', 'none' => 'Aucun', 'unnamed_moon' => 'Lune :number', 'year' => 'Chaque année', ], ], 'resets' => [ '' => 'Aucun', 'month' => 'Mois', 'year' => 'Année', ], ], 'panels' => [ 'intercalary' => 'Jours Intercalaires', 'leap_year' => 'Année bissextile', 'months' => 'Mois', 'weeks' => 'Semaines', 'years' => 'Nom d\'années', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Durée en jour', 'month' => 'A la fin de quel mois', 'name' => 'Nom de l\'intercalaire', ], 'month' => [ 'alias' => 'Alias de mois', 'length'=> 'Nombre de jours', 'name' => 'Nom du mois', 'type' => 'Type', ], 'moon' => [ 'fullmoon' => 'Pleine lune chaque (jours)', 'name' => 'Nom de la lune', 'offset' => 'Décalage de la première pleine lune', ], 'seasons' => [ 'day' => 'Jour de départ', 'month' => 'Mois de départ', 'name' => 'Nom de la saison', ], 'weeks' => [ 'name' => 'Nom de la semaine', 'number' => 'Nombre', ], 'year' => [ 'name' => 'Nom', 'number' => 'Année', ], ], 'placeholders' => [ 'colour' => 'Couleur', 'comment' => 'Anniversaire, festival, solstice', 'date' => 'La date actuelle', 'leap_year_amount' => 'Nombre de jours à ajouter lors d\'une année bissextile', 'leap_year_month' => 'Mois durant lequel les jours sont à ajouter', 'leap_year_offset' => 'Nombre d\'années entre chaque année bissextile', 'leap_year_start' => 'Première année bissextile', 'length' => 'Durée du rappel en jours', 'months' => 'Nombre de mois dans une année', 'recurring_until' => 'Année de dernière réoccurence (laisser vide pour infini)', 'seasons' => 'Nombre de saisons', 'suffix' => 'Suffix de l\'époque actuelle (AC, BC)', 'type' => 'Type de calendrier', 'weekdays' => 'Nombre de jours dans une semaine', ], 'show' => [ 'missing_details' => 'Le calendrier ne peut pas être affiché. Un calendrier a besoin d\'au moins 2 mois et de 2 jours de la semaine pour être affiché correctement.', 'moon_1first_quarter' => ':moon premier quartier', 'moon_full' => ':moon pleine lune', 'moon_last_quarter' => ':moon dernier quartier', 'moon_new' => ':moon nouvelle lune', 'tabs' => [ 'events' => 'Rappels', 'weather' => 'Météo', ], ], 'sorters' => [ 'after' => 'Aujourd\'hui et après', 'before'=> 'Aujourd\'hui et avant', ], 'validators' => [ 'format' => 'Le format de calendrier est invalid.', 'moon_offset' => 'Le décalage de début de lune ne peut pas être plus grand que la durée du premier mois du calendrier.', ], 'warnings' => [ 'event_length' => 'Les rappels de plusieurs années ne s\'affichent que durant les deux premières années. En savoir plus dans notre :documentation.', ], ]; ================================================ FILE: lang/fr/callouts.php ================================================ [ 'subscription' => 'En savoir plus sur les abonnements', 'upgrade' => 'Passer en premium', ], 'booster' => [ 'actions' => [ 'boost' => 'Boost :campaign', 'superboost' => 'Superboost :campaign', ], 'learn-more' => 'Que sont les boosters?', 'limitation' => 'Pour accéder à cette fonctionnalité, la campagne doit être boostée.', 'limitations' => [ 'boosted' => 'Pour accéder à cette fonctionnalité, la campagne doit être boostée.', 'superboosted' => 'Pour accéder à cette fonctionnalité, la campagne doit être superboostée.', ], 'multiple' => 'Pour accéder à cette fonctionnalité, la campagne doit être superboostée.', 'pitches' => [ 'element-class' => 'Cette élément peut être personnalisé avec une classe CSS dans une :boosted-campaign.', 'icon' => 'Débloques des milliers d\'icônes de FontAwesome avec une :boosted-campaign.', ], 'titles' => [ 'boosted' => 'Fonctionnalité boostée', 'superboosted' => 'Fonctionnalité superboostée', ], ], 'premium' => [ 'learn-more' => 'Qu\'est-ce qu\'une campagne Premium?', 'limitation' => 'Pour accéder à cette fonctionnalité, les fonctionnalités Premium doivent être activées pour :campaign.', 'multiple' => 'Pour accéder à ces fonctionnalités, les fonctionnalités Premium doivent être activées pour :campaign.', 'title' => 'Fonctionnalité de campagne Premium', 'unlock' => 'Débloquer les fonctionnalités Premium pour :campaign', ], 'subscribe' => [ 'pitch-image' => 'Abonnes-toi pour télécharger des fichiers jusqu\'à :max MB.', 'share-booster' => 'Boostes :campaign pour augmenter la taille de fichier pour tous les membres de la campagne.', 'share-premium' => 'Augmenter la taille de téléchargement des fichiers pour tous les membres de la campagne avec une campagne Premium.', ], ]; ================================================ FILE: lang/fr/campaigns/achievements.php ================================================ 'Félicitations!', 'connections' => '{0} Aucune relation créée|{1} Une relation créée|[2,*] :amount relations créées', 'created' => '{0} Aucun(e) :singular créé(e)|{1} Un :singular créé(e)|[2,*] :amount :plural créé(e)s', 'dead' => '{0} Aucun mort mystérieuse|{1} Une mort mystérieuse|[2,*] :amount morts mystérieuses', 'goal' => 'Objectif :number', 'goal_reached' => 'Succès débloqué', 'level' => 'niveau :number', 'markers' => '{0} Aucun marqueur de carte créé|{1} Un marqueur de carte créé|[2,*] :amount marqueurs de carte créés', 'painter' => '{0} Aucun thème créé|{1} Un thème créé|[2,*] :amount thèmes créés', 'pitch' => 'Les succès de campagne sont un moyen amusant de célébrer les étapes importantes de ton aventure dans Kanka. Suis tes progrès, implique tes joueurs et mets en valeur tes réalisations avec une campagne premium.', 'plugins' => '{0} Aucun plugin installé|{1} Un plugin installé|[2,*] :amount plugins installés', 'remaining' => [ 'generic' => 'de plus pour débloquer le niveau suivant.', ], 'spotlight' => [ 'active' => [ 'cta' => 'Voir la mise en avant', ], 'private' => [ 'cta' => 'Vérifier les paramètres publics', 'helper' => 'Rend ta campagne publique pour être éligible à la mise en avant.', ], 'public' => [ 'cta' => 'Découvrir le fonctionnement de la mise en avant', 'helper' => 'Les campagnes sélectionnées sont mises en avant sur la vitrine Kanka et le blog.', ], ], 'spotlighted' => '{0} Pas encore sélectionné|[1,*] Mis en avant', 'tagged' => '{0} Aucune entrée étiquetée|{1} Une entrée étiquetée|[2,*] :amount entrées étiquetées', 'titles' => [ 'calendars' => 'Gardien du temps', 'characters' => 'Donneur de nom', 'connections' => 'Cupidon', 'creatures' => 'Éleveur', 'dead' => 'Meurtrier', 'events' => 'Maître de l\'histoire', 'families' => 'Planification familiale', 'locations' => 'Constructeur', 'markers' => 'Cartographe', 'organisations' => 'Fusions et acquisitions', 'plugins' => 'Connaisseur en plugins', 'quests' => 'Mastermind', 'spotlighted' => 'Mis en avant', 'tags' => 'Sous contrôle', 'themes' => 'Peintre', ], 'tutorial' => 'Les succès suivent les actions importantes dans cette campagne, comme créer des entrées ou utiliser des fonctionnalités clés. Ils sont uniquement informatifs et se mettent à jour automatiquement pendant que tu explores et construis', ]; ================================================ FILE: lang/fr/campaigns/applications.php ================================================ [ 'accept' => 'Accepter', 'reject' => 'Décliner', ], 'apply' => [ 'apply' => 'Appliquer', 'help' => 'Cette campagne est ouverte à de nouveaux membre. Postules pour la rejoindre en remplissant ce formulaire. Une notification sera envoyée lorsque les administrateurs de la campagne examine ta candidature.', 'remove_text' => 'ta candidature', 'success' => [ 'apply' => 'Ta candidature a été enregistrée. Tu peux la modifier ou l\'annuler à tout moment. Une notification sera envoyée lorsque les administrateurs de la campagne l\'examine.', 'remove'=> 'Ta candidature a été retirée.', 'update'=> 'Ta candidature a été modifiée. Tu peux toujours la modifier ou l\'annuler à tout moment. Une notification sera envoyée lorsque les administrateurs de la campagne l\'examine.', ], 'title' => 'Rejoindre :name', ], 'dashboard_widget' => [ 'add' => 'Ajouter un widget', 'has_widget' => 'Le widget de candidature est sur le tableau de bord.', 'has_widget_help' => 'Le tableau de bord de ta campagne présente le widget de candidature aux joueurs potentiels.', 'no_widget' => 'Pas de widget de candidature sur le tableau de bord.', 'no_widget_help' => 'Ta campagne est ouverte mais ne le montre pas sur le tableau de bord. Ajoute un widget pour que les joueurs puissent la découvrir et postuler.', 'success' => 'Widget de candidature ajouté au tableau de bord.', 'title' => 'Widget du tableau de bord', ], 'experience' => [ 'intermediate' => 'Intermédiaire (Quelques parties)', 'new' => 'Débutant (Première fois)', 'veteran' => 'Vétéran (Années d\'expérience)', ], 'fields' => [ 'additional_notes' => 'Autre chose', 'application' => 'Application', 'availability_days' => 'Jours disponibles', 'character_concept' => 'Concept de personnage', 'experience_level' => 'Niveau d\'expérience', 'external_link' => 'Fiche de personnage / Lien', 'intro' => 'Introduction de la campagne', 'new_application' => 'Nouvelle candidature de joueur', 'player_count' => 'Nombre de joueurs', 'playstyle_tags' => 'Styles de jeu', 'pref_rp_combat' => 'Équilibre', 'pref_tone' => 'Préférence de ton', 'reason' => 'Motif d\'approbation / de rejet', 'schedule' => 'Planning', 'schedule-placeholder' => 'Chaque vendredi à 19h', 'time_end' => 'À', 'time_start' => 'De', 'timezone' => 'Fuseau horaire', ], 'filters' => [ 'all' => 'Tout afficher', 'approved' => 'Afficher les approuvées', 'pending' => 'Afficher les en attente', 'rejected' => 'Afficher les refusées', 'title' => 'Filtres', ], 'headers' => [ 'availability' => 'Disponibilité et planning', 'preferences' => 'Préférences de style de jeu', ], 'helpers' => [ 'applications_closed' => 'Ta campagne est publique, mais les nouveaux joueurs ne peuvent pas postuler tant que le statut n\'est pas sur "Ouvert".', 'availability_days' => 'Sélectionne les jours où tu es généralement disponible pour jouer.', 'experience_level' => 'Quel est ton niveau de familiarité avec ce système de jeu?', 'external_link' => 'Lien vers D&D Beyond, Google Docs ou autres fiches de personnage externes.', 'fill_setup' => 'Remplis le formulaire de configuration publique pour pouvoir ouvrir ta campagne au public.', 'filters_incomplete' => 'Ta campagne est ouverte aux candidatures, mais tu n\'as pas terminé la configuration des filtres (système, fuseau horaire ou tags). Les compléter facilitera la recherche pour les bons joueurs.', 'modal' => 'Une campagne qui est ouverte aux candidatures et qui est publique peut avoir des utilisateurs demander de joindre la campagne.', 'no_applications_title' => 'Aucune application trouvée', 'no_applications_v2' => 'Il n\'y a actuellement aucune demande en attente pour rejoindre la campagne, mais tu peux aider les joueurs à trouver leur prochaine aventure! Des informations détaillées et des filtres de recherche facilitent la découverte de ton histoire.', 'reason' => 'Si une raison est fournie, le/la demandeur en sera informé.', 'role' => 'En cas d\'approbation, le rôle auquel le candidat est ajouté.', ], 'labels' => [ 'casual' => 'Casual / Détente', 'combat_focused' => 'Axé combat', 'rp_heavy' => 'RP intensif', 'serious' => 'Sérieux / Immersif', ], 'open' => [ 'closed' => 'La campagne est fermée', 'open' => 'La campagne est ouverte', 'title' => 'Campagne ouverte', ], 'placeholders' => [ 'additional_notes' => 'Déclencheurs, limites strictes ou questions spécifiques.', 'character_concept' => 'Décris brièvement qui tu veux jouer, son histoire et comment il s\'intègre dans le monde.', 'intro' => 'Une brève explication de ta campagne, affichée en haut du formulaire de candidature.', 'note' => 'Ecris ta candidature pour rejoindre la campagne.', 'player_count' => '4-6 joueurs', 'reason' => 'Ta raison', ], 'public' => [ 'private' => 'La campagne est privée', 'public' => 'La campagne est publique', 'title' => 'Campaign publique', ], 'setup' => [ 'done' => 'Paramètres de campagne publique remplis.', 'prioritise' => 'Prioriser cette campagne', 'prioritise_conflict' => 'Tu ne peux prioriser qu\'une campagne à la fois. Désactive la priorisation sur :campaign d\'abord.', 'prioritise_help' => 'Les campagnes priorisées apparaissent en haut de la liste des campagnes publiques, devant les autres campagnes ouvertes.', 'prioritise_upgrade' => 'Cette fonctionnalité est réservée aux abonnés :link.', 'prioritised' => 'Campagne priorisée', 'setup' => 'Configure les paramètres de campagne publique pour ouvrir ta campagne au public.', 'success' => 'Configuration sauvegardée. Une fois tous les champs remplis, la campagne pourra être ouverte au public.', 'success_complete' => 'La campagne peut être ouverte au public!', 'title' => 'Configuration de campagne publique', 'tutorial' => 'La campagne ne pourra être ouverte au public que lorsque tous ces champs seront remplis.', ], 'timezone' => 'Fuseau horaire et langue', 'title' => 'Candidatures', 'toggle' => [ 'closed' => 'Fermé aux candidatures', 'label' => 'Status', 'open' => 'Ouvert aux candidatures', 'success' => 'Le status de candidature de la campagne a été modifié.', 'success_open' => 'La campagne est maintenant publique et les utilisateurs peuvent postuler.', 'title' => 'Status des candidatures', ], 'tutorial' => 'Les candidatures permettent aux gens de demander l\'accès à cette campagne. Les candidats remplissent un court formulaire, et les admins peuvent examiner, accepter ou refuser chaque demande. Les utilisateurs approuvés sont ajoutés à la campagne avec le rôle que tu leur attribues pendant la revue', 'update' => [ 'approve' => 'Sélectionner le rôle auquel l\'utilisateur sera ajouté à la campagne.', 'approved' => 'Candidatures approuvée.', 'reject' => 'Ecrire une raison optionnelle pourquoi la candidature est rejetée.', 'rejected' => 'Candidature déclinée.', ], 'warnings' => [ 'applications_closed' => 'Les candidatures sont actuellement fermées.', 'filters_incomplete' => 'Les filtres de la campagne sont incomplets.', ], 'weekdays' => [ 'fri' => 'Ven', 'mon' => 'Lun', 'sat' => 'Sam', 'sun' => 'Dim', 'thu' => 'Jeu', 'tue' => 'Mar', 'wed' => 'Mer', ], ]; ================================================ FILE: lang/fr/campaigns/builder.php ================================================ 'Construit visuellement un thème pour la campagne à l\'aide de cette interface. Fais défiler l\'écran vers le bas pour voir l\'impact des changements sur les différents éléments de la campagne. Lorsqu\'une couleur est sélectionnée, une couleur "contrastante" est automatiquement sélectionnée pour colorer le texte. Pour en savoir plus sur les thèmes, consultes notre :docs.', 'pitch' => 'Hey, nous avons un constructeur de thème si tout ce que tu veux faire est de changer quelques couleurs de la campagne 😉', 'pitch-go' => 'Aller au constructeur de thème', 'reset' => 'Le thème a été réinitialisé.', 'success' => 'Le thème a été enregistré.', 'title' => 'Constructeur de thème', ]; ================================================ FILE: lang/fr/campaigns/categories.php ================================================ [ 'permission-disabled' => 'Cette catégorie est désactivée.', ], 'helpers' => [ 'aliases' => 'Ajouter des alias et des identrées secrètes aux entrées du monde.', 'media' => 'Télécharger des fichiers média (images, pdfs, audio) et des liens externes vers les entrées.', ], 'tab' => 'Catégories', ]; ================================================ FILE: lang/fr/campaigns/dashboard-header.php ================================================ [ 'success' => 'Entête de campagne modifié.', 'title' => 'Modifier l\'entête de campagne', ], ]; ================================================ FILE: lang/fr/campaigns/default-images.php ================================================ [ 'add' => 'Ajouter une nouvelle image par défaut', ], 'call-to-action' => 'Définis des vignettes personnalisée pour tous les personnages, lieux ou autres entrées de la campagne. Ces images sont ensuite affichées sur les différentes listes.', 'create' => [ 'error' => 'Problème lors de la sauvegarde. Est-ce que :type est déjà créé?', 'helper' => 'Ajouter une image qui sera utilisée comme vignette par défaut pour les entrées du type sélectionné.', 'success' => 'Image par défaut pour :type créée.', 'title' => 'Nouvelle image par défaut', ], 'destroy' => [ 'success' => 'Image par défaut pour :type retirée.', ], 'empty' => 'Aucune catégorie ne possède actuellement une image par défaut.', 'helper' => 'Utilisée pour toutes les entrées sans image de cette catégorie.', 'reset' => [ 'helper' => 'Tu es sûr de vouloir retirer toutes les images de remplacement?', 'success' => 'Toutes les images de remplacement ont été retirées avec succès.', 'title' => 'Réinitialiser les images de remplacement', 'warning' => 'Cette action est permanente et ne peut pas être annulée.', ], 'title' => 'Images de remplacement', 'tutorial' => 'Définis des images par défaut pour les entrées sans image personnalisée. Ces vignettes apparaissent immédiatement dans toute la campagne et gardent les listes visuellement cohérentes.', ]; ================================================ FILE: lang/fr/campaigns/defaults.php ================================================ [ 'character_personality_visibility' => 'Visibilité par défaut de la personnalité des personnages', 'connections' => 'Vue des connexions d\'entrée', 'connections_mode' => 'Style de la carte des connexions', 'descendants' => 'Filtrage par défaut des sous-listes', 'entity_privacy' => 'Visibilité des nouvelles entrées', 'gallery_visibility' => 'Visibilité par défaut des images de la galerie', 'post_collapsed' => 'Mise en page des nouveaux articles', 'private_mention_visibility' => 'Mentions d\'entrées privées', 'related_visibility' => 'Visibilité du contenu lié', ], 'helpers' => [ 'character_visibility' => 'Définit la visibilité des traits de personnalité quand tu crées des personnages', 'connections' => 'Choisis si les pages de connexions d\'entrées affichent par défaut une carte visuelle ou une liste', 'connections_mode' => 'Définit le style de mise en page par défaut des cartes de connexions (premium)', 'descendants' => 'Quand tu consultes les sous-listes d\'une entrée (comme les personnages d\'un lieu), choisis d\'afficher seulement les enfants directs ou tous les descendants', 'display' => 'Définis les options d\'affichage par défaut des pages d\'entrée', 'entity' => 'Contrôle la visibilité que Kanka applique automatiquement au nouveau contenu', 'entity_privacy' => 'Définit la visibilité des nouveaux personnages, lieux, etc', 'gallery_visibility' => 'Valeur de visibilité par défaut pour les images que tu ajoutes à la galerie', 'post_collapsed' => 'Quand tu crées des articles, définis s\'il est réduit ou développé', 'privacy' => 'Définis les visibilités par défaut du nouveau contenu. Ces réglages s\'appliquent quand tu crées du contenu et peuvent être changés pour chaque élément', 'private_mention_visibility' => 'Quand tu mentionnes une entrée privée dans du contenu visible, choisis si son nom apparaît ou non', 'related_visibility' => 'Contrôle la visibilité des articles, propriétés et relations ajoutés aux entrées', ], 'sections' => [ 'display' => 'Affichage par défaut des entrées', 'entity' => 'Valeurs par défaut des entrées', 'media' => 'Valeurs par défaut des médias', 'mention' => 'Comportement des mentions', ], 'tutorial' => 'Optimise la création de contenu avec des réglages par défaut malins. Choisis les visibilités par défaut pour les entrées, articles, images et autres contenus. Ces préférences s\'appliquent automatiquement quand tu crées du contenu, te faisant gagner du temps tout en gardant ta campagne organisée', 'update' => [ 'success' => 'Valeurs par défaut de la campagne mises à jour', ], 'values' => [ 'collapsed' => [ 'collapsed' => 'Réduit', 'default' => 'Par défaut', 'expanded' => 'Développé', ], 'connections' => [ 'explorer' => 'Carte des connexions (premium)', 'list' => 'Interface en liste', ], 'descendants' => [ 'all' => 'Afficher tous les descendants par défaut', 'direct' => 'Afficher les descendants directs par défaut', ], 'mentions' => [ 'private' => 'Masquer le nom de la cible', 'visible' => 'Afficher le nom de la cible', ], ], ]; ================================================ FILE: lang/fr/campaigns/delete.php ================================================ 'sauvegade', 'confirm' => 'Si tu es sûr de vouloir supprimer définitivement :campagne, écrit :code dans le champ ci-dessous.', 'confirm-button' => 'Supprimer définitivement :name', 'helper' => 'La suppression d\'une campagne est une action permanente qui ne peut être annulée. Cette action supprimera de nos serveurs toutes les données relatives à la campagne, y compris les images et les ressources. Nous te recommendons de faire une :backup avant de continuer.', 'issue' => 'Le problème suivant doit être résolu avant que la campagne puisse être supprimée.', 'members' => 'Tous les autres membres doivent être retirés de la campagne.', 'success' => ':name a été définitivement supprimé.', 'title' => 'Suppression', ]; ================================================ FILE: lang/fr/campaigns/export.php ================================================ [ 'download' => 'Télécharger', 'export' => 'Exporter la campagne', ], 'confirm' => [ 'notification' => 'Les membres du rôle :admin seront notifiés lorsque l\'export sera prêt à être téléchargé.', 'title' => 'Confirmation d\'export', 'type' => 'Type d\'export', 'warning' => 'Tu es sur le point d\'exporter les données de la campagne. Ce processus peut prendre beaucoup de temps selon la taille de la campagne. Tu peux continuer à utiliser Kanka pendant que nos serveurs génèrent l\'export.', ], 'errors' => [ 'limit' => 'La campagne a déjà été exportée une fois aujourd\'hui. Exporter la campagne sera possible de nouveau demain.', 'premium' => 'L\'export Markdown est une fonctionnalité réservée aux campagnes premium', ], 'expired' => 'Liens expiré', 'helpers' => [ 'json' => 'Pour sauvegarder et restaurer — peut être utilisé comme import de campagne', 'markdown' => 'Pour partager et lire — format lisible par un humain', 'premium' => 'Seulement disponible avec une campagne premium.', ], 'progress' => 'Progrès', 'size' => 'Taille', 'status' => [ 'failed' => 'Échoué', 'finished' => 'Terminé', 'running' => 'En cours', 'scheduled' => 'Programmé', ], 'success' => 'L\'export de la campagne a été mis en file d\'attente pour traitement. Tous les membres du rôle :admin seront notifiés une fois que le fichier sera prêt à être téléchargé.', 'title' => 'Export', 'type' => 'Type', 'types' => [ 'json' => 'JSON', 'md' => 'Markdown', ], ]; ================================================ FILE: lang/fr/campaigns/gallery.php ================================================ [ 'close' => 'Fermer', 'file-link' => 'Lien de fichier', 'focus_point' => 'Définir le centrage', 'image-link' => 'Lien d\'image', 'reset_focus' => 'Réinitialiser le centrage', 'save' => 'Enregistrer', 'upgrade' => 'Augmenter l\'espace de stockage', ], 'breadcrumb' => 'Galerie', 'bulk' => [ 'destroy' => [ 'confirm' => 'Es-tu sûr de vouloir supprimer définitivement les éléments sélectionnés? Cette action est permanente.', 'success' => '{0}Aucun fichier supprimé.|{1}Un fichier supprimé.|{2,*} :count les fichiers supprimés.', ], ], 'cta' => 'Gérer et réutiliser des images à travers la campagne.', 'destroy' => [ 'folder' => 'Dossier :name supprimé.', 'success' => 'Image :name supprimée.', ], 'errors' => [ 'max' => 'Prière de ne que sélectionner jusqu\'à :count fichiers à la fois.', 'permissions' => 'Tes rôles de campagne n\'ont pas la permission :permission pour pouvoir télécharger des images à la galerie de la campagne.', 'storage' => 'Il n\'y a pas assez d\'espace de stockage pour télécharger les images sélectionnées. Espace de stockage disponible: :available.', ], 'fields' => [ 'created_by' => 'Téléchargé par', 'details' => 'Détails', 'ext' => 'Ext', 'file_type' => 'Type de fichier', 'folder' => 'Dossier', 'image_mentioned_in' => '{0} Mentionné comme image sur aucune entrée.|{1} Mentionné comme image sur une entrée.|[2,*] Mentionné comme image sur :count entrées.', 'image_used_in' => '{0} Utilisé comme image sur aucune entrée.|{1} Utilisé comme image sur une entrée.|[2,*] Utilisé comme image sur :count entrées.', 'link' => 'Lien', 'name' => 'Nom', 'size' => 'Taille', 'unused' => 'N\'est utilisé nulle part', 'used_in' => 'Utilisé dans', ], 'focus' => [ 'locked' => 'Définir le point focal d\'une image requiert une campagne premium.', 'removed' => 'Centrage de l\'image retiré.', 'updated' => 'Centrage de l\'image mis à jour.', ], 'new_folder' => [ 'title' => 'Nouveau dossier', ], 'no_folder' => 'Sans dossier', 'pitch' => 'Télécharge des images dans la galerie de la campagne directement depuis l\'éditeur de text.', 'placeholders' => [ 'search' => 'Recherche d\'image...', ], 'storage' => [ 'of' => 'de', 'title' => 'Espace', ], 'title' => 'Galerie de la campagne :campaign', 'update' => [ 'folder' => 'Dossier modifié', 'success' => 'Image modifiée.', ], 'uploader' => [ 'add' => 'Ajouter', 'new_folder' => 'Nouveau dossier', 'or' => 'ou', 'select_file' => 'Sélectionner un fichier', 'well' => 'Déposer le fichier à télécharger', ], ]; ================================================ FILE: lang/fr/campaigns/import.php ================================================ [ 'import' => 'Envoyer l\'export', ], 'csv' => [ 'continue' => 'Continuer', 'fields_helper' => 'Sélectionne une colonne à assigner à chacun des champs de l\'entrée.', 'no_preview' => 'Aucune donnée d\'aperçu disponible', 'preview' => 'Aperçu', 'select_module' => 'Sélection de catégorie', 'select_one' => 'Sélectionner', 'selected_tags' => 'Tags sélectionnés', 'set_column' => 'Définir la colonne', 'set_fields' => 'Définir les champs', 'submit' => 'Soumettre l\'import CSV', 'traits' => 'Traits de personnage', 'traits_helper' => 'Tu peux ajouter des traits aux personnages. L\'en-tête sélectionné sera utilisé comme nom du trait, et la valeur correspondante comme valeur du trait.', 'type_helper' => 'Sélectionne la catégorie dans laquelle tu veux importer les nouvelles entrées.', 'validation_error' => 'Au moins 1 colonne doit être entièrement remplie', ], 'description_v2' => 'Importe des entrées, articles, propriétés, galeries et autres données depuis un export de campagne ou de nouvelles entrées depuis un fichier .CSV dans cette campagne. L\'import s\'exécute en arrière-plan et peut prendre du temps. Toi et les autres administrateurs serez notifiés quand il sera terminé.', 'fields' => [ 'file_v2' => 'Fichier CSV ou fichier ZIP d\'export', 'updated' => 'Dernière modification', ], 'form' => 'Formulaire d\'upload', 'limitation_v2' => 'Seuls les fichiers zip et csv sont acceptés. Max :size.', 'progress' => [ 'uploading' => 'Téléchargement', ], 'status' => [ 'failed' => 'Echoué', 'finished' => 'Terminé', 'invalid' => 'Données invalides', 'processing' => 'En cours de traitement', 'queued' => 'Programmé', 'ready' => 'Prêt pour le mapping', 'running' => 'En cours', 'validating' => 'Validation en cours', ], 'subscription' => [ 'pitch' => 'Restaure une sauvegarde de campagne ou importe depuis une autre campagne. Disponible avec les abonnements :wyvern ou :elemental.', ], 'title' => 'Import', ]; ================================================ FILE: lang/fr/campaigns/invites.php ================================================ [ 'helper' => 'Créer un lien d\'invitation à envoyer à tes joueurs, afin qu\'ils puissent participer à la campagne.', ], ]; ================================================ FILE: lang/fr/campaigns/limits.php ================================================ 'Les campagnes gratuites peuvent avoir jusqu\'à :limit :thing. Les campagnes premium ont des :thing illimités, ou tu peux en supprimer un pour libérer de la place.', 'title' => 'Limite atteinte', ]; ================================================ FILE: lang/fr/campaigns/logs.php ================================================ [ 'list' => 'Nous gardons une trace de tous les changements principaux apportés à la campagne pendant :amount jours. Ces journaux ne détaillent pas les modifications individuelles, mais plutôt l\'état général de la campagne.', 'nothing' => 'Il n\'y a aucun journal à afficher. Garde à l\'esprit que les journaux ne sont conservés que pendant :amount jours.', 'title' => 'Aucun journal', ], 'pitch' => 'Suis les changements généraux apportés à la campagne pendant :amount jours grâce à une campagne premium.', 'premium' => [ 'helper' => 'Les journaux datant de plus de :amount jours ne peuvent être consultés qu\'avec une campagne premium.', ], 'title' => 'Journal d\'audit', ]; ================================================ FILE: lang/fr/campaigns/members.php ================================================ [ 'limited' => ':amount de membres :total.', 'title' => 'Membres disponibles', 'unlimited' => ':amount de membres illimités.', ], 'roles' => [ 'admin' => 'Tu ne peux pas ajouter directement des utilisateurs au rôle :admin ici. Cela se fait dans l’interface du rôle :admin.', 'helper' => 'Ajouter ou retirer des rôles au membre :user.', 'success' => 'Rôles modifiés pour :user.', 'title' => 'Modification de rôles de membre', ], ]; ================================================ FILE: lang/fr/campaigns/modules.php ================================================ [ 'create' => 'Créer une catégorie', 'customise' => 'Personnaliser', ], 'create' => [ 'helper' => 'Créer une nouvelle catégorie personnalisée pour organiser des entrées qui n\'entrent pas dans les autres catégories.', 'success' => 'Nouvelle catégorie créée.', 'title' => 'Nouvelle catégorie', ], 'delete' => [ 'confirm' => 'Saisir :code pour confirmer la suppression permanente de la catégorie personnalisée :name.', 'confirm-button' => '{0} Supprimer définitivement :name|{1} Supprimer définitivement :name et :count entrée|[2,] Supprimer définitivement :name et :count entrées', 'entities' => '{1} Ceci supprimera définitivement :count entrée.|[2,] Ceci supprimera définitivement :count entrées.', 'helper' => 'Es-tu sûr de vouloir supprimer la catégorie :name? Ceci supprimera de manière permanente toutes les entrées, favoris, et widgets lié à celle-ci.', 'success' => 'Catégorie :name supprimée.', 'title' => 'Suppression de catégorie', ], 'errors' => [ 'disabled' => 'La catégorie :name est désactivé. :fix', 'empty-custom' => 'Ajoute des catégories personnalisées pour organiser les données qui ne rentrent pas dans ceux par défaut.', 'limit' => 'Il n\'est actuellement que possible de créer :max catégories personnalitées pendant qu\'on termine de construire cette nouvelle fonctionnalité.', 'limit-title' => 'Limite des catégories personnalisées atteinte.', 'subscription-limit' => 'Tu a atteint le nombre maximal de catégories personnalisées disponibles. La personne qui débloque les fonctionnalités premium peut souscrire à un abonnement supérieur pour augmenter cette limite.', ], 'fields' => [ 'icon' => 'Icône de la catégorie', 'image' => 'Image de remplacement', 'plural' => 'Nom au pluriel de la catégorie', 'singular' => 'Nom au singulier de la catégorie', 'status' => 'Status de la catégorie', 'update_name' => 'Renommer le favori avec le nouveau nom de la catégorie', ], 'helpers' => [ 'custom' => 'Ceci est une catégorie personnalisée.', 'icon' => 'L\'icône :fontawesome, par example :example.', 'plural' => 'Le pluriel des entrées de de la catégorie. Par example, potions.', 'roles' => 'Sélection de rôle qui ont la permission de voir les entrées de cette nouvelle catégorie. Ceci peut être modifié plus tard dans les permissions des rôles.', 'singular' => 'Le singulier d\'une entrée de cette nouvelle catégorie. Par example, potion.', 'status' => 'Les catégories désactivées sont cachées de la navigation et des menus. Aucune donnée n\'est supprimée.', 'tutorial' => 'Les catégories contrôlent quelles fonctionnalités sont visibles dans la campagne. Active celles que tu utilises et masque les autres. Désactiver une catégorie ne supprime jamais de données; ça les enlève juste de la navigation et des menus de création', ], 'pitch' => 'Renommer et changer l\'icône associée à cette catégorie pour l\'ensemble de la campagne.', 'pitch-custom' => 'Créer des catégories personnalisées pour organiser des entrées uniques.', 'pitch-title' => 'Débloque les catégories personnalisées', 'rename' => [ 'helper' => 'Modifier le nom et l\'icône de la catégorie tout au long de la campagne. Laisser vide pour utiliser le nom par défaut de Kanka.', 'success' => 'Catégorie personnalisée.', 'title' => 'Personnaliser la catégorie :module', ], 'reset' => [ 'default' => 'Ceci réinitialisera que les catégories par défaut, pas les catégorie personnalisées.', 'success' => 'Les catégories ont été réinitialisé.', 'title' => 'Réinitialiser des noms et icônes personnalisés des catégories', 'warning' => 'Es-tu ous sûr de vouloir rétablir les noms et icônes d\'origine des catégorie?', ], 'sections' => [ 'custom' => 'Catégories personnalisées', 'default' => 'Catégories par défaut', 'early-access' => 'Accès anticipé', 'features' => 'Fonctionnalités', ], 'states' => [ 'disable' => 'Désactiver', 'disabled' => 'La catégorie est désactivée', 'enable' => 'Activer', 'enabled' => 'La catégorie est activée', ], 'status' => [ 'enabled' => 'Catégorie activée', ], ]; ================================================ FILE: lang/fr/campaigns/overview.php ================================================ [ 'title' => 'Abonnés', ], 'member' => [ 'title' => 'Adhésion', ], 'premium' => [ 'enable' => 'Activer les fonctions premium', ], 'status' => [ 'title' => 'Visibilité', ], ]; ================================================ FILE: lang/fr/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'Désactiver les plugins', 'enable' => 'Activer les plugins', 'update' => 'Mettre à jour les plugins', ], 'changelog' => 'Changements', 'disable' => 'Désactiver le plugin', 'enable' => 'Activer le plugin', 'find-plugins' => 'Trouver des plugins', 'import' => 'Importer', 'update' => 'Mettre à jour le plugin', 'update-to' => 'Mettre à jour à la version :version', 'update_available' => 'Mise à jour disponible!', ], 'bulks' => [ 'delete' => '{1} :count plugin retiré.|[2,*] :count plugins retirés.', 'disable' => '{1} :count plugin désactivé.|[2,*] :count plugins désactivés.', 'enable' => '{1} :count plugin activé.|[2,*] :count plugins activés.', 'update' => '{1} :count plugin mis à jour.|[2,*] :count plugins mis à jour.', ], 'destroy' => [ 'success' => 'Le plugin :plugin a été retiré.', ], 'disabled' => [ 'success' => 'Le plugin :plugin a été désactivé.', ], 'empty_list' => 'La campagne n\'a actuellement aucun plugin. Visiter la bibliothèque de plugins pour ajouter des plugins à la campagne, et revenir ici pour les activer.', 'enabled' => [ 'success' => 'Le plugin :plugin a été activé.', ], 'errors' => [ 'invalid_plugin' => 'Plugin invalide.', ], 'fields' => [ 'name' => 'Nom du plugin', 'obsolete' => 'Ce plugin a été marqué comme étant obsolète par l\'équipe de Kanka, indiquant qu\'il ne fonctionne plus comme prévu originalement par le/la créateur-rice.', 'status' => 'Status', 'type' => 'Type de plugin', ], 'import' => [ 'button' => 'Importer', 'created' => 'Les entrées suivantes ont été créées:', 'fields' => [ 'only_new' => 'Seulement les nouvelles entrées', 'private' => 'Entrées privées', ], 'helper' => ':count entrées du plugin :plugin seront importées. Si ce plugin a déjà été importé dans le passé, les changements fait aux entrées déjà importées peuvent être perdus.', 'no_new_entities' => 'Il n\'y a pas de nouvelles entrées à importer.', 'option_only_import' => 'Seulement importer les nouvelles entrées, et ignorer les entrées déjà précédemment importées.', 'option_private' => 'Importer toutes les entrées comme privées.', 'success' => '{1} Importé :count entrée du plugin :plugin.|[2,*] Importé :count entrées du plugin :plugin.', 'title' => 'Importer :plugin', 'updated' => 'Les entrées suivantes ont été modifiées:', ], 'info' => [ 'description' => 'Affichage des dernières mises à jour du plugin :plugin.', 'helper' => 'Dès qu\'un plugin a une nouvelle version, tu peux mettre à jour le plugin à la version la plus récente.', 'installed' => 'Installé', 'title' => 'Mises à jour du plugin :plugin', 'updates' => 'Mises à jour', 'versions' => 'Versions', ], 'pitch' => 'Installes et gères les plugins du :marketplace.', 'status' => [ 'always' => 'Ce type de plugin est toujours actif, sauf s\'il est supprimé.', 'disabled' => 'Désactivé', 'enabled' => 'Activé', ], 'templates' => [ 'name' => ':name de :user', ], 'title' => 'Plugins de la campagne :name', 'types' => [ 'attribute' => 'Character Sheet', 'pack' => 'Content Pack', 'theme' => 'Thème', ], 'update' => [ 'success' => 'Le plugin :plugin a été mis à jour.', ], ]; ================================================ FILE: lang/fr/campaigns/public.php ================================================ [ 'new' => 'Rends-la publique pour que la communauté puisse la découvrir, ou garde-la privée pour les membres invités uniquement.', 'permissions' => 'Rendre ta campagne publique ne partage pas automatiquement le contenu. Configure ce que les visiteurs publics peuvent voir dans les réglages du rôle :public.', ], 'title' => 'Changer la visibilité de la campagne', 'update' => [ 'private' => 'La campagne est dorénavant privée, et seuls ses membres y ont accès.', 'public' => 'La campagne est dorénavant publique. Ce changement peut prendre un peu de temps pour apparaître dans les :public-campaigns.', 'unlisted' => 'La campagne n\'est plus répertoriée. Toute personne avec le lien peut y accéder, mais elle n\'apparaît pas sur la page :public-campaigns.', ], ]; ================================================ FILE: lang/fr/campaigns/recovery.php ================================================ [ 'recover' => 'Récupérer', 'recover_selected' => 'Récupérer la sélection', ], 'error' => 'Une erreur est survenue lors de la récupération.', 'fields' => [ 'deleted' => 'Supprimé', 'deleted_at' => 'Supprimé le :date par :user', ], 'name_link' => ':name récupéré', 'order' => [ 'newest' => 'Ordonner par: Récent', 'newest_first' => 'Plus récent en premier', 'oldest' => 'Ordonner par: Ancienté', 'oldest_first' => 'Plus ancien en premier', 'type' => 'Ordonner par: Type', 'type_order' => 'Type', ], 'premium' => 'La récupération d\'éléments est une fonctionnalité de campagne premium.', 'success_v2'=> '{1} :count élément a été récupéré.|[2,*] :count éléments ont été récupérés.', 'title' => 'Récupération d\'éléments supprimés', 'tutorial' => 'Affiche et restaure les éléments récemment supprimés de la campagne. Les entrées, article et autres données associées peuvent être récupérés pendant :amount jours avant d\'être supprimés définitivement. Restaurer un élément le remet avec toutes ses données intactes.', ]; ================================================ FILE: lang/fr/campaigns/roles.php ================================================ [ 'status' => 'Status: :status', ], 'create' => [ 'helper' => 'Créer un nouveau rôle pour la campagne.', ], 'overview' => [ 'limited' => ':amount de :total rôles créés.', 'title' => 'Rôles disponibles', 'unlimited' => ':amount de rôles illimités créés.', ], 'permissions' => [ 'campaign-features' => 'Fonctionnalités de campagne', 'content-modules' => 'Contenu de catégories', 'toggle' => [ 'action' => 'Tout cocher', 'tooltip' => 'Cocher la permission :action pour toutes les catégorie.', ], ], 'public' => [ 'helpers' => [ 'click' => 'Clique sur n\'importe quelle catégorie pour activer ou désactiver l\'accès public à toutes les entrées qu\'elle contient', 'intro' => 'Contrôle ce que les non-membres peuvent voir dans la campagne', 'main' => 'Sélectionne quelles catégories sont visibles pour toute personne qui consulte la campagne, qu\'elle soit connectée ou non. Ça inclut les visiteurs publics et les utilisateurs de Kanka qui ne sont pas membres de la campagne', 'preview' => 'Aperçu en tant que non-membre', ], ], 'show' => [ 'title' => 'Permissions :role - :campaign', ], 'toggle' => [ 'disabled' => 'Les membre du rôle :role ne peuvent plus :action les :entities.', 'enabled' => 'Les membre du rôle :role peuvent maintenant :action les :entities.', ], 'warnings' => [ 'adding-to-admin' => 'Les membres du rôle :name ont accès à tous les éléments de la campagne, et ne peuvent pas être retiré par d\'autres membres du rôle. Après :amount minutes, seules le/la membre peut se retirer du rôle.', ], ]; ================================================ FILE: lang/fr/campaigns/share.php ================================================ [ 'change_visibility' => 'Changer la visibilité', 'copy' => 'Copier le lien', 'copy_public_link' => 'Copier le lien public', 'make_public' => 'Rendre public et copier le lien', ], 'helpers' => [ 'private_explanation' => 'Seuls les membres peuvent accéder aux campagnes privées.', 'public_explanation' => 'Cette campagne est publique. N\'importe qui avec le lien peut la parcourir.', 'unlisted_explanation' => 'N\'importe qui avec le lien peut parcourir cette campagne, mais elle n\'apparait pas dans les répertoires publics.', ], 'labels' => [ 'member_link' => 'Seuls les membres peuvent ouvrir ceci', 'public_link' => 'Lien public', ], 'status' => [ 'private' => 'Campagne privée', 'public' => 'N\'importe qui avec le lien peut voir cette campagne', ], 'success' => [ 'copied_members' => 'Lien réservé aux membres copié.', 'copied_public' => 'Lien public copié, n\'importe qui avec le lien peut voir.', 'made_public' => 'La campagne est maintenant publique.', ], 'title' => 'Partager la campagne', ]; ================================================ FILE: lang/fr/campaigns/sidebar.php ================================================ [ 'reset' => 'Réinitialiser par défaut', ], 'call-to-action' => 'Modifies l\'ordre, les icônes et les noms des éléments de la navigation de la campagne.', 'helpers' => [ 'bookmarks' => 'Les favoris ne sont pas listés ici car chacun possède son propre réglage :position qui détermine où il apparaît dans la barre latérale.', 'image' => 'Ajouter une image pour représenter la campagne. Cette image sera utilisée dans la barre latérale et dans l\'interface de changement de campagne. Cette image peut être modifiée à tout moment en éditant la campagne.', 'reordering'=> 'Réordonner la navigation en cliquant sur l\'icône sur la gauche.', ], 'image-success' => 'La nouvelle image de la campagne a été enregistrée. Cette image peut être modifiée à nouveau en éditant la campagne.', 'reset' => [ 'success' => 'Réinitialisation effectuée.', 'title' => 'Réinitialiser la configuration', 'warning' => 'Es-tu sûr de vouloir réinitialiser la navigation à ses valeurs par défauts?', ], 'success' => 'Navigation sauvegardée.', 'title' => 'Configuration de navigation pour :campaign', 'tooltips' => [ 'image' => 'Modifier cette image de fond', ], ]; ================================================ FILE: lang/fr/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Calendriers', 'title' => 'Garant de temps', ], 'murderer' => [ 'goal' => 'Personnages décédés', 'title' => 'Meurtier/Meurtrière', ], ], 'fields' => [ 'created' => 'Créé le', 'creator' => 'Créé par', 'entries' => 'Total d\'entrées', 'from-elements' => 'Des éléments', 'from-entities' => 'Des entrées', 'from-posts' => 'Des articles', 'general' => 'Général', 'words' => 'Nombre total de mots', ], 'title2' => 'Statistiques', 'titles' => [ 'calendars' => 'Chronométreur/euse niveau :level', 'characters'=> 'Donneur/euse de nom niveau :level', 'dead' => 'Meutrier/ère niveau :level', 'families' => 'Orthogénie niveau :level', 'locations' => 'Architecte niveau :level', 'quests' => 'Maître/esse d\'oeuvre niveau :level', 'races' => 'Éleveur/euse niveau :level', ], 'tutorial' => 'Les statistiques de la campagne affichent le nombre d\'entrée et l\'activité récente. Les données se mettent à jour toutes les :amount heures. Utilise ça pour suivre l\'évolution et l\'usage au fil du temps.', ]; ================================================ FILE: lang/fr/campaigns/styles.php ================================================ [ 'builder' => 'Constructeur de thème', 'current' => 'Thème actuel: theme', 'disable' => 'Désactiver', 'enable' => 'Activer', 'new' => 'Nouveau style', ], 'bulks' => [ 'delete' => '{1} :count style retiré.|[2,*] :count styles retirés.', 'disable' => '{1} :count style désactivé.|[2,*] :count styles désactivés.', 'enable' => '{1} :count style activé.|[2,*] :count styles actvités.', ], 'create' => [ 'success' => 'Nouveau style créé.', 'title' => 'Nouveau style', ], 'delete' => [ 'success' => 'Style style :name supprimé.', ], 'errors' => [ 'max_content' => 'La règle CSS ne peut pas dépasser :amount charactères.', 'max_reached' => 'Nombre maximal (:max) de styles atteint.', ], 'fields' => [ 'content' => 'Règles CSS', 'is_enabled' => 'Activé', 'length' => 'Longueur', 'modified' => 'Modifié', 'name' => 'Nom', 'order' => 'Ordre', ], 'helpers' => [ 'here' => 'sur notre blog', 'is_enabled' => 'Activer ce style sur chaque page.', 'main' => 'Tu peux créer des règles CSS personnalisées pour ta campagne premium. Ces styles sont chargés après les thèmes du marketplace activés pour la campagne. Tu peux en savoir plus sur comment personnalisé ta campagne :here.', 'tutorial' => 'Contrôle le style visuel de la campagne. Choisis les couleurs, les préférences de mise en page et les autres options de présentation. Ces changements n\'affectent que cette campagne et peuvent être modifiés à tout moment.', ], 'pitch' => 'Définis du CSS pour complètement personnalisé le look de la campagne.', 'placeholders' => [ 'name' => 'Nom du style', ], 'reorder' => [ 'save' => 'Enregister le nouvel ordre', 'success' => '{1} :count style réordonné.|[2,*] :count styles réordonnés.', 'title' => 'Réordonner les styles', ], 'theme' => [ 'none' => 'Utiliser les préférences de l\'utilisateur', 'override' => 'Forçage de thème', 'success' => 'Thème de la campagne modifié.', 'title' => 'Modifier le thème de la campagne.', ], 'title' => 'Styles de la campagne', 'toggle' => [ 'disable' => 'Style désactivé.', 'enable' => 'Style activé.', ], 'update' => [ 'success' => 'Style :name modifié.', 'title' => 'Modifier le style', ], ]; ================================================ FILE: lang/fr/campaigns/vanity.php ================================================ 'Le nom :vanity est disponnible!', 'forever' => 'Une fois défini, ne peut plus être modifié. :docs', 'helper-v2' => 'Donne à la campagne une adresse web personnalisée et facile à retenir. Au lieu de :default, la campagne pourrait apparaître comme :example', 'rule' => 'Le champ :field a besoin d\'au moins une lettre.', 'rule2' => 'Le champ :field ne permet par les charactères suivants: /.', 'set' => 'L\'URL unique de la campagne est :vanity.', ]; ================================================ FILE: lang/fr/campaigns/visibilities.php ================================================ [ 'public' => 'Visible dans les campagnes publiques', 'unlisted' => 'Cachée des campagnes publiques', ], 'helpers' => [ 'premium' => 'Pour activer cette option, les fonctionnalités premiums doivent être débloquée pour cette campagne.', 'private' => 'Seulement les membres ont accès', 'public' => 'N\'importe qui avec un lien peut accéder', ], 'titles' => [ 'private' => 'Privée', 'public' => 'Publique', 'unlisted' => 'Publique (pas répertoriée)', ], ]; ================================================ FILE: lang/fr/campaigns/webhooks.php ================================================ [ 'action' => 'Changer le status', 'add' => 'Créer un webhook', 'bulks' => [ 'delete_success' => '{1} Supprimé :count webhook.|[2,*] Supprimé :count webhooks.', 'disable' => 'Désactiver', 'disable_success' => '{1} Desactivé :count webhook.|[2,*] Desactivé :count webhooks.', 'enable' => 'Activer', 'enable_success' => '{1} Activé :count webhook.|[2,*] Activé :count webhooks.', ], 'test' => 'Tester le webhook', 'update' => 'Modifier le webhook', ], 'create' => [ 'success' => 'Webhook créé avec succès', 'title' => 'Ajouter un nouveau webhook', ], 'destroy' => [ 'success' => 'Webhook supprimé avec succès', ], 'edit' => [ 'success' => 'Webhook modifié avec succès', 'title' => 'Modifier le webhook', ], 'error' => [ 'pitch' => 'Débloques les fonctionalités premium pour avoir accès aux webhooks.', ], 'fields' => [ 'enabled' => 'Activé', 'event' => 'Événement', 'events' => [ 'deleted' => 'Entrée supprimée', 'edited' => 'Entrée modifiée', 'new' => 'Nouvelle entrée', ], 'message' => 'Message', 'private_entities' => [ 'helper' => 'Ne pas déclencher le webhook lors de la mise à jour d\'entrées privées.', 'skip' => 'Ignorer les entrées privées', ], 'type' => 'Type', 'types' => [ 'custom' => 'Message', 'payload' => 'Payload', ], 'url' => 'URL', ], 'helper' => [ 'active' => 'Si le webhook est actuellement activé', 'message' => 'Ajouter un message personnalisé avec prise en charge des paramètres', 'status' => 'Basculer l\'état actif du webhook', 'tutorial' => 'Utilise des webhooks pour envoyer des mises à jour en temps réel de la campagne vers des outils externes. Les événements se déclenchent automatiquement quand des entrées sont créées, modifiées ou supprimées. Tu peux ajouter plusieurs webhooks et les tester depuis cette page', ], 'placeholders' => [ 'message' => '{who} a apporté des modifications à {name}, consulter le site {url}', 'url' => 'URL du webhook cible', ], 'premium' => 'Sois notifié sur Discord ou dans d\'autres applications lorsque ta campagne change, pour les nouvelles entrées, les mises à jour, et plus encore.', 'test' => [ 'success' => 'Test envoyé', ], 'title' => 'Webhooks', 'toggle' => [ 'disable' => 'Webhook désactivé.', 'enable' => 'Webhook activé.', ], ]; ================================================ FILE: lang/fr/campaigns.php ================================================ [ 'success' => 'Campagne créée.', 'title' => 'Nouvelle Campagne', ], 'edit' => [ 'success' => 'Campagne modifiée.', ], 'entity_personality_visibilities' => [ 'private' => 'Les nouveaux personnages ont leur personnalité privée par défault.', ], 'entity_visibilities' => [ 'private' => 'Nouvelles entrées privées', ], 'errors' => [ 'access' => 'Accès refusé pour cette campagne.', 'premium' => 'Cette fonctionnalité n\'est que disponible pour les campagnes Premium.', 'unknown_id' => 'Campagne inconnue.', ], 'export' => [], 'fields' => [ 'billboard' => 'Description du Billboard', 'boosted' => 'Boosté par', 'entity_count' => 'Nombre d\'entrées', 'entry' => 'Description de la campagne', 'followers' => 'Followers', 'genre' => 'Genre(s)', 'header_image' => 'Image de fond pour le tableau de bord', 'image' => 'Image', 'locale' => 'Langue', 'name' => 'Nom', 'open' => 'Ouvert aux applications', 'premium' => 'Premium débloqué par :name', 'public' => 'Visibilité de la campagne', 'public_campaign_filters' => 'Filtres publics', 'superboosted' => 'Superboosté par', 'system' => 'Système', 'theme' => 'Thème', 'vanity' => 'URL unique', ], 'following' => 'Suivant', 'helpers' => [ 'boosted' => 'Cette campagne est boostée ce qui active certaines fonctionnalités. Plus de détails sur la page :settings.', 'css' => 'Définir du code CSS pour la campagne qui sera chargé sur chaque page. Abuser de cette fonctionnalité peut mener à une suppression du CSS. Les abus répétés peuvent mener à une suppression de la campagne.', 'dashboard' => 'Contrôler la manière dont l\'en-tête de campagne s\'affiche sur le tableau de bord.', 'excerpt' => 'Le contenu de ce champ sera affiché sur le tableau de bord dans le widget d\'en-tête de campagne. Si ce champ est vide, les 1000 premiers caractères de la description de la campagne seront utilisés à la place.', 'header_image' => 'Image affichée en arrière-plan de l\'en-tête de campagne sur le tableau de bord.', 'hide_history' => 'Activer cette option pour cacher l\'historique d\'une entrée aux membres qui ne sont pas dans le groupe admin de la campagne.', 'hide_members' => 'Activer cette option pour cacher la liste des membres de la campagne aux membres qui ne sont pas dans le groupe admin de celle-ci.', 'locale' => 'La langue dans laquelle la campagne est écrite. Ceci est utilisé pour générer du contenu ainsi que pour grouper les campagnes publiques.', 'name' => 'Le nom de la campagne doit contenir au minimum 4 caractères.', 'no_entry' => 'La campagne n\'a pas encore de description! Corrigeons cela.', 'premium' => 'Certaines fonctionnalités sont disponibles parce que les fonctionnalités Premium de cette campagne sont débloquées. Pour en savoir plus, consultez la page :settings.', 'public_campaign_filters' => 'Aides les autres utilisateurs à trouver la campagne parmi les autres campagnes publiques en fournissant les détails suivants.', 'public_no_visibility' => 'Attention! La campagne est public, mais le rôle publique de la campagne n\'a pas d\'accès. :fix.', 'system' => 'Si la campagne est publiquement visible, elle sera affichée dans la page :link.', 'systems' => 'A définir.', 'theme' => 'Définir le thème pour la campagne qui surplante le thème de l\'utilisateur.', 'view_public' => 'Pour afficher la campagne comme le ferait un utilisateur public, ouvres :link dans une fenêtre de navigation privée.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Copier le lien dans le presse-papier.', 'link' => 'Nouveau Lien', ], 'create' => [ 'buttons' => [ 'create' => 'Créer une invitation', ], 'success_link' => 'Liens créé: :link', 'title' => 'Invite un ami à la campagne', ], 'destroy' => [ 'success' => 'Invitation annulée.', ], 'error' => [ 'inactive_token' => 'Ce code d\'activation a déjà été utilisé, ou la campagne n\'existe plus.', 'invalid_token' => 'Ce code d\'activation n\'est plus valide.', 'join' => 'Connecte-toi ou crée un compte pour joindre :campaign.', ], 'fields' => [ 'created' => 'Envoyé', 'role' => 'Rôle', 'token' => 'Jeton', 'type' => 'Type', 'usage' => 'Nombre max d\'utilisation', ], 'helpers' => [ 'role' => 'Les utilisateurs doivent rejoindre avant de pouvoir être promus au rôle d\'administrateur.', 'usage' => 'Nombre de fois où le lien d\'invitation peut être utilisé avant qu\'il ne devienne inactif.', ], 'unlimited_validity' => 'Illimité', 'usages' => [ 'five' => '5 fois', 'no_limit' => 'Sans limite', 'once' => '1 fois', 'ten' => '10 fois', ], ], 'leave' => [ 'action' => 'Quitter la campagne', 'confirm' => 'Es-tu sûr de vouloir quitter la campagne :name? Tu n\'auras plus accès aux données, sauf si un admin de la campagne t\'invite à nouveau.', 'confirm-button' => 'Oui, quitter la campagne', 'error' => 'Impossible de quitter la campagne.', 'fix' => 'Aller aux membres de la campagne', 'no-admin-left' => 'Quitter la campagne n\'est pas possible car celle-ci se retrouverait sans admin. Ajoute un autre membre au rôle admin en premier.', 'success' => 'Tu as quitté la campagne.', 'title' => 'Quitter la campagne', ], 'members' => [ 'actions' => [ 'remove' => 'Retirer de la campagne', 'switch' => 'Basculer', 'switch-back' => 'Retour à mon compte', 'switch-entity' => 'Basculer', ], 'fields' => [ 'banned' => 'Compte suspendu', 'joined' => 'Rejoint', 'last_login' => 'Dernière connexion', 'name' => 'Membre', 'role' => 'Rôle', 'roles' => 'Rôles', ], 'helpers' => [ 'switch' => 'Basculer vers ce membre', ], 'impersonating' => [ 'message' => 'Tu visualises la campagne en tant qu\'un autre membre. Certaines fonctionnalités ont été désactivées, mais le reste fonctionne exactement comme le verrait le membre.', 'title' => 'Se faisant passer pour :name', ], 'invite' => [ 'description' => 'Invite tes amis et joueurs à participer à la campagne en créant un lien d\'invitation et en leur envoyant l\'URL générée! En acceptant l\'invitation, ils seront ajoutés en tant que membres dans le rôle défini par l\'invitation.', 'more' => 'Tu peux ajouter plus de rôles sur la :link.', 'title' => 'Invitation', ], 'removal' => 'Retrait de ":member" de la campagne.', 'roles' => [ 'member' => 'Membre', 'owner' => 'Administrateur', 'player' => 'Joueur', 'public' => 'Public', 'viewer' => 'Observateur', ], 'switch_back_success' => 'Tu es de retour à ton compte.', ], 'mentions' => [], 'modules' => [], 'open_campaign' => [], 'options' => [], 'overview' => [ 'entity-count' => '{0} Aucune entrée|{1} :amount entrée|[2,*] :amount entrées', 'follower-count' => '{0} Aucun abonné|{1} :amount abonné|[2,*] :amount abonnés', ], 'panels' => [ 'dashboard' => 'Tableau de bord', 'privacy' => 'Confidentialité par défaut', 'setup' => 'Configuration', 'sharing' => 'Partage', 'systems' => 'Systèmes', 'ui' => 'Interface', ], 'placeholders' => [ 'locale' => 'La langue utilisée', 'name' => 'Le nom de la campagne', 'system' => 'D&D 5e, 3.5, Pathfinder, Tigres Volants, Gurps', ], 'privacy' => [ 'hidden' => 'Caché', 'private' => 'Privé', 'visible' => 'Visible', ], 'public' => [ 'helpers' => [ 'introduction' => 'Les campagnes sont privées par défaut, et peuvent être définie comme publique. Cela permet à n\'importe qui de les accéder, et rend la campagne visible dans les :public-campaigns se la campagne a des entrées visibles au rôle :public-role. Une campagne public est visible par tous, mais pour que le contenu soit visible, le rôle :public-role doit avoir des permissions adéquates.', ], ], 'roles' => [ 'actions' => [ 'add' => 'Ajouter un rôle', 'duplicate' => 'Copier le rôle', 'permissions' => 'Gérer les permissions', 'rename' => 'Renommer le rôle', 'save' => 'Enregistrer le rôle', ], 'admin_role' => 'rôle admin', 'bulks' => [ 'delete' => '{1} :count rôle retiré.|[2,*] :count rôles retirés.', 'edit' => '{1} :count rôle modifié.|[2,*] :count rôles modifiés.', ], 'create' => [ 'success' => 'Rôle :name créé.', 'title' => 'Nouveau rôle', ], 'destroy' => [ 'success' => 'Rôle :name supprimé.', ], 'edit' => [ 'success' => 'Rôle :name modifié.', 'title' => 'Modifier le rôle :name', ], 'fields' => [ 'copy_permissions' => 'Copier les permissions', 'name' => 'Nom', 'permissions' => 'Permissions', 'type' => 'Type', 'users' => 'Utilisateurs', ], 'helper' => [ '1' => 'Une campagne peut avoir autant de rôles que désiré. Le rôle "Admin" a automatiquement accès à tout dans une campagne, et chaque autre rôle peut être configuré pour avoir des accès spécifiques à divers entrées (personnages, lieux, etc).', '2' => 'Les entrées individuelles peuvent avoir leurs propres permissions sous l\'onglet "Permissions" de celles-ci. Cet onglet apparait dès le moment qu\'une campagne a plusieurs membres ou rôles.', '3' => 'Il y a deux options possibles. Soit le mode "opt-out", ou les rôles ont le droit de lire toutes les entrées, couplé à l\'option "Privé" sur les entrées pour les cacher. Sinon, il est possible de ne pas donner de droits généraux aux rôles, et à la place donner des rôles individuellement sur les entrées pour les rendre visibles.', '4' => 'Les campagne boostées ont un nombre illimité de rôles.', 'permissions_helper' => 'Créer une copie du rôle et de ses permissions, sur les catégories et sur les entrées.', ], 'hints' => [ 'campaign_not_public' => 'Le rôle Public a des permissions mais la campagne est privée. La campagne peut être rendue publique sous l\'onglet Partager en modifiant la campagne.', 'empty_role' => 'Ce rôle n\'a pas encore de membre.', 'role_admin' => 'Les membres du rôle :name ont automatiquement accès à tout le contenu et toutes les fonctionnalités de la campagne.', 'role_permissions' => 'Permettre au rôle \':name\' les actions suivantes sur toutes les entrées.', ], 'members' => 'Membres', 'modals' => [ 'details' => [ 'campaign' => 'Les permissions de campagne sont les suivants:', 'entities' => 'Voici un petit récapitulatif de ce que les membres de ce rôle peuvent faire quand une permission est sélectionnée.', 'more' => 'Pour plus d\'information, voir notre tutoriel sur Youtube', 'title' => 'Détails de permission', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Créer', 'articles' => 'Articles', 'dashboard' => 'Tableau de bord', 'delete' => 'Supprimer', 'edit' => 'Modifier', 'gallery' => [ 'browse' => 'Parcourir', 'manage' => 'Accès complet', 'upload' => 'Ajouter', ], 'manage' => 'Gérer', 'members' => 'Membres', 'permission' => 'Gérer les permissions', 'read' => 'Voir', 'toggle' => 'Changer pour tous', ], 'helpers' => [ 'add' => 'Permettre de créer des entrées de cette catégorie. Un membre aura automatiquement le droit de voir et modifier les entrées qu\'il ou elle crée si le rôle n\'a pas le droit de voir ou de modifier les entrées de cette catégorie.', 'articles' => 'Permet d\'ajouter, modifier et supprimer des articles même si le membre ne peut pas modifier l\'entrée.', 'dashboard' => 'Permettre de modifier les tableaux de bords et les widgets du tableau de bord.', 'delete' => 'Permettre la suppression des entrées de cette catégorie.', 'edit' => 'Permettre la modifications des entrées de cette catégorie.', 'gallery' => [ 'browse' => 'Permettre de parcourir la galerie, et définir l\'image d\'entrée depuis la galerie.', 'manage' => 'Permettre l\'accès complet à la galerie, avec la possibilité de modifier et supprimer des images.', 'upload' => 'Permet de télécharger des images dans la galerie. L\'utilisateur ne verra que les images qu\'il/elle a téléchargées si elles ne sont pas combinées avec l\'autorisation de parcourir.', ], 'manage' => 'Permettre la gestion de la campagne tel qu\'un membre du rôle admin, sans permettre aux membres de supprimer la campagne.', 'members' => 'Permettre l\'invitation de nouveaux membre à la campagne.', 'not_public' => 'La campagne n\'est pas publique. Les autorisations pour le rôle public peuvent être définies, mais elles seront ignorées. Il faut modifier la campagne pour la rendre publique.', 'permission' => 'Permettre la gestion des permissions d\'une entrées de ce type qu\'ils peuvent modifier.', 'read' => 'Permettre la lecture des entrées de ce type qui ne sont pas privées.', ], ], 'placeholders' => [ 'name' => 'Nom du rôle', ], 'title' => 'Rôles de la campagne :name', 'types' => [ 'owner' => 'Propriétaire', 'public' => 'Publique', 'standard' => 'Standard', ], 'users' => [ 'actions' => [ 'add' => 'Ajouter', 'remove' => ':user du rôle :role.', 'remove_user' => 'Retirer l\'utilisateur du rôle', ], 'create' => [ 'success' => 'Utilisateur ajouté au rôle.', 'title' => 'Ajouter un utilisateur au rôle :name', ], 'destroy' => [ 'success' => 'Utilisateur supprimé du rôle.', ], 'errors' => [ 'cant_kick_admins' => 'Pour éviter des abus, il n\'est pas possible de supprimer des membres du rôle :admin de la campagne. En cas de problème, contactes-nous sur :discord ou a :email.', 'needs_more_roles' => 'Tu dois d\'ajouter à un autre rôle de la campagne avant de pouvoir de retirer du rôle :admin.', ], 'fields' => [ 'name' => 'Nom', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Activer', ], 'boosted' => 'Cette fonctionnalité est actuellement en beta et seulement accessible pour les :boosted.', 'deprecated' => [ 'help' => 'Cette catégorie est obsolète, ce qui signifie qu\'elle n\'est plus maintenue, et plus testée avec chaque mise-à-jour. Utilise cette catégorie en sachant qu\'el sera éventuellement retirée de Kanka.', 'title' => 'Obsolète', ], 'disabled' => 'La catégorie :module est désactivée.', 'enabled' => 'La catégorie :module est activée.', 'errors' => [ 'module-disabled' => 'La catégorie demandée est actuellement désactivée dans la configuration. :fix.', ], 'helpers' => [ 'abilities' => 'Créer des pouvoirs, compétences, sorts, etc. qui peuvent être assignés aux entrées.', 'assets' => 'Télécharger des fichiers, liens, et définir des alias pour les entrées.', 'bookmarks' => 'Créer des favoris vers des entrées ou listes filtrées qui apparaissent dans la navigation.', 'calendars' => 'Un endroit pour définir les calendriers du monde.', 'characters' => 'Créer et suivre les personnes qui peuplent le monde.', 'conversations' => 'Conversations fictives entre des personnages ou entre membres de la campagne.', 'creatures' => 'Pour les créatures, animaux, et monstre qui habitent le monde.', 'dice_rolls' => 'Pour ceux qui utilisent Kanka pour une campagne JdR, un système pour des jets de dés.', 'entity_attributes' => 'Gérer les propriétés d\'entrées de la campagne, tel que les points de vie ou leur vitesse.', 'events' => 'Jours fériés, festivals, désastres, anniversaires, guerres.', 'families' => 'Clans ou familles, leurs relations et leurs membres.', 'inventories' => 'Gérer l\'inventaires des entrées de la campagne.', 'items' => 'Armes, véhicules, artéfacts, objets légendaires.', 'journals' => 'Observations écrites par des personnages, ou préparation de session pour le maître de jeu.', 'locations' => 'Planètes, plaines, continents, rivières, pays, temples, tavernes.', 'maps' => 'Ajouter des cartes et y ajouter des couches et des marqueurs pointant vers des entrées de la campagne.', 'notes' => 'Histoires, légendes, religions, magie, races.', 'organisations' => 'Cultes, unités militaires, factions, guildes.', 'quests' => 'Gestionnaire de quêtes avec personnages et lieux.', 'races' => 'Si la campagne a plus d\'une race, cette catégorie permet de facilement organiser celles-ci.', 'tags' => 'Chaque entrée peut avoir plusieurs étiquettes. Les étiquettes peuvent appartenir à d\'autres étiquettes.', 'timelines' => 'Représenter l\'histoire du monde de manière visuelle avec des chronologies.', 'whiteboards' => 'Dessiner et écrire sur des tableaux blancs pour visualiser le monde et ses objectifs.', ], ], 'sharing' => [ 'filters' => 'Les campagnes publiques sont visible sur la page de :public-campaigns. Remplir les champs suivants rend la campagne plus facilement trouvable.', 'language' => 'La langue dans laquelle le contenu de la campagne est écrite.', 'system' => 'Si la campagne est liée à un jeu de rôle, le système utilisé par la campagne.', ], 'show' => [ 'actions' => [ 'edit' => 'Modifier la campagne', ], 'tabs' => [ 'achievements' => 'Succès', 'customisation' => 'Personnalisation', 'danger' => 'Danger', 'data' => 'Données', 'default-images' => 'Images par défaut', 'defaults' => 'Défauts', 'deletion' => 'Suppression', 'export' => 'Export', 'import' => 'Import', 'logs' => 'Journaux', 'management' => 'Gestion', 'members' => 'Membres', 'plugins' => 'Plugins', 'recovery' => 'Récupération', 'roles' => 'Rôles', 'sidebar' => 'Navigation', 'stats' => 'Statistiques', 'styles' => 'Thèmes', 'webhooks' => 'Webhooks', ], 'title' => 'Campagne :name', ], 'status' => [ 'free' => 'Fonctionnalités Premium désactivées.', 'legacy' => [ 'title' => 'Fonctionnalités Boostées (anciennement)', ], 'premium' => 'Fonctionnalités Premium activées par :name.', 'title' => 'Fonctionnalités Premium', ], 'superboosted' => [], 'themes' => [ 'none' => 'Aucun (utilise la configuration de l\'utilisateur)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Seulement visible aux admins de la campagne', 'visible' => 'Visible aux membres', ], 'fields' => [ 'entity_history' => 'Historique d\'une entrée', 'member_list' => 'Liste des membres de la campagne', ], 'helpers' => [ 'entity-history' => 'Contrôler qui peut voir l\'historique des changements récents fait aux entrées de la campagne.', 'member-list' => 'Contrôler qui peut voir les membres de la campagne.', 'theme' => 'Afficher la campagne dans le thème choisi par l\'utilisateur, ou forcer l\'affichage dans un des thèmes suivants.', ], 'members' => [ 'hidden' => 'Seulement visible aux admins de la campagne', 'visible' => 'Visible aux membres', ], ], 'visibilities' => [ 'private' => 'Privée', 'public' => 'Publique', 'unlisted' => 'Public (non répertorié)', ], 'warning' => [], ]; ================================================ FILE: lang/fr/characters.php ================================================ [ 'add_appearance' => 'Ajouter une apparence', 'add_personality' => 'Ajouter un trait de personnalité', ], 'create' => [ 'title' => 'Créer une nouvelle personne', ], 'families' => [ 'helper' => 'Réorganise et contrôle quelles familles de :name sont visibles ou cachées aux non-admins.', 'reorder' => [ 'success' => 'Les familles du personage ont été mises à jour avec succès.', ], 'title2' => 'Gérer les familles', ], 'fields' => [ 'age' => 'Age', 'is_appearance_pinned' => 'Physique épinglé', 'is_dead' => 'Mort', 'is_personality_pinned' => 'Personnalité épinglée', 'is_personality_visible' => 'Personnalité visible', 'life' => 'Vie', 'physical' => 'Physique', 'pronouns' => 'Pronoms', 'sex' => 'Sexe', 'status' => 'Statut', 'title' => 'Titre', 'traits' => 'Traits', ], 'helpers' => [ 'age' => 'Il est possible de lier cette entrée avec un calendrier de la campagne pour automatiquement calculer l\'âge. :more.', ], 'hints' => [ 'is_appearance_pinned' => 'Si sélectionné, le physique du personnage sera visible sur la page vue d\'ensemble sous l\'entrée.', 'is_dead' => 'Ce personnage est mort.', 'is_missing' => 'Ce personnage est porté disparu.', 'is_personality_visible' => 'Tout le monde peut voir les traits de personmalités de ce personnage.', 'personality_not_visible' => 'Les traits de personnalités de ce personnage sont actuellement seulement visibles pour les admin de la campagne.', 'personality_visible' => 'Les traits de personnalités sont visibles pour tous.', ], 'labels' => [ 'appearance' => [ 'entry' => 'Description de l\'apparance', 'name' => 'Nom de l\'apparance', ], 'personality' => [ 'entry' => 'Description du trait de personnalité', 'name' => 'Nom du trait de personnalité', ], ], 'lists' => [ 'empty' => 'Créé ton premier héros, méchant ou acolyte pour donner vie à ton univers.', ], 'organisations' => [ 'create' => [ 'success' => 'Personne ajoutée à l\'organisation.', 'title' => 'Nouvelle Organisation pour :name', ], 'destroy' => [ 'success' => 'Organisation de personne supprimée.', ], 'edit' => [ 'success' => 'Organisation de personne modifiée.', 'title' => 'Modifier l\'Organisation pour :name', ], 'fields' => [ 'role' => 'Rôle', ], ], 'personality_visibility' => [ 'admin' => 'Membres du rôle :admin seulement', 'all' => 'Tout le monde peut voir', ], 'placeholders' => [ 'age' => 'Âge', 'appearance_entry' => 'Description', 'appearance_name' => 'Cheveux, Yeux, Peau, Taille', 'name' => 'Nom du personnage', 'personality_entry' => 'Détails', 'personality_name' => 'Trait de personnalité: objectifs, maniérismes, peurs, liens', 'physical' => 'Physique', 'pronouns' => 'Il, Elle', 'sex' => 'Sexe', 'title' => 'Titre', 'traits' => 'Traits', 'type' => 'PNJ, Joueurs, Autre', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Quêtes dont le personnage est l\'auteur.', 'quest_member' => 'Quêtes dont le personnage est membre.', ], ], 'races' => [ 'helper' => 'Réorganise et contrôle quelles races de :name sont visibles ou cachées aux non-admins.', 'reorder' => [ 'success' => 'Mise à jour réussie des races de personnage.', ], 'title2' => 'Gérer les races', ], 'sections' => [ 'appearance' => 'Physique', 'personality' => 'Personnalité', ], 'status' => [ 'alive' => 'En vie', 'dead' => 'Mort', 'missing' => 'Disparu', ], 'warnings' => [ 'personality_hidden' => 'Tu n\'as pas le droit de modifier les traits de personnalité de ce personnage.', ], ]; ================================================ FILE: lang/fr/colours.php ================================================ 'Aqua', 'black' => 'Noir', 'blue' => 'Bleu', 'brown' => 'Brun', 'green' => 'Vert', 'grey' => 'Gris', 'light-blue' => 'Bleu clair', 'maroon' => 'Bordeaux', 'navy' => 'Bleu foncé', 'none' => 'Aucune', 'orange' => 'Orange', 'pink' => 'Rose', 'purple' => 'Violet', 'red' => 'Rouge', 'teal' => 'Cyan', 'white' => 'Blanc', 'yellow' => 'Jaune', ]; ================================================ FILE: lang/fr/concept.php ================================================ 'campagne boostée', 'premium-campaign' => 'campagne Premium', 'premium-campaign-count' => '{0} Aucune campagne Premium |{1} 1 campagne Premium |[2,*] :count campagnes Premium', 'premium-campaigns' => 'campaigns Premium', 'premium-feature' => 'Fonctionnalité Premium', 'superboosted-campaign' => 'campagne superboostée', ]; ================================================ FILE: lang/fr/confirm/editing.php ================================================ 'Retourner', 'description' => 'Le système a détecté qu\'un autre utilisateur est en train de modifier cette page! Veux-tu retourner à la page précédante, ou ignorer cette alerte, au risque de perdre des données?', 'ignore' => 'Modifier quand-même', 'members' => 'Membres qui sont en train de modifier cette page:', 'title' => 'Attention', 'user' => ':user depuis :since', ]; ================================================ FILE: lang/fr/confirm.php ================================================ [ 'bulk' => 'Es-tu sûr de vouloir supprimer les éléments sélectionnés?', 'helper' => 'Es-tu sûr de vouloir supprimer :name?', 'recover' => 'Cette action peut être annulée pendant :day jours sur la page :recover.', 'recoverable' => 'Cette action peut être annulée pendant :day jours avec une :premium-campaign.', 'title' => 'Supprimer l\'élément', ], ]; ================================================ FILE: lang/fr/connections/web.php ================================================ [ 'add' => 'Connecter un existant', 'back' => 'Retour aux connections', 'download' => 'Télécharger le PNG', 'download-pdf' => 'Télécharger en PDF', 'download-png' => 'Télécharger l\'image (.png)', 'reset-layout' => 'Réinitialiser le rendu', 'view' => 'Voir le réseau', 'zoom-fit' => 'Ajuster le zoom', ], 'cta' => [ 'text' => 'Voici un aperçu du réseau complet des connexions de la campagne. Les campagnes standard permettent afficher jusqu\'à :amount nœuds. Les campagnes premium débloquent le réseau complet avec toutes les connexions et une navigation totale. Débloque les options premium pour visualiser l\'intégralité de la structure de ton monde en un coup d\'œil.', 'title' => 'Aperçu limité à :amount connexions', ], 'title' => 'Réseau des connexions', ]; ================================================ FILE: lang/fr/conversations.php ================================================ [ 'title' => 'Nouvelle Conversation', ], 'fields' => [ 'is_closed' => 'Fermée', 'messages' => 'Messages', 'participants' => 'Participants', ], 'hints' => [ 'empty' => 'Il n\'y a aucun participant dans cette convertation.', 'participants' => 'Ajoute des participants à la conversation.', ], 'lists' => [ 'empty' => 'Enregistre les dialogues, les lettres ou les échanges entre les personnages et les factions.', ], 'messages' => [ 'destroy' => [ 'success' => 'Message supprimé.', ], 'is_updated' => 'Modifié', 'load_previous' => 'Messages précédents', 'placeholders' => [ 'message' => 'Ton message', ], ], 'participants' => [ 'create' => [ 'success' => 'Participant :entity ajouté à la conversation.', ], 'destroy' => [ 'success' => 'Participant :entity retiré de la conversation.', ], 'helper' => 'Ajouter et retirer des participants de :name.', 'modal' => 'Participants', 'title' => 'Participants de :name', ], 'placeholders' => [ 'name' => 'Nom de la conversation', 'type' => 'In Game, Préparation, Quête', ], 'show' => [ 'is_closed' => 'La conversation est fermée.', ], 'tabs' => [ 'participants' => 'Participants', ], 'targets' => [ 'characters' => 'Personnages', 'members' => 'Membres', ], ]; ================================================ FILE: lang/fr/cookieconsent.php ================================================ 'Accepter les cookies', 'dismiss' => 'Fermer', 'header' => 'Consentement à l\'utilisation de cookies', 'link' => 'En savoir plus', 'message' => 'Kanka utilise des cookies pour garantir une expérience optimale du site web.', 'policy' => 'Politique en matière de cookies', 'reject' => 'Rejeter', ]; ================================================ FILE: lang/fr/creatures.php ================================================ [ 'title' => 'Nouvelle créature', ], 'fields' => [ 'is_dead' => 'Morte', 'is_extinct' => 'Éteinte', ], 'hints' => [ 'is_dead' => 'Cette créature est morte', 'is_extinct' => 'Cette créature est éteinte', ], 'lists' => [ 'empty' => 'Ajoute des bêtes, des monstres ou des créatures mythiques que tes héros pourraient affronter ou avec lesquels ils pourraient se lier d\'amitié.', ], 'placeholders' => [ 'type' => 'Herbivore, aquatique, mythique', ], ]; ================================================ FILE: lang/fr/crud.php ================================================ [ 'actions' => 'Actions', 'apply' => 'Appliquer', 'back' => 'Retour', 'change' => 'Changer', 'close' => 'Fermer', 'confirm' => 'Confirmer', 'copy' => 'Copier', 'copy_mention' => 'Copier mention [ ]', 'copy_to_campaign' => 'Copier vers une campagne', 'disable' => 'Désactiver', 'enable' => 'Activer', 'explore_view' => 'Vue Imbriquée', 'export' => 'Export', 'find_out_more' => 'En savoir plus', 'go_to' => 'Aller à :name', 'help' => 'Aide', 'json-export' => 'Export (JSON)', 'markdown-export' => 'Export (Markdown)', 'move' => 'Déplacer', 'new' => 'Nouveau', 'new_child' => 'Nouvel enfant', 'new_post' => 'Nouvelle entrée', 'next' => 'Suivant', 'open' => 'Ouvrir', 'print' => 'Imprimer', 'reorder' => 'Réordonner', 'reset' => 'Réinitialiser', 'save-changes' => 'Sauvegarder les changements', 'share' => 'Partager', 'transform' => 'Transformer', ], 'add' => 'Ajouter', 'alerts' => [ 'copy_attribute' => 'La mention de la propriété à été copiée au presse-papier.', 'copy_invite' => 'Le lien d\'invitation a été copié au presse-papier.', 'copy_mention' => 'La mention avancée de cette entrée a été copiée au presse-papier.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Opération de masse', 'kits' => 'Appliquer un kit de propriétés', 'permissions' => 'Changer les permissions', ], 'age' => [ 'helper' => 'Il est possible de préfixer le numéro avec + ou - pour modifier l\'âge dynamiquement.', ], 'buttons' => [ 'label' => 'Pour la sélection', ], 'edit' => [ 'locations' => 'Action pour les lieux', 'tagging' => 'Action pour les étiquettes', 'tags' => [ 'add' => 'Ajouter', 'remove' => 'Retirer', ], 'title' => 'Modifications de plusieurs entrées', ], 'errors' => [ 'admin' => 'Seulement les membres administrateur de la campagne peuvent changer le statut des entrées.', 'general' => 'Un problème est survenu lors de l\'exécution. Prière de ressayer de nouveau ou nous contacter en cas de problème persistant. Message d\'erreur: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Remplacer', ], 'helpers' => [ 'override' => 'Si sélectionné, les permissions des entrées sélectionnées seront remplacées par celles-ci. Si non-sélectionné, les permissions sélectionnées seront ajoutées à celles existantes.', ], 'title' => 'Changer les permissions pour plusieurs entrées', ], ], 'bulk_templates' => [ 'bulk_title' => 'Appliquer un kit de propriétés aux entrées', ], 'cancel' => 'Annuler', 'copy_to_campaign' => [ 'bulk_title' => 'Copier vers une campagne', 'panel' => 'Copier', 'title' => 'Copier \':name\' vers une autre campagne', ], 'create' => 'Créer', 'datagrid' => [ 'empty' => 'Rien à afficher.', ], 'delete_modal' => [ 'callout' => 'Oyé!', 'confirm' => 'Confirmer la suppression', 'permanent' => 'Cette action est permanente.', 'recoverable' => 'Les entrées supprimées peuvent être récupérées pendant :day jours avec une :boosted-campaign.', 'title' => 'Confirmation de la suppression', ], 'destroy_many' => [], 'dynamic' => [ 'permission' => 'Tu n\'as pas les permissions nécessaires pour créer une entrée de la catégorie :module.', 'unknown' => 'Entrée non valide de la catégorie :module.', ], 'edit' => 'Modifier', 'errors' => [ 'boosted_campaigns' => 'Cette fonctionnalité n\'est que disponible que pour les :boosted.', 'unavailable_feature' => 'Fonctionnalité indisponible', ], 'fields' => [ 'archived' => 'Archivé', 'calendar_date' => 'Date calendrier', 'category' => 'Catégorie', 'child' => 'Enfant', 'closed' => 'Fermée', 'colour' => 'Couleur', 'copy_abilities' => 'Copier les pouvoirs', 'copy_inventory' => 'Copier l\'inventaire', 'copy_links' => 'Copier les liens d\'entrée', 'copy_permissions' => 'Copier les permissions (cela ignore les permissions définies dans l\'onglet de permissions)', 'copy_posts' => 'Copier les articles (cela inclus les permissions des articles)', 'copy_reminders' => 'Copier les rappels', 'created_by' => 'Créé par', 'creator' => 'Créateur/ice', 'date_range' => 'Plage de dates', 'excerpt' => 'Extrait', 'has_attributes' => 'Possède des propriétés', 'has_entity_files' => 'Possède des fichiers', 'has_entry' => 'Possède une description', 'has_image' => 'Possède une image', 'has_posts' => 'Possède des articles', 'header_image' => 'Image d\'en-tête', 'image' => 'Image', 'is_closed' => 'La conversation est fermée et n\'acceptera plus de nouveau messages.', 'is_private' => 'Privé', 'is_private_v3' => 'Seulement afficher cet élément aux membres du rôle :admin-role. Cette option remplace toutes autres permissions.', 'is_star' => 'Epinglé', 'locations' => ':first dans :second', 'name' => 'Nom', 'names' => 'Noms', 'parent' => 'Parent', 'position' => 'Position', 'replace_mentions' => 'Remplace les mentions de propriétés avec ceux de la nouvelle entrée.', 'template' => 'Modèle', 'tooltip' => 'Infobulle', 'type' => 'Type', 'updated_by' => 'Mis à jour par', 'visibility' => 'Visibilité', 'word-count' => 'Nombre de mots: :number', ], 'files' => [ 'errors' => [ 'max' => 'Nombre maximal de fichier (:max) atteint pour cette entrée.', 'max_size' => 'La campagne a atteint la capacité maximale de stockage de fichiers.', 'no_files' => 'Aucun fichier.', ], 'hints' => [ 'limit' => 'Chaque entrée peut avoir un nombre maximal de :max fichiers uploadés.', 'limitations' => 'Formats pris en charge: :formats. Taille maximale: :size', ], ], 'filter' => 'Filtre', 'filters' => [ 'all' => 'Afficher tous les descendants', 'clear' => 'Effacer les filtres', 'copy_helper' => 'Utilises les filtres copiers dans ton presse-papier comme valeurs de filtre pour les widget de tableau de bord et liens rapides.', 'copy_to_clipboard' => 'Copier les filtres', 'direct' => 'Afficher seulement les descendants directs', 'filtered' => 'Affichant :count de :total :entity.', 'lists' => [ 'desktop' => [ 'all' => 'Afficher tous les descendants (:count)', 'filtered' => 'Afficher les descendants directs (:count)', ], 'paginated' => 'Passer facilement des enfants directs à tous les niveaux de descendance. Les résultats sont paginés pour optimiser les performances.', ], 'mobile' => [ 'clear' => 'Effacer', 'copy' => 'Presse-papier', ], 'options' => [ 'any' => 'Quelconque', 'children' => 'Correspond à ceci ou ses descendants', 'exclude' => 'Ne correspond pas à', 'hide' => 'Cacher', 'include' => 'Correspond à', 'none' => 'Aucun(e)', 'show' => 'Afficher', ], 'show' => 'Afficher les filtres', 'sorting' => [ 'asc' => ':field ascendant', 'desc' => ':field descendant', 'helper' => 'Contrôler l\'ordre d\'affichage des résultats.', ], 'title' => 'Filtres', ], 'fix-this-issue' => 'Réparer ce problème', 'forms' => [ 'actions' => [ 'calendar' => 'Ajouter une date de calendrier', ], 'copy_options' => 'Option de copie', ], 'helpers' => [ 'copy_options' => 'Copier les éléments liés suivant de la source à la nouvelle entrée.', 'linking' => 'Lier d\'autres entrées', 'parent' => 'Sélectionne le parent duquel cette entrée sera l\'enfant.', 'per-page' => 'Résultats par page', ], 'hidden' => 'Caché', 'hints' => [ 'calendar_date' => 'Une date de calendrier permet un triage plus facile dans les listes, et lie l\'entrée avec un calendrier à travers en rappel.', 'image_dimension' => 'Dimentions recommendées: :dimension pixels.', 'image_limitations' => 'Formats supportés: :formats. Taille maximale: :size.', 'image_recommendation' => 'Dimensions recommandées: :width par :height px.', 'is_star' => 'Les éléments épinglés sont affichés sur le menu de l\'entrée.', 'kit' => 'Le kit de propriétés sélectionné sera appliqué lors de la sauvegarde de l\'entrée.', 'tooltip' => 'Remplace l\'infobulle automatiquement généré avec le texte ci-dessous.', ], 'history' => [ 'created_clean' => 'Créé par :name :date', 'created_date_clean' => 'Créé :date', 'unknown' => 'Inconnu', 'updated_clean' => 'Dernière modification par :name :date', 'updated_date_clean' => 'Dernière modification :date', 'view' => 'Voir l\'historique', ], 'image' => [ 'error' => 'Impossible de récupérer l\'image demandée. Il est possible que le site web ne nous permet pas de télécharger des images (cela arrive par example avec squarespace et DeviantArt), ou le lien n\'est plus valide.', ], 'is_private' => 'Cet élément est privé et pas visible.', 'keyboard-shortcut' => 'Raccourci clavier :code', 'navigation' => [ 'cancel' => 'annuler', 'or_cancel' => 'ou :cancel', 'skip_to_content' => 'Aller au contenu', ], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Ajouter', 'deny' => 'Refuser', 'ignore' => 'Ignorer', 'remove' => 'Retirer', ], 'bulk_entity' => [ 'allow' => 'Permettre', 'deny' => 'Refuser', 'inherit' => 'Hériter', ], 'delete' => 'Supprimer', 'edit' => 'Modifier', 'private' => 'Rendre privé', 'toggle' => 'Basculer', 'view' => 'Voir', ], 'fields' => [ 'member' => 'Membre', 'role' => 'Rôle', ], 'helpers' => [ 'setup' => 'Utilise cette interface pour affiner la manière dont les rôles et les utilisateurs peuvent interagir avec cette entrée. :allow permettra à l\'utilisateur ou au rôle d\'effectuer cette action. :deny leur empêchera de prendre cette action. :inherit utilisera le rôle de l\'utilisateur ou l\'autorisation de leur rôle. Un utilisateur avec :allow peut effectuer l\'action en question, même si son rôle est en :deny.', ], 'success' => 'Permissions enregistrées.', 'title' => 'Permissions', 'too_many_members' => 'Cette campagne a trop de members (>10) pour afficher cette interface correctement. Prière d\'utiliser le boutton Permission sur la vue de l\'entrée pour gérer les permissions.', ], 'placeholders' => [ 'calendar' => 'Choix du calendrier', 'entry' => 'Utilises @ suivi de 3 lettres pour mentionner d\'autres entrées de la campagne.', 'fallback' => 'Choix de le/la :module', 'gallery_image' => 'Choix d\'une image depuis la galerie', 'image_url' => 'Ou depuis une URL', 'journal' => 'Choix d\'un journal', 'location' => 'Choix du lieu', 'multiple' => 'Sélectionner un(e) ou plusieurs', 'name' => 'Nom de l\'entrée', 'organisation' => 'Choix d\'une organisation', 'parent' => 'Choix d\'un parent', 'search' => 'Écrire pour chercher', 'tag' => 'Choix d\'une étiquette', 'template' => 'Choix d\'un modèle', 'timeline' => 'Choix d\'une chronologie', 'type' => 'Type de l\'entrée', 'user' => 'Choix d\'un utilisateur', ], 'relations' => [], 'remove' => 'Supprimer', 'reorder' => [ 'empty' => 'Aucun élément à réorganiser.', ], 'save' => 'Enregistrer', 'save_and_close' => 'Enregistrer et Fermer', 'save_and_copy' => 'Enregistrer et Copier', 'save_and_new' => 'Enregistrer et Nouveau', 'save_and_update' => 'Enregistrer et continuer la modification', 'save_and_view' => 'Enregistrer et Afficher', 'search' => 'Rechercher', 'select' => 'Sélection', 'tabs' => [ 'abilities' => 'Pouvoirs', 'inventory' => 'Inventaire', 'mentions' => 'Mentions', 'overview' => 'Aperçu', 'permissions' => 'Permissions', 'premium' => 'Premium', 'profile' => 'Profil', 'reminders' => 'Rappels', ], 'timestamps' => [ 'edited' => 'Modifié :ago', ], 'titles' => [ 'editing' => 'Modification de :name', 'new' => 'Nouveau/elle :module', ], 'tooltips' => [], 'update' => 'Modifier', 'users' => [ 'unknown' => 'Inconnu', ], 'view' => 'Voir', 'visibilities' => [ 'admin' => 'Admin', 'admin-self' => 'Soi-même & Admin', 'all' => 'Tous', 'members' => 'Membres', 'self' => 'Soi-même', ], ]; ================================================ FILE: lang/fr/dashboard.php ================================================ [ 'customise' => 'Personnaliser le tableau de bord', 'follow' => 'Suivre', 'join' => 'Joindre', 'unfollow' => 'Ne plus suivre', ], 'dashboards' => [ 'actions' => [ 'edit' => 'Modifier', 'new' => 'Nouveau', ], 'create' => [ 'helper' => 'Crée un nouveau tableau de bord pour :name, et attribue les rôles qui peuvent le voir ou l\'avoir comme tableau de bord par défaut.', 'success' => 'Nouveau tableau de bord :name créé.', 'title' => 'Nouveau tableau de bord', ], 'custom' => [ 'text' => 'Modification du tableau de bord :name de la campagne.', ], 'default' => [ 'text' => 'Modification du tableau de bord par défaut de la campagne.', 'title' => 'Tableau de bord par défaut', ], 'delete' => [ 'success' => 'Tableau de bord :name supprimé.', ], 'fields' => [ 'copy_widgets' => 'Copier les widgets', 'name' => 'Nom du tableau de bord', 'visibility' => 'Visibilité', ], 'helpers' => [ 'copy_widgets' => 'Dupliquer les widgets depuis :name vers ce nouveau tableau de bord.', ], 'pitch' => 'Crées plusieurs tableaux de bord avec des permissions pour chaque rôle de la campagne.', 'placeholders' => [ 'name' => 'Nom du tableau de bord', ], 'update' => [ 'success' => 'Tableau de bord :name modifié.', 'title' => 'Modifier le tableau de bord :name', ], 'visibility' => [ 'default' => 'Défaut', 'none' => 'Aucune', 'visible' => 'Visible', ], ], 'helpers' => [ 'follow' => 'Suivre une campagne la rend visibile dans le changeur de campagne (en haut à droite) après tes campagnes.', 'join' => 'Cette campagne est ouverte à de nouveaux membres. Cliquer pour postuler pour rejoindre.', ], 'setup' => [ 'actions' => [ 'add' => 'Ajouter un widget', 'back_to_dashboard' => 'Retour au tableau de bord', 'edit' => 'Modifier un widget', 'new' => 'Nouveau widget :type', ], 'reorder' => [ 'helper' => 'Fais-moi glisser pour me déplacer', 'success' => 'Widgets réordonnés.', ], 'title' => 'Configuration du tableau de bord de campagne', 'tutorial' => [ 'blog' => 'notre tutoriel', 'text' => 'Besoin d\'aide avec la mise en place du tableau de bord de la campagne? Tu peux lire :blog pour de l\'aide et inspiration.', ], ], 'title' => 'Tableau de bord', 'widgets' => [ 'advanced_options_boosted' => 'Activer plus d\'options comme afficher les épingles avec une :boosted_campaign.', 'calendar' => [ 'actions' => [ 'next' => 'Changer la date au prochain jour', 'previous' => 'Changer la date au jour précédent', ], 'previous_events' => 'Précédents', 'upcoming_events' => 'Prochainement', ], 'campaign' => [ 'helper' => 'Ce widget affiche l\'entête de campagne. Ce widget est tout le temps visible sur le tableau de bord de défaut.', ], 'create' => [ 'helper' => 'Sélectionne un type de widget à ajouter au tableau de bord :name.', 'helper-default' => 'Sélectionne un type de widget à ajouter au tableau de bord par défaut.', 'success' => 'Widget ajouté au tableau de bord.', 'title' => 'Nouveau widget', ], 'delete' => [ 'success' => 'Widget retiré du tableau de bord.', ], 'fields' => [ 'class' => 'Classe CSS', 'dashboard' => 'Tableau de bord', 'name' => 'Nom de widget personnalisé', 'optional-entity' => 'Liens vers une entrée', 'order' => 'Ordre d\'affichage', 'size' => 'Taille', 'width' => 'Largeur', ], 'helpers' => [ 'class' => 'Définition d\'une classe css ajoutée au widget.', 'filters' => 'Cliquer pour en savoir plus sur les options de filtrage.', ], 'orders' => [ 'name_asc' => 'Nom ascendant', 'name_desc' => 'Nom descendant', 'oldest' => 'Anciennement modifié', 'recent' => 'Récemment modifié', ], 'preview' => [ 'displays' => [ 'expand' => 'Entrée extensible', 'full' => 'Entrée complète', ], 'fields' => [ 'display' => 'Affichage', ], ], 'random' => [ 'helpers' => [ 'name' => 'Le nom de l\'entrée au hasard peut être référencé avec {name}', ], 'type' => [ 'all' => 'Tous', ], ], 'recent' => [ 'advanced_filter' => 'Filtre avancé', 'advanced_filters' => [ 'mentionless' => 'Sans mention (entrées qui ne mentionnent pas d\'autres entrées)', 'unmentioned' => 'Non mentionné (entrées qui ne sont pas mentionnées par d\'autres entrées)', ], 'all-entities' => 'Toutes les entrées', 'entity-header' => 'Utiliser l\'image d\'en-tête de l\'entrée', 'filters' => 'Filtres', 'help' => 'Afficher seulement la dernière entrée modifiée avec un aperçu de celle-ci.', 'helpers' => [ 'entity-header' => 'Si l\'entrée à une image d\'en-tête (limité aux campagnes boostées), le widget utilisera cette image au lieu de l\'image principale de l\'entrée.', 'show_attributes' => 'Afficher les propriétés épinglées de l\'entrée.', 'show_members' => 'Si l\'entrée est une famille ou organisation, afficher les membres sous l\'entrée.', 'show_relations' => 'Afficher les relations épinglées de l\'entrée.', ], 'show_attributes' => 'Afficher les propriétés épinglées', 'show_members' => 'Afficher les membres', 'show_relations' => 'Afficher les relations épinglées', 'singular' => 'Singulier', 'tags' => 'Filtrer la liste des entrées récemment modifiées sur une ou plusieurs étiquettes.', 'title' => 'Récemment modifié', ], 'tabs' => [ 'advanced' => 'Avancé', 'setup' => 'Général', ], 'unmentioned' => [ 'title' => 'Entrée non mentionnées', ], 'update' => [ 'success' => 'Widget modifié.', ], 'widths' => [ '0' => 'Automatique', '12'=> 'Complet (100%)', '3' => 'Minuscule (25%)', '4' => 'Petit (33%)', '6' => 'Moitié (50%)', '8' => 'Grand (66%)', '9' => 'Large (75%)', ], ], ]; ================================================ FILE: lang/fr/dashboards/onboarding.php ================================================ [ 'continue' => 'Commence ton worldbuilding', 'skip' => 'Passer pour l\'instant', ], 'families' => [ 'varren' => [ 'title' => 'Maison Varren', ], ], 'fields' => [ 'name' => 'Nom du monde', ], 'intro' => 'Ton monde est prêt. Personalise-le avant de te lancer.', 'placeholders' => [ 'name' => 'Tu peux changer ça à tout moment.', ], 'quests' => [ 'crown' => [ 'title' => 'Récupère la couronne brisée', ], ], 'roles' => [ 'co-writer' => 'Co-auteur', 'contributor' => 'Contributeur', ], 'selection' => [ 'campaign' => 'Campagne de JDR', 'campaign-description' => 'Gère ta campagne, tes persos et tes sessions pour ton groupe de JDR.', 'helper' => '(Ton choix personnalisera le contenu de démo et la mise en page de ton tableau de bord.)', 'intro' => 'Qu\'est-ce que tu crées?', 'story' => 'Roman ou univers d\'histoire', 'story-description' => 'Développe les persos, les lieux et la chronologie de ton histoire pendant que tu écris.', 'title' => 'Type de monde', 'worldbuilding' => 'Projet créatif', 'worldbuilding-description' => 'Construis et organise l\'histoire, les lieux et les gens de ton univers.', ], 'splash' => 'Salut :name 👋 Ton monde est prêt.', 'success' => 'Bienvenue ! On a personnalisé ton monde pour un démarrage rapide.', 'title' => 'Approprie-toi ce monde', 'ttrpg' => [ 'tags' => [ 'npcs' => 'PNJs', ], ], 'widgets' => [ 'active-quests' => 'Quêtes actives', ], ]; ================================================ FILE: lang/fr/dashboards/premium.php ================================================ 'Crée des tableaux de bord personnalisés pour ta campagne et les pages d\'accueil de tes joueurs. Épingle des entrées, des cartes, des calendriers, des galeries et bien plus dans la disposition de ton choix.', 'title' => 'Tableaux de bord personnalisés', ]; ================================================ FILE: lang/fr/dashboards/setup.php ================================================ [ 'add' => 'Ajouter un widget', ], 'sections' => [ 'switch' => 'Passer à...', ], 'tooltips' => [ 'add' => 'Clique pour ajouter un nouveau widget au tableau de bord.', 'switch' => 'Clique pour passer à un autre tableau de bord.', ], ]; ================================================ FILE: lang/fr/dashboards/widgets/calendar.php ================================================ 'Affiche les prochains rappels.', 'name' => 'Calendrier', ]; ================================================ FILE: lang/fr/dashboards/widgets/campaign.php ================================================ 'Affiche un aperçu de la campagne.', 'name' => 'Campagne', ]; ================================================ FILE: lang/fr/dashboards/widgets/gallery.php ================================================ 'Affiche les images d\'un dossier de galerie sous forme de carrousel.', 'fields' => [ 'folder' => 'Dossier de galerie', 'show_name' => 'Noms des images', ], 'helpers' => [ 'premium' => 'Présente les illustrations de ton monde directement sur ton tableau de bord.', 'show_name' => 'Afficher le nom de l\'image sous chaque image.', ], 'name' => 'Galerie', ]; ================================================ FILE: lang/fr/dashboards/widgets/header.php ================================================ 'Affiche un texte d\'entête', 'name' => 'Entête', ]; ================================================ FILE: lang/fr/dashboards/widgets/help.php ================================================ 'Affiche des liens vers l\'aide et les ressources communautaires.', 'name' => 'Aide & Communauté', 'title' => 'Aide & Communauté', ]; ================================================ FILE: lang/fr/dashboards/widgets/join.php ================================================ 'Postuler pour rejoindre!', 'description' => 'Affiche le bouton de candidature ainsi que l\'introduction de la campagne.', 'name' => 'Joueurs recherchés', 'register' => 'S\'inscrire pour postuler', 'title' => 'Joueurs recherchés', 'update' => 'Mettre à jour ta candidature', ]; ================================================ FILE: lang/fr/dashboards/widgets/onboarding.php ================================================ 'Affiche une liste de tâches pour bien commencer ton worldbuilding', 'name' => 'Premiers pas', 'tasks' => [ 'campaign' => [ 'name' => 'Ton premier monde est prêt', ], 'character' => [ 'helper' => 'Ajoute quelqu\'un qui exist dans ton monde.', 'name' => 'Crée ton premier personnage.', ], 'invite' => [ 'helper' => 'Ajoute des gens avec rôles à ta campagne.', 'name' => 'Invites un ami ou co-auteur', ], 'location' => [ 'helper' => 'Ancre ton monde avec un lieu.', 'name' => 'Crée ton premier lieu.', ], 'rename' => [ 'helper' => 'Défini un vrai nom pour ta campagne.', 'name' => 'Renomme ton monde', ], 'widgets' => [ 'helper' => 'Ajoute ou réorganise les widgets.', 'name' => 'Personnalise ton tableau de bord.', ], ], ]; ================================================ FILE: lang/fr/dashboards/widgets/preview.php ================================================ 'Affiche une entrée spécifique.', 'name' => 'Entrée sélectionnée', ]; ================================================ FILE: lang/fr/dashboards/widgets/random.php ================================================ 'Affiche une entrée aléatoire de la campagne.', 'name' => 'Entrée aléatoire', ]; ================================================ FILE: lang/fr/dashboards/widgets/recent.php ================================================ 'Affiche une liste des entrées modifiées récemment.', 'name' => 'Entrées modifiées récemment', ]; ================================================ FILE: lang/fr/dashboards/widgets/welcome.php ================================================ 'Affiche un message de bienvenue avec des conseils et astuces sur le tableau de bord.', 'endings' => [], 'focus' => [ 'text' => 'Ici, c\'est moi!', 'title' => 'Hey', ], 'intros' => [ '1' => 'Dis bonjour à ton nouveau chez toi pour la création de monde, :user! Nous avons mis en place ta première campagne et inclus deux exemples de :characters et :locations Ceux-ci sont également visibles ici sur le tableau de bord.', '2' => 'Pour commencer, cliques sur le gros bouton :new-entity (ou appuies sur :letter sur ton clavier), puis cliques sur :characters pour créer ton premier personnage. C\'est aussi simple que cela! Tu peux retrouver tous tes personnages, lieux et autres :entities dans la barre latérale située à gauche de la page.', '3' => 'Voici nos 5 meilleures astuces pour utiliser Kanka', ], 'name' => 'Bienvenue', 'title' => 'Bienvenue à :kanka! 🎉', 'tricks' => [ '1' => 'Lors de la rédaction de description, n\'écris pas les noms des éléments. Au lieu de cela, tappes :code et trois lettres pour créer des :mention vers d\'autres entrées. Ces mentions seront automatiquement mises à jour lorsque tu changeras leurs noms.', '2' => 'Pour modifier le nom, le thème ou l\'image de la campagne, cliques sur :world dans la barre latérale, puis sur le bouton :edit.', '3' => 'Écris les informations secrètes sur les entrées avec les :posts au lieu de la description principale.', '4' => 'Invites tes amis en cliquant sur :world et :members. Là-bas, tu peux créer des liens d\'invitation.', '5' => 'Tu peux supprimer ce message de bienvenue et afficher d\'autres informations sur cette page (appelée tableau de bord). Fais défiler la page vers le bas et cliques sur le bouton :button.', 'mention' => 'mentions', ], ]; ================================================ FILE: lang/fr/datagrids.php ================================================ [ 'back_to' => 'Retour à :name', 'filters' => 'Filtres', ], 'bulks' => [ 'selected' => 'sélectionné(s)', ], 'columns' => [ 'reset' => 'Réinitialiser par défaut', 'title' => 'Contrôler les colonnes visibles', ], 'display' => [ 'per_page' => 'Résultats par page', 'sort_by' => 'Trier par', 'title' => 'Affichage', ], 'filters' => [ 'clear' => 'Effacer les filtres', ], 'modes' => [ 'flatten' => 'Changer à la vue applatie', 'grid' => 'Changer à la vue de grille', 'nested' => 'Changer à la vue imbriquée', 'table' => 'Changer à la vue de table', ], 'subscription' => [ 'cta' => 'Découvrir les abonnements', 'helper' => 'Visualise jusqu\'à :max entrées en un coup d\'oeil avec un abonnement Kanka, plus plein d\'autres avantages pour booster ta création de monde.', 'title' => 'Abonnement requis', ], 'tooltips' => [ 'nested' => 'Cette entrée possède des enfants. Cliques sur l\'image pour les afficher.', ], ]; ================================================ FILE: lang/fr/datetime.php ================================================ 'jour', 'days' => 'jours', 'elapsed_ago' => 'il y à :duration', 'hour' => 'heure', 'hours' => 'heures', 'just_now' => 'à l\'instant', 'minute' => 'minute', 'minutes' => 'minutes', 'month' => 'mois', 'months' => 'mois', 'second' => 'seconde', 'seconds' => 'secondes', 'week' => 'semaine', 'weeks' => 'semaines', 'year' => 'année', 'years' => 'années', ]; ================================================ FILE: lang/fr/default.php ================================================ 'Titre de la page', ]; ================================================ FILE: lang/fr/dice_roll_results.php ================================================ [ 'title' => 'Résultats des jet de dés.', ], ]; ================================================ FILE: lang/fr/dice_rolls.php ================================================ [ 'title' => 'Nouveau jet de dés', ], 'destroy' => [ 'dice_roll' => 'Jet de dés retiré.', ], 'fields' => [ 'created_at' => 'Jeté à', 'parameters' => 'Paramètres', 'results' => 'Résultats', 'rolls' => 'Jets', ], 'hints' => [ 'parameters' => 'Quelles sont mes options de dés?', ], 'index' => [ 'actions' => [ 'results' => 'Résultats', ], ], 'lists' => [ 'empty' => 'Crée et enregistre des jets pour votre campagne afin de suivre les résultats directement dans Kanka.', ], 'placeholders' => [ 'name' => 'Nom du jet de dés', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Jet', ], 'error' => 'Problème lors du jet de dés. Les paramêtres ne sont pas conformes.', 'fields' => [ 'creator' => 'Créateur', 'date' => 'Date', 'result' => 'Résultat', ], 'hint' => 'Tous les jets de ce modèle.', 'success' => 'Dés jetés.', ], 'show' => [ 'tabs' => [ 'results' => 'Résultats', ], ], ]; ================================================ FILE: lang/fr/emails/activity/email.php ================================================ 'L\'e-mail de ton compte Kanka a été changé à :email.', 'title' => 'E-mail changé', ]; ================================================ FILE: lang/fr/emails/activity/password.php ================================================ 'Le mot de passe de ton compte sur Kanka a été modifié.', 'help' => 'Si ce n\'était pas toi, contactes nous au :email.', 'title' => 'Mot de passe modifié', ]; ================================================ FILE: lang/fr/emails/purge/first.php ================================================ 'Si tu utilises régulièrement ton compte, ne t\'inquiète pas, nous ne supprimons que les comptes et les campagnes qui ne sont pas utilisés activement.', 'help' => 'Besoin d\'aide pour utiliser Kanka? Rejoins notre :discord ou contactes-nous à :email', 'intro_account' => 'Nous t\'informons que ton compte sera supprimé dans :amount jours, car tu ne l\'as pas utilisé au cours des derniers :duration mois.', 'intro_campaigns' => 'Nous t\'informons que ton compte et les campagnes suivantes seront supprimés dans :amount jours, car tu ne l\'as pas utilisé au cours des derniers :duration mois.', 'keep' => 'Si tu souhaites que ton compte reste actif, prière de te connecter dans les :amount prochains jours.', 'title' => 'Ton compte Kanka sera supprimé dans :amount jours', 'warning' => [ 'account' => 'Une fois cette opération effectuée, toutes les données associées à ton compte, sous :email, seront définitivement supprimées.', 'campaigns' => 'Une fois cette opération effectuée, toutes les données associées à ton compte, sous email :email, ainsi que les campagnes suivantes seront définitivement supprimées.', ], ]; ================================================ FILE: lang/fr/emails/purge/second.php ================================================ 'Ceci est un dernier rappel que le compte Kanka sous l\'email :email sera supprimé dans :amount jours puisque tu ne l\'as pas utilisé dans les derniers :duration mois.', 'title' => 'Dernier rappel: ton compte Kanka sera supprimé dans :amount jours', ]; ================================================ FILE: lang/fr/emails/subscriptions/expiring.php ================================================ 'mettre à jours tes détails de carte', 'primary' => 'Ceci est un avertissement automatique que ta :brand **** :last expire bientôt.', 'title' => 'Carte expirante pour ton abonnement', 'valid' => 'Si tu souhaites conserver ton abonnement, prière de :action.', ]; ================================================ FILE: lang/fr/emails/subscriptions/paypal-expiring.php ================================================ 'Cordialement,', 'cta' => 'Renouveler ton abonnement', 'dear' => 'Bonjour :name,', 'intro' => 'Ton abonnement Kanka via PayPal expire le **:date**. Après cette date, ton compte reviendra au niveau gratuit.', 'loss' => [ 'ads' => 'Expérience sans publicité', 'campaign' => 'Campagne premium **:campaign**|Campagnes premium **:campaign** et :count autre', 'discord' => 'Ton rôle Discord **:role**', 'players' => '{1}:count joueur perdra l\'accès|[2,*]:count joueurs perdront l\'accès', 'plugins' => '{1}:count plugin|[2,*]:count plugins', 'title' => 'Voici ce que tu perdras:', ], 'title' => 'Ton abonnement Kanka expire bientôt', ]; ================================================ FILE: lang/fr/emails/subscriptions/upcoming.php ================================================ 'Si tu souhaites annuler ton abonnement avant son renouvèlement, connectes-toi sur ton compte Kanka et :link.', 'closing' => 'Cordialement,', 'dear' => 'Cher/Chère :name', 'link' => 'annules ton abonnement', 'notice' => 'Informations importantes sur le renouvellement:', 'primary' => 'Ce message est un rappel automatique que nous allons bientôt débiter ta carte :brand **** :last le :date, pour ton abonnement Kanka.', 'title' => 'Paiement annuel pour ton abonnement Kanka', 'valid' => 'Assures toi que ta carte de crédit sera valide à la date du paiement', ]; ================================================ FILE: lang/fr/emails/subscriptions/validation.php ================================================ 'Vérification d\'email de ton compte Kanka.', ]; ================================================ FILE: lang/fr/emails/validation.php ================================================ 'Problème avec la validation, prière de ressayer.', 'modal' => 'Un email validé est requis pour les abonnements. Vérifies ta boîte de réception pour un lien permettant de confirmer ton courriel avant de poursuivre le processus d\'abonnement.', 'success' => 'Validation réussie.', ]; ================================================ FILE: lang/fr/emails/welcome/2024.php ================================================ 'Happy worldbuilding et merci de participer à ce voyage avec nous,', 'header' => 'Bienvenue dans le meilleur endroit pour créer ton monde, :name!', 'lead_1' => 'Kanka est le fruit du travail de joueurs de RPG passionnés qui souhaitaient une approche simple et collaborative de la construction de mondes, sans compromis sur les fonctionnalités.', 'lead_2' => 'Kanka en est le résultat. Nous sommes là pour t\'aider à organiser ta campagne et te permettre de te concentrer sur la réalisation de ton monde.', 'ps' => 'PS : Si tu veux nous contacter, tu peux nous trouver sur :discord, ou à :email.', 'what_1' => 'Pour t\'aider à démarrer, nous t\'avons créé ta première campagne et inclus deux exemples de personnages et de lieux. Tu peux :start si tu préfères.', 'what_2' => 'Tu peux également consulter ces ressources:', 'what_3' => 'Notre :kb si tu as des questions de base sur les fonctionnalités de Kanka et sur te compte.', 'what_4' => 'La :doc couvre tous les aspects de manière plus approfondie.', 'what_5' => 'Enfin, les "campaigns présentent ce que d\'autres ont fait avec Kanka.', 'what_new' => 'en commencer une nouvelle', 'what_now' => 'Et maintenant?', 'why' => 'Tu as reçu cet e-mail parce que tu t\'es inscrit sur notre site web.', ]; ================================================ FILE: lang/fr/emails/welcome.php ================================================ [ 'basics' => [ 'text_1' => 'Avec un outil aussi vaste que Kanka, il peut être difficile de savoir par où commencer ou ce qu\'il faut faire. Notre :kb couvre les questions les plus élémentaires que tu peux te poser. Pour plus d\'aide, tu peux te rendre sur notre :doc.', 'title' => 'L\'essentiel', ], 'chat' => [ 'text_1' => 'Nous adorons avoir des nouvelles de nos utilisateurs. Nous sommes très actifs sur :discord, où tu trouveras un grand nombre de nos utilisateurs dévoués, une équipe d\'onboarding, ainsi que les fondateurs de Kanka, qui pourront répondre à toutes tes questions. Tu peux également nous envoyer un courriel à :email.', 'title' => 'Envie de papoter?', ], 'intro' => [ 'header' => 'Bienvenue dans la meilleure communauté de worldbuilding, :name!', 'link' => 'Vers ton monde!', 'text_1' => 'Dis bonjour à ton nouveau chez toi pour le worldbuilding, :name! La communauté est dans notre ADN, et nous sommes ravis que tu nous rejoignes. Kanka est le fruit du travail de joueurs de RPG passionnés qui croient en une manière simplifiée et communautaire d\'aborder le worldbuilding, sans faire de compromis sur les fonctionnalités.', 'text_2' => 'Nous avons mis en place ta première campagne et inclus deux exemples de personnages et de lieux pour t\'aider à démarrer.', ], 'preview' => 'Fais partie de la meilleure communauté de worldbuilding, :name!', ], 'header' => 'Bienvenue sur Kanka :name!', 'header_sub' => 'Félicitations, tu as fait le premier pas dans la création de ton monde sur :kanka!', 'pricing' => 'tarification', 'section_1' => 'Que faire maintenant?', 'section_11' => 'Crée ton monde,', 'section_2' => 'La ressource la plus importante est :discord, où tu trouveras de nombreux utilisateurs dévoués, une équipe d\'accueil, ainsi que le fondateur de Kanka, qui pourront répondre à toutes tes questions.', 'section_4' => 'Notre :youtube propose des vidéos couvrant les bases de Kanka. Même si tous les sujets ne sont pas encore couverts, on ajoute régulièrement des nouvelles vidéos.', 'section_4_v2' => 'Notre :knowledge-base couvre les questions les plus fréquentes. Pour une aide pour complète, notre :documentation couvre beaucoup plus de sujets en détails.', 'section_6' => 'Nous contacter', 'section_7' => 'Si tu n\'as pas trouvé de réponse à tes questions, ou si tu souhaites simplement nous contacter, tu peux nous trouver sur :facebook, ou tu peux nous envoyer un courriel à :email. On est une petite équipe de 2 amis, mais on répond à chaque e-mail qu\'on reçoit, donc n\'hésite pas!', 'section_8' => 'Une dernière chose', 'section_9_v2' => 'On a fait en sorte que toutes les fonctionnalités de base de Kanka soient gratuites, et elles le seront toujours. Cependant, si tu souhaites nous soutenir dans ce projet, tu peux t\'abonner à Kanka pour débloquer plus de fonctionnalités. Plus d\'information sur notre page de :pricing.', 'social_account' => 'Si tu as des problèmes pour te connecter à ton compte, tu utilises un login :provider. La méthode d\'authentification peut être changée dans les paramètres du compte.', 'title' => 'Commencer avec Kanka!', ]; ================================================ FILE: lang/fr/entities/abilities.php ================================================ [ 'add' => 'Ajouter', 'reset' => 'Réinitialiser les charges', 'sync' => 'Ajouter depuis les races', ], 'charges' => [ 'left' => ':amount restant', ], 'create' => [ 'helper' => 'Ajouter un ou plusieurs pouvoirs à :name.', 'success' => 'Pouvoir :ability ajouté à :entity.', 'success_multiple' => 'Les pouvoirs :abilities ont été ajouté à :entity.', 'title' => 'Ajouter des pouvoirs', ], 'fields' => [ 'note' => 'Note', 'position' => 'Position', ], 'groups' => [ 'unorganised' => 'Inorganisé', ], 'helpers' => [ 'note' => 'Ce champ peut référencer des entrées en utilisant les mentions avancées (ex :code) et les propriétés d\'une entrée (ex :attr).', 'recharge' => 'Réinitialiser toutes les charges des pouvoirs qui ont été utilisées.', 'sync' => 'Importer les pouvoirs définis sur les races du personnage.', ], 'import' => [ 'errors' => [ 'no_race' => 'Ce personnage n\'as pas de race.', 'not_character' => 'Cette entrée n\'est pas un personnage.', ], 'helper' => 'Attacher des pouvoir des races auxquelles :name appartient:', 'no_abilities' => 'Actuellement, il n\'existe aucune possibilité d\'importer des pouvoirs provenant des races auxquelles :name appartient.', 'race_abilities' => '{1} :name (:count pouvoir)|[2,*] :name (:count pouvoirs)', 'success' => '{1} :count pouvoir ajouté.|[2,*] :count pouvoirs ajoutés.', ], 'recharge' => [ 'success' => 'Toutes les charges ont été réinitialisées.', ], 'reorder' => [ 'parentless' => 'Aucun parent', 'success' => 'Pouvoirs réordonnés.', ], 'show' => [ 'helper' => 'Attache des pouvoirs à cette entrée. Il est toujours possible de modifier ou de supprimer un pouvoir. Les pouvoirs qui appartiennent au même parent sont groupés ensemble et agissent comme filtres.', 'reorder' => 'Réordonner', 'title' => 'Pouvoirs de:name', ], 'types' => [ 'unorganised' => 'Les pouvoirs sont regroupés en fonction de leur parent, ou se retrouvent ici.', ], 'update' => [ 'success' => 'Pouvoir d\'entrée :ability mis à jour.', 'title' => 'Pouvoirs de :name', ], ]; ================================================ FILE: lang/fr/entities/actions.php ================================================ [ 'set' => 'Définir comme archétype', 'toggle' => 'Statut de modèle mis à jour.', 'unset' => 'Retirer comme modèle', ], 'archive' => [ 'success' => ':name a été archivé.', 'title' => 'Archiver', ], 'convert' => 'Convertir la catégorie', 'copy-campaign' => 'Copier vers la campagne', 'json-export' => 'Export JSON', 'markdown-export' => 'Export Markdown', 'templates' => [], 'tooltips' => [ 'edit' => 'Modifier cette entrée', ], 'transfer' => 'Transférer de campagne', 'unarchive' => [ 'success' => ':name n\'est plus archivé.', 'title' => 'Désarchiver', ], ]; ================================================ FILE: lang/fr/entities/aliases.php ================================================ [ 'add' => 'Ajouter un alias', ], 'create' => [ 'helper' => 'Créés un alias pour :name, ce qui permettra de le retrouver dans la recherche globale et dans les mentions :code.', 'success' => 'Alias :name ajouté à :entity.', 'title' => 'Ajouter un alias à :name.', ], 'destroy' => [ 'success' => 'L\'alias :name a été retiré.', ], 'fields' => [ 'name' => 'Nom', ], 'helpers' => [ 'primary' => 'Définir un ou plusieurs alias sur l\'entrée la rentra trouvable avec la recherche global (tout en haut) et à travers les mentions.', ], 'limit' => 'Cette campagne a atteint sa limite d\'alias. Pour obtenir un nombre illimité d\'alias, débloques les fonctionnalités premium.', 'pitch' => 'Créés des alias vers cette entrée pour facilement la trouver dans la recherche et les mentions.', 'placeholders' => [ 'name' => 'Nouvel alias', ], 'unboosted' => [], 'update' => [ 'success' => 'Alias :name modifié pour :entity.', 'title' => 'Modifier l\'alias pour :name', ], ]; ================================================ FILE: lang/fr/entities/assets.php ================================================ [ 'alias' => 'Alias ou identrée secrète', 'file' => 'Ajouter un fichier', 'link' => 'Liens extèrne', ], 'copy_alias' => [ 'success' => 'Mention d\'alias copié au presse-papier.', 'title' => 'Clique pour copier la mention de l\'alias dans le presse-papiers.', ], 'show' => [ 'title' => 'Ressources de :name', ], ]; ================================================ FILE: lang/fr/entities/attributes.php ================================================ [ 'apply_kit' => 'Appliquer un kit de propriétés', 'load' => 'Charger', 'manage' => 'Gérer', 'more' => 'Plus d\'options', 'remove_all' => 'Tout supprimer', 'save_and_edit' => 'Appliquer et modifier', 'save_and_story'=> 'Appliquer et voir', 'show_hidden' => 'Afficher les propriétés cachées', 'toggle_privacy'=> 'Privé/Public', ], 'errors' => [ 'api' => 'Données invalides', 'loop' => 'Il y a une boucle sur la calculation de cette propriété!', 'no_attribute_selected' => 'Sélectionner d\'abord une ou plusieurs propriétés.', 'too_many_v2' => 'Le nombre maximum de champs est atteint (:count/:max). Supprimer d\'abord certaines propriétés avant de pouvoir en ajouter d\'autres.', ], 'fields' => [ 'community_templates' => 'Modèles Communautaires', 'is_private' => 'Propriétés privées', 'is_star' => 'Épinglé', 'kit' => 'Kit', 'preferences' => 'Préférences', 'property' => 'Propriété', 'value' => 'Valeur', ], 'filters' => [ 'name' => 'Nom de la propriété', 'value' => 'Valeur de la propriété', ], 'helpers' => [ 'delete_all' => 'Es-tu certain de vouloir supprimer toutes les propriétés de cette entrée?', 'is_private' => 'Seulement permettre aux membres du rôle :admin-role de voir les propriétés de cette entrée.', 'setup' => 'Tu peux représenter des valeurs comme les points de vie ou l\'intelligence d\'une entrée avec les propriétés. Ajoutes des propriétés manuellement en cliquant le bouton :manage, ou applique ceux d\'un kit de propriétés.', ], 'index' => [ 'success' => 'Propriétés modifiées pour :entity.', 'title' => 'Propriétés pour :name', ], 'labels' => [ 'checkbox' => 'Nom de la case à cocher', 'name' => 'Nom de la propriété', 'section' => 'Nom de la section', 'value' => 'Valeur de la propriété', ], 'live' => [ 'success' => 'Propriété :attribute modifiée.', 'title' => 'Modification de :attribute', ], 'placeholders' => [ 'attribute' => 'Nombre de quêtes, niveau de difficulté, initiative, population', 'block' => 'Nom du bloc', 'checkbox' => 'Nom de la case à cocher', 'icon' => [ 'class' => 'Classes FontAwesome ou RPG Awesome: fas fa-users', 'name' => 'Nom de l\'icône', ], 'kit' => 'Sélectionner un kit', 'number' => 'Nom du chiffre', 'random' => [ 'name' => 'Nom de la propriété', 'value' => '1-100 ou une liste de valeurs séparées par une virgule', ], 'section' => 'Nom de la section', 'value' => 'Valeur de la propriété', ], 'ranges' => [ 'text' => 'Options disponibles: :options', ], 'sections' => [ 'unorganised' => 'Non organisé', ], 'show' => [ 'hidden' => 'Propriétés cachées', 'title' => 'Propriétés de :name', ], 'template' => [ 'load' => [ 'success' => 'Modèle chargé', 'title' => 'Chargement à partir d\'un modèle', ], 'pitch' => 'Charge les propriétés depuis un kit de propriétés ou depuis les plugins installés via :plugin.', 'success' => 'Kit de propriétés :name appliqué pour :entity.', 'title' => 'Appliquer un kit de propriétés pour :name', ], 'title' => 'Propriétés', 'toasts' => [ 'bulk_deleted' => 'Propriétés supprimés', 'bulk_privacy' => 'Propriétés de privacité modifié', 'lock' => 'Propriété verouillé', 'pin' => 'Propriété épinglé', 'unlock' => 'Propriété déverouillé', 'unpin' => 'Propriété non-épinglé', ], 'tutorials' => [], 'types' => [ 'attribute' => 'Texte', 'block' => 'Bloc', 'checkbox' => 'Case à cocher', 'icon' => 'Icône', 'kits' => 'Kits', 'number' => 'Nombre', 'random' => 'Aléatoire', 'section' => 'Section', 'text' => 'Texte multiligne', ], 'update' => [ 'success' => 'Les propriétés de :entity ont été modifiées.', ], 'visibility' => [ 'entry' => 'Propriété affiché sur le menu d\'entrée.', 'private' => 'Propriété seulement visible aux membres du rôle "Admin".', 'public' => 'Propriété visible par tous les membres.', 'tab' => 'Propriété visible sous l\'onglet Propriétés.', ], ]; ================================================ FILE: lang/fr/entities/children.php ================================================ 'Enfants', ]; ================================================ FILE: lang/fr/entities/events.php ================================================ [ 'helper' => 'Créer un rappel pour lier :name à un calendrier.', ], 'fields' => [ 'type' => 'Type d\'événement', ], 'helpers' => [ 'characters' => 'Définir le type comme la date de naissance ou de mort pour ce personnage calculera automatiquement leur âge. :more.', 'founding' => 'Définir le type comme :type calculera automatiquement l\'âge de fondation de l\'entrée.', ], 'show' => [ 'actions' => [ 'add' => 'Ajouter un rappel', ], 'title' => 'Rappels pour :name', ], 'types' => [ 'birth' => 'Naissance', 'birthday' => 'Anniversaire', 'death' => 'Décès', 'founded' => 'Fondé', 'primary' => 'Principal', ], 'years-ago' => '{1} il y a :count année|[2,*] il y a :count année', ]; ================================================ FILE: lang/fr/entities/files.php ================================================ [ 'max' => [ 'helper' => 'Tu ne peux pas joindre plus de fichiers à moins d\'en supprimer un existant.', 'limit' => 'Cette entrée a atteint le nombre maximum de fichiers', ], 'upgrade' => [ 'limit' => 'Tu as atteint la limite de :limit fichiers pour cette entrée.', 'premium' => 'Passer à une campagne premium pour joindre un nombre illimité de fichiers et bénéficier d\'une flexibilité créative encore plus grande.', 'upgrade' => 'Passe à une campagne premium pour pouvoir joindre jusqu\'à :limit fichiers et débloquer encore plus de flexibilité créative.', ], ], 'create' => [ 'helper' => 'Ajouter un fichier à :name. Le fichier sera pris en compte dans la limite de stockage de la galerie.', 'success_plural' => '{1} Fichier :file ajouté.|[2,*] :count fichiers ajoutés.', 'title' => 'Nouveau fichier', ], 'destroy' => [ 'success' => 'Fichier :file retiré.', ], 'fields' => [ 'file' => 'Fichier', 'files' => 'Fichiers', 'name' => 'Nom de fichier', ], 'max' => [ 'title' => 'Limite atteinte', ], 'update' => [ 'success' => 'Fichier :name modifié.', 'title' => 'Modifier le fichier', ], ]; ================================================ FILE: lang/fr/entities/image.php ================================================ [ 'change_focus' => 'Changer le point de focus', 'change_visibility' => 'Changer la visibilité', 'copy_url' => 'Copier l\'URL de l\'image', 'copy_url_success' => 'URL de l\'image copiée dans ton presse-papiers.', 'replace_image' => 'Remplacer l\'image', 'save-replace' => 'Remplacer l\'image', 'save_focus' => 'Enregistrer le focus', 'view' => 'Voir l\'image', ], 'call-to-action' => 'Cliquer sur l\'image de l\'entrée pour définir le point de focus de l\'image. Ceci remplace l\'estimation automatique.', 'focus' => [ 'breadcrumb' => 'Focus d\'image', 'helper' => 'Cliquer sur l\'image pour définir le point de focus. Cliquer sur le point de focus pour le retirer.', 'panel_title' => 'Focus d\'image', 'success' => 'Point de focus mis à jour.', 'title' => 'Focus d\'image pour l\'entrée :name', 'unboosted' => 'Définir un point de focus pour l\'image est réservé aux :boosted-campaigns.', 'warning' => 'Le point focal des images de la :gallery est partagé par toutes les entrées qui utilisent la même image.', ], 'gallery_permissions' => [ 'admin' => 'Cette image de la galerie n\'est visible que par les membres du rôle :admin de la campagne.', 'adminself' => 'Cette image de la galerie n\'est visible que par le :creator et les membres du rôle :admin de la campagne.', 'member' => 'Cette image de la galerie n\'est visible que par les membres de la campagne.', 'self' => 'Cette image de la galerie n\'est visible que par toi.', ], 'replace' => [ 'breadcrumb' => 'Remplacement d\'image', 'panel_title' => 'Remplacement d\'image de l\'entrée', 'success' => 'Image remplacée.', 'title' => 'Remplacement d\'image pour :name', ], 'visibility' => [ 'helper' => 'Changer la visibilité de l\'image de galerie, controllant ainsi qui peut la voir.', 'updated' => 'Visibilité de l\'image modifiée.', ], ]; ================================================ FILE: lang/fr/entities/inventories.php ================================================ [ 'copy_from_entity' => 'Copier depuis une autre entrée', 'copy_inventory' => 'Copier l\'inventaire', 'generate' => 'Générer', 'multiple' => 'Ajouter des objets', ], 'copy' => [ 'helper' => 'Copie l\'inventaire complet d\'une entrée à :name.', ], 'create' => [ 'helper' => 'Ajouter un objet à l\'inventaire de :name. Il peut éventuellement être lié à un objet existant de la campagne.', 'success' => 'Objet :item ajouté à :entity.', 'success_bulk' => '{0} Aucun objet ajouté à :entity.|{1} :count objet ajouté à :entity.|[2,*] :count objets ajoutés à :entity.', 'title' => 'Ajouter à l\'inventaire', ], 'default_position' => 'Non organisé', 'destroy' => [ 'success' => 'Objet :item retiré de :entity.', 'success_position' => 'Les éléments de :position ont été retirés de :entity.', ], 'fields' => [ 'amount' => 'Quantité', 'copy_entity_entry_v2' => 'Utiliser l\'entrée de l\'objet', 'description' => 'Description', 'is_equipped' => 'Equipé', 'item_amount' => 'Nombre d\'objets', 'match_all' => 'Avec toutes les étiquettes', 'name' => 'Nom', 'position' => 'Position', 'qty' => 'Qté', 'replace' => 'Remplacer l\'inventaire', ], 'generate' => [ 'helper' => 'Générer un inventaire pour :name basé sur des objets de la campagne.', 'title' => 'Générer un inventaire', ], 'helpers' => [ 'amount' => 'Nombre d\'objets', 'copy_entity_entry_v2' => 'Affiche l\'entrée de l\'objet au lieu de la description personnalisée.', 'description' => 'Ajouter une description personnalisée à l\'objet', 'is_equipped' => 'Marquer cet objet comme étant équipé.', 'name' => 'Donner un nom à l\'objet. Un nom est requis si aucun objet n\'est sélectionné', 'replace' => 'Remplacer l\'inventaire actuel avec les nouveaux éléments générés.', ], 'placeholders' => [ 'amount' => 'Un nombre', 'description' => 'Usé, abimé, atténué', 'name' => 'Requis si aucun object n\'est sélectionné', 'position' => 'Equipé, Sac-à-dos, Entrepôt, Banque', ], 'show' => [ 'helper' => 'Les entrées peuvent avoir des objets attachés pour créer un inventaire.', 'title' => 'Inventaire de :name', 'unsorted' => 'Autre', ], 'togglers' => [ 'hide' => [ 'price' => 'Cacher prix', 'quantity' => 'Cacher quantité', 'size' => 'Cacher taille', 'weight' => 'Cacher poids', ], 'show' => [ 'price' => 'Afficher prix', 'quantity' => 'Afficher quantité', 'size' => 'Afficher taille', 'weight' => 'Afficher poids', ], ], 'tooltips' => [ 'equipped' => 'Cet objet est équipé', ], 'tutorials' => [ 'all' => 'Suis ce que :name possède, garde ou vend en ajoutant des objets à cet inventaire', ], 'update' => [ 'success' => 'Objet :item modifié pour :entity.', 'title' => 'Modifier un objet sur :name', ], ]; ================================================ FILE: lang/fr/entities/links.php ================================================ [ 'add' => 'Ajouter un lien', ], 'call-to-action' => 'Ajoutes des liens vers des ressources externes sur cette entrée, comme DnDBeyond, et ils s\'afficheront directement sur la vue d\'ensemble de l\'entrée.', 'create' => [ 'helper' => 'Ajouter un lien externe vers :name, par exemple vers sa page DnDBeyond.', 'success' => 'Lien :name ajouté à :entity.', 'title' => 'Ajouter un lien à :name', ], 'destroy' => [ 'success' => 'Lien :name retiré de :entity.', ], 'fields' => [ 'icon' => 'Icone', 'name' => 'Nom', 'position' => 'Position', 'url' => 'URL', ], 'go' => [ 'actions' => [ 'confirm' => 'Je suis sûr', 'trust' => 'Ne plus me demander', ], 'description' => 'Ce lien te redirige vers :link. Es-tu sûr de vouloir y aller?', 'title' => 'Départ de Kanka', ], 'helpers' => [ 'icon' => 'Personaliser l\'icône affichée pour le favori. Utiliser les options de :fontawesome ou laisser le champ vide.', 'parent' => 'Afficher ce favori après un élément de la navigation, au lieu de l\'afficher dans la section des favoris.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Les campagnes boostées peuvent définir des liens sur les entrées.', 'title' => 'Liens pour :name', ], 'unboosted' => [], 'update' => [ 'success' => 'Lien :name modifié pour :entity.', 'title' => 'Modifier le lien pour :name', ], ]; ================================================ FILE: lang/fr/entities/logs.php ================================================ [ 'create' => 'Créé', 'create_post' => 'Créé l\'article ":post"', 'delete' => 'Supprimé', 'delete_post' => 'Supprimé un article', 'reorder_post' => 'Articles réordonnés', 'restore' => 'Restoré', 'reveal' => 'Afficher les détails', 'update' => 'Mis à jour', 'update_post' => 'Modifié l\'article ":post"', 'view' => 'Voir les changements', ], 'call-to-action' => 'Des journaux complets des changements sont disponibles pendant :amount jours pour les campagnes premium.', 'fields' => [ 'action' => 'Action', 'date' => 'Date', ], 'filters' => [ 'keywords' => 'Mot cléf', ], 'impersonated' => 'Imité par :name', 'none' => 'Aucun', 'show' => [ 'title' => 'Historique de :name', ], 'tooltips' => [ 'post' => 'Aller à l\'article', ], ]; ================================================ FILE: lang/fr/entities/map-points.php ================================================ 'Cette entrée est affichée sur les cartes suivantes.', 'title' => ':name Point de Carte', ]; ================================================ FILE: lang/fr/entities/mentions.php ================================================ [ 'element' => 'Elément', 'type' => 'Type', ], 'helper' => 'Cette liste affiche les entrées qui mentionnent cette entrée dans leur description.', 'mentioned_in_v2' => 'Cette entrée est mentionnée dans :count entrées, articles, ou campagnes. :more.', 'see_more' => 'Afficher les détails', 'show' => [ 'title' => 'Mention d\'entrée :name', ], 'title' => 'Entrée mentionnée', ]; ================================================ FILE: lang/fr/entities/move.php ================================================ [ 'copy' => 'Copier', 'transfer' => 'Transférer', ], 'errors' => [ 'permission' => 'Une entrée de cette catégorie ne peut pas être créée dans la campagne ciblée.', 'permission_update' => 'Cette entrée ne peut pas être déplacée.', 'same_campaign' => 'Une autre campagne doit être sélectionnée.', 'unknown_campaign' => 'Campagne inconnue.', ], 'fields' => [ 'campaign' => 'Campagne cible', 'copy' => 'Faire une copie', 'select_one' => 'Sélectionner une campagne', ], 'helpers' => [ 'copy' => 'Créer une copie de cette entrée dans la campagne cible.', ], 'panel' => [ 'description' => 'Sélectionner une campagne vers laquelle cette entrée sera déplacée ou copiée.', 'description_bulk_copy' => 'Sélectionner une campagne vers laquelle cette entrée sera copiée.', 'title' => 'Déplacer ou copier une entrée vers une autre campagne', ], 'success' => 'L\'entrée :name a été déplacée.', 'success_copy' => 'L\'entrée :name a été copiée.', 'title' => 'Déplacer :name', 'warnings' => [ 'custom' => 'Cette entrée n\'est pas d\'une catégorie par défaut, mais d\'une catégorie personnalisée ":module". Elle sera créée en tant que Note dans la campagne cible.', ], ]; ================================================ FILE: lang/fr/entities/notes.php ================================================ [ 'add' => 'Ajouter un article', 'add_role' => 'Ajouter un rôle', 'add_user' => 'Ajouter un membre', ], 'collapsed' => [ 'closed' => 'L\'article est fermé et seul le titre est visible', 'open' => 'L\'article est pleinement visible', ], 'copy_mention' => [ 'copy' => 'Copier la mention avancée', 'copy_with_name' => 'Copier la mention avancées avec le nom', 'success' => 'La mention avancée de l\'article a été copiée au presse-papier.', ], 'create' => [ 'success' => 'Article \':name\' ajouté à :entity.', ], 'destroy' => [ 'success' => 'L\'article :name a été retirée.', ], 'edit' => [ 'success' => 'L\'article :name pour :entity a été modifié.', ], 'fields' => [ 'creator' => 'Créé par', 'display' => 'Affichage', 'name' => 'Nom', 'position' => 'Position', ], 'footer' => [ 'created' => 'Créé par :user au :date', 'updated' => 'Modifié par :user au :date', ], 'hint' => 'Les informations qui n\'entrent pas vraiment dans les champs pré-définis ou qui doivent être privées peuvent être ajoutées en tant qu\'article.', 'hints' => [ 'reorder' => 'Les articles peuvent être réarrangée en cliquant sur l\'icône :icon à côté de Histoire dans le menu de l\'entrée.', ], 'index' => [], 'move' => [ 'copy' => 'Créer une copie sur l\'entrée cible', 'copy_success' => 'L\'article :name a été copiée vers :entity avec succès.', 'copy_title' => 'Guarder une copie', 'description' => 'Sélectionnes une entrée vers laquelle l\'article doit être déplacé ou copié.', 'entity' => 'Entrée cible', 'move_success' => 'L\'article :name a été déplacé vers :entity avec succès.', ], 'placeholders' => [ 'name' => 'Nom de l\'article, observation ou remarque', ], 'show' => [ 'advanced' => 'Permissions Avancées', 'title' => 'Article :name pour :entity', ], 'states' => [ 'collapsed' => 'Rétréci', 'expanded' => 'Étendu', ], 'warning' => [], ]; ================================================ FILE: lang/fr/entities/permissions.php ================================================ [ 'text' => 'Cette entrée est privée. Les permissions détaillées peuvent toujours être définies, mais tant que cette entrée est privée, ceux-ci seront ignorés, et seulement les membres du rôle :admin pourront voir cette entrée.', 'warning' => 'Attention', ], 'quick' => [ 'empty-permissions' => 'Aucun rôle ou utilisateur hors des admins de la campagne n\'ont accès à cette entrée.', 'manage' => 'Gérer les permissions', 'screen-reader' => 'Ouvrir les paramètres de sécurité', 'success' => [ 'private' => 'L\'entrée :entity est maintenant cachée.', 'public' => 'L\'entrée :entity est maintenant visible.', ], 'title' => 'Permissions', 'viewable-by' => 'Visible par', ], 'toggle' => [ 'label' => 'Confidentialité de l\'entrée', 'private' => [ 'description' => 'Seulement visible aux membres du rôle :admin.', 'title' => 'Privé', ], 'public' => [ 'description' => 'Visibles aux rôles et membres suivants.', 'title' => 'Publique', ], ], ]; ================================================ FILE: lang/fr/entities/pins.php ================================================ 'Liens', 'title' => 'Épingles', ]; ================================================ FILE: lang/fr/entities/profile.php ================================================ [ 'edit_profile' => 'Modifier le profil', ], 'aliases' => 'Alias', 'history' => 'Historique', 'show' => [ 'tab_name' => 'Profil', 'title' => 'Profil de :name', ], ]; ================================================ FILE: lang/fr/entities/quests.php ================================================ 'Cette entrée fait partie des quêtes suivantes.', 'title' => 'Quêtes de :name', ]; ================================================ FILE: lang/fr/entities/relations.php ================================================ [ 'mode-map' => 'Outil de visualisation des relations', 'mode-table' => 'Table des relations et relation', ], 'bulk' => [ 'delete' => '{1} :count relation supprimée.|[2,*] :count relations supprimées.', 'fields' => [ 'delete_mirrored' => 'Supprimer la relation liée', 'unmirror' => 'Délier la relation', 'update_mirrored' => 'Actualiser la relation liée', ], 'helpers' => [ 'delete_mirrored' => 'Aussi supprimer les relations liées.', 'unmirror' => 'Délier les relations liées.', 'update_mirrored' => 'Actualiser les relations liées.', ], 'success' => [ 'editing' => '{1} :count relation modifiée.|[2,*] :count relations modifiées.', 'editing_partial' => '{1} :count/:total relation modifiée.|[2,*] :count/:total relations modifiées.', ], ], 'call-to-action' => 'Explorer visuellement les relations d\'une entrée et la manière dont elle est connectée au reste de la campagne.', 'connections' => [ 'map_point' => 'Point de carte', 'mention' => 'Mention', 'quest_element' => 'Élément de quête', 'timeline_element' => 'Élément de timeline', ], 'create' => [ 'helper' => 'Créer un lien entre :name et une ou plusieurs entrées.', 'new_title' => 'Nouvelle relation', 'success_bulk' => '{1} Ajout de :count relation à :entity.|[2,*] Ajout de :count relations à :entity.', ], 'delete_mirrored' => [ 'helper' => 'Cette relation est dupliquée sur l\'entrée cible. Sélectionner cette option pour aussi supprimer la relation symétrique.', 'option' => 'Supprimer la relation miroir', ], 'destroy' => [ 'mirrored' => 'Ceci supprimera aussi la relation liée et est permanent.', 'success' => 'Relation :target supprimée pour :entity.', ], 'fields' => [ 'attitude' => 'Attitude', 'is_pinned' => 'Épinglé', 'link' => 'Lien réciproque', 'mirror_relation' => 'Rôle réciproque', 'owner' => 'Source', 'role' => 'Rôle', 'target' => 'Cible', 'targets' => 'Cibles', 'two_way' => 'Créer une relation miroir', 'unmirror' => 'Délier cette relation de la relation miroir.', ], 'filters' => [ 'connection' => 'Relation de la relation', 'name' => 'Cible de la relation', ], 'helper' => 'Définir des relations entre entrées avec leurs description, attitude et visibilité. Les relations peuvent aussi être épinglées sur le menu de l\'entrée.', 'helpers' => [ 'description' => 'Détailler la nature du lien entre les deux entrées.', 'link' => 'Créer une relation correspondante sur les cibles.', 'mirror_relation' => 'Comment la cible voit cette entrée (laisser vide pour copier ci-dessus).', 'no_relations' => 'Cette entrée n\'a actuellement aucune relation vers d\'autres entrées de la campagne.', ], 'hints' => [ 'attitude' => 'Ce champ optionnel peut être utilisé pour définir l\'ordre ascendant dans lequel s\'affichent les relations.', 'two_way' => 'Sélectionne pour créer une copie de la relation sur la cible.', ], 'index' => [ 'title' => 'Relations', ], 'linked' => [ 'break' => 'Rompre le lien', 'helper' => 'Cette relation est synchronisée avec :link', 'label' => 'Relation liée', 'unmirror-helper' => 'Convertir en relation indépendante ne supprimera rien.', ], 'options' => [ 'mentions' => 'Défaut + liés + mentions', 'only_relations' => 'Seule les relations directes', 'related' => 'Défaut + liés', 'relations' => 'Défaut', 'show' => 'Afficher', ], 'panels' => [ 'related' => 'Éléments liés', ], 'placeholders' => [ 'attitude' => 'de -100 à 100, 100 étant très positif.', 'role' => 'Rival, Meilleur ami, Frère/Sœur', ], 'show' => [ 'title' => 'Relations de :name', ], 'types' => [ 'family_member' => 'Membre de famille', 'organisation_member' => 'Membre d\'organisation', ], 'update' => [ 'success' => 'Relation :target modifiée pour :entity.', 'title' => 'Modifier la relation de :name', ], ]; ================================================ FILE: lang/fr/entities/reminders.php ================================================ [ 'add' => 'Lier à un calendrier', 'remove' => 'Retirer la date de calendrier', ], 'helpers' => [ 'pitch' => 'Laisse de côté le calendrier du monde réel et connecte cette entrée à un calendrier de ton univers pour mieux t\'immerger dans ta chronologie fictive.', ], ]; ================================================ FILE: lang/fr/entities/share.php ================================================ [ 'copy' => 'Copier le lien', 'make_public' => 'Rendre la campagne publique', ], 'fields' => [ 'campaign_access' => 'Paramètres de campagne', 'visibility_mode' => 'Corriger la visibilité', ], 'helpers' => [ 'campaign_access' => 'Pour partager ceci publiquement, la campagne elle-même doit d\'abord être rendue publique.', 'entity_permissions_warning' => 'Rendre cette campagne publique permet à tout le monde de la voir. Les entrées marquées comme privées restent cachées.', 'hidden_explanation' => 'La campagne est publique, mais cette entrée est actuellement cachée aux non-membres.', 'hidden_unlisted_explanation' => 'La campagne est non listée, seuls ceux qui ont le lien peuvent la trouver.', 'member-link' => 'Partager uniquement avec les membres', 'private_explanation' => 'Seuls les membres peuvent accéder à cette entrée.', 'public_explanation' => 'La campagne et cette entrée sont publiques. N\'importe qui avec le lien peut la voir.', 'unlisted_explanation' => 'La campagne est non listée et cette entrée est visible. N\'importe qui avec le lien peut la voir.', ], 'labels' => [ 'member_link' => 'Lien réservé aux membres', 'public_link' => 'Lien public', 'share_link' => 'Lien de partage', ], 'options' => [ 'keep_private' => 'Garder la campagne privée', 'make_all_public' => 'Montrer toutes les :module aux non-membres', 'make_campaign_public' => 'Rendre la campagne publique', 'make_entity_public' => 'Montrer :name aux non-membres', ], 'status' => [ 'hidden' => 'Non visible pour les non-membres', 'private' => 'Cette campagne est privée', 'public' => 'Visible pour les non-membres', 'unlisted' => 'Visible par n\'importe qui avec le lien', ], 'success' => [ 'copied' => 'Lien copié dans le presse-papiers!', 'copied_members' => 'Lien réservé aux membres copié.', 'copied_public' => 'Lien public copié, n\'importe qui avec le lien peut voir l\'entrée.', 'updated' => 'Paramètres de visibilité mis à jour.', ], 'title' => 'Partager l\'entrée', ]; ================================================ FILE: lang/fr/entities/statuses.php ================================================ [ 'alive' => 'Vivant', 'dead' => 'Mort', 'missing' => 'Disparu', ], 'creature' => [ 'dead' => 'Mort', 'extinct' => 'Éteint', ], 'family' => [ 'extinct' => 'Éteinte', ], 'item' => [ 'destroyed' => 'Détruit', 'lost' => 'Perdu', 'owned' => 'Possédé', ], 'location' => [ 'destroyed' => 'Détruit', ], 'organisation' => [ 'defunct' => 'Défunte', ], 'quest' => [ 'abandoned' => 'Abandonné', 'completed' => 'Terminé', 'not_started' => 'Non commencé', 'ongoing' => 'En cours', ], 'race' => [ 'extinct' => 'Éteinte', ], 'remove' => 'Retirer', ]; ================================================ FILE: lang/fr/entities/story.php ================================================ [ 'collapse_all' => 'Tout réduir', 'expand_all' => 'Tout étendre', 'load_more' => 'Voir plus', 'login_for_more' => 'Se connecter pour voir plus d\'entrées', ], 'reorder' => [ 'helper' => 'Fais glisser les articles pour les réorganiser sur la page d\'aperçu de l\'entrée.', 'icon_tooltip' => 'Réordonner', 'panel_title' => 'Réordonner les éléments', 'save' => 'Enregistrer les changements', 'success' => 'Les éléments ont été réordonnés.', ], 'update' => [ 'title' => 'Modifier l\'entrée :entity', ], 'warning' => [], ]; ================================================ FILE: lang/fr/entities/tags.php ================================================ [ 'helper' => 'Ajouter ou retirer des étiquettes à :name.', 'title' => 'Étiqueter une entrée', ], ]; ================================================ FILE: lang/fr/entities/timelines.php ================================================ 'Les chronologies dans lesquelles apparaît cette entrée sont affichées ci-dessous.', 'show' => [ 'title' => 'Chronologies de :name', ], ]; ================================================ FILE: lang/fr/entities/tooltips.php ================================================ ':name n\'a pas encore de contenu.', 'fix' => 'Ajouter une déscription', 'formatting' => 'HTML pris en charge:text (:text), structure (:layout), listes/tableaux et images.', 'helper' => 'Remplacer le texte d’aperçu généré automatiquement par défaut affiché au survol des liens vers cette entrée.', 'label' => 'Teaser', 'placeholder' => 'Décris brièvement cette entrée en une ou deux phrases.', 'premium' => 'Débloque la possibilité de personnaliser le teaser au survol avec une :boosted-campaign.', ]; ================================================ FILE: lang/fr/entities/transform.php ================================================ [ 'convert' => 'Change la catégorie', ], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Type inconnu ou invalide', ], 'success' => '{1} :count entrée transformée à la nouvelle catégorie :type.|[2,*] :count entrées transformées à la nouvelle catégorie :type.', ], 'confirm' => [ 'checkbox' => 'Je comprends qu\'en changeant :entity vers une autre catégorie, les éléments suivants seront perdus:', 'label' => 'Confirmer la perte de données', ], 'documentation' => 'Documentation : conversion des catégories d\'entrées', 'fields' => [ 'current' => 'Catégorie actuelle', 'select_one' => 'Sélectionner un', 'target' => 'Nouveau type de l\'entrée', ], 'panel' => [ 'bulk_description' => 'Changes la catégorie de plusieurs entrées. Attention cependant, certaines informations peuvent être perdues dû au différent champs sur les entrés.', 'bulk_title' => 'Transformer plusieurs entrées', 'title' => 'Transformer une entrée', 'warning' => 'Certaines données peuvent ne pas être reprises si la nouvelle catégorie utilise des champs différents', ], 'success' => 'L\'entrée :name a été transformée.', 'title' => 'Transformer :name', ]; ================================================ FILE: lang/fr/entities.php ================================================ 'Pouvoirs', 'ability' => 'Pouvoir', 'archetype' => 'Archétype', 'archetypes' => 'Archétypes', 'article' => 'Article', 'articles' => 'Articles', 'attribute_template' => 'Kit de propriétés', 'attribute_templates' => 'Kits de propriétés', 'bookmark' => 'Favori', 'bookmarks' => 'Favoris', 'calendar' => 'Calendrier', 'calendars' => 'Calendriers', 'campaign' => 'Campagne', 'campaigns' => 'Campagnes', 'character' => 'Personnage', 'characters' => 'Personnages', 'conversation' => 'Conversation', 'conversations' => 'Conversations', 'creator' => [ 'actions' => [ 'create' => 'Créer :type', 'full' => 'Aller au formulaire complet', 'more' => 'Ajouter plus de détails', ], 'back' => 'Retour à la sélection', 'bulk_names' => 'Ajouter un nom par ligne', 'duplicate' => 'Il y a d\'autres entrées de cette catégorie avec le même nom.', 'helper_v2' => 'Créé rapidement les fondations d\'une nouvelle entrée sans interrompre ton flow.', 'helpers' => [ 'archetype' => 'Sélectionner un archétype sur lequel les nouvelles entrées seront basées', ], 'missing_v2' => 'Seuls les catégories activées et auxquels tu as la permission de créer des entrées sont visibles dans cette interface. :learn-more.', 'modes' => [ 'archetypes' => 'Sélection d\'archétype', 'bulk' => 'Ajout en bloc', 'default' => 'Ajout rapide', ], 'name' => [ 'new' => 'Nouveau nom', 'remove' => 'Retirer', ], 'success_multiple' => '{1} Nouvelle entrée :link créée.|[2,*] Nouvelles entrées :link créées.', 'success_multiple_posts' => '{1} Nouveau article :link créé.|[2,*] Nouveaux articles :link créés.', 'title' => 'Nouvelle Entrée', 'titles' => [ 'everything' => 'Tout', 'quick-access' => 'Accès rapide', ], 'tooltip' => 'Créer une nouvelle entrée sans quitter la page actuelle', 'tooltips' => [ 'create' => 'Créer une entrée et poursuivre avec la sélection d\'entrée', 'create_more' => 'Créer une entrée et poursuivre avec la création d\'une autre entrée du même type', 'edit' => 'Créer une entrée et poursuivre avec la modification de la nouvelle entrée', ], ], 'creature' => 'Créature', 'creatures' => 'Créatures', 'dice_roll' => 'Jet de dés', 'dice_rolls' => 'Jets de dés', 'entries' => 'Entrées', 'entry' => 'Entrée', 'event' => 'Événement', 'events' => 'Événements', 'families' => 'Familles', 'family' => 'Famille', 'inventories' => 'Inventaires', 'item' => 'Objet', 'items' => 'Objets', 'journal' => 'Journal', 'journals' => 'Journaux', 'location' => 'Lieu', 'locations' => 'Lieux', 'map' => 'Carte', 'maps' => 'Cartes', 'media' => 'Média', 'new' => [], 'note' => 'Note', 'notes' => 'Notes', 'organisation' => 'Organisation', 'organisations' => 'Organisations', 'properties' => 'Propriétés', 'quest' => 'Quête', 'quest_element' => 'Elément de quête', 'quests' => 'Quêtes', 'race' => 'Race', 'races' => 'Races', 'relation' => 'Relation', 'relations' => 'Relations', 'reminders' => 'Rappels', 'status' => 'Statut', 'tag' => 'Etiquette', 'tags' => 'Etiquettes', 'templates' => 'Modèles', 'timeline' => 'Chronologie', 'timeline_element' => 'Elément de chronologie', 'timelines' => 'Chronologies', 'whiteboard' => 'Tableau blanc', 'whiteboards' => 'Tableaux blancs', ]; ================================================ FILE: lang/fr/entries/archetypes.php ================================================ [ 'how' => 'Comment définir des archétypes', ], 'success' => [ 'set' => ':name défini comme archétype.', 'unset' => ':name n\'est plus défini comme archétype.', ], ]; ================================================ FILE: lang/fr/entries/archive.php ================================================ 'Cette entrée est archivée et n\'apparaît plus dans les listes ou les recherches.', ]; ================================================ FILE: lang/fr/entries/bulk.php ================================================ [ 'copy_to_campaign' => '{1} :count entrée copiée vers :campaign.|[2,*] :count entrées copiées vers :campaign.', 'delete' => '{1} :count entrée supprimée.|[2,*] :count entrées supprimées.', 'editing' => '{1} :count entrée mise à jour.|[2,*] :count entrées mises à jour.', 'editing_partial' => '{1} :count/:total entrée mise à jour.|[2,*] :count/:total entrées mises à jour.', 'permissions' => '{1} Permissions modifiées pour :count entrée.|[2,*] Permissions modifiées pour :count entrées.', 'private' => '{1} :count entrée est maintenant privée.|[2,*] :count entrées sont maintenant privées.', 'public' => '{1} :count entrée est maintenant visible.|[2,*] :count entrées sont maintenant visibles.', 'templates' => '{1} :count entrée a reçu un kit.|[2,*] :count entrées ont reçu un kit.', ], ]; ================================================ FILE: lang/fr/entries/fields.php ================================================ [ 'placeholder' => 'Nom de l\'entrée', ], 'type' => [ 'placeholder' => 'Type de l\'entrée', ], ]; ================================================ FILE: lang/fr/entries/tabs.php ================================================ 'Alias', 'identity' => 'Identrée', 'media' => 'Média', 'properties' => 'Propriétés', 'relations' => 'Relations', ]; ================================================ FILE: lang/fr/errors.php ================================================ [ 'body' => 'Tu n\'as pas accès à cette page!', 'title' => 'Accès refusé.', ], '403-form' => [ 'help' => 'Ceci peut être dû à la session qui n\'est plus active. Prière de se reconnecter dans une autre fenêtre avant de ressayer d\'enregistrer.', ], '404' => [ 'body' => 'Désolé, la page demandée ne peut être trouvée.', 'title' => 'Page Inconnue', ], '500' => [ 'body' => [ '1' => 'Oups, quelque chose s\'est mal passé.', '2' => 'Un rapport avec l\'erreur rencontrée nous a été envoyé, mais quelques fois ça aide si nous avons plus de détails.', ], 'title' => 'Erreur', ], '503' => [ 'body' => [ '1' => 'Kanka est actuellement en maintenance, ce qui d\'habitude signifie qu\'une mise à jour est en cours!', '2' => 'Désolé pour le dérangement. Tout reviendra à la normale dans quelques minutes.', ], 'json' => 'Kanka est actuellement en maintenance. Prière de ressayer dans quelques minutes.', 'title' => 'Maintenance', ], 'back-to-campaigns' => 'Revenir à une de tes campagnes', 'footer' => 'Si tu as besoin d\'aide, contacte-nous a hello@kanka.io ou sur le :discord.', 'log-in' => 'La connexion à ton compte pourrait te permettre de trouver ce que tu cherches.', 'post_layout' => 'Mise en page d\'article invalide.', 'private-campaign' => [ 'auth' => [ 'helper' => 'Tu n\'as pas accès à cette campagne.', ], 'guest' => [ 'helper' => 'La campagne que tu essaye d\'accéder est privée et tu n\'es pas connecté à ton compte.', 'login' => 'Se connecter pourrait te donner accès au contenu.', ], 'title' => 'Campagne privée', ], ]; ================================================ FILE: lang/fr/events.php ================================================ [ 'title' => 'Nouvel Evénement', ], 'events' => [ 'helper' => 'Les événements qui ont cette entrée comme événement parent sont affichés ici.', ], 'fields' => [ 'date' => 'Date', ], 'helpers' => [ 'date' => 'Ce champ peut contenir n\'importe quelle valeur et n\'est pas lié aux calendriers de la campagne. Pour lier cet événement à un calendrier, il faut se rendre sur l\'onglet rappels de cet événement.', ], 'lists' => [ 'empty' => 'Ajoute des moments importants tels que des batailles, des couronnements ou des découvertes à l\'histoire de ton monde.', ], 'placeholders' => [ 'date' => 'La date de l\'événement', 'type' => 'Cérémonie, Festival, Désastre, Bataille, Naissance', ], 'tabs' => [ 'calendars' => 'Entrées calendrier', ], ]; ================================================ FILE: lang/fr/export.php ================================================ 'Contenu', 'hidden_campaign' => 'Campagne cachée', 'index' => 'Index d\'entrée', ]; ================================================ FILE: lang/fr/families/trees.php ================================================ [ 'clear' => 'Tout effacer', 'first' => 'Ajouter un/e fondateur/trice', 'founder' => 'Nouveau fondateur', 'rename-relation' => 'Renommer la relation', 'reset' => 'Annuler les modifications', 'save' => 'Enregister', ], 'modal' => [ 'first-title' => 'Sélectionner une entrée', 'helper' => 'Remplacer l\'entrée avec une autre de la campagne', 'relation' => 'Relation', 'title' => 'Remplacer l\'entrée', ], 'modals' => [ 'clear' => [ 'confirm' => 'Es-tu sûr de vouloir réinitialiser l\'arbre de famille?', ], 'entity' => [ 'add' => [ 'founder' => 'Fondateur/trice', 'member' => 'Membre', 'success' => 'Entrée ajoutée.', 'title' => 'Ajouter une entrée', ], 'child' => [ 'success' => 'Enfant ajouté.', 'title' => 'Ajouter un enfant', ], 'edit' => [ 'helper' => 'Sélectionner des options si la relation est inconnue. Un personnage peut être ajouté plus tard.', 'success' => 'Entrée modifiée.', 'title' => 'Modifier une entrée', ], 'founder' => [ 'title' => 'Ajouter un nouveau fondateur', ], 'remove' => [ 'confirm' => 'Es-tu sûr de vouloir retirer cette entrée de l\'arbre de famille?', 'success' => 'Entrée retirée.', ], ], 'relations' => [ 'add' => [ 'success' => 'Relation ajoutée.', 'title' => 'Ajouter une relation', ], 'edit' => [ 'success' => 'Relation modifiée.', 'title' => 'Modifier une relation', ], 'unknown' => 'Inconnu', ], 'reset' => [ 'confirm' => 'Es-tu sûr de vouloir annuler toutes les modifications faites à l\'arbre de famille?', ], ], 'pitch' => 'Créer un arbre de famille détaillé pour les familles de la campagne.', 'success' => [ 'cleared' => 'Arbre de famille effacé.', 'reseted' => 'Arbre de famille réinitialisé.', 'saved' => 'Arbre de famille enregistré.', ], 'title' => ':name Arbre de famille', 'unknown' => 'non établi', ]; ================================================ FILE: lang/fr/families.php ================================================ [ 'title' => 'Nouvelle Famille', ], 'fields' => [], 'hints' => [ 'is_extinct' => 'Cette famille est éteinte.', 'members' => 'Les membres d\'une famille sont affichés ici. Un personnage peut être ajouté à une famille lors de l\'édition du personnage en utilisant le champ "Famille".', ], 'lists' => [ 'empty' => 'Suis les lignées, les clans ou les maisons nobles qui relient tes personnages entre eux.', ], 'members' => [ 'create' => [ 'helper' => 'Ajouter un ou plusieurs membres à :name.', 'success' => '{0} Aucun membre ajouté.|{1} 1 membre ajouté.|[2,*] :count membres ajoutés.', 'title' => 'Nouveau membres', ], ], 'placeholders' => [ 'name' => 'Nom de la famille', 'type' => 'Royale, Noble, Éteinte', ], 'show' => [ 'tabs' => [ 'tree' => 'Arbre de famille', ], ], ]; ================================================ FILE: lang/fr/faq.php ================================================ [ 'answer' => 'Réponse', 'category' => 'Question', 'locale' => 'Langue', 'order' => 'Ordre', 'question' => 'Question', ], 'gods-and-religions' => [ 'answer' => 'Les Dieux sont des personnages, et les religions sont des organisations dans Kanka. Pour rapidement trouver les dieux, nous recommandons aussi d\'utiliser des étiquettes.', 'question' => '', ], 'permissions' => [ 'answer' => 'Absolument, c\'est ce pourquoi nous avons créé Kanka! Vous pouvez inviter n\'importe qui à votre campagne, et leur donner des rôles et des permissions. Nous avons créé un système extrêmement flexible (vous pouvez à la fois utiliser des options de permission et de restriction) pour couvrir toutes les situations possibles.', 'question' => 'Puis-je sélectionner les informations que mes joueurs peuvent voir?', ], 'sections' => [ 'community' => 'Communauté', 'general' => 'Général', 'other' => 'Autre', 'permissions' => 'Permission', 'pricing' => 'Prix', 'worldbuilding' => 'Worldbuilding', ], 'show' => [ 'return' => 'Retour à la FAQ', 'timestamp' => 'Dernière mise à jour :date', 'title' => 'FAQ :name', ], 'user-switch' => [ 'answer' => 'Les permissions peuvent rapidement devenir compliquées, surtout au sein de grande campagne. En tant qu\'administateur d\'une campagne, naviguer vers la page affichant les membres d\'une campagne permet de cliquer sur le bouton "Basculer". Cela permet d\'afficher la campagne avec les permissions de ce compte.', 'question' => 'Les permissions de ma campagne sont définies, comment les tester?', ], ]; ================================================ FILE: lang/fr/fields.php ================================================ [ 'label' => 'Description', ], 'entry' => [ 'label' => 'Entrée', ], 'gallery' => [ 'placeholder' => 'Choix d\'une image de la galerie', ], 'gallery-header' => [ 'description' => 'Si l\'entrée n\'a pas d\'image d\'entête, affiche une image de la galerie.', ], 'gallery-image' => [ 'description' => 'Si l\'entrée n\'a pas d\'image, affiche une image de la galerie.', ], 'header-image' => [ 'boosted-description' => 'Afficher une image d\'arrière plan dans l\'entête de l\'entrée avec une :boosted-campaign.', 'description' => 'Afficher une image d\'arrière plan dans l\'entête de l\'entrée. Pour le meilleur résultat, utiliser une image très large.', 'title' => 'Image d\'entête', ], 'tooltip' => [], ]; ================================================ FILE: lang/fr/filters.php ================================================ [ 'bookmark' => 'Marquer', ], 'alerts' => [ 'copy' => 'Les filtres ont été copier à ton presse-papier.', ], 'bookmark' => [ 'helper' => 'Créer un nouveau signet pour cette vue en utilisant les filtres actuels.', 'name' => ':module (filtrée)', 'premium' => 'Pour ajouter plus des favoris, il faut activer les fonctionalités premium.', 'success' => 'Favori créé.', ], 'helpers' => [ 'guest' => 'Prière de te connecter à ton compte pour filter les résultats.', 'icon' => 'Donne à ce signet une icône spéciale :fontawesome, par exemple :example.', 'icon-premium' => 'Donne à ce favori une icône spéciale :fontawesome, comme :example, avec un :premium.', ], ]; ================================================ FILE: lang/fr/footer.php ================================================ 'Notre équipe', 'blog' => 'Blog', 'boosters' => 'Boosters', 'community' => 'Communauté', 'company' => 'Entreprise', 'contact' => 'Contact', 'copyright' => 'Tous droits réservés :copy :year Owlchester SNC', 'documentation' => 'Documentation', 'features' => 'Fonctionnalités', 'kb' => 'Base de connaissance', 'language-switcher' => [ 'other' => 'Autres langues', 'title' => 'Choix de la langue', ], 'made' => 'Fabriqué avec ❤️ à Genève, Suisse', 'newsletter' => 'Newsletter', 'platform' => 'Platforme', 'plugins' => 'Bibliothèque de Plugins', 'premium' => 'Campagnes Premium', 'press-kit' => 'Press kit', 'pricing' => 'Prix', 'privacy' => 'Confidentialité', 'public-campaigns' => 'Campagnes publiques', 'resources' => 'Ressources', 'roadmap' => 'Feuille de route', 'security' => 'Sécurité', 'server-time' => 'C\'est l\'heure de notre serveur (:server)', 'showcase' => 'Vitrine', 'status' => 'Status de service', 'terms' => 'Termes', 'thanks' => 'Uniquement possible grâce à nos abonnés.', 'translator_call' => 'Kanka est traduit dans d\'autres langues grâce à notre communauté incroyable. Si tu souhaites traduire Kanka dans ta langue, contacte-nous sur le :discord!', 'whats-new' => 'Nouveautés', ]; ================================================ FILE: lang/fr/front/community-votes.php ================================================ 'Votes Communautaires', ]; ================================================ FILE: lang/fr/front/hall-of-fame.php ================================================ 'Panthéon', ]; ================================================ FILE: lang/fr/front/kb.php ================================================ 'Base de connaissance', ]; ================================================ FILE: lang/fr/front/newsletter.php ================================================ [ 'learn_more' => 'En savoir plus', 'subscribe' => 'S\'abonner', ], 'fields' => [ 'firstname' => 'Prénom', 'lastname' => 'Nom', 'notifications' => 'Notifications', ], 'groups' => [ 'all' => 'Recevoir des mises à jour occasionnelles sur les nouvelles fonctionnalités, les votes et les événements communautaires, etc.', 'newsletter' => 'Newsletter', ], 'headline' => 'S\'abonner à une ou toutes nos newsletters pour rester à jour avec Kanka.', 'title' => 'Mises à jour mail', ]; ================================================ FILE: lang/fr/front.php ================================================ [ 'public' => [ 'filters' => [ 'is-premium' => 'Cette campagne est Premium!', ], ], ], 'cookie' => [ 'dismiss' => 'Compris!', 'link' => 'En savoir plus', 'message' => 'Kanka utilise des cookies pour assurer une bonne expérience sur notre site web.', ], 'features' => [ 'api' => [ 'link' => 'Documentation d\'API', ], 'patreon' => [ 'api_calls' => 'Limite de requête API (90)', 'boosts' => 'Boosters de campagne', 'default_image' => 'Images par défaut pour les entrées', 'discord' => 'Canal :discord privé', 'free' => 'Gratuit', 'hall_of_fame' => 'Nom dans la :link', 'impact' => 'Impact sur les futures fonctionnalités', 'monthly_vote' => 'Participation dans les votes communautaires', 'pagination' => 'Augmentation de la limite des résultats paginés', 'upload_limit' => 'Taille des fichiers augmentés', 'upload_limit_map' => 'Taille des cartes augmentées', ], ], 'home' => [ 'seo' => [ 'meta-description' => 'Kanka est un gestionnaire de campagne JDR communautaire, qui facilite l\'organisation et la planification de tes campagnes JDR', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Accueil', 'login' => 'Login', 'register' => 'Inscription', 'register_free' => 'Inscription gratuite', ], 'meta' => [ 'description' => 'Kanka est un outil digital et flexible pour la création de monde et gestionnaire de campagne de jeu de rôle.', 'title' => 'Kanka - Gestionnaire en ligne de campagne de jeu de rôle et outil de création de monde', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Gratuit', 'month' => 'Mois', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Création de monde, jeux de rôle, gestionnaire de jeux de rôle', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/fr/gallery.php ================================================ [ 'gallery' => 'Depuis la galerie', 'url' => 'Télécharger une image à partir d\'une URL', ], 'browse' => [ 'layouts' => [ 'large' => 'Grands aperçus', 'small' => 'Petits aperçus', ], 'search' => [ 'placeholder' => 'Recherche d\'une image dans la galerie', ], 'title' => 'Galerie', 'unauthorized' => 'Aucun de tes rôles n\'a l\'autorisation de "parcourir la galerie".', ], 'cta' => [ 'action' => 'Débloquer plus d\'espace de stockage', 'helper' => 'Débloquer jusqu\'à :taille GiB d\'espace de stockage avec une :premium-campaign.', 'title' => 'Stockage plein', ], 'delete' => [ 'success' => '[0] Aucun élément supprimé|[1] Un élément supprimé|{2,*} :count éléments supprimés', ], 'download' => [ 'errors' => [ 'copy_failed' => 'Nos serveurs n\'ont pas pu télécharger l\'image donnée.', 'gallery_full_free' => 'L\'espace de stockage de la galerie est plein. Activer les fonctions premium pour obtenir plus d\'espace de stockage.', 'gallery_full_premium' => 'L\'espace de stockage de la galerie est plein. Supprimer d\'abord les fichiers inutilisés.', 'invalid_format' => 'Le fichier n\'est pas un format de fichier valide.', 'too_big' => 'Le fichier est trop lourd.', 'unauthorized' => 'Aucun de tes rôles n\'a l\'autorisation de "ajouter à la galerie".', ], ], 'file' => [ 'saved' => 'Sauvegardé', ], 'filters' => [ 'only_unused' => 'Afficher uniquement les fichiers inutilisés', 'sort' => 'Ordonner', ], 'move' => [ 'success' => '[0] Aucun élément déplacé|[1] Un élément déplacé|{2,*} :count éléments déplacés', ], 'update' => [ 'home' => 'Dossier d\'accueil', 'success' => '[0] Aucun élément mise à jour|[1] Mise à jour d\'un élément|{2,*} :count éléments mis à jour', ], ]; ================================================ FILE: lang/fr/general.php ================================================ 'Désélectionner tout', 'documentation' => 'En savoir plus sur cette fonctionnalité dans notre documentation.', 'done' => 'Terminé', 'learn-more' => 'En savoir plus', 'no' => 'Non', 'required' => 'Requit', 'select_all' => 'Tout sélectionner', 'success' => [ 'created' => ':name créé.', 'deleted' => ':name retiré.', 'deleted-cancel' => ':name retiré. :cancel.', 'updated' => ':name modifié.', ], 'tutorial' => 'Voir le tuto', 'yes' => 'Oui', ]; ================================================ FILE: lang/fr/genres.php ================================================ 'Histoire alternative', 'cyberpunk' => 'Cyberpunk', 'fantasy' => 'Fantaisie', 'historical' => 'Historique', 'many_worlds' => 'Plusieurs mondes', 'modern' => 'Moderne', 'occult' => 'Occulte', 'post_apocalyptic' => 'Post-apocalyptique', 'pulp' => 'Pulp', 'science_fantasy' => 'Science-fantastique', 'science_fiction' => 'Science-fiction', 'space_opera' => 'Space opera', 'steampunk' => 'Steampunk', 'superhero' => 'Super-héros', 'urban_fantasy' => 'Fantaisie urbaine', 'western' => 'Western', ]; ================================================ FILE: lang/fr/header.php ================================================ [ 'title' => 'Nouveautés Kanka', ], 'notifications' => [ 'dismiss' => 'Éliminer', 'no-unread' => 'Aucune notification non lue', 'read_all' => 'Tout lire', ], 'qq' => [ 'tooltip' => 'Créer une entrée ou un article', ], 'toggle_navigation' => 'Basculer la navigation', 'user' => [ 'settings' => 'Paramêtres', 'sign-out' => 'Se déconnecter', 'upgrade' => 'S\'abonner', 'your-profile' => 'Ton profile', ], ]; ================================================ FILE: lang/fr/helpers.php ================================================ [ 'description' => 'Les filtres suivants sont disponibles pour l\'API :name.', 'title' => 'Filtres d\'API', ], 'attributes' => [ 'link' => 'Options des propriétés', ], 'calendar-widget' => [ 'info' => 'Pour quoi ces événements sont-ils affichés?', 'title' => 'Widget de calendrier', ], 'entity_templates' => [], 'filters' => [ 'title' => 'Comment utiliser les filtres', ], 'link' => [ 'description' => 'Un lien vers une entrée peut être facilement inséré en utilisant \'@\' dans le text. \'#\' peut être utilisé pour avoir une liste de mois depuis les calendriers de la campagne.', ], 'public' => 'Une vidéo sur Youtube explique comment fonctionne les campagnes publiques.', 'troubleshooting' => [ 'description' => 'Un membre de l\'équipe Kanka t\'as envoyé vers cette page. Sélectionnes une campagne dans la liste pour générer un jeton. Ce jeton nous permettra de rejoindre la campagne en tant qu\'administrateur.', 'errors' => [ 'token_exists' => 'Un jeton existe déjà pour :campaign.', ], 'save_btn' => 'Générer le jeton', 'select_campaign' => 'Sélectionner une campagne', 'subtitle' => 'J\'ai besoin d\'aide!', 'success' => 'Copies le jeton et envois le à un membre de l\'équipe de Kanka.', 'title' => 'Dépannage', ], 'widget-filters' => [ 'description' => 'Les entrées affichées peuvent être filtrées en indiquant une liste de champs et leur valeur. Par example, utiliser :example pour filter les personnages morts et de type NPC.', 'link' => 'filtres de widget', 'title' => 'Filtres de widget de tableau de bord', ], ]; ================================================ FILE: lang/fr/history.php ================================================ [ 'show-old' => 'Modifications', ], 'cta' => 'Afficher un journal de toutes les modifications récentes faites à la campagne.', 'empty' => 'Aucune valeure', 'fields' => [ 'action' => 'Action', 'category' => 'Catégorie', 'details' => 'Détails', 'when' => 'Quand', 'who' => 'Qui', ], 'filters' => [ 'all-actions' => 'Toutes les actions', 'all-users' => 'Tous les membres', 'no-results' => 'Aucun résultat à afficher. Essayes avec d\'autres filtres ou reviens après avoir fait des modifications aux entrées de la campagne.', ], 'helpers' => [ 'base' => 'Cette interface contient les modifications récentes apportées aux entrées de la campagne jusqu\'à :amount mois, en affichant les modifications les plus récentes en premier.', 'changes' => 'Les champs suivants avaient précédemment ces valeurs.', ], 'log' => [ 'create' => ':user a créé :entity', 'create_post' => ':user a créé la note ":post" sur :entity', 'delete' => ':user a supprimé :entity', 'delete_post' => ':user a supprimé une note sur :entity', 'reorder_post' => ':user a réordonné les articles sur :entity', 'restore' => ':user a restauré :entity', 'update' => ':user a modifié :entity', 'update_post' => ':user a supprimé la note ":post" sur :entity', 'update_tree' => ':user a modifié l\'arbre de famille de :entity', ], 'title' => 'Historique', 'unknown' => [ 'entity' => 'une entrée inconnue', ], ]; ================================================ FILE: lang/fr/items.php ================================================ [ 'creators' => [ 'action' => 'Action pour les créateurs', 'remove' => 'Supprimer tous les créateurs', ], ], 'create' => [ 'title' => 'Ajouter un objet', ], 'fields' => [ 'creators' => 'Créateurs', 'is_equipped' => 'Équipé', 'price' => 'Prix', 'size' => 'Taille', 'weight' => 'Poids', ], 'hints' => [], 'lists' => [ 'empty' => 'Ajoute des armes, des artefacts ou des objets importants à ton monde.', ], 'placeholders' => [ 'price' => 'Prix de l\'objet', 'size' => 'Taille, Dimensions, Capacité', 'type' => 'Arme, Potion, Coffre', 'weight'=> 'Poid de l\'objet', ], 'show' => [ 'tabs' => [ 'inventories' => 'Inventaires', ], ], ]; ================================================ FILE: lang/fr/journals.php ================================================ [ 'title' => 'Nouveau Journal', ], 'fields' => [ 'author' => 'Auteur', 'date' => 'Date', ], 'lists' => [ 'empty' => 'Créé des entrées de journal pour suivre tes aventures, les pensées de tes personnages, ou encore les résumés et la préparation des sessions.', ], 'placeholders' => [ 'author' => 'Qui a écrit le journal', 'date' => 'Date du journal', 'type' => 'Session, One Shot, Brouillon', ], ]; ================================================ FILE: lang/fr/languages.php ================================================ [ 'ca' => 'Catalan', 'cs' => 'Tchèque', 'de' => 'Allemand', 'el' => 'Grèque', 'en' => 'Anglais', 'en-US' => 'Anglais Américain', 'es' => 'Espagnol', 'fr' => 'Français', 'gl' => 'Galicien', 'he' => 'Hebreux', 'hr' => 'Croate', 'hu' => 'Hongrois', 'it' => 'Italien', 'nb' => 'Norvégien (Bokmal)', 'nl' => 'Hollandais', 'pl' => 'Polonais', 'pt-BR' => 'Portugais Brésilien', 'ru' => 'Russe', 'sk' => 'Slovaque', 'tr' => 'Turque', ], 'header'=> 'Langues', ]; ================================================ FILE: lang/fr/lists.php ================================================ [ 'learn' => 'En savoir plus sur cette catégorie', 'public'=> 'Voir des examples', ], 'empty' => [ 'title' => 'Pas encore de :plural.', ], ]; ================================================ FILE: lang/fr/locations.php ================================================ [ 'title' => 'Ajouter un lieu', ], 'fields' => [ 'is_destroyed' => 'Détruit', 'title' => 'Titre', ], 'helpers' => [ 'characters' => 'Afficher tous les personnages dans ce lieu et sous-lieux, ou seulement ceux qui sont ici.', ], 'hints' => [ 'is_destroyed' => 'Ce lieu est détruit.', ], 'lists' => [ 'empty' => 'Ajoutez ta première ville, taverne ou ruine cachée pour ancrer ton monde.', ], 'placeholders' => [ 'title' => 'Titre', 'type' => 'Village, Royaume, Ruine', ], ]; ================================================ FILE: lang/fr/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Activer le mode d\'édition', 'exit-edit-mode' => 'Sortir du mode d\'édition', 'finish-drawing' => 'Finir de dessiner le polygone', ], 'notifications' => [ 'start-drawing' => 'Clique sur la carte pour commencer à dessiner le polygon', ], 'toggle' => 'Ouvrir/fermer les groupes', ]; ================================================ FILE: lang/fr/maps/groups.php ================================================ [ 'add' => 'Ajouter un nouveau groupe', ], 'bulks' => [ 'delete' => '{1} Retiré :count groupe.|[2,*] Retiré :count groupes.', 'patch' => '{1} Modifié :count groupe.|[2,*] Modifié :count groupes.', ], 'create' => [ 'helper' => 'Ajoute un nouveau groupe à :name. Des marqueurs pourront ensuite être attribués à ce groupe.', 'success' => 'Groupe :name créé.', 'title' => 'Nouveau Groupe', ], 'delete' => [ 'success' => 'Groupe :name supprimé.', ], 'edit' => [ 'success' => 'Groupe :name modifié.', 'title' => 'Modifier le groupe :name', ], 'fields' => [ 'is_shown' => 'Afficher les marqueurs du groupe', 'parent' => 'Groupe parent', 'position' => 'Position', ], 'helper' => [ 'amount_v3' => 'Les marqueurs peuvent être groupé ensemble dans des groupes. Chaque groupe peut être activé pour rapidement afficher ou cacher les marqueurs de celui-ci.', ], 'hints' => [ 'is_shown' => 'Si sélectionné, les marqueurs du groups seront affichés par défaut.', ], 'index' => [ 'title' => 'Groupes de :name', ], 'pitch' => [ 'max' => [ 'helper' => 'Tu ne peux pas ajouter d\'autres groupes à moins d\'en supprimer un existant.', 'limit' => 'Cette carte a atteint sa limite de groupes', ], 'upgrade' => [ 'limit' => 'Cette carte a atteint sa limite de :limit groupes', 'upgrade' => 'Passe à une campagne premium pour ajouter jusqu\'à :limit groupes et débloquer encore plus de flexibilité créative.', ], ], 'placeholders' => [ 'name' => 'Magasins, trésors, PNJs', 'position' => 'Première', 'position_list' => 'Après :name', ], 'reorder' => [ 'save' => 'Enregister l\'ordre', 'success' => '{1} Réordonné :count groupe.|[2,*] Réordonné :count groupes.', 'title' => 'Réordonner les groupes', ], ]; ================================================ FILE: lang/fr/maps/layers.php ================================================ [ 'add' => 'Ajouter une nouvelle couche', ], 'base' => 'Couche de base', 'bulks' => [ 'delete' => '{1} Retiré :count couche.|[2,*] Retiré :count couches.', 'patch' => '{1} Modifié :count couche.|[2,*] Modifié :count couches.', ], 'create' => [ 'success' => 'Couche :name créée.', 'title' => 'Nouvelle Couche', ], 'delete' => [ 'success' => 'Couche :name supprimée.', ], 'edit' => [ 'success' => 'Couche :name modifiée.', 'title' => 'Modifier la couche :name', ], 'fields' => [ 'position' => 'Position', 'type' => 'Type de couche', ], 'helper' => [ 'amount_v2' => 'Définis des couches sur une carte pour changer l\'image d\'arrière-plan affichée sous les marqueurs.', 'is_real' => 'Les couches ne sont pas disponibles quand la carte utilise OpenStreetMaps.', ], 'index' => [ 'title' => 'Couches de :name', ], 'pitch' => [ 'max' => [ 'helper' => 'Tu ne peux pas ajouter d\'autres couches à moins d\'en supprimer une existante.', 'limit' => 'Cette carte a atteint sa limite de couches', ], 'upgrade' => [ 'limit' => 'Cette carte a atteint sa limite de :limit couches', 'upgrade' => 'Passe à une campagne premium pour ajouter jusqu\'à :limit couches et débloquer encore plus de flexibilité créative.', ], ], 'placeholders' => [ 'name' => 'Sous-sol, Niveau 2, Epave', 'position' => 'Première', 'position_list' => 'Après :name', ], 'reorder' => [ 'save' => 'Enregistrer le nouvel ordre', 'success' => '{1} Réordonné :count couche.|[2,*] Réordonné :count couches.', 'title' => 'Réordonné les couches', ], 'short_types' => [ 'overlay' => 'Overlay', 'overlay_shown' => 'Overlay (affiché par défaut)', 'standard' => 'Standard', ], 'types' => [ 'overlay' => 'Overlay (affiché par défaut la couche active)', 'overlay_shown' => 'Overlay affiché par défaut', 'standard' => 'Couche standard (basculer entre les couches)', ], ]; ================================================ FILE: lang/fr/maps/markers.php ================================================ [ 'entry' => 'Écrire une entrée personnalisés pour ce marqueur.', 'remove' => 'Supprimer le marqueur', 'reset-polygon' => 'Réinitialiser les positions', 'save_and_explore' => 'Sauvegarder et explorer', 'start-drawing' => 'Commencer à dessiner', 'update' => 'Modifier le marqueur', ], 'bulks' => [ 'delete' => '{1} Retiré :count marqueur.|[2,*] Retiré :count marqueurs.', 'patch' => '{1} Modifié :count marqueur.|[2,*] Modifié :count marqueurs.', ], 'circle_sizes' => [ 'custom' => 'Personnalisé', 'huge' => 'Énorme', 'large' => 'Grand', 'small' => 'Petit', 'standard' => 'Moyen', 'tiny' => 'Minuscule', ], 'create' => [ 'success' => 'Marqueur :name créé.', 'title' => 'Nouveau marqueur', ], 'delete' => [ 'success' => 'Marqueur :name supprimé.', ], 'details' => [ 'from-entity' => 'De l\'entrée', ], 'edit' => [ 'success' => 'Marqueur :name modifié.', 'title' => 'Modifier le marqueur :name', ], 'fields' => [ 'bg_colour' => 'Couleur de fond', 'circle_radius' => 'Radius du cercle', 'copy_elements' => 'Copier les éléments', 'custom_icon' => 'Icône personnalisée', 'custom_shape' => 'Forme personnalisée', 'font_colour' => 'Couleur d\'icône', 'group' => 'Groupe de marqueur', 'icon' => 'Icône', 'is_draggable' => 'Déplaçable', 'latitude' => 'Latitude', 'longitude' => 'Longitude', 'opacity' => 'Opacité', 'pin_size' => 'Taille du marqueur', 'polygon_style' => [ 'stroke' => 'Couleur de la bordure', 'stroke-opacity' => 'Opacité de la bordure', 'stroke-width' => 'Taille de la bordure', ], 'popupless' => 'Infobulle', 'size' => 'Taille', ], 'helpers' => [ 'base' => 'Ajouter des marqueurs en cliquant sur la carte.', 'copy_elements' => 'Copier les groupes, couches, et marqueurs.', 'copy_elements_to_campaign' => 'Copier les groupes, couches, et marqueurs de la carte. Les marqueurs liés à des entrées seront transformés en marqueurs standards.', 'css' => 'Définir une class CSS personnalisés pour le marqueur.', 'custom_icon_v2' => 'Utilises des icônes de :fontawesome, :rpgawesome, ou avec un SVG personalisé. Découvres comment dans notre :docs.', 'custom_radius' => 'Sélectionner l\'option personnalisée pour définir une taille.', 'draggable' => 'Cocher pour permettre au marqueur d\'être déplacé en mode exploration.', 'is_popupless' => 'Désactiver l\'infobulle lors du survol du marqueur.', 'label' => 'Un label est affiché comme bloque de texte sur la carte. Le text affiché sera le nom du marqueur ou le nom de l\'entrée liée.', 'polygon' => [ 'edit' => 'Cliquer sur le carte pour ajouter des coordonnées au polygone.', ], ], 'hints' => [ 'entry' => 'Modifier le marqueur pour y écrire une entrée personnalisée.', ], 'icons' => [ 'custom' => 'Personnalisé', 'entity' => 'Entrée', 'exclamation' => 'Point d\'exclamation', 'marker' => 'Marqueur', 'question' => 'Point d\'interrogation', ], 'index' => [ 'title' => 'Marqueurs de :name', ], 'pitches' => [ 'poly' => 'Dessines des formes polygonales personnalisées pour représenter les bordures et autres formes inégales.', ], 'placeholders' => [ 'custom_icon' => 'Essaie :example1 ou :example2', 'custom_shape' => '100,100, 200,240, 340,110', 'name' => 'Nom du marqueur', ], 'presets' => [ 'helper' => 'Cliques sur un préréglage pour le charger, ou crées-en un nouveau.', ], 'shapes' => [ '0' => 'Cercle', '1' => 'Carré', '2' => 'Triangle', '3' => 'Personnalisée', ], 'sizes' => [ '0' => 'Minuscule', '1' => 'Standard', '2' => 'Petit', '3' => 'Grand', '4' => 'Enorme', ], 'tabs' => [ 'circle' => 'Cercle', 'label' => 'Label', 'marker' => 'Marqueur', 'polygon' => 'Polygone', 'preset' => 'Préréglage', ], ]; ================================================ FILE: lang/fr/maps.php ================================================ [ 'back' => 'Retour à :name', 'edit' => 'Modifier la carte', 'explore' => 'Explorer', ], 'create' => [ 'title' => 'Nouvelle carte', ], 'errors' => [ 'chunking' => [ 'error' => 'Une erreur est survenue durant le traitement de la carte. Contactes l\'équipe sur :discord pour de l\'aide.', 'running' => [ 'edit' => 'La carte ne peut pas être modifiée pendant qu\'elle est en traitement.', 'explore' => 'La carte ne peut pas être affichée pendant qu\'elle est en traitement.', 'time' => 'Ceci peut prendre plusieurs minutes à plusieurs heures et dépend de la taille de la carte.', ], ], 'dashboard' => [ 'missing' => 'La carte a besoin d\'une image pour être affichée sur le tableau de bord.', ], 'explore' => [ 'missing' => 'Il faut ajouter une image à la carte avant de pouvoir l\'explorer.', ], ], 'fields' => [ 'center_marker' => 'Marqueur', 'center_x' => 'Longitude par défaut', 'center_y' => 'Latitude par défaut', 'centering' => 'Centrage', 'distance_measure' => 'Mesure de distance', 'distance_name' => 'Unité de distance', 'grid' => 'Grille', 'has_clustering' => 'Grouper les marqueurs', 'initial_zoom' => 'Zoom initial', 'is_real' => 'Utiliser OpenStreetMaps', 'max_zoom' => 'Zoom maximal', 'min_zoom' => 'Zoom minimal', 'tabs' => [ 'coordinates' => 'Coordonnées', 'marker' => 'Marqueur', ], ], 'helpers' => [ 'center' => 'Modifier les valeurs par défaut défini le centre de la carte lors de l\'affichage de celle-ci. Ci les champs sont vides, le centre de la carte sera utilisé.', 'centering' => 'Centrer la carte sur un marqueur est prioritaire sur les coordinnées.', 'chunked_zoom' => 'Les cartes fragmentées ont leurs paramètres minimum et maximum définis par le processus de segmentation.', 'distance_measure' => 'Définir une unité de distance activera l\'outil de calcul de distance dans le mode d\'exploration.', 'distance_measure_2' => 'Pour que 100 pixels mesurent 1 kilomètre, choisir une valeur de 0.0041.', 'grid' => 'Définir une taille de grille qui s\'affichera dans le mode d\'exploration.', 'has_clustering' => 'Regrouper automatiquement les marqueurs lorsqu\'ils sont proches les uns des autres.', 'initial_zoom' => 'Le zoom initial est utilisé pour afficher la carte quand celle-ci est chargée. La valeur par défaut est de :default, la valeur max est de :max et la valeur min est de :min.', 'is_real' => 'Sélectionner cette option utilisera les données d\'OpenStreetMaps pour afficher le monde réel au lieu de l\'image téléchargée. Cette option désactive les couches.', 'max_zoom' => 'La valeur maximale à laquelle la carte peut être agrandie. La valeur par défaut est de :default, et la valeur maximale est de :max.', 'min_zoom' => 'La valeur minimale à laquelle la carte peut être rétrécie. La valeur par défaut est de :default, et la valeur minimale est de :min.', 'missing_image' => 'Enregister la carte avec une image avant de pouvoir ajouter des couches et des marqueurs.', ], 'lists' => [ 'empty' => 'Télécharge une carte pour visualiser les lieux et explorer la géographie de ton monde.', ], 'panels' => [ 'groups' => 'Groupes', 'layers' => 'Couches', 'legend' => 'Légende', 'markers' => 'Marqueurs', 'settings' => 'Paramètres', ], 'placeholders' => [ 'center_marker' => 'Laisser vide pour afficher au milieu', 'center_x' => 'Laisser vide pour afficher au milieu', 'center_y' => 'Laisser vide pour afficher au milieu', 'distance_name' => 'Km, miles, pieds, hamburgers', 'grid' => 'Distance entre les éléments de la grille. Laisser vide pour cacher la grille.', 'name' => 'Nom de la carte', 'type' => 'Donjon, Ville, Univers', ], 'show' => [ 'tabs' => [ 'maps' => 'Cartes', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'La cartes est en traitement. Ce processus peut prendre plusieurs minutes à plusieurs heures.', ], ], ]; ================================================ FILE: lang/fr/misc.php ================================================ [ 'member' => 'Devenir un membre', 'remove_v5' => 'Kanka, c\'est un projet de deux potes. Soutiens notre aventure et profites d\'une expérience sans pub — pour moins qu\'une choppe à la taverne.', ], ]; ================================================ FILE: lang/fr/notes.php ================================================ [ 'title' => 'Nouvelle Note', ], 'fields' => [ 'notes' => 'Sous-notes', ], 'lists' => [ 'empty' => 'Stocke les idées, références, règles ou informations qui ne trouvent pas leur place ailleurs.', ], 'placeholders' => [ 'note' => 'Choix d\'une note parent', 'type' => 'Religion, Race, Moyen de transport', ], ]; ================================================ FILE: lang/fr/notifications.php ================================================ [ 'discord' => [ 'invalid' => 'Ton jeton Discord a expiré. Prière de resynchroniser ton compte Discord et ton compte Kanka.', ], ], 'campaign' => [ 'application' => [ 'approved' => 'Ton application pour rejoindre la campagne :campaign a été approuvée.', 'approved_message' => 'Ton application pour rejoindre la campagne :campaign a été approuvée. Raison: :reason', 'new' => 'Nouvelle application pour :campaign.', 'rejected' => 'Ton application pour rejoindre la campagne :campaign a été rejetée. Raison: :reason', 'rejected_no_message' => 'Ton application pour rejoindre la campagne :campaign a été rejetée.', ], 'asset_export' => 'Un export des images de la campagne est disponible. Ce liens sera disponible durant :time minutes.', 'boost' => [ 'add' => 'La campagne :campaign est à présent boostée par :user.', 'remove' => ':user ne boost plus la campagne :campaign.', 'superboost' => 'La campagne :campaign est superboostée par :user.', ], 'created' => 'Tu as créé :campaign.', 'deleted' => 'La campagne :campaign a été supprimée.', 'export' => 'Un export de la campagne est disponible. Ce lien est disponible pendant :time minutes.', 'export_error' => 'Une erreur est survenue lors de l\'export de la campagne. Prière de nous contacter si ce problème persiste.', 'hidden' => 'La campagne :campaign est maintenant cachée de la page des campagnes publiques.', 'import' => [ 'csv_ready' => 'L\'import CSV pour :campaign est prêt.', 'csv_success' => ':count entrées importées avec succès via import CSV dans :campaign.', 'failed' => 'L\'import de la campagne :campaign a échoué.', 'success' => 'L\'import de la campagne :campaign est terminé.', ], 'join' => ':user a rejoint la campagne :campaign.', 'leave' => ':user a quitté la campagne :campaign.', 'new_owner' => 'Tu es devenu un admin de :campaign.', 'plugin' => [ 'deleted' => 'Le plugin :plugin a été supprimé du marketplace et retiré de la campagne :campaign.', ], 'premium' => [ 'add' => 'Les fonctionnalités Premium ont été débloquées pour la campagne :campaign par :user.', 'remove' => ':user ne débloque plus les fonctionnalités Premium pour la campagne :campaign.', ], 'removed-image' => 'L\'image ou l\'entête de :entity a été retirée dû à une plainte pour droit d\'auteur.', 'role' => [ 'add' => 'Tu es maintenant membre du rôle :role de la campagne :campaign.', 'remove' => 'Tu ne fais plus partie du rôle :role de la campagne :campaign.', ], 'troubleshooting' => [ 'joined' => 'Le membre de l\'équipe Kanka :user a rejoins la campagne :campaign.', ], ], 'clear' => [ 'action' => 'Tout supprimer', 'success' => 'Notifications supprimées.', 'title' => 'Vider les notifications', ], 'features' => [ 'approved' => 'Ton idée :feature a été acceptée.', 'finished' => 'Ton idée :feature est maintenant disponible dans Kanka!', 'rejected' => 'Ton idée :feature a été rejetée, raison: :reason.', ], 'header' => ':count notifications', 'index' => [ 'title' => 'Notifications', ], 'map' => [ 'chunked' => 'La carte :name a fini d\'être traitée et est maintenant utilisable.', ], 'no_notifications' => 'Il n\'y a actuellement aucune notification.', 'plugins' => [ 'comments' => [ 'new_comment' => ':user a laissé un nouveau commentaire sur le plugin :plugin.', 'new_reply' => ':user a répondu à ton commentaire dans :plugin.', ], ], 'subscriptions' => [ 'charge_fail' => 'Une erreur est survenue lors du paiement. Kanka va ressayer encore une fois. Si rien ne change, prière de nous contacter.', 'deleted' => 'Ton abonnement à Kanka a été annulé après trop d\'essais ratés avec ta méthode de paiement. Va sur la page d\'abonnement et mets à jour tes données de paiement.', 'ended' => 'Ton abonnement a Kanka est terminée. Tes campagnes premium et rôles Discord ont été retirés. Nous espérons te revoir bientôt!', 'failed' => 'Problème lors du traitement de la méthode de paiement, merci de les mettre à jour.', 'started' => 'L\'abonnement à Kanka a commencé.', 'trial' => 'Ton essai gratuit de Kanka est terminé. Nous espérons que tu l\'as apprécié et nous espérons te revoir bientôt!', ], 'unread' => 'Nouvelle notification', ]; ================================================ FILE: lang/fr/onboarding/attributes.php ================================================ 'Les propriétés te permettent d\'ajouter à :name de petites infos réutilisables, comme l\'âge, les points de vie, le rang dans une faction ou n\'importe quelles stats perso que tu suis. C\'est parfait pour des données que tu veux consulter, trier ou réutiliser sur plusieurs entrées', 'title' => 'Stocke des données structurées pour cette entrée', ]; ================================================ FILE: lang/fr/onboarding/characters.php ================================================ 'C\'est suffisant pour commencer.', 'text' => 'Concentre-toi sur l\'essentiel : le nom, une courte description et un ou deux traits marquants. Tu pourras ajouter plus tard des détails comme les liens, les propriétés et les portraits', 'title' => 'Crée ton premier personnage', ]; ================================================ FILE: lang/fr/onboarding/locations.php ================================================ 'Garde ça simple pour l\'instant', 'text' => 'Commence petit. Donne un nom au lieu et ajoute une phrase qui explique pourquoi il compte. Tu pourras ensuite développer avec des cartes, des habitants et des lieux imbriqués une fois que le monde prend forme', 'title' => 'Crée ton premier lieu', ]; ================================================ FILE: lang/fr/onboarding/posts.php ================================================ 'Les articles te permettent de séparer le texte public des notes privées, des secrets réservés au MJ, et des infos complémentaires. Crée autant d\'articles que tu veux et choisis qui peut voir chacun', 'title' => 'Utilise les articles pour les secrets et les détails en plus', ]; ================================================ FILE: lang/fr/onboarding/reminders.php ================================================ 'Crée des rappels pour les dates limites, les moments importants dans l\'histoire, les anniversaires, ou tout ce qui est lié à :name et que tu ne veux pas oublier. Les rappels que tu ajoutes ici s\'affichent sur cette page et sur n\'importe quelle date du calendrier à laquelle tu les relies.', 'title' => 'Garde ici toutes les infos qui bougent vite', ]; ================================================ FILE: lang/fr/onboarding/tags.php ================================================ 'PNJs', ]; ================================================ FILE: lang/fr/organisations.php ================================================ [ 'title' => 'Nouvelle Organisation', ], 'fields' => [ 'is_defunct' => 'Défunte', 'members' => 'Membres', ], 'hints' => [ 'is_defunct' => 'Cette organisation n\'est plus en opération.', ], 'lists' => [ 'empty' => 'Créé des guildes, des factions ou des sociétés secrètes pour façonner la structure du pouvoir dans ton monde.', ], 'members' => [ 'actions' => [ 'add_multiple' => 'Ajouter des membres', ], 'create' => [ 'helper' => 'Ajouter un ou plusieurs membres à :name.', 'success_multiple' => '{1} Ajouté :count membre à :name.|[2,*] Ajouté :count membres à :name.', ], 'destroy' => [ 'success' => 'Membre retiré de l\'organisation', ], 'edit' => [ 'helper' => 'Change le status de membre pour :name.', 'title' => 'Modifier Membre pour :name', ], 'fields' => [ 'parent' => 'Superieur', 'pinned' => 'Épinglé', 'role' => 'Rôle', 'status' => 'Status de membre', ], 'helpers' => [ 'all_members' => 'Tous les personnages qui sont membres de cette organisation et des sous-organisations.', 'members' => 'Tous les personnages directement membres de cette organisation.', 'pinned' => 'Définir sur le membre doit être affiché dans les épingles des entrées associées.', ], 'pinned' => [ 'both' => 'Les deux', 'none' => 'Aucun', ], 'placeholders' => [ 'parent' => 'Qui est le supérieur de ce membre', 'role' => 'Chef, Membre, Prêtre, Maître d\'arme', ], 'status' => [ 'active' => 'Membre actif', 'inactive' => 'Membre inactif', 'unknown' => 'Status inconnu', ], ], 'placeholders' => [ 'type' => 'Culte, Bande, Rebellion', ], ]; ================================================ FILE: lang/fr/pagination.php ================================================ 'Suivant »', 'previous' => '« Précédent', 'showing' => 'Affichant', 'of' => 'de', 'to' => 'à', 'results' => 'résultats', ]; ================================================ FILE: lang/fr/partials.php ================================================ [ 'description' => 'Il y a un souci avec la saisie.', 'title' => 'Whoops!', ], ]; ================================================ FILE: lang/fr/passwords.php ================================================ 'Les mots de passe doivent contenir au moins six caractères et être identiques.', 'reset' => 'Votre mot de passe a été réinitialisé!', 'sent' => 'Nous vous avons envoyé par courriel le lien de réinitialisation du mot de passe!', 'token' => "Ce jeton de réinitialisation du mot de passe n'est pas valide.", 'user' => "Aucun utilisateur n'a été trouvé avec cette adresse courriel.", ]; ================================================ FILE: lang/fr/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/fr/permissions.php ================================================ [ 'delete' => 'Permission de supprimer cet élément', 'edit' => 'Permission de modifier cet élément', 'view' => 'Permission de voir cet élément', ], 'members' => [ 'inherited' => ':membre peut déjà faire ça en étant membre du rôle :role.', ], 'roles' => [ 'inherited' => 'Le rôle :role peut déjà faire ça pour la catégorie :name.', ], ]; ================================================ FILE: lang/fr/pins.php ================================================ 'En savoir plus sur les épingles dans la documentation.', 'options' => [ 'no' => 'Désépinglé', 'yes' => 'Épinglé sur la page de présentation de l\'entrée', ], ]; ================================================ FILE: lang/fr/playstyles.php ================================================ 'Casual / Ouvert aux nouveaux', 'character-focused' => 'Axé personnage', 'combat-focused' => 'Axé combat', 'episodic-one-shot-friendly' => 'Épisodique / One-shot', 'exploration-focused' => 'Axé exploration', 'linear-gm-led' => 'Linéaire / Dirigé par le MJ', 'long-term-campaign' => 'Campagne longue durée', 'narrative-first' => 'Narratif avant tout', 'roleplay-heavy' => 'RP intensif', 'roleplay-light' => 'RP léger', 'rules-light' => 'Règles légères', 'sandbox-player-driven' => 'Bac à sable / Dirigé par les joueurs', 'serious-immersive' => 'Sérieux / Immersif', 'story-driven' => 'Axé sur l\'histoire', 'tactical-crunchy' => 'Tactique / Technique', ]; ================================================ FILE: lang/fr/post_layouts.php ================================================ 'Organisations de personnage', 'connection_map' => 'Carte des relations', 'helper' => 'Cet article est configuré pour afficher la sous-page :subpage de l\'entrée.', 'location_characters' => 'Personnages de lieu', 'location_events' => 'Événements de lieu', 'location_quests' => 'Quêtes de lieu', 'pitch' => [ 'custom' => 'Affiche le contenu des sous-pages de cette entrée directement dans l\'aperçu avec les mises en page d\'articles. Par exemple, affiche l\'inventaire de :entity.', 'title' => 'Mise en formes avancées', ], 'premium' => 'Certaines options de mise en page sont désactivées car elles nécessitent une campagne premium.', 'quest_elements' => 'Éléments de quête', ]; ================================================ FILE: lang/fr/posts/templates.php ================================================ [ 'set' => 'Définir comme modèle réutilisable', 'unset' => 'Retirer comme modèle réutilisable', ], 'helper' => 'Les articles suivants ont été définis comme modèles réutilisables.', 'tab' => 'Charger depuis les modèles', 'tooltips' => [ 'click-to-edit' => 'Modifier cet article modèle', ], ]; ================================================ FILE: lang/fr/posts.php ================================================ [ 'title' => 'Nouvel article', ], 'fields' => [ 'description' => 'Description', 'layout' => 'Mise en page', 'name' => 'Nom', ], 'helpers' => [ 'new' => 'Ajouter un nouvel article à cette entrée.', 'visibility' => 'Modifier la visibilité de l\'article :name.', ], 'move' => [ 'copy' => [ 'helper' => 'Garder une copie de l\'article sur :name.', ], 'helper' => 'Déplacer ou copier l\'article :name vers une autre entrée.', 'title' => 'Déplacer l\'article', ], 'permissions' => [ 'actions' => [ 'members' => 'Ajouter des membres', 'roles' => 'Ajouter des rôles', ], 'helpers' => [ 'members' => 'Ajouter un ou plusieurs membres qui auront des permissions spéciales sur l\'article.', 'roles' => 'Ajouter un ou plusieurs rôles qui auront des permissions spéciales sur l\'article.', ], ], 'placeholders' => [ 'name' => 'Nom de l\'article', ], 'position' => [ 'dont_change' => 'Tel quel', 'first' => 'Premier', 'last' => 'Dernier', ], 'remove' => [ 'title' => 'Supprimer l\'article', ], 'visibility' => [ 'helper' => 'Modifier la visibilité de l\'article :name.', 'title' => 'Visibilité de l\'article', ], ]; ================================================ FILE: lang/fr/presets.php ================================================ [ 'create' => 'Créer un nouveau préréglage', ], 'create' => [ 'success' => 'Préréglage :name créé.', 'title' => 'Nouveau préréglage', ], 'destroy' => [ 'success' => 'Préréglage :name supprimé.', ], 'edit' => [ 'success' => 'Préréglage :name modifié.', 'title' => 'Modifier le préréglage :name', ], 'fields' => [ 'name' => 'Nom du préréglage', ], 'lists' => [ 'empty' => 'Il n\'y a actuellement aucun préréglage dans la campagne.', ], 'placeholders' => [ 'name' => 'Le nom du préréglage', ], ]; ================================================ FILE: lang/fr/profiles.php ================================================ [ 'success' => 'Photo de profil modifiée.', ], 'edit' => [ 'success' => 'Profil modifié', ], 'fields' => [ 'avatar' => 'Avatar', 'bio' => 'Biographie', 'email' => 'Email', 'hide_subscription' => 'Cacher mon nom du :hall_of_fame.', 'last_login_share' => 'Partager la date de ma dernière connexion avec les autres membres de mes campagnes.', 'link' => 'Lien social', 'login_sharing' => 'Partage de la dernière connexion', 'name' => 'Nom', 'new_password' => 'Nouveau mot de passe', 'new_password_confirmation' => 'Confirmation du nouveau mot de passe', 'newsletter' => 'Je souhaite être contacté par email de temps en temps.', 'password' => 'Mot de passe actuel', 'profile-name' => 'Nom de profil', 'pronouns' => 'Pronons', 'settings' => 'Paramètres', 'subscription_hiding' => 'Cacher l\'abonnement', 'theme' => 'Thème', ], 'helpers' => [ 'link' => 'Modifie la façon dont un lien vers ton profil social apparaît sur ton :profile et le :marketplace. Si tu laisses ce champ vide, aucun lien ne s\'affichera.', 'profile-name' => 'Modifie ton nom sur ton :profile et le :marketplace. Si vide, le nom du compte sera utilisé.', 'pronouns' => 'Modifie la façon dont tes pronoms apparaissent sur ton :profile et le :marketplace. Si tu laisses ce champ vide, aucun pronom ne s\'affichera.', ], 'link' => [ 'button' => 'Profile social de :name', ], 'newsletter' => [ 'helpers' => [ 'header' => 'S\'adonner aux newsletters par email pour être notifié des changements dans Kanka.', ], 'options' => [ 'monthly' => 'Newsletter Kanka', ], 'title' => 'Newsletter', 'updated' => 'Préférence de la newsletter modifiée.', ], 'password' => [ 'success' => 'Mot de passe modifié.', ], 'placeholders' => [ 'bio' => 'Une courte bio affichée sur le profil public.', 'email' => 'Adresse email', 'name' => 'Nom tel qu\'affiché', 'new_password' => 'Nouveau mot de passe', 'new_password_confirmation' => 'Confirmation du nouveau mot de passe', 'password' => 'Saisie du mot de passe actuel', ], 'sections' => [ 'dangerzone' => 'Zone dangereuse', 'delete' => [ 'confirm' => 'Oui, supprimer mon compte', 'delete' => 'Supprimer mon compte', 'goodbye' => 'Pour confirmer cette action, écris :code dans le champ ci-dessous.', 'helper' => 'Supprimer ton compte supprimera aussi toutes les campagnes où tu es le seul membre. Ceci est permanent et ne peut pas être défait.', 'subscribed' => 'Prière d\'annuler ton :subscription avant de pouvoir supprimer ton compte.', 'title' => 'Suppression du compte', 'warning' => 'Toutes les données relatives au compte seront supprimées. Êtes-vous certain?', ], 'password' => [ 'title' => 'Modification du mot de passe', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'La biographie est visible sur ton :link.', 'profile' => 'profil public', ], 'success' => 'Paramètres modifiés.', ], 'theme' => [ 'success' => 'Thème modifié.', 'themes' => [ 'dark' => 'Sombre', 'default' => 'Défaut', 'future' => 'Futur', 'midnight' => 'Bleu Minuit', ], ], 'title' => 'Profil', 'workflows' => [ 'created' => 'Afficher l\'entrée créée', 'default' => 'Liste des entrées', ], ]; ================================================ FILE: lang/fr/quests.php ================================================ [ 'title' => 'Ajouter une quête', ], 'elements' => [ 'create' => [ 'success' => 'L\'entrée :entity ajoutée à la quête.', 'title' => 'Nouvel élément pour :name', ], 'destroy' => [ 'success' => 'L\'élément de quête :entity retiré.', ], 'edit' => [ 'success' => 'L\'élément de quête :entity modifié.', 'title' => 'Modifier l\'élément de quête pour :name', ], 'fields' => [ 'copy_entity_entry' => 'Utiliser la description de l\'entrée', 'entity_or_name' => 'Sélection soit d\'une entrée de la campagne, soit d\'un nom pour cet élément.', ], 'helpers' => [ 'copy_entity_entry' => 'Affiche la description de l\'entrée liée à la place de la description personnalisée.', ], 'placeholders' => [ 'name' => 'Nom de l\'élément', ], ], 'fields' => [ 'copy_elements' => 'Copier les éléments de la quête', 'date' => 'Date', 'element_role' => 'Rôle', 'instigator' => 'Instigateur', 'is_completed' => 'Completée', 'location' => 'Lieu de départ', 'role' => 'Rôle', 'status' => 'Statut', ], 'helpers' => [ 'is_completed' => 'Sélectionner si la quête est considérée comme completée.', 'status' => 'Le statut actuel de la quête.', ], 'hints' => [ 'is_abandoned' => 'Cette quête a été abandonnée.', 'is_completed' => 'Cette quête est terminée.', 'is_ongoing' => 'Cette quête est en cours.', ], 'lists' => [ 'empty' => 'Créé des quêtes pour enregistrer les objectifs, les scénarios ou les motivations des personnages.', ], 'placeholders' => [ 'date' => 'Date réelle de la quête', 'entity' => 'Nom d\'un élément dans la quête', 'location' => 'Le lieu de départ de la quête', 'role' => 'Le rôle de l\'entrée dans la quête.', 'type' => 'Principale, side quest, personnage', ], 'show' => [ 'actions' => [ 'add_element' => 'Ajouter un élément', ], 'tabs' => [ 'elements' => 'Éléments', ], ], 'status' => [ 'abandoned' => 'Abandonnée', 'completed' => 'Terminée', 'not_started' => 'Non commencée', 'ongoing' => 'En cours', ], ]; ================================================ FILE: lang/fr/races.php ================================================ [ 'title' => 'Nouvelle Race', ], 'fields' => [ 'is_extinct' => 'Éteinte', 'members' => 'Membres', ], 'hints' => [ 'is_extinct' => 'Cette race est éteinte.', ], 'lists' => [ 'empty' => 'Défini les espèces, les cultures ou les peuples qui peuplent ton monde.', ], 'members' => [ 'create' => [ 'helper' => 'Ajouter un ou plusieurs personnages à :name.', 'submit' => 'Ajouter membres', 'success' => '{0} Aucun membre ajouté.|{1} 1 membre ajouté.|[2,*] :count membres ajoutés.', 'title' => 'Nouveaux membres', ], ], 'placeholders' => [ 'type' => 'Humain, Fée, Borg', ], ]; ================================================ FILE: lang/fr/redirects.php ================================================ 'La session a expiré. Merci d\'essayer à nouveau.', 'unknown_entity' => 'Désolé, élément \':entity\' inconnu.', ]; ================================================ FILE: lang/fr/referrals.php ================================================ [ 'copy' => 'Copier', ], 'benefits' => 'Construisez des mondes ensemble.', 'fields' => [ 'link' => 'Ton lien de parrainage:', ], 'stats' => [ 'badge' => 'Badge: Worldbuilder :level', 'empty' => 'Personne pour l’instant. Partage ton lien pour commencer.', 'invited' => 'Tu as invité:', 'subscribers' => 'Abonnés parrainés: :amount', 'users' => '[1] un utilisateur|{2,*} :amount utilisateurs', ], 'title' => 'Invite des amis sur Kanka', 'toasts' => [ 'copied' => 'Lien copié au presse-papiers', ], ]; ================================================ FILE: lang/fr/releases.php ================================================ [ 'event' => 'Événement', 'livestream' => 'Livestream', 'other' => 'Autre', 'release' => 'Version', 'vote' => 'Vote communautaire', ], 'index' => [ 'description' => 'Les dernières annonces de Kanka', 'title' => 'Annonces', ], 'post' => [ 'footer' => 'De :name :date', ], 'show' => [ 'return' => 'Retour aux annonces', 'title' => 'Version :name', ], ]; ================================================ FILE: lang/fr/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chronicles of Darkness', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/fr/search/fulltext.php ================================================ 'Recherche du terme :term dans les entrées, les articles, les propriétés, et autres.', 'title' => 'Recherche complète', ]; ================================================ FILE: lang/fr/search.php ================================================ 'Recherche complète', 'lookup' => [ 'empty' => 'Aucun résultat', 'hint' => 'Écrire au minimum 3 lettres pour cherche les entrées de la campagne.', 'keyboard' => 'appuyer :k pour rechercher, :esc pour fermer', 'lists' => 'Listes', 'recents' => 'Récents', 'results' => 'Résultats', ], 'no_results' => 'Aucun résultat.', 'placeholder' => 'CHERCHER', 'placeholders' => [ 'entry' => 'Rechercher une entrée', ], 'preview' => [ 'links' => 'Liens', 'no-connections' => 'Aucune relation épinglée à afficher', ], 'title' => 'Recherche', ]; ================================================ FILE: lang/fr/seo.php ================================================ 'Tableau de bord de :campaign', 'entity-list' => 'Explorer la :module de :campaign', ]; ================================================ FILE: lang/fr/settings/account.php ================================================ 'Gères ton email, ton mot de passe, tes paramètres de sécurité et d\'autres paramètres de ton compte.', 'title' => 'Info de compte', ]; ================================================ FILE: lang/fr/settings/api.php ================================================ [ 'title' => 'Applications authorisées', ], 'clients' => [ 'empty' => 'Aucun client OAuth n\'a été créé.', 'form' => [ 'name' => 'Nom du client', 'name_helper' => 'Quelque chose que tes utilisateurs reconnaîtront et en qui ils auront confiance.', 'name_placeholder' => 'Nom du client', 'redirect' => 'URL de redirection', 'redirect_helper' => 'L\'URL de rappel d\'autorisation de ton application.', 'redirect_placeholder' => 'http://ma-superbe-app.info/callback', ], 'new' => 'Nouveau client', 'title' => 'Clients OAuth', 'update'=> 'Modifier le client', ], 'fields' => [ 'client' => 'ID du client', 'client_name' => 'Nom du client', 'scopes' => 'Scopes', 'secret' => 'Secret', 'token_name' => 'Nom du jeton', ], 'new' => [ 'copy' => 'Jeton d\'accès copié au presse-papier', 'title' => 'Ton nouveau jeton d\'accès personnel:', ], 'revoke' => 'Révoker', 'revoke-confirm' => 'Confirmer la révocation', 'tokens' => [ 'empty' => 'Tu n\'as pas encore créé de jeton d\'accès.', 'form' => [ 'name' => 'Nom du jeton', 'name_placeholder' => 'Nom du jeton', ], 'new' => 'Créer le jeton', 'title' => 'Jetons d\'accès personnels', ], ]; ================================================ FILE: lang/fr/settings/appearance.php ================================================ [ 'learn-more' => 'En savoir plus dans notre documentation.', 'save' => 'Sauvegarder les modifications', ], 'campaign-switcher' => [ 'alphabetical' => 'Alphabétiquement (A-Z)', 'date_created' => 'Date de création (plus ancienne en premier)', 'date_joined' => 'Date d\'adhésion (plus ancienne en premier)', 'r_alphabetical' => 'Alphabétiquement (Z-A)', 'r_date_created' => 'Date de création (plus récente en premier)', 'r_date_joined' => 'Date d\'adhésion (plus récente en premier)', ], 'dismissible' => [ 'main' => 'Gères l\'apparence et certains fonctionements de Kanka. La configuration de campagnes peuvent remplacer certains de ces paramètres.', ], 'editors' => [ 'default' => 'Défaut (:name)', 'helpers' => [ 'feedback' => 'Aide-nous à l\'améliorer en donnant ton avis (2min)', 'legacy' => 'L\'ancien éditeur de texte (TinyMCE) ne supporte pas les mentions sur mobile, les galeries de campagne ni d\'autres fonctionnalités avancées.', 'tiptap' => 'Ceci est notre nouvel éditeur expérimental qui est activement développé et mis à jour régulièrement. Il ne contient pas encore toutes les fonctionnalités auxquelles tu es habitué.', ], 'legacy' => 'Ancien (:name)', 'tiptap' => 'Expérimental 2026', ], 'explore' => [ 'grid' => 'Grille (défaut)', 'table' => 'Table', ], 'fields' => [ 'campaign-order' => 'Ordre des campagnes', 'date-format' => 'Formatage de date', 'editor' => 'Éditeur de texte', 'entity-explore' => 'Listes d\'entrées', 'mentions' => 'Mentions lors de l\'édition', 'new-entity-workflow' => 'Flux de nouvelle entrée', 'pagination' => 'Résultats par page', 'theme' => 'Préférence de thème', ], 'helpers' => [ 'advanced-mentions' => 'Lors de l\'édition de textes, contrôler si les mentions sont affichées en tant que nom de l\'entrée ou en tant que mention avancée.', 'campaign-order' => 'Changer l\'ordre dans lequel les campagnes sont affichées dans l\'interface.', 'date-format' => 'Contrôler le format dans lequel afficher les dates ancrées dans notre calendrier.', 'editors' => 'Choisis l\'éditeur de texte à utiliser pour les grands champs de texte.', 'entity-explore' => 'Contrôler la manière dont s\'affiche les listes d\'entrées des campagnes.', 'new-entity-workflow' => 'Contrôler l\'interface vers laquelle tu es redirigé après avoir créé une nouvelle entrée.', 'overridable' => 'Les campagnes peuvent individuellement remplacer cette préférence.', 'pagination' => 'Pour les listes qui s\'étendent sur plusieurs pages, définis le nombre d\'éléments visibles sur chaque page.', 'theme' => 'Choisi la manière que Kanka s\'affiche pour toi.', ], 'mentions' => [ 'advanced' => 'Afficher les mentions avancées :code', 'default' => 'Nom de l\'entrée', ], 'success' => 'Options d\'affichage enregistrées.', 'values' => [ 'pagination' => ':amount résultats par page', 'pagination-sub' => ':amount (disponible aux abonnés)', ], ]; ================================================ FILE: lang/fr/settings/boosters.php ================================================ [ 'boost_name' => 'Booster :name', ], 'available' => 'Boosters disponibles :amount/:total', 'benefits' => [ 'boosted' => 'Booster une campagne avec :one booster débloque l\'accès au :marketplace, les options de thèmages, des téléchargements plus grand pour tous les membres de la campagne, récupérer des entrées supprimées, et :more.', 'more' => 'd\'autres fonctionnalités incroyables.', 'superboosted' => 'Superbooster une campagne avec :amount boosters débloque tous les bénéfices d\'une campagne boostée, en plus de la galerie de campagne, des logs complètes de changements aux entrées, et :more.', ], 'boost' => [ 'actions' => [ 'confirm' => 'Booste-la!', 'remove' => 'Ne plus booster :campaign', 'subscribe' => 'S\'abonner à Kanka', 'upgrade' => 'Mettre à niveau ton abonnement', ], 'confirm' => 'Wouah! Tu es sur le point de booster :campaign. Ceci assignera un (:cost) de tes boosters de campagne.', 'duration' => 'Les boosters assignés restent assignés jusqu\'à ce que tu les retires manuellement, ou quand ton abonnement prend fin.', 'errors' => [ 'boosted' => 'Oups, on dirait que :campaign est déjà boosté!', 'out-of-boosters' => 'Oh non! Tu n\'as pas assez de boosters disponible. Tu as :available est a besoin de :cost. Tu peux soit arrêter de booster une autre campagne, ou :upgrade.', ], 'pitch' => 'Abonne-toi pour accéder aux boosters de campagne.', 'success' => 'La campagne :campaign est maintenant boostée. Régale-toi avec les incroyables fonctionnalités!', 'title' => 'Booster :campaign', 'upgrade' => 's\'abonner à un niveau plus élevé', ], 'campaign' => [ 'boosted' => 'Boosté par :user depuis :time', 'premium' => 'Premium grace à :user depuis :time', 'standard' => 'Standard', 'superboosted' => 'Superboosté par :user depuis :time', 'unboosted' => 'Non-boostée', ], 'intro' => [ 'anyone' => 'Tu n\'es pas limité à seulement booster des campagnes que tu as créé. Tu peux booster n\'importe quelle campagne dont tu es membre ou que tu peux voir. Cela inclus les campagnes où tu es un joueur, ou une :public que tu apprécies.', 'data' => 'Quand une campagne n\'est plus boostée, l\'accès aux fonctionnalités boostées est retiré. Par contre, aucune information est supprimée, du coups booster la campagne à nouveau dans le future restaure l\'accès aux données.', 'first' => 'Les fonctionnalités avancées sont déverrouillées en affectant tes boosters pour booster ou superbooster une campagne. Le nombre de boosters dont tu disposes est déterminé par ton abonnement. Ce numéro est à votre disposition à tout moment tant que tu es et restes abonné. Booster une campagne assignera l\'un de tes boosters, tandis que superbooster une campagne en assignera trois.', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Récupérer des entrées supprimées pendant :amount jours', 'customisable' => 'Contrôle créatif complet de la campagne', 'icons' => 'Accès à des milliers d\'icônes pour les cartes et chronologies.', 'plugins' => 'Étends ta campagne avec des plugins créés par la communauté', 'title' => 'Les campagnes boostées ont', 'upload' => 'Taille de fichier plus grand pour tous les membres de la campagne', 'visual' => 'Visualise des arbres généalogiques et les relations entre les entrées', ], 'description' => 'Assignes des boosters aux campagnes et aides à débloquer des fonctionnalités incroyables pour tous les membres de la campagne. Pas impressionné par les campagnes boostées? Nous avons ce qu\'il te faut avec des campagnes superboostées!', 'more' => 'Jettes un coup d\'oeil sur la liste complète des fonctionnalités sur la page :boosters.', 'title' => 'Accèdes au niveau supérieur avec la personnalisation et des avantages pour tous les membres de la campagne.', ], 'ready' => [ 'available' => 'Tes boosters disponibles.', 'pricing' => 'Tous les niveaux d\'abonnement contiennent au moins un booster de campagne et commencent à :amount par mois.', 'pricing-amount' => ':currency:amount', 'title' => 'Booster une campagne', ], 'superboost'=> [ 'actions' => [ 'confirm' => 'Superbooste-la!', 'instead' => 'Superbooste-la pour :count!', 'remove' => 'Ne plus superbooster :campaign', ], 'confirm' => 'Oooh! Tu es le point de booster :campagne. Cela attribuera trois (:cost) de tes boosters de campagne disponibles.', 'errors' => [ 'boosted' => 'Oups, on dirait que :campaign est déjà superboostée!', ], 'success' => 'La campagne :campaign est maintenant superboostée. Régale-toi avec les nouvelles fonctionnalités!', 'title' => 'Superbooster :campaign', 'upgrade' => 'Prêts pour l\'ultime expérience Kanka? Superbooster :campaign assignera :cost boosters de campagne supplémentaires.', ], 'title' => 'Boosters de campagne', 'unboost' => [ 'confirm' => 'Oui, je suis sûr', 'status' => [ 'boosting' => 'booster', 'superboosting' => 'superbooster', ], 'success' => 'La campagne :campaign n\'est plus boostée, et tes boosters sont à nouveau disponibles.', 'title' => 'Ne plus booster une campagne', 'warning' => 'Es-tu sûr de vouloir arrêter :action :campaign? Cela libérera tes boosters assignés et masquera tout le contenu et les fonctionnalités liés aux avantages jusqu\'à ce que la campagne soit à nouveau boostée.', ], ]; ================================================ FILE: lang/fr/settings/premium.php ================================================ [ 'remove' => 'Retirer Premium', 'unlock' => 'Débloquer Premium', ], 'create' => [ 'actions' => [ 'confirm' => 'Débloquer Premium!', ], 'confirm' => 'Waouw! Tu es sur le point de débloquer des fonctionnalités premium pour :campaign. Cette campagne utilisera l\'une de tes campagnes Premium disponibles.', 'duration' => 'Les campagnes Premium le restent jusqu\'à ce qu\'elles soient supprimées manuellement ou que ton abonnement prenne fin.', 'pitch_2026' => 'Bénéficie de rôles, membres, thèmes personnalisés, plugins illimités, et bien plus pour tes campagnes.', 'success' => 'La campagne :campaign est maintenant Premium. Éclate-toi avec les chouettes fonctionnalités!', ], 'exceptions' => [ 'already' => 'Les fonctionnalités Premium ont déjà été débloquées pour cette campagne.', 'out-of-stock' => 'Tu n\'as pas assez de campagnes Premium disponibles pour débloquer cette campagne. Supprime le statut Premium d\'une autre campagne ou :upgrade.', ], 'pitch' => [ 'description' => 'Passes au niveau supérieur pour les campagnes et débloques des fonctionnalités incroyables pour tous les membres.', 'title' => 'Les campagnes Premium reçoivent', ], 'ready' => [ 'available' => 'Tes campagnes Premium disponnibles.', 'pricing' => 'Tous nos abonnements contiennent au moins une campagne Premium et commence à :amount par mois.', 'pricing-amount' => ':currency:amount', 'title' => 'Débloquer Premium', ], 'remove' => [ 'confirm' => 'Ouais, je suis sûr', 'cooldown' => 'Les fonctionnalités premiums pour :campaign peuvent être retirées après :date.', 'success' => 'Les fonctionnalités premium ont été désactivée de la campagne :campaign. Tu peux maintenant débloquer les fonctionnalités Premium dans une autre campagne.', 'title' => 'Désactiver les fonctionnalités Premium', 'warning' => 'Es-tu sûr de vouloir désactivé les fonctionnalités Premium de :campaign? Cela te permettra de débloquer une autre campagne. Rien ne sera supprimé, tout le contenu et toutes les fonctionnalités liés aux avantages seront cachées jusqu\'à ce que le statut Premium de la campagne soit réactivé.', ], ]; ================================================ FILE: lang/fr/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Désactiver l\'authentification à deux facteurs', 'disable-confirm' => 'Cliquer pour confirmer', 'finish' => 'Termine la configuration et connecte-toi', ], 'activation_helper' => 'Pour terminer la configuration de l\'authentification à deux facteurs de ton compte, suis ces instructions.', 'disable' => [ 'helper' => 'S tu souhaites désactiver l\'authentification à deux facteurs, clique sur le bouton ci-dessous. Garde à l\'esprit que cela rendra ton compte vulnérable à toute personne connaissant tes informations de connexion.', 'title' => 'Désactiver l\'authentification à deux facteurs', ], 'enable_instructions' => 'Pour démarrer le processus d\'activation, génère un code QR d\'authentification, puis scanne-le dans l\'application Google Authenticator (:ios, :android) ou une autre application d\'authentification similaire.', 'enabled' => 'L\'authentification à deux facteurs est actuellement activée sur ton compte.', 'error_enable' => 'Code invalide, ressaye', 'fields' => [ 'otp' => 'Saisi le mot de passe à usage unique fourni par l\'application d\'authentification', 'qrcode' => 'Scanne le code QR suivant avec ton application d\'authentification pour générer un mot de passe à usage unique', ], 'generate_qr' => 'Générer un code QR', 'helper' => 'L\'authentification à deux facteurs renforce la sécurité d\'accès en exigeant deux méthodes (également appelées facteurs) pour vérifier ton identrée à chaque connexion.', 'learn_more' => 'En savoir plus sur l\'authentification à deux facteurs.', 'social' => 'L\'authentification à deux facteurs Kanka n\'est activée que pour les utilisateurs qui se connectent à l\'aide de leur adresse e-mail et de leur mot de passe. Modifie ta méthode de connexion dans les paramètres de ton compte avant de pouvoir activer cette option.', 'success_disable' => 'L\'authentification à deux facteurs est désactivée.', 'success_enable' => 'L\'authentification à deux facteurs est maintenant activée. Reconnecte-toi pour terminer le processus.', 'success_key' => 'Ton code QR a été généré. Pour terminer la configuration, suis les étapes suivantes.', 'title' => 'Authentification à deux facteurs', ], 'actions' => [ 'social' => 'Changer au login Kanka', 'update_email' => 'Modifier l\'email', 'update_password' => 'Modifier le mot de passe', ], 'email' => 'Modification de l\'email', 'email_success' => 'Email modifié.', 'password' => 'Modification du mot de passe', 'password_success' => 'Mot de passe modifié.', 'social' => [ 'error' => 'Tu utilises déjà le login Kanka pour ce compte.', 'helper' => 'Ton compte est géré par :provider. Tu peux changer au login Kanka en fournissant un login et un mot de passe.', 'success' => 'Ton compte utilise dorénavant le login Kanka.', 'title' => 'Social à Kanka', ], 'title' => 'Compte', ], 'api' => [ 'helper' => 'Bienvenue à l\'API de Kanka. Les personal access token permettent d\'accéder aux données d\'un utilisateur lors des requêtes à l\'API.', 'link' => 'Lire la documentation', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Lier', 'remove' => 'Retirer', ], 'benefits' => 'Kanka supporte quelques intégrations avec d\'autres services. D\'autres services seront ajoutés dans le futur.', 'discord' => [ 'confirm' => 'Es-tu sûr de vouloir déconnecter ton compte de Discord? Cela retirera tous tes rôles synchronisés.', 'errors' => [ 'add' => 'Une erreur est survenue lors du liage de Discord avec le compte Kanka.', ], 'success' => [ 'add' => 'Compte Discord lié.', 'remove' => 'Compte Discord délié.', ], 'text' => 'Lier ton compte Discord avec ton compte Kanka pour automatiquement avoir accès à ton rôle d\'abonnement et aux canneaux privés.', 'unlock' => 'Accès aux rôles Discord', ], 'title' => 'Intégration d\'app', ], 'billing' => [ 'placeholder' => 'Si tu souhaites que tes coordonnées ou informations fiscales supplémentaires soient ajoutées à tes reçus (adresse professionnelle, numéro de TVA, etc.), saisi-les ci-dessous et elles apparaîtront sur tous tes reçus.', 'save' => 'Enregister les informations', 'title' => 'Facturation', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'La campagne :name est déjà boostée.', 'exhausted_boosts' => 'Tu n\'as plus de boost disponible. Retire un boost d\'une campagne avant de pouvoir l\'attribuer à une autre.', 'exhausted_superboosts' => 'Tu n\'as plus de boosts. Tu as besoin de 3 boosts pour superbooster une campagne.', ], ], 'countries' => [ 'austria' => 'Autriche', 'belgium' => 'Belgique', 'france' => 'France', 'germany' => 'Allemagne', 'italy' => 'Italie', 'netherlands' => 'Pays-Bas', 'spain' => 'Espagne', ], 'layout' => [ 'title' => 'Mise en page', ], 'menu' => [ 'account' => 'Compte', 'api' => 'API', 'appearance' => 'Apparence', 'apps' => 'Apps', 'boosters' => 'Boosters', 'notifications' => 'Notifications', 'other' => 'Autre', 'patreon' => 'Patreon', 'payment_options' => 'Options de paiement', 'personal_settings' => 'Paramètres Personnels', 'premium' => 'Campagnes Premium', 'profile' => 'Profil', 'settings' => 'Paramètres', 'subscription' => 'Abonnement', 'subscription_status' => 'Status d\'abonnement', ], 'patreon' => [ 'deprecated' => 'Fonction obsolète - si tu souhaites supporter Kanka, fais-le avec un abonnement. La liaison Patreon est toujours active pour nos Patrons qui ont lié leur compte avant le changement d\'abonnement.', 'pledge' => 'Pledge: :name', 'remove' => [ 'button' => 'Délier le compte Patreon', 'success' => 'Ton compte Patreon a été délié.', 'text' => 'Délier le compte Patreon de Kanka supprime les bonus, le nom du Hall of Fame, les boosters de campagne et d\'autres fonctionnalités liées au supporter de Kanka. Aucun contenu boosté ne sera perdu (par exemple les en-têtes d\'entrée). Lors du réabonnement, toutes les données seront à nouveau visibles, y compris la possibilité de booster des campagnes précédemment boostées.', 'title' => 'Délier le compte Patreon de Kanka', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Mettre à jour le profil', ], 'avatar' => 'Image de profil', 'success' => 'Mise à jour effectuée.', 'title' => 'Profil personnel', ], 'referrals' => [ 'title' => 'Parrainages', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Annuler l\'abonnement', 'subscribe' => 'Abonner', 'update_currency' => 'Changer la devise', ], 'billing' => [ 'helper' => 'Les informations de paiement sont gérées et sauvegardées de manière sécurisée à travers :stripe. Cette méthode de paiement sera utilisée pour tous les abonnements.', 'saved' => 'Méthode de paiement', ], 'cancel' => [ 'grace' => [ 'text' => 'Ton abonnement est déjà configuré pour se terminer le :date, après quoi tes campagnes premium redeviendront des campagnes standard et les autres avantages liés au soutien de Kanka seront désactivés.', 'title' => 'Période de grâce', ], 'options' => [ 'competitor' => 'Passer à un concurrent', 'financial' => 'L\'abonnement est trop cher', 'missing_features' => 'Fonctionnalités manquantes', 'not_for' => 'L\'abonnement n\'est pas pour moi', 'not_playing' => 'La campagne n\'est plus active ou en pause', 'not_using' => 'Je n\'utilise pas Kanka actuellement', 'other' => 'Autre', 'testing' => 'Juste en train de tester Kanka.', ], 'text' => 'Désolé de te voir partir! L\'annulation de ton abonnement le gardera actif jusqu\'au la fin du mois payé, après quoi tu perdras les bonus de ta campagne et les autres avantages liés au soutien de Kanka. N\'hésite pas à remplir le formulaire suivant pour nous informer de ce que nous pouvons améliorer, ou ce qui a conduit à ta décision.', 'title' => 'Annulation de l\'abonnement', ], 'cancelled' => 'L\'abonnement a été annulé. Un nouvel abonnement peut être fait dès que celui-ci arrive à terme le :date.', 'change' => [ 'text' => [ 'downgrade_monthly' => 'Tu rétrogrades vers l’offre :tier pour :downgrade, puis facturation mensuelle de :amount.', 'downgrade_yearly' => 'Tu rétrogrades vers l’offre :tier pour :downgrade, puis facturation annuelle de :amount.', 'monthly' => 'Abonnement au niveau :tier, facturé mensuellement pour :amount.', 'upgrade_monthly' => 'Tu passes au niveau :tier pour :upgrade, ensuite facturé mensuellement pour :amount.', 'upgrade_paypal' => 'Tu passes au niveau :tier pour :upgrade jusqu\'au :date.', 'upgrade_yearly' => 'Tu passes au niveau :tier pour :upgrade, ensuite facturé annuellement pour :amount.', 'yearly' => 'Abonnement au niveau :tier, facturé annuellement pour :amount.', ], 'title' => 'Changement d\'abonnement', ], 'coupon' => [ 'check' => 'Vérifier', 'invalid' => 'Code promotionnel invalide.', 'label' => 'Code promotionnel', 'percent_off' => 'Nous appliquerons un rabais de :percent% sur la première année de l\'abonnement!', ], 'currencies' => [ 'brl' => 'BRL', 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Changer la devise de facturation', ], 'errors' => [ 'callback' => 'Notre gestionnaire de paiement nous a remonté une erreur. Prière de réessayer et nous contacter si le problème persiste.', 'failed' => 'Nous rencontrons actuellement des problèmes avec notre système de facturation. Prière de nous contacter à :email pour obtenir de l\'aide.', 'subscribed' => 'Erreur lors de la gestion de l\'abonnement. Stripe nous a fourni l\'erreur suivante.', ], 'fields' => [ 'active_since' => 'Actif depuis', 'active_until' => 'Active jusqu\'à', 'billing' => 'Facturation', 'currency' => 'Devise', 'payment_method' => 'Méthode de paiement', 'plan' => 'Abonnement actuel', 'reason' => 'Raison', 'reset' => 'Réinitialiser les informations de facturation', 'reset_billing' => 'Je comprends que le fait de changer de devise entraînera la perte de mon historique de facturation et m\'obligera à saisir à nouveau mon mode de paiement.', ], 'helpers' => [ 'alternatives' => 'Payez votre abonnement en utilisant :method. Ce mode de paiement ne sera pas renouvelé automatiquement à la fin de votre abonnement. :method n\'est disponible qu\'en Euros.', 'alternatives-2' => 'Payes ton abonnement en utilisant la méthode :method. Il s\'agit d\'un seul paiement qui ne se renouvelle pas automatiquement à la fin de l\'abonnement.', 'alternatives_warning' => 'La mise à niveau de l\'abonnement lors de l\'utilisation de cette méthode n\'est pas possible. Veuillez créer un nouvel abonnement à la fin de votre abonnement actuel.', 'alternatives_yearly' => 'En raison des restrictions entourant les paiements récurrents, :method n\'est disponible que pour les abonnements annuels', 'currency_block' => 'Il n\'est pas possible de changer de devise pendant que tu asun abonnement Kanka actif, mais tu peux changer de devise à la fin de ton abonnement actuel.', 'currency_reset' => 'Si tu changes de devise, ton historique de facturation sera effacé et tu devras saisir à nouveau une méthode de paiement.', 'paypal_v3' => 'Payer pour ton abonnement annuel en toute sécurité avec PayPal.', 'stripe' => 'La facturation est traité en toute securité par :stripe.', ], 'manage_subscription' => 'Gérer l\'abonnement', 'payment_method' => [ 'actions' => [ 'add' => 'Ajouter', 'add_new' => 'Ajouter une méthode de paiement', 'change' => 'Modifier la méthode de paiement', 'save' => 'Enregister la méthode de paiement', 'show_alternatives' => 'Autres méthodes de paiement', ], 'add_one' => 'Aucune méthode de paiement actuellement saisie.', 'alternatives' => 'Un abonnement peut être souscrit avec ces méthodes de paiement. Cette action ne générera qu\'une seule facture et ne renouvellera pas automatiquement l\'abonnement chaque mois.', 'card' => 'Carte', 'card_name' => 'Nom sur la carte', 'country' => 'Pays de résidence', 'ending' => 'Se terminant par', 'helper' => 'Cette carte sera utilisée pour les abonnements.', 'new_card' => 'Ajouter une méthode de paiement', 'saved' => ':brand se terminant par :last4', ], 'paypal_expiring' => 'Ton abonnement PayPal expire le :date. Renouvelle-le maintenant pour éviter de perdre l\'accès.', 'periods' => [ 'monthly' => 'Menuel', 'yearly' => 'Annuel', ], 'placeholders' => [ 'downgrade_reason' => '(facultatif) dis-nous pourquoi tu downgrade ton abonnement.', 'reason' => '(facultatif) dis-nous pourquoi tu ne souhaites plus être abonné à Kanka. Manquait-il une fonctionnalité? Ta situation financière a-t-elle changé?', ], 'plans' => [ 'cost_monthly' => ':currency :amount facturé mensuellement', 'cost_yearly' => ':currency :amount facturé annuellement', ], 'sub_status' => 'Information d\'abonnement', 'subscription' => [ 'actions' => [ 'cancel' => 'Annuler l\'abonnement', 'downgrading' => 'Prière de nous contacter pour un déclassement', 'rollback' => 'Changer à Kobold', 'subscribe' => 'Changer à :tier mensuel', 'subscribe_annual' => 'Changer à :tier annuel', ], ], 'success' => [ 'alternative' => 'Le paiement a été enregistré. Une notification sera générée dès le paiement traité et l\'abonnement activé.', 'callback' => 'Ton abonnement est réussi! Ton compte sera mis à jour dès que notre gestionnaire de paiement nous informera des changements (cela peut prendre quelques minutes).', 'currency' => 'Devise préférée sauvegardée.', 'subscribed' => 'Ton abonnement est réussi! N\'oublie pas de t\'abonner à la newsletter Community Vote pour être averti lorsqu\'un vote sera ouvert. Tu peux modifier tes paramètres de newsletter sur ta page de profil.', ], 'tiers' => 'Niveaux d\'abonnements', 'trial_period' => 'Les abonnements annuels ont une période d\'annulation de 14 jours. Nous contacter à :email pour annuler un abonnement et recevoir un remboursement.', 'upgrade_downgrade' => [ 'button' => 'Information sur l\'upgrade/downgrade', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Tes bonus restent activés jusqu\'à la fin de la période de paiement.', 'boosts' => 'La même chose se passe pour les campagnes boostées. Les fonctionnalités boostées deviennent invisibles mais les données ne sont pas supprimé lorsqu\'une campagne n\'est plus boostée.', 'kobold' => 'Pour annuler ton abonnement, change au tier Kobold.', 'premium' => 'Il en va de même pour tes campagnes Premium. Les fonctionnalités Premium deviennent invisibles mais ne sont pas supprimées lorsqu\'une campagne n\'a plus le status Premium', ], 'title' => 'Lors de l\'annulation d\'un abonnement', ], 'downgrade' => [ 'bullets' => [ 'end' => 'L\'abonnement actuel reste actif jusqu\'à la fin du cycle de paiement, après quoi le nouvel abonnement sera mis en place.', ], 'provide_reason' => 'Si tu peux, partages avec nous pourquoi tu downgrades ton abonnement.', 'title' => 'Lors du passage à un niveau inférieur', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'La méthode de paiement sera facturée immédiatement et les nouvelles fonctionnalités seront accessibles.', 'prorate' => 'Lors du changement de Owlbear à Elemental, seulement la différence sera facturée.', ], 'title' => 'Lors du passage à un niveau supérieur', ], ], 'warnings' => [ 'incomplete' => 'Nous n\'avons pas pu débiter la carte de crédit. Vérifier les informations de la carte et mettre à jour si nécessaire. Nous essayerons à nouveau durant les prochains jours. Si ça échoue de nouveau, l\'abonnement sera annulé.', 'patreon' => 'Ce compte est actuellement lié à Patreon. Prière de délier le compte dans les paramètres :patreon avant de pouvoir s\'abonner à Kanka.', ], ], ]; ================================================ FILE: lang/fr/sidebar.php ================================================ [ 'count' => 'Membre de :member', 'created_campaigns' => 'Tes campagnes', 'follow_more' => 'Trouver des campagnes', 'followed_campaigns'=> 'Campagnes suivies', 'new_campaign' => 'Nouvelle campagne', 'public_campaigns' => 'Campagnes publiques', 'reorder' => 'Réordonner', 'updated' => 'Mis à jour', ], 'dashboard' => 'Tableau de bord', 'entity-creator' => 'Créateur Rapide', 'gallery' => 'Galerie', 'game' => 'Jeu', 'other' => 'Autre', 'recent' => 'Changements récents', 'relations' => 'Relations', 'settings' => 'Configuration', 'time' => 'Temps', 'world' => 'Monde', ]; ================================================ FILE: lang/fr/spotlights.php ================================================ [ 'actions' => [ 'retract' => 'Retirer la candidature', ], 'description' => 'Ta candidature a été envoyée et est en cours d’examen. Tu recevras une notification lorsqu’elle sera approuvée ou refusée.', 'title' => 'Candidature envoyée', ], 'apply' => [ 'errors' => [ 'empty' => 'La question :field nécessite plus de contenu', ], ], 'approved' => [ 'description' => 'Félicitations! Ta candidature a été approuvée et est maintenant mise en avant sur la page :spotlight.', 'title' => 'Candidature approuvée', ], 'faq' => [ 'finisher' => 'Envoyer une candidature ne garantit pas la sélection. Nous lisons chaque candidature, mais ne pouvons pas toutes les mettre en avant.', 'how' => [ 'a' => [ 'end' => 'Pas le nombre d’abonnés. Pas la popularité. Pas le statut de membre', 'lead' => 'Nous sélectionnons 1–3 campagnes par mois.', 'req1' => 'Une identrée et des thèmes clairs', 'req2' => 'Un worldbuilding réfléchi', 'req3' => 'Des histoires ou des approches intéressantes', 'requirements' => 'La sélection est éditoriale, pas compétitive. Nous recherchons:', ], 'q' => 'Comment les campagnes sont-elles sélectionnées?', ], 'reapply' => [ 'a' => 'Oui. Si ta campagne n’est pas sélectionnée, tu peux postuler à nouveau plus tard, surtout si ton monde a évolué.', 'q' => 'Puis-je postuler plus d’une fois?', ], 'selected' => [ 'a' => [ 'end' => 'Tu seras averti avant la publication.', 'lead' => 'Si elle est sélectionnée:', 'req1' => 'Ta campagne reçoit le succès Campagne mise en avant', 'req2' => 'Nous publions un article sur le :blog et le :showcase', 'req3' => 'Nous pouvons légèrement modifier tes réponses pour plus de clarté', ], 'q' => 'Que se passe-t-il si ma campagne est sélectionnée?', ], 'what' => [ 'a' => 'La vitrine met en avant des campagnes exceptionnelles créées avec Kanka. Les campagnes sélectionnées sont présentées sur la Vitrine Kanka et dans un court article de blog sous forme d’interview.', 'q' => 'Qu’est-ce que la vitrine?', ], 'who' => [ 'a' => [ 'end' => 'Aucune taille minimale. Aucune restriction de système.', 'lead' => 'Toute campagne publique sur Kanka peut postuler', 'req1' => 'Être accessible publiquement', 'req2' => 'Montrer une utilisation active (contenu, historique ou joueurs)', 'req3' => 'Représenter des mondes dont les autres peuvent s’inspirer', 'requirements' => 'Ta campagne doit:', ], 'q' => 'Qui peut postuler?', ], ], 'form' => [ 'actions' => [ 'apply' => 'Envoyer la candidature', 'retract' => 'Retirer la candidature', 'save' => 'Enregistrer le brouillon', ], 'draft' => 'Ceci est un brouillon de ta candidature. Tu peux l’enregistrer et y revenir plus tard.', 'not-public' => 'Cette campagne n’est pas visible publiquement et ne peut pas postuler au Spotlight.', 'preset' => 'Parle-nous un peu de :campaign et explique pourquoi tu penses qu’elle mérite d’être mise en avant. Tu peux enregistrer et revenir à ces questions plus tard.', 'required' => 'Ce champ est obligatoire.', 'title' => 'Formulaire de candidature au Spotlight', ], 'overview' => [ 'cta' => 'Postuler au Spotlight avec :name', 'not-public' => ':name n’est pas une campagne visible publiquement.', 'showcase' => 'Voir la vitrine', ], 'placeholders' => [ 'inspiration' => 'Livres, jeux, histoire, musique, ambiance', 'kanka' => 'Explique-nous pourquoi Kanka est devenu l’outil idéal pour ton monde', 'proud' => 'Ça peut être le lore, les joueurs, la longévité, le statut', 'stories' => 'Tragédie, héroïsme, politique, famille choisie, chaos total et assumé', 'time' => 'Des mois, des années, des décennies, sur plusieurs vies?', 'world' => 'Thèmes, émotions, conflits (l’accroche)', ], 'questions' => [ 'inspiration' => 'Qu’est-ce qui inspire ce monde', 'kanka' => 'Pourquoi fais-tu jouer des parties sur Kanka?', 'proud' => 'De quoi es-tu le plus fier?', 'share' => 'Autoriser l’équipe Kanka à utiliser ta réponse dans ses supports marketing.', 'stories' => 'Quel genre d’histoires émergent à la table?', 'time' => 'Depuis combien de temps construis-tu ce monde?', 'world' => 'De quoi parle vraiment ce monde?', ], 'rejected' => [ 'description' => 'Ta candidature a été refusée. Réessaie plus tard.', 'title' => 'Candidature refusée', ], 'retract' => [ 'success' => 'Ta candidature a été retirée avec succès. Tu peux maintenant la modifier à nouveau.', ], 'rules' => <<<'TEXT' Nous sélectionnons 1–3 campagnes chaque mois pour les mettre en avant sur le :showcase de Kanka. La sélection n’est pas garantie. Les campagnes mises en avant reçoivent un succès permanent et une interview publiée. TEXT , 'started' => 'Pour commencer, sélectionne l’une de tes campagnes.', 'title' => 'Postuler au Spotlight', ]; ================================================ FILE: lang/fr/starter.php ================================================ [ 'name' => 'Monde de :user', ], 'character1' => [ 'age' => '[20/30/40 ans]', 'background' => [ 'cur' => 'Actuellement [occupation/rôle]', 'loc' => 'A grandi à [ville/région]', 'seeking' => 'En quête de [objectif/motivation]', 'title' => 'Historique', ], 'description' => [ 'intro' => '[Une brève introduction de ton personnage - qui il est, d\'où il vient et ce qu\'il veut.]?', 'template' => 'Ceci est un personnage modèle que tu peux personnaliser. Remplace les détails ci-dessous par les informations de ton propre personnage. Tu pourras toujours ajouter d\'autres champs plus tard.', 'tip' => 'Conseil: Commence par un nom et une description en une phrase. Tu pourras étoffer les détails au fil du développement de ton monde.', ], 'name' => '[Nom de ton personnage]', 'personality' => [ 'trait1' => [ 'name' => 'Trait 1', 'value' => '[Courageux/Prudent/Ambitieux]', ], 'trait2' => [ 'name' => 'Trait 2', 'value' => '[Loyal/Indépendant/Rusé]', ], 'trait3' => [ 'name' => 'Trait 3', 'value' => '[Optimiste/Cynique/Pragmatique]', ], ], 'physical' => [ 'build' => [ 'name' => 'Corpulence', 'value' => '[Mince/Moyen/Musclé]', ], 'features' => [ 'name' => 'Traits distinctifs', 'value' => '[Cicatrices, tatouages, vêtements distinctifs]', ], ], ], 'character2' => [ 'description' => [ 'first' => 'Un personnage secondaire qui aide ou voyage avec :mention. Personnalise ces détails pour qu\'ils correspondent à ton histoire.', 'second'=> 'Conseil: Les personnages secondaires n\'ont pas besoin d\'autant de détails que les protagonistes. Concentre-toi sur ce qui les rend utiles ou intéressants pour ton histoire.', ], 'name' => '[Nom du personnage allié]', 'relation' => '[Ami/Mentor/Rival]', 'skills' => [ 'first' => '[Compétence 1: Combat/Magie/Soin/Artisanat]', 'second'=> '[Compétence 2: Social/Savoir/Technique]', 'third' => '[Compétence 3: Talent unique ou spécialité]', 'title' => 'Compétences et capacités', ], ], 'city' => [ 'description' => 'Le cœur battant du royaume, où marchands, nobles et roturiers se côtoient dans des marchés animés et de grandes places. Les vieux murs de la ville tiennent toujours, bien que celle-ci les ait dépassés depuis longtemps.', 'districts' => [ 'first' => 'Quartier noble: Manoirs et jardins', 'fourth'=> 'Quais: Port fluvial, entrepôts', 'second'=> 'Quartier du marché: Commerce, artisanat, tavernes', 'third' => 'Vieille ville: Cité fortifiée d\'origine', 'title' => 'Quartiers', ], 'locations' => [ 'first' => 'Le Palais Royal (centre du Quartier noble)', 'second'=> 'Le Grand Bazar (Quartier du marché)', 'third' => 'L’Auberge de l’Épée Rouillée (lieu populaire des aventuriers)', 'title' => 'Lieux notables', ], 'name' => '[Ta capitale]', 'type' => 'Capitale', ], 'kingdom' => [ 'description' => 'Un royaume prospère, réputé pour ses terres fertiles et ses forêts séculaires. La famille royale règne depuis trois générations, préservant la paix par la diplomatie et le commerce.', 'features' => [ 'capital' => [ 'name' => 'Capitale', ], 'exp' => [ 'name' => 'Export principal', 'value' => 'Céréales, bois de construction', ], 'gov' => [ 'name' => 'Gouvernement', 'value' => 'Monarchie héréditaire', ], 'pop' => [ 'name' => 'Population', 'value' => '~50,000', ], 'title' => 'Caractéristiques notables', ], 'name' => '[Nom de ton royaume]', 'recent' => [ 'first' => 'Recrudescence du banditisme sur les routes de l\'est', 'second'=> 'Mauvaises récoltes dans les provinces du sud', 'title' => 'Événements récents', ], 'type' => 'Royaume', ], 'kingdom1' => [], 'kingdom2' => [], 'name' => ':name (exemple)', 'note1' => [], ]; ================================================ FILE: lang/fr/subscription.php ================================================ [ 'main' => 'Abonne-toi à Kanka pour débloquer des images plus grandes, une expérience sans publicité, :boosters et :more. Nous utilisons :stripe pour gérer les paiements, avec aucune information sur la carte de crédit sauvegardé ou transitant sur nos servers.', 'more' => 'd\'autres fonctionnalités incroyables', ], 'errors' => [ 'grace' => 'Ton abonnement actuel prend fin à la date :date, après quoi tu pourras te réabonner.', 'invalid_card_country' => [ 'brl' => 'Nous sommes désolés, mais nous n\'acceptons actuellement que les paiements en BRL pour les clients possédant une carte de crédit brésilienne. Si tu penses qu\'il s\'agit d\'une erreur, contacte-nous à l\'adresse :email.', ], 'invalid_currency' => 'Tu avais précédemment un abonnement en :old, ce qui t\'empêche d\'avoir un nouvel abonnement en :new. Priere de changer ta devise en :old, ou nous contacter à :email si tu souhaites changer de devise.', ], ]; ================================================ FILE: lang/fr/subscriptions/cancellation.php ================================================ [ 'label' => 'Autre chose à partager?', 'placeholder' => 'Nous lisons chaque réponse.', ], 'intro' => 'On est triste de te voir partir! Ton abonnement sera actif jusqu\'au :date, après quoi tes campagnes premiums se convertirons en campagnes standards et ces bénéfices seront retirés.', 'loss' => [ 'ads' => [ 'title' => 'Experience sans pub pour toi et tes joueurs', ], 'discord' => [ 'title' => 'Le rôle ":role" dans notre communauté Discord', ], 'downgrade' => 'Tu peux passer à un abonnement inférieur au lieu de l\'annuler afin de conserver la plupart de tes avantages.', 'premium' => [ 'players' => '{1}:count joueur perdra accès aux fonctionnalités premiums|[2,*]:count joueurs perdront accès aux fonctionnalités premiums', 'plugins' => '{1}Accès à :count plugin installé|[2,*]Accès à :count plugins installés', 'storage' => 'Ton :current', 'title' => '{0}Fonctionnalités premiums pour ":campaign"|{1}Fonctionnalités premiums pour ":campaign" et :count autre campagne|[2,*]Fonctionnalités premium pour ":campaign" et :count autres campagnes', ], 'roadmap' => 'Nous améliorons constamment Kanka grâce aux idées soumises par les utilisateurs sur notre :roadmap. Peut-être que cette fonctionnalité manquante est imminente.', 'title' => 'Avant d\'annuler, voici ce que tu perderas', ], 'pause' => [ 'button' => 'Suspendre la souscription pour 1 à 3 mois', 'helper' => 'Tout garder. Aucun paiement. Reprendre à tout moment.', ], 'secondary' => [ 'competitor' => [ 'legend_keeper' => 'Legend Keeper', 'notion_obsidian' => 'Notion ou Obsidian', 'other' => 'Autre chose', 'world_anvil' => 'World Anvil', ], 'financial' => [ 'forgot' => 'J\'avais oublié que j\'étais encore abonné', 'lower_price' => 'Un prix moins élevé m\'aiderait', 'not_often' => 'Je ne l\'utilise pas assez souvent pour en justifier le coût', ], 'label' => 'Peux-tu nous en dire un peu plus?', 'not_for' => [ 'better_fit' => 'J\'ai trouvé une meilleure alternative', 'expected_different' => 'Je m\'attendais à quelque chose de différent', 'terminology' => 'La terminologie ne correspond pas à ma façon de penser', 'too_complex' => 'Trop complexe pour démarrer', ], 'not_playing' => [ 'campaign_finished' => 'La campagne est terminée', 'group_fell_apart' => 'Le groupe s\'est dispersé', 'on_break' => 'En pause, je reviendrai peut-être', ], 'not_using' => [ 'lost_motivation' => 'J\'ai perdu la motivation pour ce projet', 'on_break' => 'Ma campagne est en pause mais je reviendrai peut-être', 'too_busy' => 'J\'ai été trop occupé ces derniers temps', ], ], 'select_reason' => 'Veuillez sélectionner une raison pour continuer.', ]; ================================================ FILE: lang/fr/subscriptions/cancelled.php ================================================ [ 'adfree' => 'Une expérience sans pub', 'discord' => 'Des rôles réservés aux abonnés sur :discord', 'helper' => 'Ton abonnement reste actif jusqu\'au :date. D\'ici là, tu continues de profiter de tous les avantages, comme :', 'limit' => 'Des limites de téléversement augmentées', 'more' => 'Et encore plus!', 'premium' => 'Les campagnes premium et leurs fonctionnalités', 'title' => 'Tes avantages sont encore actifs', ], 'change' => [ 'action' => 'Se réabonner maintenant', 'helper' => 'On serait ravi de t\'avoir à nouveau parmi nous! Tu peux te réabonner à tout moment et reprendre là où tu t\'étais arrêté.', 'title' => 'Tu as changé d\'avis?', ], 'contact' => [ 'feedback' => 'Si on aurait pu faire mieux, ton avis nous intéresse :', 'helper' => 'Même sans abonnement, tu fais toujours partie de la communauté Kanka. Continue à forger tes mondes, et n\'hésite pas à revenir quand le moment sera venu.', 'send' => 'Contacte-nous pour partager ton retour', 'title' => 'Restons en contact', ], 'next' => [ 'data' => 'Tes données restent en sécurité, rien n\'est supprimé, et tu peux te réabonner à tout moment', 'discord' => 'Tu n\'auras plus accès aux fonctionnalités et salons réservés aux abonnés', 'helper' => 'Une fois ton abonnement terminé :', 'premium' => 'Les campagnes avec le premium perdront cet avantage', 'title' => 'Et après?', ], 'seo_title' => 'Désolé de te voir partir', 'subtitle' => 'Merci d\'avoir été abonné, ton soutien a vraiment compté pour nous. Voici ce qui t\'attend maintenant :', 'title' => 'Désolé de te voir partir, :name', ]; ================================================ FILE: lang/fr/subscriptions/confirm.php ================================================ [ 'pay' => 'Payer :currency:amount maintenant', 'paypal' => 'Payer :currency:amount avec PayPal', 'subscribe' => 'S’abonner pour :currency:amount', ], 'helpers' => [ 'auto-renew' => [ 'monthly' => 'Ton abonnement est renouvelé automatiquement chaque mois. Ta prochaine date de facturation est au :date.', 'none' => 'Le paiement par PayPal est un paiement unique et ne se renouvelle pas automatiquement. Tu pourras te réabonner une fois que ton abonnement prend fin après le :date.', 'yearly' => 'Ton abonnement est renouvelé automatiquement tous les 12 mois. Ta prochaine date de facturation est au :date.', ], 'paypal' => 'Tu seras redirigé vers PayPal pour effectuer cette transaction.', 'refund' => 'Nous offrons une politique de remboursement de 14 jours pour tous les abonnements annuels. Il te suffit de nous envoyer un courriel à l\'adresse :email pour entamer une procédure de remboursement.', 'tiny' => 'Merci de soutenir une petite équipe de worldbuilders passionnés.', ], 'title' => 'Abonnement :name', ]; ================================================ FILE: lang/fr/subscriptions/faq.php ================================================ [ 'answer' => 'Absolument! Ton abonnement te permet d\'activer des campagnes premium dont tous les membres bénéficient. Peu importe leur propre abonnement, tous les participants d\'une campagne premium profitent des fonctionnalités avancées, ce qui fait de Kanka une plateforme idéale pour la création de mondes en collaboration.', 'question' => 'Est-ce que je peux annuler mon abonnement à tout moment?', ], 'cost' => [ 'answer' => 'Kanka propose trois niveaux d\'abonnement nommés d\'après des monstres de D&D: Owlbear, Wyvern et Elemental Les prix varient selon ta devise préférée (USD, EUR ou BRL). En choisissant un abonnement annuel plutôt qu\'un paiement mensuel, tu bénéficies de deux mois gratuits.', 'question' => 'Combien coûte un abonnement?', ], 'data' => [ 'answer' => 'Rassure-toi, nous ne supprimons jamais tes données à la fin d\'un abonnement. Les campagnes premium redeviennent simplement des campagnes standard, avec les fonctionnalités premium désactivées temporairement. Lorsque tu te réabonnes, toutes tes données et paramètres premium sont immédiatement restaurés, exactement comme tu les avais laissés.', 'question' => 'Qu\'advient-il de mes données si j\'annule mon abonnement?', ], 'discount' => [ 'answer' => 'Oui! Nos abonnés annuels bénéficient de deux mois gratuits par rapport à la facturation mensuelle. C\'est notre façon de te remercier pour ton engagement à long terme avec Kanka.', 'question' => 'Existe-t-il des réductions pour les abonnements annuels?', ], 'downgrade' => [ 'answer' => 'Tu peux modifier ton niveau d\'abonnement à tout moment. En cas de mise à niveau, nous te facturerons uniquement la différence entre ton abonnement actuel et le nouveau plan pour le reste de ta période de facturation. En cas de rétrogradation, le nouveau tarif réduit prendra effet à la date de ton prochain renouvellement, sans interruption de tes avantages actuels.', 'question' => 'Comment mettre à niveau ou rétrograder mon abonnement?', ], 'fail' => [ 'answer' => 'Si un paiement échoue, nous t\'enverrons immédiatement un email et tenterons automatiquement de débiter ta carte jusqu\'à trois fois supplémentaires. Si ces tentatives échouent, ton abonnement sera mis en pause. Tu pourras facilement résoudre ce problème en mettant à jour tes informations de :billing avec une méthode valide.', 'question' => 'Que se passe-t-il si mon paiement échoue?', ], 'help' => [ 'answer' => 'Ton abonnement finance notre temps, nos serveurs et la liberté de garder Kanka durable sans courir après la croissance à tout prix. Il nous permet de corriger les bugs plus vite, de créer des fonctionnalités auxquelles nous croyons vraiment et de rester à l’écoute de la communauté plutôt que des investisseurs. En clair:il permet à Kanka de rester vivant et de s’améliorer.', 'question' => 'Comment mon abonnement aide-t-il Kanka?', ], 'methods' => [ 'answer' => 'Nous acceptons les paiements par carte bancaire et PayPal en USD, EUR et BRL. La sécurité de ton paiement est essentielle pour nous ; toutes les transactions par carte bancaire sont traitées de manière sécurisée par notre prestataire de paiement de confiance, :stripe.', 'question' => 'Quels sont les moyens de paiement acceptés?', ], 'refund' => [ 'answer' => 'Oui! Nous offrons une politique de remboursement intégral sous 14 jours, sans poser de questions, pour tous les abonnements annuels. Il te suffit de nous envoyer un email à :email pour demander ton remboursement, et nous nous occupons du reste.', 'question' => 'Offrez-vous des remboursements?', ], 'renewal' => [ 'answer' => 'Oui, pour les abonnements par carte bancaire, nous renouvelons automatiquement ton abonnement au même tarif à la fin de ta période de facturation. Les abonnements PayPal font exception : ils nécessitent un renouvellement manuel, car PayPal ne permet pas le renouvellement automatique pour notre service.', 'question' => 'Serai-je facturé automatiquement lors du renouvellement de mon abonnement?', ], 'security' => [ 'answer' => 'Ta sécurité financière est notre priorité. Nous travaillons avec :stripe, un prestataire de paiement conforme aux normes PCI qui applique les standards les plus stricts en matière de sécurité des paiements. Toutes les données sensibles sont gérées et stockées par Stripe selon les protocoles conformes au RGPD, et non sur nos serveurs.', 'question' => 'Mes informations de paiement sont-elles sécurisées?', ], 'sharing' => [ 'answer' => 'Absolument! Ton abonnement te permet d\'activer des campagnes premium dont tous les membres bénéficient. Peu importe leur propre abonnement, tous les participants d\'une campagne premium profitent des fonctionnalités avancées, ce qui fait de Kanka une plateforme idéale pour la création de mondes en collaboration.', 'question' => 'Puis-je partager mon compte/abonnement avec d\'autres personnes?', ], 'title' => 'Questions fréquemment posées', 'trial' => [ 'answer' => 'Nous ne proposons pas d\'essai gratuit traditionnel, mais la version gratuite de Kanka offre déjà de puissants outils de création de mondes et de gestion de campagnes pour bien commencer. Lorsque tu es prêt à aller plus loin, un abonnement débloque des fonctionnalités premium comme une limite d\'upload d\'images plus élevée, une expérience sans publicité, des rôles Discord exclusifs et d\'autres améliorations pour ton monde.', 'question' => 'Existe-t-il une version d\'essai gratuite?', ], 'update' => [ 'answer' => 'Mettre à jour tes informations de facturation est simple : rends-toi sur la page :billing dans les paramètres de ton compte. Là, tu peux modifier tes méthodes de paiement, mettre à jour ta carte bancaire ou changer ton adresse de facturation si nécessaire.', 'question' => 'Comment mettre à jour mes informations de facturation?', ], 'why' => [ 'answer' => 'Kanka est créé et maintenu par une petite équipe indépendante. Les abonnements nous permettent d’y travailler sur le long terme, de l’améliorer régulièrement et d’éviter les dark patterns. Tu ne paies pas une entreprise, tu finances directement les personnes qui conçoivent, développent et soutiennent la plateforme.', 'question' => 'Pourquoi Kanka facture-t-il des abonnements?', ], ]; ================================================ FILE: lang/fr/subscriptions/finish.php ================================================ [ 'action' => 'Connecte ton compte Discord', 'enjoy' => 'Rends-toi sur Discord pour profiter de tes nouveaux avantages', 'helper' => 'En tant qu\'abonné, tu débloques des rôles exclusifs et des salons privés dans notre communauté Discord. Mais d\'abord, tu dois connecter ton compte Discord.', 'title' => 'Débloque tes avantages Discord', ], 'header' => 'Tu es maintenant abonné·e, bienvenue dans le cercle secret des bâtisseurs de mondes!', 'help' => [ 'contact-us' => 'contacte-nous', 'helper' => 'Si tu as des questions ou des retours, :contact-us ou visite notre :docs. On est là pour rendre ton aventure de worldbuilding magique.', 'title' => 'Besoin d\'aide?', ], 'next' => 'Ton soutien nous aide à grandir et à améliorer Kanka pour tout le monde. Voici ce que tu peux faire ensuite pour débloquer tous les avantages de ton abonnement :', 'premium' => [ 'action' => 'Activer', 'helper' => 'Active des fonctionnalités premium comme les catégories personnalisées, l\'accès aux :plugins, et bien plus encore!', 'title' => 'Active les options premium sur une campagne', ], 'roadmap' => [ 'action' => 'Voir la feuille de route & proposer des idées', 'helper' => 'On construit Kanka pour toi. Consulte la feuille de route publique et propose ou vote pour les fonctionnalités que tu aimerais voir!', 'title' => 'Aide à façonner l\'avenir de Kanka', ], 'title' => 'Merci de soutenir Kanka!', ]; ================================================ FILE: lang/fr/subscriptions/free-trial.php ================================================ [ 'accept' => 'Commencer mon essai gratuit', 'magic' => 'Aucune carte bancaire requise. C\'est purement magique.', ], 'final' => [ 'magic' => 'Pas d\'engagement, pas de piège. Juste plus de magie pour ta campagne.', 'title' => 'Commencer mes 15 jours d\'essai maintenant', ], 'header' => 'On a vu ta dévotion dans les royaumes de Kanka. Pour honorer ton aventure, on t\'offre :what. Pas besoin d\'or, de gemmes ou de carte bancaire.', 'included' => [ 'title' => 'Ce qui est inclus', 'upsell'=> [ 'action' => 'Voir tous les niveaux d\'abonnement', 'pitch' => 'Ceci n\'est que le niveau :tier. Prêt à débloquer encore plus?', ], ], 'pitch' => [ 'title' => 'Profite de 15 jours d\'essai gratuit! Découvre toutes les fonctionnalités premium et vois ce que tu as manqué.', ], 'started' => [ 'header' => 'Tu as démarré ton essai gratuit avec succès, bienvenue dans le guilde des créateurs de mondes!', 'title' => 'Bienvenue dans ton essai gratuit!', ], 'tease' => [ 'helper' => 'Tu veux débloquer des envois de fichiers plus volumineux et plus d\'emplacements pour campagnes premium? Visite la page :subscription pour découvrir ce qui t\'attend.', 'title' => 'Mais tu en veux plus', ], 'title' => 'Une nouvelle quête t\'attend, aventurier!', 'what' => ':amount jours d\'essai gratuit du niveau :tier', 'why' => [ 'helper' => 'Tu as créé, exploré et fait grandir ta campagne. Ta loyauté n\'est pas passée inaperçue. Considère ceci comme un cadeau de remerciement de la part de l\'équipe de Kanka.', 'title' => 'Tu as mérité cette récompense', ], ]; ================================================ FILE: lang/fr/subscriptions/paypal-renew.php ================================================ [ 'permission' => 'Ton abonnement n\'est pas sur le point d\'expirer dans les 14 prochains jours.', ], 'intro' => 'Ton abonnement expire le :date. Le renouveler avant cette date prolongera ton accès d\'une année complète, et te permettra de continuer à profiter de tes avantages sans interruption.', 'success' => 'Ton abonnement a été renouvelé avec succès jusqu\'au :date.', ]; ================================================ FILE: lang/fr/subscriptions/paypal.php ================================================ [ 'contact' => 'Si ce problème persiste, contacte nous à :email.', 'failed' => 'PayPal n\'a pas pu générer de facture. Prière de ressayer.', 'incomplete' => 'Paiement PayPal incomplet. Prière de ressayer.', 'rejected' => 'PayPal a généré une facture invalide. Prière de ressayer.', ], ]; ================================================ FILE: lang/fr/subscriptions/promos.php ================================================ [ 'inactive' => 'Cette promotion n\'est plus disponible.', 'invalid' => 'Promotion inconnue.', 'only-new' => 'Cette promotion n\'est que disponible pour les nouveau abonnés.', ], ]; ================================================ FILE: lang/fr/subscriptions/renew.php ================================================ [ 'renew' => 'Renouveller l\'abonnement', ], 'helper' => 'Cependant, tu peux décider de renouveller ton abonnement et profiter de tous les bénéfices sans interruption.', 'success' => 'Ton abonnement a été renouvelé pour un an.', 'title' => 'Renouvellement de l\'abonnement', ]; ================================================ FILE: lang/fr/subscriptions.php ================================================ [ 'failed' => 'Stripe n\'a pas pu débiter la méthode de paiement saisie. L\'abonnement Kanka a été désactivé.', ], ]; ================================================ FILE: lang/fr/tags.php ================================================ [ 'actions' => [ 'add' => 'Ajouter une nouvelle étiquette', 'add_entity' => 'Ajouter à une entity', ], 'create' => [ 'attach_success' => '{1} Ajout de :count entrée à l\'étiquette :name.|[2,*] Ajout de :count entrées à l\'étiquette :name.', 'attach_success_entity' => 'Etiquettes modifiées pour :name.', 'entity' => 'Etiquetter :name', 'helper' => 'Etiquetter une ou plusieurs entrées avec :name', 'title' => 'Etiquetter', ], ], 'create' => [ 'title' => 'Nouvelle étiquette', ], 'fields' => [ 'children' => 'Enfants', 'icon' => 'Icône', 'is_auto_applied' => 'Appliquer automatiquement aux nouvelles entrées', 'is_hidden' => 'Caché de l\'entête et des infobulles', ], 'helpers' => [ 'icon' => 'Utilise des icônes de :fontawesome ou :rpgawesome. L\'icône sera affichée à la place du nom du tag dans les listes.', 'no_children' => 'Il n\'y a actuellement aucune entrée avec cette étiquette.', 'no_posts' => 'Il n\'y a actuellement aucun article avec cette étiquette.', ], 'hints' => [ 'children' => 'Cette liste contient toutes les entrées directement dans cette étiquette et toutes les étiquettes enfants.', 'is_auto_applied' => 'Si cette option est activée, les nouvelles entrées auront automatiquement cette étiquette.', 'is_hidden' => 'Si activé, cette étiquette ne s\'affichera pas dans l\'entête d\'entrée, ni dans les infobulles.', 'tag' => 'Affichées ci-dessous sont toutes les étiquettes enfants de cette étiquette.', ], 'lists' => [ 'empty' => 'Utilise des balises pour regrouper et filtrer les entrées dans ton univers afin de faciliter la navigation.', ], 'placeholders' => [ 'icon' => 'Essaie :example1 ou :example2', 'type' => 'Légende, Guerres, Histoire, Religion', ], 'show' => [ 'tabs' => [ 'children' => 'Enfants', ], ], 'transfer' => [ 'entities' => [ 'helper' => 'Transférer les entrées étiquetées avec :name à une autre étiquette.', 'title' => 'Transférer les entrées', ], 'fail' => 'Les entrées de :tag n\'ont pas pu être transférées vers :newTag', 'fail_post' => 'Les articles de :tag n\'ont pas pu être transférés vers :newTag', 'posts' => [ 'helper' => 'Transférer les articles étiquetés avec :name à une autre étiquette.', 'title' => 'Transférer les articles', ], 'success' => 'Les entrées de :tag ont été transférées vers :newTag', 'success_post' => 'Les articles de :tag ont été transférés vers :newTag', 'transfer' => 'Transférer', ], ]; ================================================ FILE: lang/fr/teams.php ================================================ [ 'lead' => 'Worldbuilding amusant et fiable', 'translations' => 'Traductions', ], 'leads' => [ 'translators' => 'Kanka est traduit en plusieurs langues grâce à ces incroyables contributeurs.', ], 'people'=> [ 'itzamna' => [ 'title' => 'Développeur Junior', ], 'jay' => [ 'title' => 'Fondateur & Lead Developer', ], 'jon' => [ 'title' => 'Co-Foundateur & Business Manager', ], 'kaz' => [ 'title' => 'Destroyeur de bug', ], 'laura' => [ 'title' => 'Social Media', ], ], ]; ================================================ FILE: lang/fr/tiers.php ================================================ [ 'pay' => [ 'monthly' => 'Paiement mensuel', 'save' => 'économiser deux mois', 'yearly' => 'Paiement annuel', ], 'subscribe' => [ 'choose' => 'Choisir :tier', 'downgrade' => 'Rétrograder vers :tier', 'monthly' => ':tier mensuel', 'upgrade' => 'Passer à :tier', 'yearly' => ':tier annuel', ], ], 'current' => 'Abonnement actuel', 'features' => [ 'api_requests' => ':amount requêtes API / min', 'boosters' => 'Boosters de campagne', 'discord' => 'Rôles :discord', 'feature_influence' => 'Influence sur les nouvelles fonctionnalités', 'file_size' => ':size taille de fichier uploadé', 'import' => 'Import de campagne', 'modules' => ':count catégories personnalisées', 'nice_image' => 'Images par défaut pour les entrées sans images', 'no_ads' => 'Sans publicité', 'pagination' => 'Jusqu\'à :amount résultats par page', 'premium' => 'Chaque offre inclut: :storage GiB de stockage, :modules catégories personnalisées', 'roadmap' => 'Voter sur les idées dans la feuille de route.', 'storage' => ':count GiB de stockage', ], 'helpers' => [ 'premium' => 'Ces bonus s’appliquent aux campagnes premium que tu débloques.', ], 'periods' => [ 'billed_monthly' => 'facturé mensuellement', 'billed_yearly' => 'facturé annuellement', ], 'pricing' => ':currency :amount / mois', 'ribbons' => [ 'best-value' => 'Meilleure valeur', 'current' => 'Abonnement actuel', ], 'target' => [ 'elemental' => 'Pour les créateurs chevronnés qui jonglent avec les mondes épiques et les campagnes complexes', 'owlbear' => 'Parfait pour les créateurs solo qui souhaitent mettre leur campagne en valeur', 'wyvern' => 'Idéal pour les maîtres de jeu qui gèrent plusieurs aventures et pour les projets collaboratifs', ], 'tiny' => 'petite équipe', 'why' => 'Kanka est créé par une :tiny de worldbuilders passionnés. Les abonnements financent les personnes, les serveurs et le temps nécessaires pour améliorer la plateforme de manière durable. Pas de dark patterns, pas de pression d’investisseurs, pas de course à la croissance infinie. Le développement est régulier et guidé par notre communauté.', ]; ================================================ FILE: lang/fr/timelines/elements.php ================================================ [ 'copy_with_name' => 'Copier la mention avancée avec le nom de l\'élément', 'success' => 'Mention avancée vers l\'élément copier au press-papier.', ], 'create' => [ 'success' => 'Élément ajouté à la chronologie.', 'title' => 'Nouvel élément de chronologie', ], 'delete' => [ 'success' => 'L\'élément :name a été supprimé.', ], 'edit' => [ 'success' => 'L\'élément a été modifié.', 'title' => 'Modifier l\'élément de la chronologie', ], 'fields' => [ 'date' => 'Date', 'era' => 'Ère', 'icon' => 'Icône', 'use_entity_entry' => 'Afficher l\'entrée texte de l\'entrée liée. Le text de cet élément sera afficher en premier s\'il est présent.', 'use_event_date' => 'Utiliser la date de l\'événement lié.', ], 'helpers' => [ 'date' => 'Si l\'élément est lié à un événement, afficher la date de l\'événement.', 'entity_is_private' => 'L\'entrée de cet élément est privé.', 'icon' => 'Copier le HTML d\'une icône depuis :fontawesome ou :rpgawesome.', 'is_collapsed' => 'L\'élément s\'affiche de manière minimisé par défaut.', ], 'placeholders' => [ 'date' => 'ex. Le 42 Mars, ou 1332-1337', 'name' => 'Requis si pas d\'entrée sélectionnée.', 'position' => 'Position dans la liste des éléments de l\'ère. Laisser vide pour ajouter à la fin.', ], ]; ================================================ FILE: lang/fr/timelines/eras.php ================================================ [ 'add' => 'Ajouter une ère', ], 'bulks' => [ 'delete' => '{0} :count ère supprimée.|{1} :count ère supprimée.|[2,*] :count ères supprimées.', ], 'create' => [ 'success' => 'L\'ère :name a été créée.', 'title' => 'Nouvelle ère', ], 'delete' => [ 'success' => 'L\'ère :name a été supprimée.', ], 'edit' => [ 'success' => 'L\'ère :name a été modifiée.', 'title' => 'Modifier l\'ère :name', ], 'fields' => [ 'abbreviation' => 'Abbreviation', 'end_year' => 'Année de fin', 'is_collapsed' => 'Réduit', 'start_year' => 'Année de début', ], 'helpers' => [ 'eras' => 'La chronologie a besoin d\'être créée avant de pouvoir ajouter des ères.', 'is_collapsed' => 'L\'ère est réduite (minimisée) par défaut.', 'primary' => 'Séparer la chronologie en plusieurs ères. Une chronologie a besoin au moins d\'une ère pour fonctionner correctement.', ], 'index' => [ 'title' => 'Ères de :name', ], 'placeholders' => [ 'abbreviation' => 'AD, BC, BCE', 'end_year' => 'L\'année durant laquelle l\'ère prend fin. Laisser vide si c\'est l\'ère en cours.', 'name' => 'Âge modern, âge de bronze, guerres galactiques', 'start_year' => 'L\'année durant laquelle l\'ère commence. Laisser vide si c\'est la première ère.', ], 'reorder' => [], ]; ================================================ FILE: lang/fr/timelines.php ================================================ [ 'add_element' => 'Ajouter un élément à l\'ère :era', 'back' => 'Retour à :name', 'save_order' => 'Enregistrer les changements', ], 'create' => [ 'title' => 'Nouvelle chronologie', ], 'fields' => [ 'copy_elements' => 'Copier les éléments', 'copy_eras' => 'Copier les ères', 'eras' => 'Ères', 'reverse_order' => 'Inverser l\'ordre des ères', ], 'helpers' => [ 'no_era_v2' => 'Cette chronologie ne possède pas d\'ères. Les ères peuvent être ajoutées ici, après quoi des éléments pourront y être ajouté.', 'reverse_order' => 'Activer pour afficher les ères dans le sens chronologique inversé (plus ancien en premier)', ], 'lists' => [ 'empty' => 'Créé une frise chronologique visuelle pour consigner les événements majeurs et suivre l\'évolution du monde.', ], 'placeholders' => [ 'type' => 'Principale, chronique du monde, chronologie du royaume', ], 'reorder' => [ 'empty' => 'Il faut en premier ajouter des ères et éléments à la chronologie pour pouvoir la réordonner.', 'success' => 'Chronologie réordonnée.', 'title' => 'Réordonner la chronologie', ], 'show' => [ 'tabs' => [ 'reorder-elements' => 'Réordonner les éléments', ], ], ]; ================================================ FILE: lang/fr/tiptap.php ================================================ 'Donne ton avis', 'survey'=> 'Tu essaies le nouvel éditeur? :share (ça prend juste 2 minutes)', ]; ================================================ FILE: lang/fr/users/profile.php ================================================ [ 'wordsmith' => 'Un vrai maître des mots! Vainqueur d\'un évènement.', ], 'fields' => [ 'achievements' => 'Accomplissements', 'banned' => 'Cet utilisateur est suspendu.', 'entities_created' => 'Entrées créées :help :count', 'member_since' => 'Membre depuis :date', 'public_campaigns' => 'Campagnes publiques', 'subscriber_since' => 'Abonné depuis :date', ], 'helpers' => [ 'entities_created' => 'Cette valeur est recalculée chaque jour.', ], 'title' => 'Profile :name', ]; ================================================ FILE: lang/fr/validation.php ================================================ 'Le champ :attribute doit être accepté.', 'active_url' => "Le champ :attribute n'est pas une URL valide.", 'after' => 'Le champ :attribute doit être une date postérieure au :date.', 'after_or_equal' => 'Le champ :attribute doit être une date postérieure ou égale au :date.', 'alpha' => 'Le champ :attribute doit contenir uniquement des lettres.', 'alpha_dash' => 'Le champ :attribute doit contenir uniquement des lettres, des chiffres et des tirets.', 'alpha_num' => 'Le champ :attribute doit contenir uniquement des chiffres et des lettres.', 'array' => 'Le champ :attribute doit être un tableau.', 'before' => 'Le champ :attribute doit être une date antérieure au :date.', 'before_or_equal' => 'Le champ :attribute doit être une date antérieure ou égale au :date.', 'between' => [ 'numeric' => 'La valeur de :attribute doit être comprise entre :min et :max.', 'file' => 'La taille du fichier de :attribute doit être comprise entre :min et :max kilo-octets.', 'string' => 'Le texte :attribute doit contenir entre :min et :max caractères.', 'array' => 'Le tableau :attribute doit contenir entre :min et :max éléments.', ], 'boolean' => 'Le champ :attribute doit être vrai ou faux.', 'confirmed' => 'Le champ de confirmation :attribute ne correspond pas.', 'date' => "Le champ :attribute n'est pas une date valide.", 'date_equals' => 'Le champ :attribute doit être une date égale à :date.', 'date_format' => 'Le champ :attribute ne correspond pas au format :format.', 'different' => 'Les champs :attribute et :other doivent être différents.', 'digits' => 'Le champ :attribute doit contenir :digits chiffres.', 'digits_between' => 'Le champ :attribute doit contenir entre :min et :max chiffres.', 'dimensions' => "La taille de l'image :attribute n'est pas conforme.", 'distinct' => 'Le champ :attribute a une valeur en double.', 'email' => 'Le champ :attribute doit être une adresse email valide.', 'exists' => 'Le champ :attribute sélectionné est invalide.', 'file' => 'Le champ :attribute doit être un fichier.', 'filled' => 'Le champ :attribute doit avoir une valeur.', 'gt' => [ 'numeric' => 'La valeur de :attribute doit être supérieure à :value.', 'file' => 'La taille du fichier de :attribute doit être supérieure à :value kilo-octets.', 'string' => 'Le texte :attribute doit contenir plus de :value caractères.', 'array' => 'Le tableau :attribute doit contenir plus de :value éléments.', ], 'gte' => [ 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :value.', 'file' => 'La taille du fichier de :attribute doit être supérieure ou égale à :value kilo-octets.', 'string' => 'Le texte :attribute doit contenir au moins :value caractères.', 'array' => 'Le tableau :attribute doit contenir au moins :value éléments.', ], 'image' => 'Le champ :attribute doit être une image.', 'in' => 'Le champ :attribute est invalide.', 'in_array' => "Le champ :attribute n'existe pas dans :other.", 'integer' => 'Le champ :attribute doit être un entier.', 'ip' => 'Le champ :attribute doit être une adresse IP valide.', 'ipv4' => 'Le champ :attribute doit être une adresse IPv4 valide.', 'ipv6' => 'Le champ :attribute doit être une adresse IPv6 valide.', 'json' => 'Le champ :attribute doit être un document JSON valide.', 'lt' => [ 'numeric' => 'La valeur de :attribute doit être inférieure à :value.', 'file' => 'La taille du fichier de :attribute doit être inférieure à :value kilo-octets.', 'string' => 'Le texte :attribute doit contenir moins de :value caractères.', 'array' => 'Le tableau :attribute doit contenir moins de :value éléments.', ], 'lte' => [ 'numeric' => 'La valeur de :attribute doit être inférieure ou égale à :value.', 'file' => 'La taille du fichier de :attribute doit être inférieure ou égale à :value kilo-octets.', 'string' => 'Le texte :attribute doit contenir au plus :value caractères.', 'array' => 'Le tableau :attribute doit contenir au plus :value éléments.', ], 'max' => [ 'numeric' => 'La valeur de :attribute ne peut être supérieure à :max.', 'file' => 'La taille du fichier de :attribute ne peut pas dépasser :max kilo-octets.', 'string' => 'Le texte de :attribute ne peut contenir plus de :max caractères.', 'array' => 'Le tableau :attribute ne peut contenir plus de :max éléments.', ], 'mimes' => 'Le champ :attribute doit être un fichier de type : :values.', 'mimetypes' => 'Le champ :attribute doit être un fichier de type : :values.', 'min' => [ 'numeric' => 'La valeur de :attribute doit être supérieure ou égale à :min.', 'file' => 'La taille du fichier de :attribute doit être supérieure à :min kilo-octets.', 'string' => 'Le texte :attribute doit contenir au moins :min caractères.', 'array' => 'Le tableau :attribute doit contenir au moins :min éléments.', ], 'not_in' => "Le champ :attribute sélectionné n'est pas valide.", 'not_regex' => "Le format du champ :attribute n'est pas valide.", 'numeric' => 'Le champ :attribute doit contenir un nombre.', 'present' => 'Le champ :attribute doit être présent.', 'regex' => 'Le format du champ :attribute est invalide.', 'required' => 'Le champ :attribute est obligatoire.', 'required_if' => 'Le champ :attribute est obligatoire quand la valeur de :other est :value.', 'required_unless' => 'Le champ :attribute est obligatoire sauf si :other est :values.', 'required_with' => 'Le champ :attribute est obligatoire quand :values est présent.', 'required_with_all' => 'Le champ :attribute est obligatoire quand :values sont présents.', 'required_without' => "Le champ :attribute est obligatoire quand :values n'est pas présent.", 'required_without_all' => "Le champ :attribute est requis quand aucun de :values n'est présent.", 'same' => 'Les champs :attribute et :other doivent être identiques.', 'size' => [ 'numeric' => 'La valeur de :attribute doit être :size.', 'file' => 'La taille du fichier de :attribute doit être de :size kilo-octets.', 'string' => 'Le texte de :attribute doit contenir :size caractères.', 'array' => 'Le tableau :attribute doit contenir :size éléments.', ], 'starts_with' => 'Le champ :attribute doit commencer avec une des valeurs suivantes : :values', 'string' => 'Le champ :attribute doit être une chaîne de caractères.', 'timezone' => 'Le champ :attribute doit être un fuseau horaire valide.', 'unique' => 'La valeur du champ :attribute est déjà utilisée.', 'uploaded' => "Le fichier du champ :attribute n'a pu être téléversé.", 'url' => "Le format de l'URL de :attribute n'est pas valide.", 'uuid' => 'Le champ :attribute doit être un UUID valide', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ 'name' => 'nom', 'username' => "nom d'utilisateur", 'email' => 'adresse email', 'first_name' => 'prénom', 'last_name' => 'nom', 'password' => 'mot de passe', 'password_confirmation' => 'confirmation du mot de passe', 'city' => 'ville', 'country' => 'pays', 'address' => 'adresse', 'phone' => 'téléphone', 'mobile' => 'portable', 'age' => 'âge', 'sex' => 'sexe', 'gender' => 'genre', 'day' => 'jour', 'month' => 'mois', 'year' => 'année', 'hour' => 'heure', 'minute' => 'minute', 'second' => 'seconde', 'title' => 'titre', 'content' => 'contenu', 'description' => 'description', 'excerpt' => 'extrait', 'date' => 'date', 'time' => 'heure', 'available' => 'disponible', 'size' => 'taille', ], ]; ================================================ FILE: lang/fr/visibilities.php ================================================ [ 'admin' => 'Seuls les administrateurs de la campagne peuvent visualiser cet élément.', 'admin-self' => 'Toi et les administrateurs de la campagne êtes les seuls à pouvoir visualiser cet élément.', 'all' => 'Tout le monde peut voir cet élément.', 'members' => 'Seuls les membres de la campagne peuvent voir cet élément.', 'self' => 'Tu es le seul à voir cet élément.', ], 'picker' => [ 'admin' => 'Visible uniquement par les membres du rôle :admin.', 'admin-self' => 'Uniquement toi et les membres du rôle :admin peuvent voir ceci.', 'all' => 'Toute personne pouvant voir :entity peut voir ceci.', 'failed' => 'Échec de la mise à jour de la visibilité.', 'member' => 'Visible uniquement par les membres de la campagne. Utile pour les campagnes publiques.', 'self' => 'Uniquement toi peux voir ceci.', ], 'title' => 'Mise à jour de la visibilité', 'toast' => 'Visibilité modifiée.', 'tooltip' => 'Cliquer pour connaître les options de visibilité.', ]; ================================================ FILE: lang/fr/whiteboards/draw.php ================================================ [ 'add-circle' => 'Ajouter un cercle', 'add-entity' => 'Ajouter une entrée', 'add-image' => 'Ajouter une image', 'add-square' => 'Ajouter un carré', 'add-text' => 'Ajouter du texte', 'duplicate' => 'Dupliquer la sélection', 'end-drawing' => 'Terminer le dessin', 'lock' => 'Verrouiller', 'push-to-back' => 'Vers le devant', 'push-to-front' => 'Vers l\'arrière', 'start-drawing' => 'Commencer à dessiner', 'unlock' => 'Déverouiller', ], 'entity-search' => [ 'placeholder' => 'Écrire le nom ou l\'alias d\'une entrée', 'title' => 'Recherche d\'entrée', ], 'errors' => [ 'websockets' => [ 'disconnected' => 'La connexion au websocket a été perdue. Réessaie.', 'error' => 'Une erreur s’est produite lors de la connexion au serveur websocket.', 'unavailable' => 'Le serveur websocket est indisponible. Réessaie plus tard.', ], ], 'fields' => [ 'color' => 'Couleure', ], 'pen' => [ 'large-stroke' => 'Grand trait', 'thin-stroke' => 'Petit trait', ], 'reset' => [ 'helper' => 'Es-tu sûr de vouloir réinitialiser le tableau blanc? Cette action est irréversible.', 'title' => 'Réinitialiser le tableau blanc', ], 'roles' => [ 'edit' => 'Cet utilisateur peut modifier le tableau blanc', 'view' => 'Cet utilisateur peut voir le tableau blanc', ], 'toast' => [ 'copy' => [ 'success' => 'Éléments copiés au presse papier.', ], 'paste' => [ 'error' => 'Une erreure est survenue.', ], ], ]; ================================================ FILE: lang/fr/whiteboards.php ================================================ [ 'draw' => 'Dessiner', ], 'create' => [ 'title' => 'Nouveau tableau blanc', ], 'cta' => [ 'text' => 'Pour débloquer les tableaux blancs, une campagne doit être rendue premium par un membre de niveau :wyvern ou :elemental.', 'title' => 'Les tableaux blancs sont une fonctionnalité premium spéciale', ], 'lists' => [ 'empty' => 'Utilise un tableau blanc pour organiser visuellement tes idées, tes relations ou la structure de ton histoire.', ], 'placeholders' => [ 'type' => 'Idée, relations, structure d\'histoire', ], 'update' => [], ]; ================================================ FILE: lang/hr/abilities.php ================================================ [], 'children' => [ 'description' => 'Entiteti koji imaju sposobnost', 'title' => 'Sposobnost :name entiteta', ], 'create' => [ 'title' => 'Nova sposobnost', ], 'destroy' => [], 'edit' => [], 'entities' => [], 'fields' => [ 'charges' => 'Punjenja', ], 'helpers' => [], 'index' => [], 'placeholders' => [ 'charges' => 'Broj punjenja. Referenciraj se na atribute s {Level}*{CHA}', 'name' => 'Vatrena kugla, Upozorenje, Lukavi udarac', 'type' => 'Čarolija, podvig, napad', ], 'show' => [], ]; ================================================ FILE: lang/hr/attribute_templates.php ================================================ [], 'create' => [ 'title' => 'Novi predložak svojstva', ], 'destroy' => [], 'edit' => [], 'fields' => [], 'hints' => [ 'automatic' => 'Svojstva automatski pridjeljena iz :link predloška svojstva.', 'entity_type' => 'Ako je uključeno, kreiranje novog entiteta ovog tipa će automatski primjeniti ovaj predložak svojstva na njega.', 'parent_attribute_template' => 'Ovaj predložak svojstva može biti dijete drugog predloška svojstva. Kad se primjenjuje ovaj predložak svojstva, primjenjuju se i svi njegovi predlošci svojstva roditelji.', ], 'index' => [], 'placeholders' => [ 'name' => 'Naziv predloška svojstva', ], 'show' => [], ]; ================================================ FILE: lang/hr/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Greška', 'rendering' => 'Došlo je do pogreške prilikom prikazivanja dodatka za tržište. Molimo kontaktiraj tvorca dodatka.', ], ], 'helpers' => [], 'list' => [], ]; ================================================ FILE: lang/hr/auth.php ================================================ 'Uneseni korisnički podaci ne odgovaraju našim zapisima.', 'helpers' => [ 'password' => 'Prikaži / sakrij lozinku', ], 'login' => [ 'fields' => [ 'email' => 'Email', 'password' => 'Lozinka', ], 'or' => 'ILI', 'password_forgotten' => 'Zaboravljena lozinka?', 'submit' => 'Prijava', 'title' => 'Prijava', ], 'register' => [ 'errors' => [ 'email_already_taken' => 'Račun s ovom email adresom je već registriran.', 'general_error' => 'Dogodila se pogreška prilikom registracije. Molimo, pokušaj ponovno.', ], 'fields' => [ 'email' => 'Email', 'name' => 'Korisničko ime', 'password' => 'Lozinka', ], 'submit' => 'Registracija', 'title' => 'Registracija', ], 'reset' => [ 'fields' => [ 'email' => 'Email adresa', 'password' => 'Lozinka', 'password_confirmation' => 'Potvrdi svoju lozinku', ], 'send' => 'Pošalji poveznicu za ponovno postavljanje lozinke', 'submit' => 'Ponovno postavi lozinku', 'title' => 'Ponovi lozinku', ], 'throttle' => 'Previše pokušaja prijave. Molim, pokušaj ponovno za :seconds sekundi.', ]; ================================================ FILE: lang/hr/bookmarks.php ================================================ [ 'title' => 'Nova poveznica izbornika', ], 'destroy' => [], 'edit' => [ 'title' => 'Poveznica izbornika :name', ], 'fields' => [ 'dashboard' => 'Naslovna ploča', 'filters' => 'Filteri', 'menu' => 'Izbornik', 'position' => 'Pozicija', 'random_type' => 'Nasumičan tip entiteta', 'selector' => 'Konfiguracija brzih poveznica', ], 'helpers' => [ 'dashboard' => 'Neka brza poveznica cilja jednu od prilagođenih naslovnih ploča kampanje.', 'entity' => 'Postavi ovu poveznicu izbornika tako da ide direktno na entitet. Polje :tab kontrolira koja kartica je fokusirana. Polje :menu kontrolira koja podstranica entiteta se otvara.', 'position' => 'Pomoću ovog polja možeš upravljati redoslijedom kojim se poveznice pojavljuju na izborniku.', 'random' => 'Koristi ovo polje za brzu vezu koja upućuje na slučajni entitet. Vezu možeš filtrirati tako da ide samo do određenu vrstu entiteta.', 'selector' => 'Konfiguriraj kamo vodi ova brza poveznica kada je korisnik klikne na bočnoj traci.', 'type' => 'Postavi ovu poveznicu izbornika tako da vodi direktno na listu entiteta. Za filtriranje rezultata, kopiraj dijelove URL s filtrirane liste entiteta nakon znaka :? u polje :filter', ], 'index' => [], 'placeholders' => [ 'filters' => 'location_id=15&type=grad', 'menu' => 'Podstranica izbornika (koristite posljednji tekst URL-a)', 'tab' => 'unos, odnosi, bilješke', ], 'random_types' => [ 'any' => 'Bilo koji entitet', ], 'show' => [], ]; ================================================ FILE: lang/hr/calendars/weather.php ================================================ [ 'success' => 'Dodani vremenski uvjeti.', 'title' => 'Novi učinak vremenskih uvjeta', ], 'destroy' => [ 'success' => 'Uklonjeni vremenski uvjeti.', ], 'edit' => [ 'success' => 'Ažurirani vremenski uvjeti.', 'title' => 'Ažuriraj vremenske uvjete', ], 'fields' => [ 'effect' => 'Učinak', 'name' => 'Naziv', 'precipitation' => 'Oborine', 'temperature' => 'Temperatura', 'weather' => 'Vremenski uvjeti', 'wind' => 'Vjetar', ], 'options' => [ 'weather' => [ 'bolt' => 'Grmljavina', 'cloud' => 'Oblačno', 'cloud-rain' => 'Kišovito', 'cloud-showers-heavy' => 'Jaka kiša', 'cloud-sun' => 'Oblačno i sunčano', 'cloud-sun-rain' => 'Oblaci, sunčano i kiša', 'meteor' => 'Meteor', 'smog' => 'Smog', 'snowflake' => 'Snijeg', 'sun' => 'Sunčano', 'wind' => 'Vjetrovito', ], ], 'placeholders' => [ 'effect' => 'Magični ili prirodni učinak', 'name' => 'Neobavezan prilagođeni tekst o vremenu', 'precipitation' => 'Količina vode', 'temperature' => 'Dnevno visoka i niska', 'wind' => 'Brzine vjetra', ], ]; ================================================ FILE: lang/hr/calendars.php ================================================ [ 'add_epoch' => 'Dodaj epohu', 'add_intercalary' => 'Dodaj interkalarne dane', 'add_month' => 'Dodaj kalendarski mjesec', 'add_moon' => 'Dodaj mjesec (nebesko tijelo)', 'add_reminder' => 'Dodaj podsjetnik', 'add_season' => 'Dodaj sezonu', 'add_weather' => 'Postavi vremenski efekt', 'add_week' => 'Dodaj imenovani tjedan', 'add_weekday' => 'Dodaj dan u tjednu', 'add_year' => 'Dodaj ime godine', 'set_today' => 'Postavi kao trenutni dan', 'today' => 'Danas', ], 'checkboxes' => [ 'is_recurring' => 'Ponavlja se svake godine', ], 'create' => [ 'title' => 'Novi kalendar', ], 'destroy' => [], 'edit' => [ 'today' => 'Ažuriran datum kalendara.', ], 'event' => [ 'create' => [ 'success' => 'Kreiran događaj u kalendaru.', 'title' => 'Dodaj događaj u kalendaru na :name', ], 'destroy' => 'Uklonjen događaj iz kalendara ":name".', 'edit' => [ 'success' => 'Ažuriran događaj u kalendaru.', 'title' => 'Ažuriraj događaj kalendara u :name', ], 'helpers' => [ 'other_calendar' => 'Uređujete podsjetnik koji se nalazi na kalendaru :calendar.', ], 'success' => 'Događaj ":event" dodan u kalendar.', ], 'events' => [], 'fields' => [ 'comment' => 'Komentar', 'current_day' => 'Trenutni dan', 'current_month' => 'Trenutni mjesec', 'current_year' => 'Trenutna godina', 'date' => 'Trenutni datum', 'is_incrementing' => 'Pomični datum', 'is_recurring' => 'Ponavljajući', 'leap_year_amount' => 'Dodaj dane', 'leap_year_month' => 'Mjesec', 'leap_year_offset' => 'Svakih', 'leap_year_start' => 'Prijestupne godine', 'length' => 'Duljina događaja', 'length_days' => ':count day|:count days', 'months' => 'Kalendarski mjeseci', 'moons' => 'Mjeseci (nebeska tijela)', 'parameters' => 'Parametri', 'recurring_until' => 'Ponavlja se do godine', 'reset' => 'Tjedno ponovno postavljanje', 'seasons' => 'Sezone', 'start_offset' => 'Početni odmak', 'suffix' => 'Dometak', 'week_names' => 'Nazivi tjedana', 'weekdays' => 'Nazivi dana', ], 'helpers' => [ 'month_type' => 'Interkalarni mjeseci ne koriste dane, ali utječu na mjesece i sezone.', 'start_offset' => 'Zadano je da kalendar počinje od prvog dana u tjednu nulte godine. Promjena ovog polja utječe na to gdje je prvi dan kalendar postavljen.', ], 'hints' => [ 'event_length' => 'Koliko dugo treba trajati događaj. Događaj ne može trajati duže od dva mjeseca.', 'is_incrementing' => 'Pomični kalendari će automatski povećati svoj trenutni dan u 00:00 UTC.', 'months' => 'Tvoj kalendar bi trebao imati barem 2 kalendarska mjeseca.', 'moons' => 'Dodavanje mjeseci (nebeskih tijela) će ih dodati na kalendar za svaki puni i mladi mjesec. Ako je period punog mjeseca duži od 10 dana, prikazat će se i prva i posljednja četvrt.', 'parent_calendar' => 'Dodavanje roditeljskog kalendara uključuje podsjetnike i vremenske uvjete tog roditeljskog kalendara.', 'reset' => 'Uvijek započni početak mjeseca ili godine na prvi dan tjedna.', 'seasons' => 'Stvori sezone za svoj kalendar tako što odrediš kad svaka počinje. Kanka će se pobrinuti za ostalo.', 'weekdays' => 'Postavi nazive za dane u tjednu. Barem 2 dana u tjednu su potrebna.', 'weeks' => 'Definiraj neke nazive za bitnije tjedne u svom kalendaru.', 'years' => 'Neke godine su toliko važne da imaju svoj naziv.', ], 'index' => [], 'layouts' => [ 'month' => 'Mjesec', 'year' => 'Godina', ], 'modals' => [ 'switcher' => [ 'title' => 'Promjena godine', ], ], 'month_types' => [ 'intercalary' => 'Interkalarni', 'standard' => 'Standardni', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Pun mjesec', 'fullmoon_name' => ':moon pun mjesec', 'month' => 'Mjesečno', 'newmoon' => 'Mladi mjesec', 'newmoon_name' => ':moon mladi mjesec', 'none' => 'Ništa', 'unnamed_moon' => 'Mjesec :number', 'year' => 'Godišnje', ], ], 'resets' => [ '' => 'Nikad', 'month' => 'Mjesečno', 'year' => 'Godišnje', ], ], 'panels' => [ 'intercalary' => 'Interkalarni dani', 'leap_year' => 'Prijestupna godina', 'months' => 'Mjeseci', 'weeks' => 'Tjedni', 'years' => 'Imenovane godine', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Duljina trajanja u danima', 'month' => 'Na kraju kojeg mjeseca', 'name' => 'Naziv interkalarnosti', ], 'month' => [ 'alias' => 'Drugo ime mjeseca', 'length'=> 'Dana', 'name' => 'Naziv mjeseca', 'type' => 'Tip', ], 'moon' => [ 'fullmoon' => 'Pun mjesec svakih (dana)', 'name' => 'Naziv mjeseca', 'offset' => 'Pomak za prvi puni mjesec', ], 'seasons' => [ 'day' => 'Dan počinje', 'month' => 'Mjesec počinje', 'name' => 'Naziv godišnjeg doba', ], 'weeks' => [ 'name' => 'Naziv tjedna', 'number' => 'Broj', ], 'year' => [ 'name' => 'Naziv godine', 'number' => 'Godina', ], ], 'placeholders' => [ 'colour' => 'Boja', 'comment' => 'Rođendan, festival, solsticij', 'date' => 'Trenutni datum', 'leap_year_amount' => 'Broj dana koji se doda na svaku prijestupnu godinu', 'leap_year_month' => 'Mjesec na koji se dani dodaju', 'leap_year_offset' => 'Svakih koliko godina je prijestupna godina', 'leap_year_start' => 'Prva godina koja je prijestupna', 'length' => 'Duljina događaja u danima', 'months' => 'Broj mjeseci u godini', 'recurring_until' => 'Zadnja godina ponavljanja (ostavi prazno za ponavljanje zauvijek)', 'seasons' => 'Broj sezona', 'suffix' => 'Dometak trenutnoj eri (pr.n.e. ili n.e.)', 'type' => 'Tip kalendara', 'weekdays' => 'Broj dana u tjednu', ], 'show' => [ 'missing_details' => 'Ovaj se kalendar nije mogao prikazati. Kalendari trebaju imati barem 2 kalendarska mjeseca i barem 2 dana u tjednu da bi se pravilno prikazali.', 'tabs' => [ 'events' => 'Događaji kalendara', 'weather' => 'Vremenski uvjeti', ], ], 'sorters' => [ 'after' => 'Danas i poslije', 'before'=> 'Danas i prije', ], ]; ================================================ FILE: lang/hr/campaigns/applications.php ================================================ [ 'accept' => 'Prihvati', 'reject' => 'Odbij', ], 'apply' => [ 'apply' => 'Prijavi se', 'help' => 'Ova kampanja otvorena je za nove članove. Prijavi se ispunjavanjem obrasca. Bit ćeš obaviješten/a kad administratori kampanje pregledaju tvoju prijavu.', 'remove_text' => 'tvoja prijava', 'success' => [ 'apply' => 'Tvoja prijava je spremljena. I dalje ju možeš promijeniti ili otkazati u bilo kojem trenutku. Bit ćeš obaviješten/a kad je administratori kampanje pregledaju.', 'remove'=> 'Tvoja prijava je uklonjena.', 'update'=> 'Tvoja prijava je ažurirana. I dalje ju možeš promijeniti ili otkazati u bilo kojem trenutku. Bit ćeš obaviješten/a kad je administratori kampanje pregledaju.', ], 'title' => 'Pridruži se :name', ], 'errors' => [], 'fields' => [ 'application' => 'Prijava', ], 'helpers' => [], 'placeholders' => [ 'note' => 'Zapiši svoju prijavu za pridruživanje kampanji', ], 'update' => [ 'approve' => 'Odaberi ulogu koja će biti dodijeljena korisniku koji se pridruži kampanji.', 'approved' => 'Prijava odobrena.', 'reject' => 'Napiši neobaveznu poruku korisniku zašto je njihova prijava odbijena.', 'rejected' => 'Prijava odbijena', ], ]; ================================================ FILE: lang/hr/campaigns/dashboard-header.php ================================================ [ 'success' => 'Ažurirano zaglavlje naslovne ploče kampanje.', 'title' => 'Ažuriranje zaglavlja naslovne ploče kampanje.', ], ]; ================================================ FILE: lang/hr/campaigns/default-images.php ================================================ [ 'add' => 'Dodaj novu zadanu sliku', ], 'create' => [ 'error' => 'Pogreška prilikom spremanja novih zadanih slika entiteta. Da li je :type već postavljen?', 'success' => 'Kreirana zadana slika entiteta za :type.', 'title' => 'Nova zadana slika entiteta', ], 'destroy' => [ 'success' => 'Uklonjena zadana slika entiteta za :type.', ], 'index' => [], ]; ================================================ FILE: lang/hr/campaigns/gallery.php ================================================ [ 'close' => 'Zatvori', 'save' => 'Spremi', ], 'breadcrumb' => 'Galerija', 'destroy' => [ 'success' => 'Obrisana slika :name.', ], 'fields' => [ 'created_by' => 'Učitao/la', 'ext' => 'Ekstenzija', 'folder' => 'Direktorij', 'image_used_in' => '{1}Koristi se kao slika jednog entiteta.|[2,*]Koristi se kao slika :count entiteta.', 'name' => 'Naziv', 'size' => 'Veličina', ], 'new_folder' => [ 'title' => 'Novi direktorij', ], 'no_folder' => 'Nema direktorija', 'placeholders' => [ 'search' => 'Pretraži ime slike...', ], 'title' => 'Galerija kampanje :campaign', 'update' => [ 'success' => 'Slika modificirana.', ], 'uploader' => [ 'add' => 'Dodaj novo', 'new_folder' => 'Novi direktorij', 'or' => 'ili', 'select_file' => 'Odaberi datoteku', 'well' => 'Povuci i ispusti datoteku za učitavanje', ], ]; ================================================ FILE: lang/hr/campaigns/plugins.php ================================================ [ 'disable' => 'Onemogući dodatak', 'enable' => 'Omogući dodatak', 'import' => 'Uvoz', 'update' => 'Ažuriraj dodatak', 'update_available' => 'Ažuriranje dostupno!', ], 'destroy' => [ 'success' => 'Uklonjen dodatak :plugin.', ], 'disabled' => [ 'success' => 'Dodatak :plugin je onemogućen.', ], 'empty_list' => 'Kampanja trenutno nema dodataka. Idi na Tržnicu da ih instaliraš nekoliko pa se vrati da ih aktiviraš.', 'enabled' => [ 'success' => 'Dodatak :plugin je omogućen.', ], 'errors' => [ 'invalid_plugin' => 'Neispravan dodatak.', ], 'fields' => [ 'name' => 'Naziv dodatka', 'status' => 'Status', 'type' => 'Vrsta dodatka', ], 'import' => [ 'created' => 'Stvoreni sljedeći entiteti:', 'success' => '{1}Uvezen :count entitet iz dodatka :plugin.|[2,*]Uvezeno :count entiteta iz dodatka :plugin.', 'updated' => 'Ažurirani sljedeći entiteti:', ], 'info' => [ 'helper' => 'Kad izađe nova verzija dodatka, možeš ga ažurirati na najnoviju verziju za svoju kampanju.', 'title' => 'Ažuriranja dodatka :plugin', 'updates' => 'Ažuriranja', ], 'status' => [ 'disabled' => 'Onemogućeno', 'enabled' => 'Omogućeno', ], 'templates' => [ 'name' => ':name, autor :user', ], 'title' => 'Dodaci kampanje :name', 'types' => [ 'attribute' => 'Predložak atributa', 'pack' => 'Sadržajni paket', 'theme' => 'Tema', ], 'update' => [ 'success' => 'Dodatak :plugin ažuriran.', ], ]; ================================================ FILE: lang/hr/campaigns/recovery.php ================================================ [ 'recover' => 'Oporavi', ], 'error' => 'Došlo je do pogreške prilikom oporavka entiteta.', 'fields' => [ 'deleted' => 'Obrisano', ], 'title' => 'Oporavak entiteta za :campaign', ]; ================================================ FILE: lang/hr/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Kalendari', 'title' => 'Čuvar Vremena', ], 'murderer' => [ 'goal' => 'Mrtvi likovi', 'title' => 'Ubojica', ], ], 'targets' => [], 'titles' => [ 'calendars' => 'Čuvar Vremena razina :level', 'characters'=> 'Davatelj Imena razina :level', 'dead' => 'Ubojica razina :level', 'families' => 'Planiranje Obitelji razina :level', 'locations' => 'Graditelj razina :level', 'quests' => 'Organizator razina :level', 'races' => 'Uzgajivač razina :level', ], ]; ================================================ FILE: lang/hr/campaigns.php ================================================ [ 'success' => 'Kampanja kreirana.', 'title' => 'Nova kampanja', ], 'destroy' => [], 'edit' => [ 'success' => 'Kampanja ažurirana.', ], 'entity_note_visibility' => [], 'entity_personality_visibilities' => [ 'private' => 'Novi likovi imaju svoju osobnost postavljenu kao privatnu, osim ako to ne promijeniš.', ], 'entity_visibilities' => [ 'private' => 'Novi entiteti su privatni', ], 'errors' => [ 'access' => 'Nemaš pristup ovoj kampanji.', 'unknown_id' => 'Nepoznata kampanja.', ], 'export' => [], 'fields' => [ 'boosted' => 'Pojačali', 'entity_count' => 'Broj entiteta', 'entry' => 'Opis kampanje', 'followers' => 'Pratitelji', 'header_image' => 'Slika zaglavlja', 'image' => 'Slika', 'locale' => 'Jezik', 'name' => 'Naziv', 'open' => 'Otvoreno za prijave', 'public_campaign_filters' => 'Filteri javnih kampanja', 'superboosted' => 'Super pojačali', 'system' => 'Sustav', 'theme' => 'Tema', ], 'following' => 'Praćenje', 'helpers' => [ 'boosted' => 'Neke funkcionalnosti su otključane jer je ova kampanja pojačana. Pronađi više na stranicama :settings.', 'css' => 'Napiši svoj CSS koji će biti učitan u stranice tvoje kampanje. Zlonamjerno korištenje ove funkiconalnosti će rezultirati uklanjanjem tvog CSS-a. Ponovni ili posebno teški prijestupi mogu dovesti do uklanjanja tvoje kampanje.', 'dashboard' => 'Prilagodi način na koji se prikazuje programčić naslovne ploče kampanje popunjavanjem sljedećih polja.', 'excerpt' => 'Isječak kampanje će biti prikazan na naslovnoj ploči pa napiši nekoliko rečenica kao uvod u svoj svijet. Za najbolje rezultate, neka bude kratko.', 'header_image' => 'Slika koja se prikazuje kao pozadina u programčiću naslovne ploče zaglavlja kampanje.', 'hide_history' => 'Omogući ovu opciju za skrivanje povijesti entiteta članovima kampanje koji nisu administratori.', 'hide_members' => 'Omogući ovu opciju za sakrivanje popisa članova kampanje za članove koji nisu administratori.', 'locale' => 'Jezik u kojem je tvoja kampanja napisana. Ovo se koristi za generiranje sadržaja i grupiranje javnih kampanja.', 'name' => 'Tvoj svijet ili kampanja može imati bilo koje ime dok god sadrži 4 slova ili broja.', 'public_campaign_filters' => 'Pomozi drugima da pronađu kampanju među ostalim javnim kampanjama pružanjem sljedećih informacija.', 'public_no_visibility' => 'Pažnja! Tvoja kampanja je javna, ali javna uloga kampanje ne može pristupiti ničemu. :fix.', 'system' => 'Ako je tvoja kampanja vidljiva javnosti, sustav je pokazan na stranici :link.', 'systems' => 'Kako bi izbjegli zatrpavanje korisnika opcijama, neke funkcionalnosti Kanke su dostupne samo s određenim RPG sustavima (npr. D&D 5e blok sa statistikama za nemani). Dodavanje podržanih sustava na ovom mjestu će omogućiti te funkcionalnosti.', 'theme' => 'Prisili korisnike da koriste ovu temu za kampanju, nadjačavajući njihov odabir.', 'view_public' => 'Da bi vidio/la svoju kampanju kao javni gledatelj, otvori :link u anonimnom prozoru.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Kopiraj poveznicu u međuspremnik', 'link' => 'Nova poveznica', ], 'create' => [ 'buttons' => [ 'create' => 'Kreiraj pozivnicu', ], 'success_link' => 'Poveznica kreirana: :link', 'title' => 'Pozovi nekoga u svoju kampanju', ], 'destroy' => [ 'success' => 'Pozivnica uklonjena.', ], 'error' => [ 'inactive_token' => 'Ovaj token je već iskorišten ili kampanja više ne postoji.', 'invalid_token' => 'Ovaj token više nije validan.', ], 'fields' => [ 'created' => 'Poslano', 'role' => 'Uloga', 'type' => 'Tip', ], 'unlimited_validity' => 'Neograničeno', ], 'leave' => [ 'confirm' => 'Da li sigurno da želiš napustiti kampanju :name? Nećeš još više moći pristupiti, osim ako te administrator kampanje ne pozove ponovno.', 'error' => 'Nemoguće napustiti kampanju.', 'success' => 'Napustio/la si kampanju.', ], 'members' => [ 'actions' => [ 'switch' => 'Imitiraj', 'switch-back' => 'Povratak na mog korisnika', ], 'fields' => [ 'joined' => 'Pridružen/a', 'last_login' => 'Zadnja prijava', 'name' => 'Korisnik', 'role' => 'Uloga', 'roles' => 'Uloge', ], 'helpers' => [ 'switch' => 'Imitiraj ovog korisnika', ], 'impersonating' => [ 'message' => 'Gledaš kampanju kao drugi korisnik. Neke funkcionalnosti su onemogućene, ali ostatak se ponaša jednako onako kako bi ih taj korisnik vidio. Da se vratiš nazad na svog korisnika, iskoristi "Prekini imitaciju" gumb koji se nalazi tamo gdje se inače nalazi gumb za odjavu.', 'title' => 'Imitiranje :name', ], 'invite' => [ 'description' => 'Možeš pozvati prijatelje da se priključe tvojoj kampanji tako što im daš pozivnicu za priključivanje. Kad prihate pozivnicu, bit će dodani kao članovi u zatraženoj ulozi. Također im možeš poslati zahtjev putem emaila dok god nije Hotmail adresa, jer oni uvijek odbijaju Kankine emailove.', 'more' => 'Možeš dodati više uloga na :link.', 'title' => 'Pozvati', ], 'roles' => [ 'member' => 'Član', 'owner' => 'Administrator', 'player' => 'Igrač', 'public' => 'Javnost', 'viewer' => 'Osmatrač', ], 'switch_back_success' => 'Vratio si se na svog korisnika.', ], 'open_campaign' => [], 'panels' => [ 'dashboard' => 'Naslovna ploča', 'setup' => 'Postavljanje', 'sharing' => 'Dijeljenje', 'systems' => 'Sustavi', 'ui' => 'Sučelje', ], 'placeholders' => [ 'locale' => 'Jezični kod', 'name' => 'Naziv tvoje kampanje', 'system' => 'D&D, Pathfinder, Fate, DSA', ], 'roles' => [ 'actions' => [ 'add' => 'Dodaj ulogu', ], 'admin_role' => 'uloga administratora', 'create' => [ 'success' => 'Uloga kreirana.', 'title' => 'Kreiraj novu ulogu za: name', ], 'destroy' => [ 'success' => 'Uloga uklonjena.', ], 'edit' => [ 'success' => 'Uloga ažurirana.', 'title' => 'Uredi ulogu :name', ], 'fields' => [ 'name' => 'Naziv', 'permissions' => 'Ovlasti', 'type' => 'Tip', 'users' => 'Korisnici', ], 'helper' => [ '1' => 'Kampanja može imati koliko god uloga treba. Uloga "Administrator" automatski ima pristup svemu u kampanji, ali sve ostale uloge mogu imati određene ovlasti za različite entitete (likove, lokacije, itd).', '2' => 'Entitetima se mogu detaljnije podesiti ovlasti pregledom kartice "Ovlasti" na entitetu. Ova kartica se pojavljuje kad tvoja kampanja ima nekoliko uloga za članove.', '3' => 'Možeš ići sustavom "isključivanja", u kojem je ulogama dan pristup pregledu svih entiteta, i onda koristiti "Privatno" kućicu na entitetima da ih se sakrije. Ili možeš ne dati puno ovlasti ulogama pa postavljati vidljivost svakog entiteta zasebno.', ], 'hints' => [ 'campaign_not_public' => 'Javna uloga ima ovlasti, ali je kampanja privatna. Ovu postavku možeš promijeniti na kartici Dijeljenje prilikom uređivanja kampanje.', 'role_permissions' => 'Omogući ulozi ":name" da radi sljedeće akcije nad svim entitetima.', ], 'members' => 'Članovi', 'permissions' => [ 'actions' => [ 'add' => 'Kreiraj', 'dashboard' => 'Naslovna ploča', 'delete' => 'Obriši', 'edit' => 'Uredi', 'manage' => 'Upravljanje', 'members' => 'Članovi', 'permission'=> 'Ovlasti', 'read' => 'Pregled', 'toggle' => 'Promijeni za sve', ], ], 'placeholders' => [ 'name' => 'Naziv uloge', ], 'title' => 'Uloge kampanje :name', 'types' => [ 'owner' => 'Administrator', 'public' => 'Javnost', 'standard' => 'Standardno', ], 'users' => [ 'actions' => [ 'add' => 'Dodaj člana', 'remove' => ':user iz uloge :role', ], 'create' => [ 'success' => 'Korisnik dodan u ulogu.', 'title' => 'Dodaj člana u ulogu :name', ], 'destroy' => [ 'success' => 'Korisnik uklonjen iz uloge.', ], 'fields' => [ 'name' => 'Naziv', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Omogući', ], 'boosted' => 'Ova funkcionalnost je u testnoj verziji i trenutno je dostupna samo za :boosted.', 'helpers' => [ 'abilities' => 'Stvori sposobnosti, bilo da su to podvizi, čarolije ili moći koje se mogu dodijeliti entitetima.', 'calendars' => 'Mjesto za definiranje kalendara tvog svijeta.', 'characters' => 'Ljudi koji nastanjuju tvoj svijet.', 'conversations' => 'Izmišljeni razgovori između likova ili između korisnika kampanje. Ovaj modul je zastario.', 'dice_rolls' => 'Za one koji koriste Kanka za RPG kampanje, način za upravljanje bacanjem kockica. Ovaj modul je zastario.', 'events' => 'Praznici, festivali, katastrofe, rođendani, ratovi.', 'families' => 'Klanovi ili obitelji, njihovi odnosi i njihovi članovi.', 'inventories' => 'Upravljaj inventarom svojih entiteta.', 'items' => 'Oružje, vozila, relikvije, napitci.', 'journals' => 'Opažanja napisana od strane likova ili priprema za sesiju za voditelja igre.', 'locations' => 'Planeti, ravni postojanja, kontinenti, rijeke, države, naselja, hramovi, krčme.', 'maps' => 'Prenesi karte sa slojevima i markerima koji upućuju na druge entitete u kampanji.', 'notes' => 'Legende, religije, povijest, magija, rase.', 'organisations' => 'Kultovi, vojne jedinice, frakcije, cehovi.', 'quests' => 'Za praćenje raznih zadataka s likovima i lokacijama.', 'races' => 'Ako tvoja kampanja ima više od jedne rase, ovo će olakšati praćenje.', 'tags' => 'Svaki entitet može imati nekoliko oznaka. Oznake mogu pripadati drugim oznakama, a unosi se mogu filtrirati po oznakama.', 'timelines' => 'Prikaži povijest svog svijeta s kronologijama.', ], ], 'show' => [ 'actions' => [ 'edit' => 'Uredi kampanju', ], 'tabs' => [ 'achievements' => 'Postignuća', 'default-images' => 'Zadane slike', 'export' => 'Izvoz', 'members' => 'Članovi', 'plugins' => 'Dodaci', 'recovery' => 'Oporavak', 'roles' => 'Uloge', ], 'title' => 'Kampanja :name', ], 'superboosted' => [], 'ui' => [], 'visibilities' => [ 'private' => 'Privatna', 'public' => 'Javna', ], ]; ================================================ FILE: lang/hr/characters.php ================================================ [ 'add_appearance' => 'Dodaj fizički izgled', 'add_personality' => 'Dodaj osobnost', ], 'conversations' => [], 'create' => [ 'title' => 'Novi lik', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'fields' => [ 'age' => 'Starosna dob', 'is_dead' => 'Mrtav/a/o', 'is_personality_visible' => 'Osobnost vidljiva', 'life' => 'Život', 'physical' => 'Fizičke osobine', 'pronouns' => 'Zamjenice', 'sex' => 'Spol', 'title' => 'Titula', 'traits' => 'Osobine', ], 'helpers' => [ 'age' => 'Možeš povezati ovaj entitet s kalendarom kampanje kako bi umjesto toga automatski izračunali njihovu dob. :more.', ], 'hints' => [ 'is_dead' => 'Ovaj lik je mrtav', 'is_personality_visible' => 'Možeš sakriti cijelu sekciju osobnosti od korisnika koji nisu "Administratori".', 'personality_not_visible' => 'Osobine ličnosti ovog lika su trenutno vidljive samo administratorima.', 'personality_visible' => 'Osobine ličnosti ovog lika su vidljive svima.', ], 'index' => [], 'items' => [], 'journals' => [], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => 'Lik je dodan u organizaciju.', 'title' => 'Nova organizacija za :name', ], 'destroy' => [ 'success' => 'Organizacija lika uklonjena.', ], 'edit' => [ 'success' => 'Organizacija lika ažurirana.', 'title' => 'Ažuriraj organizaciju za :name', ], 'fields' => [ 'role' => 'Uloga', ], ], 'placeholders' => [ 'age' => 'Starosna dob', 'appearance_entry' => 'Opis', 'appearance_name' => 'Kosa, Oči, Koža, Visina', 'personality_entry' => 'Detalji', 'personality_name' => 'Ciljevi, Ponašanje, Strahovi, Veze', 'physical' => 'Fizičke osobine', 'pronouns' => 'On, Ona, Oni', 'sex' => 'Spol', 'title' => 'Titula', 'traits' => 'Osobine', 'type' => 'Lik igrača, Lik kojim upravlja voditelj igre, Božanstvo', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Zadaci kojima je lik zadavatelj.', 'quest_member' => 'Zadaci kojih je lik član.', ], ], 'sections' => [ 'appearance' => 'Fizički izgled', 'personality' => 'Osobnost', ], 'show' => [], 'warnings' => [ 'personality_hidden' => 'Nemaš dopuštenje mijenjati osobine ovog lika.', ], ]; ================================================ FILE: lang/hr/colours.php ================================================ 'Pastelno plava', 'black' => 'Crna', 'blue' => 'Plava', 'brown' => 'Smeđa', 'green' => 'Zelena', 'grey' => 'Siva', 'light-blue' => 'Svijetlo plava', 'maroon' => 'Kestenjasta', 'navy' => 'Mornaričko plava', 'none' => 'Niti jedna', 'orange' => 'Narančasta', 'pink' => 'Roza', 'purple' => 'Ljubičasta', 'red' => 'Crvena', 'teal' => 'Tirkizna', 'white' => 'Bijela', 'yellow' => 'Žuta', ]; ================================================ FILE: lang/hr/conversations.php ================================================ [ 'title' => 'Novi razgovor', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_closed' => 'Zatvoreno', 'messages' => 'Poruke', 'participants' => 'Sudionici', ], 'hints' => [ 'participants' => 'Dodaj sudionike u razgovor pritiskom na ikonu :icon u gornjem desnom kutu.', ], 'index' => [], 'messages' => [ 'destroy' => [ 'success' => 'Poruka uklonjena.', ], 'is_updated' => 'Ažurirano', 'load_previous' => 'Učitaj prethodne poruke', 'placeholders' => [ 'message' => 'Tvoja poruka', ], ], 'participants' => [ 'create' => [ 'success' => 'Sudionik :entity dodan u razgovor.', ], 'destroy' => [ 'success' => 'Sudionik :entity uklonjen iz razgovora.', ], 'modal' => 'Sudionici', 'title' => 'Sudionici u :name', ], 'placeholders' => [ 'name' => 'Naziv razgovora', 'type' => 'U igri, Priprema, Zaplet', ], 'show' => [ 'is_closed' => 'Razgovor je zatvoren.', ], 'tabs' => [ 'participants' => 'Sudionici', ], 'targets' => [ 'characters' => 'Likovi', 'members' => 'Članovi', ], ]; ================================================ FILE: lang/hr/crud.php ================================================ [ 'actions' => 'Akcije', 'apply' => 'Primijeni', 'back' => 'Natrag', 'copy' => 'Kopiraj', 'copy_mention' => 'Kopiraj [ ] spominjanje', 'copy_to_campaign' => 'Kopiraj u kampanju', 'explore_view' => 'Ugniježđeni pregled', 'export' => 'Izvoz', 'find_out_more' => 'Saznaj više', 'go_to' => 'Idi na :name', 'json-export' => 'Izvoz (json)', 'move' => 'Pomakni', 'new' => 'Novo', 'new_post' => 'Nova bilješka entiteta', 'next' => 'Sljedeće', 'reset' => 'Resetiraj', 'transform' => 'Transformiranje', ], 'add' => 'Dodaj', 'alerts' => [ 'copy_attribute' => 'Spomen atributa kopiran je u tvoj međuspremnik.', 'copy_mention' => 'Napredno spominjanje entiteta kopirano je u međuspremnik.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Skupno uređivanje i označavanje', ], 'age' => [ 'helper' => 'Možeš koristiti + i - prije broja za ažuriranje dobi za taj iznos.', ], 'edit' => [ 'tagging' => 'Akcija za oznake', 'tags' => [ 'add' => 'Dodaj', 'remove' => 'Ukloni', ], 'title' => 'Uređivanje više entiteta', ], 'errors' => [ 'admin' => 'Samo administratori kampanje mogu promijeniti privatni status entiteta.', 'general' => 'Došlo je do pogreške prilikom obrade tvoje akcije. Pokušaj ponovo i kontaktiraj nas ako se problem nastavi. Poruka o pogrešci: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Pregazi postojeće', ], 'helpers' => [ 'override' => 'Ako je uključeno, dopuštenja odabranih entiteta će biti pregažena s ovima. Ako nije uključeno, odabrana dopuštenja će biti dodana postojećim.', ], 'title' => 'Promijeni dopuštenja za nekoliko entiteta', ], ], 'bulk_templates' => [ 'bulk_title' => 'Primijeni predložak na više entiteta', ], 'cancel' => 'Otkaži', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Kopiraj entitete u drugu kampanju', 'panel' => 'Kopiraj', 'title' => 'Kopiraj ":name" u drugu kampanju', ], 'create' => 'Kreiraj', 'datagrid' => [ 'empty' => 'Nema ništa za prikazati.', ], 'delete_modal' => [ 'title' => 'Izbriši potvrdu', ], 'destroy_many' => [], 'edit' => 'Uredi', 'errors' => [ 'boosted_campaigns' => 'Ova funkcionalnost je dostupna samo za :boosted.', 'unavailable_feature' => 'Nedostupna funkcionalnost', ], 'events' => [], 'fields' => [ 'calendar_date' => 'Datum kalendara', 'closed' => 'Zatvoreno', 'colour' => 'Boja', 'copy_abilities' => 'Kopiraj Sposobnosti', 'copy_inventory' => 'Kopiraj Inventar', 'copy_links' => 'Kopiraj Poveznice entiteta', 'creator' => 'Tvorac', 'excerpt' => 'Isječak', 'has_entity_files' => 'Ima datoteke entiteta', 'has_image' => 'Ima sliku', 'header_image' => 'Slika zaglavlja', 'image' => 'Slika', 'is_closed' => 'Razgovor će biti zatvoren i više neće prihvaćati nove poruke.', 'is_private' => 'Privatno', 'is_star' => 'Prikvačeno', 'locations' => ':first u :second', 'name' => 'Naziv', 'position' => 'Položaj', 'tooltip' => 'Kratki opis', 'type' => 'Tip', 'visibility' => 'Vidljivost', ], 'files' => [ 'errors' => [ 'max' => 'Dosegnut maksimalni broj (:max) datoteka za ovaj entitet.', 'no_files' => 'Nema datoteka.', ], 'hints' => [ 'limit' => 'Svaki entitet može imati maksimalno :max datoteka prenesenih na njega.', 'limitations' => 'Podržani formati: :formats. Maksimalna veličina datoteke: :size', ], ], 'filter' => 'Filtar', 'filters' => [ 'all' => 'Filtriraj na sve potomke', 'clear' => 'Očistite filtre', 'copy_helper' => 'Kopirane filtre u međuspremniku koristi kao vrijednosti za filtre na programčićima naslovne ploče i brzim vezama.', 'copy_to_clipboard' => 'Kopiraj filtre u međuspremnik', 'direct' => 'Filtriraj na direktne potomke', 'filtered' => 'Prikazuje se :count od :total :entity.', 'mobile' => [ 'clear' => 'Očisti', 'copy' => 'Međuspremnik', ], 'options' => [ 'exclude' => 'Izuzmi', 'include' => 'Uključi', 'none' => 'Ništa', ], 'show' => 'Prikaži filtre', 'sorting' => [ 'asc' => ':field uzlazno', 'desc' => ':field silazno', 'helper' => 'Kontroliraj u kojem se prikazuju rezultati.', ], 'title' => 'Filteri', ], 'fix-this-issue' => 'Riješi ovaj problem', 'forms' => [ 'actions' => [ 'calendar' => 'Dodajte datum kalendara', ], 'copy_options' => 'Opcije kopiranja', ], 'helpers' => [ 'copy_options' => 'Kopiraj sljedeće povezane elemente iz izvora u novi entitet.', ], 'hidden' => 'Skriveno', 'hints' => [ 'calendar_date' => 'Datum kalendara omogućava jednostavno filtriranje u popisima, također održavajući događaj kalendara u odabranom kalendaru.', 'image_limitations' => 'Podržani formati: :formats. Maksimalna veličina datoteke: :size.', 'is_star' => 'Prikvačeni elementi pojavit će se na izborniku entiteta', 'tooltip' => 'Zamijeni automatski generirani kratki opis sljedećim sadržajem.', ], 'history' => [ 'unknown' => 'Nepoznato', 'view' => 'Pogledaj zapisnik entiteta', ], 'image' => [ 'error' => 'Nismo uspjeli dobiti sliku koju ste tražili. Može biti da nam web mjesto ne dopušta preuzimanje slike (uobičajeno za Squarespace i DeviantArt) ili da veza više nije valjana. Provjerite također da slika nije veća od :size.', ], 'is_private' => 'Ovaj je entitet privatan i vidljiv samo članovima administratorske uloge.', 'move' => [], 'navigation' => [ 'cancel' => 'otkaži', 'or_cancel' => 'ili :cancel', ], 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Dodaj', 'deny' => 'Zabrani', 'ignore' => 'Ignoriraj', 'remove' => 'Ukloni', ], 'bulk_entity' => [ 'allow' => 'Dopusti', 'deny' => 'Zabrani', 'inherit' => 'Naslijedi', ], 'delete' => 'Brisanje', 'edit' => 'Uređivanje', 'toggle' => 'Uključi ili isključi', ], 'fields' => [ 'member' => 'Član', 'role' => 'Uloga', ], 'helpers' => [ 'setup' => 'Koristi ovo sučelje za detaljno namještanje ovlasti uloga i korisnika za ovaj entitet. :allow će dopustiti korisniku ili ulozi da odradi tu akciju. :deny će zabraniti akciju. :inherit će koristiti ovlasti korisnikove ili glavne uloge. Korisnik kojemu je postavljano :allow, može odrađivati akciju čak i ako uloga čiji je član ima :deny.', ], 'success' => 'Ovlasti spremljene.', 'title' => 'Ovlasti', 'too_many_members' => 'Ova kampanja ima previše članova (> 10) za prikaz u ovom sučelju. Upotrijebite gumb Ovlasti na prikazu entiteta za detaljnu kontrolu ovlasti.', ], 'placeholders' => [ 'calendar' => 'Izaberi kalendar', 'gallery_image' => 'Odaberi sliku iz galerije kampanje', 'image_url' => 'Umjesto toga možete prenijeti sliku sa URL-a', 'journal' => 'Odaberi dnevnik', 'location' => 'Izaberi lokaciju', 'organisation' => 'Izaberi organizaciju', 'tag' => 'Izaberi oznaku', 'timeline' => 'Odaberite kronologiju', ], 'relations' => [], 'remove' => 'Ukloni', 'save' => 'Spremi', 'save_and_close' => 'Spremi i zatvori', 'save_and_copy' => 'Spremi i kopiraj', 'save_and_new' => 'Spremi i kreni na novo', 'save_and_update' => 'Spremi i ažuriraj', 'save_and_view' => 'Spremi i pogledaj', 'search' => 'Pretraži', 'select' => 'Odaberi', 'tabs' => [ 'abilities' => 'Sposobnosti', 'inventory' => 'Inventar', 'permissions' => 'Ovlasti', 'profile' => 'Profil', 'reminders' => 'Podsjetnici', ], 'update' => 'Ažuriraj', 'users' => [ 'unknown' => 'Nepoznato', ], 'view' => 'Vidljivost', 'visibilities' => [ 'admin' => 'Administratori', 'admin-self' => 'Ja i administratori', 'all' => 'Svi', 'members' => 'Članovi', 'self' => 'Samo ja', ], ]; ================================================ FILE: lang/hr/dashboard.php ================================================ [ 'follow' => 'Prati', 'join' => 'Pridruži se', 'unfollow' => 'Prekini pratiti', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Uredi', 'new' => 'Nova naslovna ploča', ], 'create' => [ 'success' => 'Kreirana nova naslovna ploča :name.', 'title' => 'Nova naslovna ploča', ], 'custom' => [ 'text' => 'Trenutno uređuješ naslovnu ploču :name.', ], 'default' => [ 'text' => 'Trenutno uređuješ zadanu naslovnu ploču.', 'title' => 'Zadana naslovna ploča', ], 'delete' => [ 'success' => 'Uklonjena naslovna ploča :name.', ], 'fields' => [ 'copy_widgets' => 'Kopiraj programčiće', 'name' => 'Naziv naslovne ploče', 'visibility' => 'Vidljivost', ], 'helpers' => [ 'copy_widgets' => 'Dupliciraj programčiće s nadzorne ploče :name u ovu novu.', ], 'placeholders' => [ 'name' => 'Naziv naslovne ploče', ], 'update' => [ 'success' => 'Ažurirana naslovna ploča :name', 'title' => 'Ažuriraj nadzornu ploču :name', ], 'visibility' => [ 'default' => 'Zadana', 'none' => 'Nema', 'visible' => 'Vidljiva', ], ], 'helpers' => [ 'follow' => 'Praćenje kampanje prikazat će ju u izborniku kampanje (gore desno) ispod tvojih kampanja.', 'join' => 'Ova kampanja otvorena je za nove članove. Prijavi se da bi joj se pridružio/la.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Dodajte programčić', 'back_to_dashboard' => 'Povratak na naslovnu ploču', 'edit' => 'Uredi programčić', ], 'title' => 'Postavljanje naslovne ploče kampanje', ], 'title' => 'Naslovna ploča', 'widgets' => [ 'calendar' => [ 'actions' => [ 'next' => 'Promijeni trenutni datum na sljedeći dan', 'previous' => 'Promijeni trenutni datum na prethodni dan', ], 'previous_events' => 'Prošli događaji', 'upcoming_events' => 'Nadolazeći događaji', ], 'campaign' => [ 'helper' => 'Ovaj programčić prikazuje zaglavlje kampanje i uvijek se prikazuje zadanoj nadzornoj ploči.', ], 'create' => [ 'success' => 'Programčić dodan na naslovnu ploču.', ], 'delete' => [ 'success' => 'Programčić uklonjen s naslovne ploče.', ], 'fields' => [ 'dashboard' => 'Naslovna ploča', 'name' => 'Proizvoljan naziv programčića', 'order' => 'Sortiranje', 'width' => 'Širina', ], 'orders' => [ 'name_asc' => 'Po imenu uzlazno', 'name_desc' => 'Po imenu silazno', 'recent' => 'Nedavno izmijenjeno', ], 'random' => [ 'helpers' => [ 'name' => 'Možeš referencirati nasumičan entitet s {ime}.', ], ], 'recent' => [ 'entity-header' => 'Koristi zaglavlje entiteta kao sliku', 'filters' => 'Filteri', 'help' => 'Prikaži samo posljednji ažurirani entitet, ali prikaži cijeli pregled entiteta', 'helpers' => [ 'entity-header' => 'Ako entitet ima zaglavlje entiteta (značajka pojačane kampanje), postavite ovaj programčić da koristi tu sliku umjesto slike entiteta.', 'show_attributes' => 'Prikaži prikvačene atribute entiteta ispod unosa.', 'show_members' => 'Ako je entitet obitelj ili organizacija, pokaži njezine članove ispod unosa.', 'show_relations' => 'Prikaži entitetove prikvačene veze ispod unosa.', ], 'show_attributes' => 'Prikaži prikvačene atribute', 'show_members' => 'Prikaži članove', 'show_relations' => 'Pokaži prikvačene odnose', 'singular' => 'Jedan', 'tags' => 'Filtrirajte popis nedavno izmijenjenih entiteta po navedenim oznakama.', 'title' => 'Nedavno izmijenjeno', ], 'unmentioned' => [ 'title' => 'Entiteti koji nisu nigdje spomenuti', ], 'update' => [ 'success' => 'Programčić ažuriran.', ], 'widths' => [ '0' => 'Automatski', '12'=> 'Puna (100%)', '3' => 'Sićušna (25%)', '4' => 'Mala (33%)', '6' => 'Pola (50%)', '8' => 'Široko (66%)', ], ], ]; ================================================ FILE: lang/hr/datetime.php ================================================ 'dan', 'days' => 'dani', 'elapsed_ago' => 'prije :duration', 'hour' => 'sat', 'hours' => 'sati', 'just_now' => 'upravo', 'minute' => 'minuta', 'minutes' => 'minute', 'month' => 'mjesec', 'months' => 'mjeseci', 'second' => 'sekunda', 'seconds' => 'sekunde', 'week' => 'tjedan', 'weeks' => 'tjedni', 'year' => 'godina', 'years' => 'godine', ]; ================================================ FILE: lang/hr/default.php ================================================ 'Naziv stranice', ]; ================================================ FILE: lang/hr/dice_roll_results.php ================================================ [ 'title' => 'Rezultati bacanja kockice', ], ]; ================================================ FILE: lang/hr/dice_rolls.php ================================================ [ 'title' => 'Novo bacanje kockica', ], 'destroy' => [ 'dice_roll' => 'Uklonjeno bacanje kockica.', ], 'edit' => [], 'fields' => [ 'created_at' => 'Kockice bačene u', 'parameters' => 'Parametri', 'results' => 'Rezultati', 'rolls' => 'Bacanja', ], 'hints' => [ 'parameters' => 'Koje su moje opcije kockica?', ], 'index' => [ 'actions' => [ 'results' => 'Rezultati', ], ], 'placeholders' => [ 'name' => 'Naziv bacanja kockica', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Bacanje', ], 'error' => 'Bacanje kockica neuspješno. Nije moguće analizirati parametre.', 'fields' => [ 'creator' => 'Kreator', 'date' => 'Datum', 'result' => 'Rezultat', ], 'hint' => 'Sva bacanja napravljena za ovaj predložak bacanja kockica.', 'success' => 'Kockice bačene.', ], 'show' => [ 'tabs' => [ 'results' => 'Rezultati', ], ], ]; ================================================ FILE: lang/hr/emails/welcome.php ================================================ 'Dobro došao/la u Kanku :name!', 'header_sub' => 'Čestitamo, poduzeo/la si prve korake u kreiranju svog svijeta u :kanka!', 'pricing' => 'određivanja cijena', 'section_1' => 'Kamo sada?', 'section_11' => 'Kreiraj svoj svijet,', 'section_2' => 'Najvažniji resurs je :discord, gdje ćeš pronaći puno naših korisnika, tim za pomaganje novim korisnicima, te osnivatelja Kanke, koji može odgovoriti na bilo koje pitanje o Kanki.', 'section_4' => 'Naš :youtube ima video zapise koji objašnjavaju osnove Kanke. Iako još uvijek nisu pokrivene sve teme, redovito dodajemo nove video zapise.', 'section_6' => 'Kontaktiraj nas', 'section_7' => 'Ako nisi pronašao/la odgovor na pitanje ili jednostavno želiš stupiti u kontakt, možeš nas pronaći na :facebook ili nam možeš poslati email na :email. Mi smo mali tim od 2 prijatelja, ali se trudimo odgovarati na svaki email koji primimo, tako da se nemoj ustručavati!', 'section_8' => 'Još jedna stvar', 'section_9_v2' => 'Pobrinuli smo se da su sve osnovne funkcionalnosti u Kanki besplatne, i tako će uvijek i biti. Međutim, ako želiš podržati ovaj projekt, možeš postati pretplatnik i dobiti pristup dodatnim funkcionalnostima, kao i našu vječnu zahvalnost! Saznaj više na stranici :pricing.', 'social_account' => 'Ako imaš problema s prijavom na svoj račun, mali podsjetnik da koristiš prijavu pomoću :provider. Ovo možeš promijeniti u postavkama svog računa.', 'title' => 'Započinjanje rada s Kankom', ]; ================================================ FILE: lang/hr/entities/abilities.php ================================================ [ 'add' => 'Dodaj sposobnost', 'reset' => 'Poništi korištenje sposobnosti', ], 'create' => [ 'success' => 'Sposobnost :ability dodana na :entity.', 'success_multiple' => 'Sposobnosti :abilities dodane na :entity.', 'title' => 'Dodaj sposobnost za :name', ], 'fields' => [ 'note' => 'Bilješka', 'position' => 'Pozicija', ], 'helpers' => [ 'note' => 'U ovom polju se možeš referencirati na entitete koristeći napredno spominjanje (npr. :code) i atribute entiteta (npr :attr).', ], 'import' => [ 'errors' => [ 'no_race' => 'Lik nema rasu.', 'not_character' => 'Entitet nije lik.', ], 'success' => '{1} :count sposobnost uvezena.|[2,4] :count sposobnosti uvezene.|[5,*] :count sposobnosti uvezeno.', ], 'show' => [ 'helper' => 'Dodaj sposobnosti za ovaj entitet. Uvijek možeš promijeniti vidljivost ili ukloniti sposobnost. Sposobnosti koje pripadaju istoj sposobnosti roditelju će se prikazati kao filter kutije.', 'title' => 'Sposobnosti entiteta u :name', ], 'update' => [ 'title' => 'Sposobnost entiteta za :name', ], ]; ================================================ FILE: lang/hr/entities/actions.php ================================================ [ 'set' => 'Postavi kao predložak', 'success' => [ 'set' => 'Entitet :name je postavljen kao predložak.', 'unset' => 'Entitet :name više nije postavljen kao predložak.', ], 'unset' => 'Ukloni kao predložak', ], ]; ================================================ FILE: lang/hr/entities/assets.php ================================================ [ 'file' => 'Datoteka', 'link' => 'Poveznica', ], 'show' => [ 'title' => 'Imovina :name', ], ]; ================================================ FILE: lang/hr/entities/attributes.php ================================================ [ 'manage' => 'Upravljanje', 'more' => 'Više opcija', 'remove_all' => 'Izbriši sve', ], 'errors' => [ 'loop' => 'U ovom izračunu atributa postoji beskonačna petlja!', ], 'fields' => [ 'community_templates' => 'Predlošci zajednice', 'is_private' => 'Privatni atributi', 'is_star' => 'Prikvačeno', 'value' => 'Vrijednost', ], 'helpers' => [ 'delete_all' => 'Jesi li siguran/a da želiš izbrisati sve atribute ovog entiteta?', ], 'hints' => [], 'index' => [ 'success' => 'Ažurirani atributi za :entity.', 'title' => 'Atributi za :name', ], 'placeholders' => [ 'attribute' => 'Broj osvajanja, Razina izazova, Inicijativa, Stanovništvo', 'block' => 'Naziv bloka', 'checkbox' => 'Naziv potvrdnog okvira', 'icon' => [ 'class' => 'Klase FontAwesome ili RPG Awesome: fas fa-users', 'name' => 'Naziv ikone', ], 'random' => [ 'name' => 'Naziv atributa', 'value' => '1-100 ili popis vrijednosti odvojenih zarezom', ], 'section' => 'Naziv odjeljka', 'value' => 'Vrijednost atributa', ], 'show' => [ 'title' => ':name atributi', ], 'template' => [ 'success' => 'Predložak atributa :name primijenjen na :entity', 'title' => 'Primijeni predložak atributa za :name', ], 'types' => [ 'attribute' => 'Atribut', 'block' => 'Blok', 'checkbox' => 'Potvrdni okvir', 'icon' => 'Ikona', 'random' => 'Nasumično', 'section' => 'Odjeljak', 'text' => 'Tekst u više redova', ], 'update' => [ 'success' => 'Ažurirani atributi za :entity.', ], 'visibility' => [ 'entry' => 'Atribut je prikazan u izborniku entiteta.', 'private' => 'Atribut vidljiv samo članovima uloge "Administrator".', 'public' => 'Atribut vidljiv svim članovima.', 'tab' => 'Atribut se prikazuje samo na kartici Atributi.', ], ]; ================================================ FILE: lang/hr/entities/events.php ================================================ [ 'type' => 'Tip događaja', ], 'helpers' => [ 'characters' => 'Postavljanje tipa kao datuma rođenja ili smrti za ovog lika će automatski izračunati njihovu dob. :more.', ], 'show' => [ 'actions' => [ 'add' => 'Dodaj podsjetnik', ], 'title' => ':name podsjetnici', ], 'types' => [ 'birth' => 'Rođenje', 'death' => 'Smrt', 'primary' => 'Primarno', ], ]; ================================================ FILE: lang/hr/entities/files.php ================================================ [ 'title' => 'Nova datoteka za :entity', ], 'destroy' => [ 'success' => 'Datoteka :file uklonjena.', ], 'fields' => [ 'file' => 'Datoteka', 'name' => 'Naziv datoteke', ], 'update' => [ 'success' => 'Datoteka :file ažurirana.', ], ]; ================================================ FILE: lang/hr/entities/image.php ================================================ [ 'change_focus' => 'Promijeni točku fokusa', 'replace_image' => 'Zamijeni sliku', 'save-replace' => 'Zamijeni sliku', 'save_focus' => 'Spremi fokus', 'view' => 'Pogledaj sliku', ], 'focus' => [ 'breadcrumb' => 'Fokus slike', 'helper' => 'Klikni sliku da bi postavio/la točku fokusa. Klikni točku fokusa da bi ju uklonio/la.', 'panel_title' => 'Fokus slike', 'success' => 'Fokus slike ažuriran.', 'title' => 'Fokus slike entiteta :name', 'unboosted' => 'Postavljanje točke fokusa slike je rezervirano za :boosted-campaigns.', ], 'replace' => [ 'breadcrumb' => 'Zamjena slike', 'panel_title' => 'Zamjena slike entiteta', 'success' => 'Slika zamijenjena.', 'title' => 'Zamjena slike entiteta :name', ], ]; ================================================ FILE: lang/hr/entities/inventories.php ================================================ [], 'create' => [ 'success' => 'Predmet :item dodan :entity.', 'title' => 'Dodaj predmet :name', ], 'destroy' => [ 'success' => 'Predmet :item uklonjen :entity.', ], 'fields' => [ 'amount' => 'Količina', 'description' => 'Opis', 'is_equipped' => 'Opremljen', 'name' => 'Naziv', 'position' => 'Pozicija', ], 'placeholders' => [ 'amount' => 'Bilo koja količina', 'description' => 'Rabljen, oštećen, podešen', 'name' => 'Obavezno ako nije odabran nijedan predmet', 'position' => 'Opremljeno, ruksak, spremište, banka', ], 'show' => [ 'helper' => 'Entiteti mogu imati predmete priložene sebi kako bi kreirali inventar.', 'title' => 'Inventar entiteta :name', 'unsorted' => 'Nesortirano', ], 'update' => [ 'success' => 'Predmet :item ažuriran za :entity', 'title' => 'Ažuriraj predmet na :name', ], ]; ================================================ FILE: lang/hr/entities/links.php ================================================ [ 'add' => 'Dodaj poveznicu', ], 'create' => [ 'success' => 'Dodana poveznica :name: na :entity.', 'title' => 'Dodaj poveznicu na :name', ], 'destroy' => [ 'success' => 'Uklonjena poveznica :name: s :entity.', ], 'fields' => [ 'icon' => 'Ikona', 'name' => 'Naziv', 'position' => 'Pozicija', 'url' => 'URL', ], 'helpers' => [ 'icon' => 'Možeš prilagoditi ikonu prikazanu za poveznicu. Upotrijebi bilo koju besplatnu ikonu od :fontawesome ili ostavi prazno polje za korištenje zadanih postavki.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Pojačane kampanje mogu dodati veze na entitete koji vode na vanjske web stranice.', 'title' => 'Poveznice za :name', ], 'unboosted' => [], 'update' => [ 'success' => 'Ažurirana poveznica :name za :entity.', 'title' => 'Ažuriraj poveznicu za :name', ], ]; ================================================ FILE: lang/hr/entities/logs.php ================================================ [ 'create' => 'Kreiraj', 'delete' => 'Obriši', 'restore' => 'Oporavi', 'update' => 'Ažuriraj', ], 'fields' => [ 'action' => 'Akcija', 'date' => 'Datum', ], 'impersonated' => 'Imitiranje od :name', 'show' => [ 'title' => 'Zapisi entiteta :name', ], ]; ================================================ FILE: lang/hr/entities/map-points.php ================================================ 'Ovaj entitet je označen na sljedećim kartama.', 'title' => ':name točke na kartama', ]; ================================================ FILE: lang/hr/entities/mentions.php ================================================ [], 'helper' => 'Slijedi lista entiteta koji su spomenuti u ovom entitetu u njihovom polju "Unos".', 'mentioned_in_v2' => 'Ovaj se entitet spominje u :count entiteta, bilješki ili kampanja. :more.', 'see_more' => 'Vidi detalje', 'show' => [ 'title' => 'Spominjanje entiteta :name', ], 'title' => 'Spomenuti entitet', ]; ================================================ FILE: lang/hr/entities/move.php ================================================ [ 'copy' => 'Kopiraj', ], 'errors' => [ 'permission' => 'Nije ti dopušteno stvarati entitete te vrste u ciljanoj kampanji.', 'permission_update' => 'Ne možeš premjestiti ovaj entitet.', 'same_campaign' => 'Moraš odabrati drugu kampanju u koju ćeš premjestiti entitet.', 'unknown_campaign' => 'Nepoznata kampanja.', ], 'fields' => [ 'campaign' => 'Ciljana kampanja', 'copy' => 'Napravi kopiju', 'select_one' => 'Odaberi kampanju', ], 'panel' => [ 'description' => 'Odaberi kampanju u koju želiš premjestiti ili napraviti kopiju ovog entiteta.', 'description_bulk_copy' => 'Odaberi kampanju u koju želiš kopirati odabrane entitete.', 'title' => 'Premjesti ili kopiraj entitet u drugu kampanju', ], 'success' => 'Entitet :name premješten.', 'success_copy' => 'Entitet :name kopiran.', 'title' => 'Premjesti :name', ]; ================================================ FILE: lang/hr/entities/notes.php ================================================ [ 'add' => 'Nova bilješka entiteta', 'add_user' => 'Dodaj korisnika', ], 'create' => [ 'success' => 'Dodana bilješka entiteta ":name" na :entity.', ], 'destroy' => [ 'success' => 'Uklonjena bilješka entiteta ":name" s :entity.', ], 'edit' => [ 'success' => 'Ažurirana bilješka entiteta ":name" za :entity.', ], 'fields' => [ 'creator' => 'Tvorac', 'name' => 'Naziv', ], 'hint' => 'Informacije koje se ne uklapaju u standardna polja entiteta ili koje bi trebale biti privatne mogu se dodati kao bilješke entiteta.', 'hints' => [ 'reorder' => 'Možeš promijeniti redoslijed bilješki entiteta klikom na ikonu :icon pored priče u izborniku entiteta.', ], 'index' => [], 'placeholders' => [ 'name' => 'Naziv bilješke entiteta, opažanja ili primjedbe.', ], 'show' => [ 'advanced' => 'Napredne postavke', 'title' => 'Bilješka entiteta :name za :entity', ], ]; ================================================ FILE: lang/hr/entities/permissions.php ================================================ [ 'title' => 'Dopuštenja', ], ]; ================================================ FILE: lang/hr/entities/pins.php ================================================ 'Poveznice', 'title' => 'Prikvačeno', ]; ================================================ FILE: lang/hr/entities/profile.php ================================================ [ 'edit_profile' => 'Uredi profil', ], 'show' => [ 'tab_name' => 'Profil', 'title' => ':name profil', ], ]; ================================================ FILE: lang/hr/entities/quests.php ================================================ 'Ovaj je entitet dio sljedećih zadataka.', 'title' => ':name zadaci', ]; ================================================ FILE: lang/hr/entities/relations.php ================================================ [ 'mode-map' => 'Alat za istraživanje odnosa', 'mode-table' => 'Tablica odnosa i veza', ], 'connections' => [ 'map_point' => 'Točka karte', 'mention' => 'Spomenuti', 'quest_element' => 'Element zadatka', 'timeline_element' => 'Element kronologije', ], 'create' => [], 'destroy' => [ 'success' => 'Uklonjen odnos :target s :entity.', ], 'fields' => [ 'attitude' => 'Stav', 'target' => 'Meta', 'two_way' => 'Kreiraj zrcalni odnos', ], 'helper' => 'Uspostavite odnose između entiteta sa stavovima i vidljivošću. Odnosi se mogu prikvačiti i na izbornik entiteta.', 'hints' => [ 'attitude' => 'Ovo opcionalno polje se može koristiti za postavljanje zadanog poretka odnosa na silazno po tom polju.', 'two_way' => 'Ako odaberete kreiranje zrcalnog odnosa, isti odnos će se kreirati na meti. Međutim, ako ga uredite, zrcaljenje se neće ažurirati.', ], 'options' => [ 'mentions' => 'Odnosi + srodno + spomeni', 'related' => 'Odnosi + srodno', 'relations' => 'Odnosi', 'show' => 'Prikaži', ], 'panels' => [ 'related' => 'Srodno', ], 'placeholders' => [ 'attitude' => '-100 do 100, gdje je 100 vrlo pozitivno.', ], 'show' => [ 'title' => 'Odnosi za :name', ], 'types' => [ 'family_member' => 'Član obitelji', 'organisation_member' => 'Član organizacije', ], 'update' => [ 'success' => 'Ažuriran odnos :target za :entity.', 'title' => 'Ažuriraj odnose za :name', ], ]; ================================================ FILE: lang/hr/entities/story.php ================================================ [ 'collapse_all' => 'Sažmi sve', 'expand_all' => 'Proširi sve', 'load_more' => 'Učitaj više', ], 'reorder' => [ 'icon_tooltip' => 'Promijeni redoslijed bilješki', 'panel_title' => 'Promijeni redoslijed bilješki', 'success' => 'Bilješke presložene.', ], 'update' => [ 'title' => 'Ažuriraj unos :entry', ], ]; ================================================ FILE: lang/hr/entities/timelines.php ================================================ 'Kronologije koje imaju elemente povezane s ovim entitetom prikazane su u nastavku.', 'show' => [ 'title' => 'Kronologije za :name', ], ]; ================================================ FILE: lang/hr/entities/transform.php ================================================ [], 'fields' => [ 'select_one' => 'Odaberi jedan', 'target' => 'Nova vrsta entiteta', ], 'panel' => [ 'title' => 'Transformiraj entitet', ], 'success' => 'Entitet :name transformiran.', 'title' => 'Transformiraj :name', ]; ================================================ FILE: lang/hr/entities.php ================================================ 'Sposobnosti', 'ability' => 'Sposobnost', 'attribute_template' => 'Predložak atributa', 'attribute_templates' => 'Predlošci atributa', 'calendar' => 'Kalendar', 'calendars' => 'Kalendari', 'campaign' => 'Kampanja', 'campaigns' => 'Kampanje', 'character' => 'Lik', 'characters' => 'Likovi', 'conversation' => 'Razgovor', 'conversations' => 'Razgovori', 'creator' => [ 'back' => 'Natrag na odabir', 'duplicate' => 'Postoje drugi entiteti ovog tipa s istim nazivom.', 'title' => 'Novi entitet', ], 'dice_roll' => 'Bacanje kockica', 'dice_rolls' => 'Bacanja kockica', 'event' => 'Događaj', 'events' => 'Događaji', 'families' => 'Obitelji', 'family' => 'Obitelj', 'inventories' => 'Inventari', 'item' => 'Predmet', 'items' => 'Predmeti', 'journal' => 'Dnevnik', 'journals' => 'Dnevnici', 'location' => 'Lokacija', 'locations' => 'Lokacije', 'map' => 'Karta', 'maps' => 'Karte', 'new' => [], 'note' => 'Bilješka', 'notes' => 'Bilješke', 'organisation' => 'Organizacija', 'organisations' => 'Organizacije', 'quest' => 'Zadatak', 'quests' => 'Zadaci', 'race' => 'Rasa', 'races' => 'Rase', 'tag' => 'Oznaka', 'tags' => 'Oznake', 'timeline' => 'Kronologija', 'timelines' => 'Kronologije', ]; ================================================ FILE: lang/hr/errors.php ================================================ [ 'body' => 'Izgleda da nemaš dozvolu za pristup ovoj stranici!', 'title' => 'Dozvola odbijena', ], '403-form' => [ 'help' => 'Ovo bi moglo biti zbog isteka sesije. Prije spremanja, pokušaj se ponovo prijaviti u drugom prozoru.', ], '404' => [ 'body' => 'Nažalost, stranicu koju tražiš nije moguće pronaći.', 'title' => 'Stranica nije pronađena', ], '500' => [ 'body' => [ '1' => 'Ups, izgleda da je nešto pošlo po zlu.', '2' => 'Izvješće s nađenom pogreškom nam je poslano, ali ponekad pomaže ako možemo znati malo više o tome što si radio/la.', ], 'title' => 'Pogreška', ], '503' => [ 'body' => [ '1' => 'Kanka se trenutno održava, što obično znači da je u tijeku ažuriranje!', '2' => 'Oprosti na neugodnosti. Sve će se vratiti u normalu u samo nekoliko minuta.', ], 'title' => 'Održavanje', ], '503-form' => [], 'footer' => 'Ako ti je potrebna dodatna pomoć, kontaktiraj nas na hello@kanka.io ili na :discord', ]; ================================================ FILE: lang/hr/events.php ================================================ [ 'title' => 'Novi događaj', ], 'destroy' => [], 'edit' => [], 'events' => [], 'fields' => [ 'date' => 'Datum', ], 'helpers' => [ 'date' => 'Ovo polje može sadržavati bilo što i nije povezano s kalendarima kampanje. Da bi ovaj događaj bio povezan s kalendarom, dodaj ga u kalendar ili na karticu podsjetnika ovog događaja.', ], 'index' => [], 'placeholders' => [ 'date' => 'Datum za događaj', 'type' => 'Ceremonija, Festival, Nesreća, Bitka, Rođenje', ], 'show' => [], 'tabs' => [ 'calendars' => 'Unosi kalendara', ], ]; ================================================ FILE: lang/hr/families.php ================================================ [ 'title' => 'Nova obitelj', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'members' => 'Ovdje su navedeni članovi obitelji. Lik se može dodati u obitelj uređivanjem željenog lika, upotrebom padajućeg izbornika "Obitelj".', ], 'index' => [], 'members' => [], 'placeholders' => [ 'name' => 'Ime obitelji', 'type' => 'Kraljevska, plemenita, izumrla', ], 'show' => [], ]; ================================================ FILE: lang/hr/faq.php ================================================ [ 'account_settings' => 'Postavke računa', 'answer' => 'Ako želiš izbrisati svoj račun, idi na stranicu: :account i pomakni se dolje do odjeljka za brisanje računa. Ovim ćeš izbrisati svoj račun i sve kampanje u kojima si jedini član.', 'question' => 'Kako mogu izbrisati svoj račun?', ], 'app_backup' => [ 'answer' => 'Izvodimo dvije sigurnosne kopije dnevno kako bismo spriječili gubitak podataka. Naše se vlastite kampanje nalaze na poslužitelju, tako da ne želimo riskirati!', 'question' => 'Koliko često se obavlja sigurnosno kopiranje podataka u Kanki?', ], 'attribute-templates' => [ 'answer' => <<<'TEXT' Najbolji način na koji možemo objasniti Predloške atributa je primjerom. Zamislimo da tvoj svijet ima puno lokacija, a na mnogim od tih lokacija želiš se sjetiti stvoriti prilagođeni atribut za "Stanovništvo", "Klima" i "Razina kriminala". Možeš to lako postići na svakoj lokaciji, ali može postati zamorno i ponekad možeš zaboraviti stvoriti atribut "Razina zločina". Ovdje Predlošci atributa ulaze u igru. Možeš stvoriti Predložak atributa s tim atributima (stanovništvo, klima, razina kriminala), a kasnije taj predložak primijeniti na svoje lokacije. Ovo će stvoriti atribute iz predloška na lokacijama, tako da sve što moraš učiniti je promijeniti vrijednosti, a ne moraš se prisjećati atributa! TEXT , 'question' => 'Predlošci atributa, što su oni?', ], 'backup' => [ 'answer' => 'Jednom dnevno možeš sve podatke svoje kampanje izvesti kao ZIP datoteku. U aplikaciji klikni na "Kampanja" na lijevom izborniku i klikni na "Izvoz". Tako će se stvoriti datoteka koja je dostupna 30 minuta. Ne možeš prenijeti ovaj izvoz na Kanku, on je namijenjen samo vlastitom miru ili ako više ne planiraš koristiti aplikaciju.', 'question' => 'Kako mogu sigurnosno kopirati ili izvoziti kampanju?', ], 'bugs' => [ 'answer' => 'Jednostavno se pridruži našem :discord poslužitelju i prijavi svoju pogrešku u kanalu #error-and-bugs.', 'question' => 'Kako mogu prijaviti pogrešku?', ], 'campaign-sync' => [ 'answer' => 'Kanka nema tu značajku. Međutim, ako pokušavaš imati više grupa za igru u istom svijetu, razmisli o upotrebi iste kampanje i razdvajanju grupa kombinacijom zadataka, oznaka i dopuštenja.', 'question' => 'Mogu li sinkronizirati entitete u više kampanja?', ], 'conversations' => [], 'custom' => [ 'answer' => 'Kanka dolazi s nizom unaprijed definiranih tipova entiteta koji međusobno djeluju. Dopuštanje proizvoljnih tipova entiteta zahtijeva rekonstrukciju aplikacije ispočetka i kosi se sa svrhom alata s unaprijed definiranim tipovima kako bi se ljudima pomoglo u izgradnji svijeta, a ne da se smišlja kako organizirati stvari. Nadalje, Kanka je fleksibilna s oznakama koje mogu predstavljati većinu proizvoljnih tipova entiteta.', 'question' => 'Mogu li stvoriti proizvoljne vrste entiteta?', ], 'delete-campaign' => [ 'answer' => 'Idi na naslovnu ploču kampanje i klikni na "Kampanja" na lijevom izborniku. Gumb "Izbriši" pojavit će se ako si posljednji član kampanje. Brisanje kampanje trajna je akcija kojom ćeš izbrisati sve podatke pohranjene na našim poslužiteljima, uključujući slike.', 'question' => 'Kako mogu izbrisati kampanju?', ], 'discord' => [ 'answer' => 'Za povezivanje svog Kanka računa s :discord, prvo moraš kliknuti svoj avatar u gornjem desnom kutu aplikacije i kliknuti gumb Profil. Od tamo dođi do podstranice :apps i klikni Poveži.', 'question' => 'Kako povezati moj Kanka račun s Discordom?', ], 'early-access' => [ 'answer' => 'Rani pristup način je da nagradimo naše nevjerojatne pretplatnike dajući im ekskluzivno razdoblje od 30 dana gdje mogu isprobati najnovije module prije bilo koga drugog.', 'question' => 'Što je Rani pristup?', ], 'entity-notes' => [ 'answer' => 'Svi entiteti imaju karticu "Bilješke entiteta" koja sadrži male isječke teksta koje možeš postaviti da su vidljivi samo tebi (odlično prilikom zajedničkog vođenja kampanje), samo članovima administratorske uloge ili vidljive svima. Također, možeš dati igračima dopuštenje za kreiranje i uređivanje bilješki o entitetima bez ovlaštenja za uređivanjem čitavog entiteta.', 'question' => 'Kako Kanka postupa s djelomično skrivenim informacijama?', ], 'fields' => [ 'answer' => 'Odgovor', 'category' => 'Kategorija', 'locale' => 'Jezik', 'order' => 'Poredak', 'question' => 'Pitanje', ], 'free' => [ 'answer' => <<<'TEXT' Da! Čvrsto vjerujemo da tvoja financijska situacija ne bi trebala utjecati na tvoje uživanje u RPG-ovima ili izgradnji svijeta te ćemo osnovnu aplikaciju uvijek držati besplatnom. Ako želiš preuzeti aktivniju ulogu na ovom putovanju, podrži nas i glasaj o funkcionalnostima koje su ti najvažnije, što možeš učiniti putem naše web stranice ili na :patreon. Osim glasanja o pravcu kojim će Kanka napredovati, podržavanje nas omogućava tebi pristup :boosters, povećanju ograničenja za prijenos veličine datoteke, dodavanju tvog imena u kuću slavnih, ljepše zadate ikone i još mnogo toga! TEXT , 'question' => 'Hoće li aplikacija ostati besplatna?', ], 'gods-and-religions' => [ 'answer' => 'Preporučujemo stvaranje bogova kao likova i religija kao organizacija. Ako želite brzo pronaći svoja božanstva, preporučujemo da ih označite odgovarajućom oznakom ili vrstom.', 'question' => 'Gdje stvoriti bogove i religije?', ], 'help' => [ 'answer' => 'Kao prvo, hvala ti što želiš pomoći! Uvijek smo zainteresirani za ljude koji mogu pomoći u prijevodima, testiranju novih značajki ili koji mogu pomoći novim korisnicima. Također volimo kad ljudi promoviraju Kanku da dosegne nove korisnike na mjestima o kojima nismo razmišljali. Tvoj je najbolji način djelovanja da nam se pridružiš na :discord koji sadrži kanal posvećen pomoći. Također volimo i naše pokrovitelje na :patreon ako nas želiš podržati i dobiti pristup nekim povlasticama!', 'question' => 'Želim pomoći! Što mogu učiniti?', ], 'map' => [ 'answer' => 'Svaka lokacija može sadržavati kartu (png, jpg ili svg) koja na sebi ima "točke karte" koje se mogu staviti uz kontrolu veličine, oblika, ikone i boje te kao veze do entiteta ili jednostavnih oznaka.', 'question' => 'Mogu li učitati karte na Kanku?', ], 'mobile' => [ 'answer' => 'Trenutačno nema posvećene mobilne aplikacije za Kanku, ali većina aplikacije radi na mobilnom uređaju. Jedno ograničenje je alat spominjanja koji ne radi u uređivaču teksta. Nadamo se da nam podrška na :patreon omogućiti da nekome platimo izradu mobilne aplikaciju, ali ne predviđamo da će se to dogoditi u skoroj budućnosti.', 'question' => 'Postoji li mobilna aplikacija? Planira li se?', ], 'monsters' => [ 'answer' => 'Preporučujemo korištenje modula Rase za ljude, vrste, čudovišta i sve živo što nije lik.', 'question' => 'Gdje stvoriti čudovišta?', ], 'multiworld' => [ 'answer' => 'Možeš biti dio onoliko kampanja koliko želiš, uključujući i one koje si kreirao/la. Za promjenu ili stvaranje nove kampanje, idi na naslovnu ploču kampanje i u gornjem desnom kutu klikni na trenutnu kampanju za prikaz sučelja prebacivanja kampanje.', 'question' => 'Mogu li imati više kampanja?', ], 'nested' => [ 'answer' => 'Ako više voliš svoje entitete gledati u ugnježđenom prikazu prema zadanim postavkama (na primjeru gumb Uneseni prikaz na popisu lokacija), to možeš učiniti tako da otvoriš opcije profila i izgleda. Tamo možeš provjeriti mogućnost ugniježđenog pogleda. To se odnosi samo na tvoj račun, a ne i za tvoje kampanje.', 'question' => 'Mogu li postaviti ugniježđene popise kao zadane?', ], 'organise_play' => [], 'permissions' => [ 'answer' => 'Apsolutno, zato smo izgradili Kanku! Možete pozvati sve svoje igrače u svoje kampanje i dodijeliti im uloge i dopuštenja. Izgradili smo sustav da bude izuzetno fleksibilan (možete koristiti i konfiguraciju za prijavu i isključivanje) kako bismo pokrili što više potreba i situacija.', 'question' => 'Mogu li ograničiti podatke koje moji igrači vide u kampanji?', ], 'plans' => [ 'answer' => <<<'TEXT' Dugoročni planovi za Kanku su izgraditi svestrani alat za upravljanje izgradnje svijeta i upravljanjem kampanjama, koji je agnostičan na sustav, ali uz sadržaj specifičan za sustav koji kreira zajednica u obliku "predložaka zajednice". Duži cilj je izgradnja alata koji se integrira s drugim platformama poput Virtual Table Top aplikacija koje će ih povezati sa svjetovima Kanka. Što se tiče drugog dijela, većina hobi projekata završava izgaranjem, a autor ih napušta. :patreon je postavljen s ciljem da budemo u mogućnosti raditi puno radno vrijeme na Kanki bez žrtvovanja financijske sigurnosti naših obitelji, kao i pokrivanja troškova poslužitelja. Projekt je također otvorenog koda i zajednica ga može nastaviti ako se nama ikada nešto dogodi. TEXT , 'question' => 'Koji su dugoročni planovi?', ], 'public-campaigns' => [ 'answer' => 'Možeš pregledavati stranicu :public-campaigns da vidiš kako drugi koriste Kanku za svoje kampanje.', 'question' => 'Kako drugi koriste Kanka?', ], 'renaming-modules' => [ 'answer' => 'Iako bi to bilo lako učiniti za engleski i druge jezike koji ne koriste rodovna imena, mogućnost promjene naziva modula narušila bi gramatičku ispravnost i korisničko iskustvo za većinu jezika na kojima je Kanka dostupna.', 'question' => 'Mogu li preimenovati module? Na primjer Obitelji u Klanove ili Organizacije u Frakcije?', ], 'sections' => [ 'community' => 'Zajednica', 'general' => 'Općenito', 'other' => 'Drugo', 'permissions' => 'Ovlasti', 'pricing' => 'Cijena', 'worldbuilding' => 'Izgradnja svijeta', ], 'show' => [ 'return' => 'Povratak na često postavljanja pitanja', 'timestamp' => 'Posljednji put ažurirano :date', 'title' => 'Često postavljana pitanja :name', ], 'unboost' => [ 'answer' => 'Poništavanjem pojačavanja kampanje ne brišu se podaci koji su stvoreni tijekom pojačavanja, već se jednostavno sakrivaju podaci i značajke. Ako ponovno pojačate kampanju, podaci i značajke bit će ponovno dostupni s istim postavkama kao i prije opoziva kampanje.', 'question' => 'Što se događa ako se prestane pojačavati kampanju?', ], 'user-switch' => [ 'answer' => 'Dopuštenja mogu postati škakljiva, osobito kod velikih kampanja. Kao administrator kampanje, možeš doći do stranice članova kampanje i kliknuti gumb "Imitiraj" koji će se pojaviti pored članova koji nisu administratori. Tako ćeš se prijaviti kao korisnik, što će ti omogućiti pregled kampanje kakvu taj korisnik vidi. To je najlakši način za provjeru dopuštenja tvoje kampanje.', 'question' => 'Ovlasti u mojoj kampanji su postavljene, kako ih mogu testirati?', ], 'visibility' => [ 'answer' => 'Samo ljudi koje pozoveš u kampanju mogu vidjeti i koristiti što je stvoreno. Tvoji su podaci privatni i uvijek u tvojoj kontroli. Također, možeš postaviti kampanju kao javnu kako bi tvoju kampanju mogli vidjeti i neregistrirani korisnici.', 'question' => 'Može li itko vidjeti moj svijet?', ], ]; ================================================ FILE: lang/hr/footer.php ================================================ 'Kanka je prevedena na druge jezike zahvaljujući našoj nevjerojatnoj zajednici. Ako želiš pomoći prevesti Kanku na svoj jezik, kontaktiraj nas na :discord!', ]; ================================================ FILE: lang/hr/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Glasanje zajednice', ]; ================================================ FILE: lang/hr/front/hall-of-fame.php ================================================ 'Kuća slavnih', ]; ================================================ FILE: lang/hr/front/newsletter.php ================================================ [ 'learn_more' => 'Saznaj više', 'subscribe' => 'Pretplati se', ], 'fields' => [ 'firstname' => 'Ime', 'lastname' => 'Prezime', 'notifications' => 'Obavijesti', ], 'groups' => [ 'newsletter' => 'Bilten', ], 'headline' => 'Pretplati se na jedan ili sve naše biltene kako bi bio/la u toku s Kankom.', 'title' => 'Obavijesti email porukama', ]; ================================================ FILE: lang/hr/front.php ================================================ [], 'campaigns' => [], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => 'Shvaćam!', 'link' => 'Saznaj više', 'message' => 'Ova web stranica koristi kolačiće kako bi osigurala najbolje iskustvo na našoj web stranici.', ], 'faq' => [], 'features' => [ 'api' => [ 'link' => 'API dokumentaciju', ], 'patreon' => [ 'api_calls' => 'Povećani API pozivi (90)', 'boosts' => 'Pojačivačima kampanje', 'default_image' => 'Lijepe zadane slike za entitete', 'discord' => 'Privatni Discord kanal', 'free' => 'Besplatno', 'hall_of_fame' => 'Ime u :link', 'impact' => 'Utjecaj na budućne funkcionalnosti', 'monthly_vote' => 'Sudjelovanje u glasanju zajednice', 'pagination' => 'Povećan broj rezultata na stranici', 'upload_limit' => 'Veličine za prijenos', 'upload_limit_map' => 'Veličine za prijenos karte', ], ], 'first_block' => [], 'footer' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Kanka je alat za upravljanje RPG kampanjama i izgradnju svijetova kojeg usmjerava zajednica, a koji olakšava organizaciju, planiranje i uživanje RPG kampanja koje se igraju za stolom', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Naslovna ploča', 'login' => 'Prijava', 'register' => 'Registracija', ], 'meta' => [ 'description' => 'Kanka je fleksibilni graditelj digitalnih svijetova i alat za upravljanje internetskim RPG kampanjama', 'title' => 'Kanka - alat za upravljanje internetskim RPG kampanjama i građenje svjetova', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Besplatno', 'month' => 'Mjesec', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Izgradnja svjetova, RPG koji se igraju na stolu, upravitelj RPG kampanjama', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/hr/header.php ================================================ [ 'read_all' => 'Pročitaj sve', ], 'toggle_navigation' => 'Uključi/isključi navigaciju', ]; ================================================ FILE: lang/hr/helpers.php ================================================ [], 'attributes' => [], 'dice' => [], 'entity_templates' => [], 'filters' => [ 'title' => 'Kako koristiti filtre', ], 'link' => [ 'description' => 'Možeš se jednostavno povezati s drugim entitetima u kampanji pomoću sljedećih kratica.', ], 'map' => [], 'pins' => [], 'public' => 'Pogledajte Youtube vodič na koji objašnjava javne kampanje.', 'widget-filters' => [ 'description' => 'Možeš filtrirati entitete prikazane na programčiću s nedavno izmijenjenim entitetima pružajući popis polja entiteta i vrijednosti. Na primjer, možeš koristiti :example za filtriranje mrtvih likova s kojima igrači ne mogu igrati (NPC).', 'link' => 'Filteri programčića', 'title' => 'Filteri programčića naslovne ploče', ], ]; ================================================ FILE: lang/hr/items.php ================================================ [ 'title' => 'Novi predmet', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'price' => 'Cijena', 'size' => 'Veličina', ], 'index' => [], 'inventories' => [], 'placeholders' => [ 'price' => 'Cijena predmeta', 'size' => 'Veličina, težina, dimenzije', 'type' => 'Oružje, napitak, artefakt', ], 'show' => [ 'tabs' => [ 'inventories' => 'Informacije', ], ], ]; ================================================ FILE: lang/hr/journals.php ================================================ [ 'title' => 'Novi dnevnik', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Autor', 'date' => 'Datum', ], 'helpers' => [], 'index' => [], 'journals' => [], 'placeholders' => [ 'author' => 'Tko je napisao dnevnik', 'date' => 'Stvarni datum dnevnika', 'type' => 'Sesija, Jednokratna kampanja, Nacrt', ], 'show' => [], ]; ================================================ FILE: lang/hr/languages.php ================================================ [ 'ca' => 'Katalonski', 'cs' => 'Češki', 'de' => 'Njemački', 'el' => 'Grčki', 'en' => 'Engleski', 'en-US' => 'Američki engleski', 'es' => 'Španjolski', 'fr' => 'Francuski', 'gl' => 'Galicijski', 'he' => 'Hebrejski', 'hr' => 'Hrvatski', 'hu' => 'Mađarski', 'it' => 'Talijanski', 'nb' => 'Norveški (Bokmal)', 'nl' => 'Nizozemski', 'pl' => 'Poljski', 'pt-BR' => 'Brazilski portugalski', 'ru' => 'Ruski', 'sk' => 'Slovački', 'tr' => 'Turski', ], 'header'=> 'Jezici', ]; ================================================ FILE: lang/hr/locations.php ================================================ [], 'create' => [ 'title' => 'Nova lokacija', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [], 'helpers' => [ 'characters' => 'Pregledaj sve likove na ovoj lokaciji i njenim podlokacijama ili samo one neposredno na toj lokaciji.', ], 'hints' => [], 'index' => [], 'items' => [], 'journals' => [], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'type' => 'Grad, Kraljevstvo, Ruševina', ], 'show' => [], ]; ================================================ FILE: lang/hr/maps/groups.php ================================================ [ 'add' => 'Dodaj novu grupu', ], 'create' => [ 'success' => 'Kreirana grupa :name.', 'title' => 'Nova grupa', ], 'delete' => [ 'success' => 'Obrisana grupa :name.', ], 'edit' => [ 'success' => 'Ažurirana grupa :name.', 'title' => 'Uredi grupu :name', ], 'fields' => [ 'is_shown' => 'Prikaži oznake grupe', 'position' => 'Pozicija', ], 'helper' => [], 'hints' => [ 'is_shown' => 'Prema prikaz oznaka grupe na karti kao zadanu postavku.', ], 'placeholders' => [ 'name' => 'Trgovine, Blago, NPC-i', 'position' => 'Neobavezno polje za podešavanje redoslijeda pojavljivanja grupa.', ], ]; ================================================ FILE: lang/hr/maps/layers.php ================================================ [ 'add' => 'Dodaj novi sloj', ], 'base' => 'Temeljni sloj', 'create' => [ 'success' => 'Kreiran sloj :name.', 'title' => 'Novi sloj', ], 'delete' => [ 'success' => 'Obrisan sloj :name.', ], 'edit' => [ 'success' => 'Ažuriran sloj :name.', 'title' => 'Uredi sloj :name', ], 'fields' => [ 'position' => 'Pozicija', 'type' => 'Vrsta sloja', ], 'helper' => [], 'placeholders' => [ 'name' => 'Podzemlje, Razina 2, Olupina broda', 'position' => 'Neobavezno polje za postavljanje redoslijeda u kojem se slojevi pojavljuju.', ], 'short_types' => [ 'overlay' => 'Prekrivanje', 'overlay_shown' => 'Prekrivanje (automatsko prikazivanje)', 'standard' => 'Standardno', ], 'types' => [ 'overlay' => 'Prekrivanje (prikazano iznad aktivnog sloja)', 'overlay_shown' => 'Prekrivanje prikazano kao zadano', 'standard' => 'Standardni sloj (prebacivanje između slojeva)', ], ]; ================================================ FILE: lang/hr/maps/markers.php ================================================ [ 'entry' => 'Napiši proizvoljno polje za unos za ovaj marker.', 'remove' => 'Ukloni marker', 'save_and_explore' => 'Spremi i istraži', 'update' => 'Uredi marker', ], 'create' => [ 'success' => 'Kreiran marker :name.', 'title' => 'Novi marker', ], 'delete' => [ 'success' => 'Obrisan marker :name.', ], 'edit' => [ 'success' => 'Ažuriran marker :name.', 'title' => 'Uredi marker :name', ], 'fields' => [ 'circle_radius' => 'Polumjer kruga', 'copy_elements' => 'Kopiraj elemente', 'custom_icon' => 'Proizvoljna ikona', 'custom_shape' => 'Proizvoljni oblik', 'font_colour' => 'Boja ikone', 'group' => 'Grupa markera', 'is_draggable' => 'Moguće povlačenje', 'latitude' => 'Geografska širina', 'longitude' => 'Geografska dužina', 'opacity' => 'Neprozirnost', 'pin_size' => 'Veličina markera', 'polygon_style' => [ 'stroke' => 'Boja poteza', 'stroke-opacity' => 'Neprozirnost poteza', 'stroke-width' => 'Širina poteza', ], ], 'helpers' => [ 'base' => 'Dodaj markere na kartu klikom na bilo koje mjesto.', 'copy_elements' => 'Kopiraj grupe, slojeve i markere.', 'copy_elements_to_campaign' => 'Kopiraj grupe, slojeve i markere na kartama. Markeri povezane s entitetom pretvorit će se u standardne markere.', 'custom_radius' => 'Za samostalno definiranje veličine odaberi opciju proizvoljne veličine iz padajućeg izbornika.', 'draggable' => 'Omogući za omogućavanje pomicanja markera u načinu istraživanja.', 'label' => 'Oznaka se prikazuje kao blok teksta na karti. Sadržaj će biti ime markera imena entiteta.', 'polygon' => [ 'edit' => 'Kliknite na kartu da biste taj položaj dodali koordinatama poligona.', ], ], 'icons' => [ 'custom' => 'Proizvoljno', 'entity' => 'Entitet', 'exclamation' => 'Upozorenje', 'marker' => 'Marker', 'question' => 'Pitanje', ], 'placeholders' => [ 'custom_shape' => '100,100 200,240 340,110', 'name' => 'Obavezno ako nije odabran nijedan entitet', ], 'shapes' => [ '0' => 'Krug', '1' => 'Kvadrat', '2' => 'Trokut', '3' => 'Proizvoljno', ], 'sizes' => [ '0' => 'Sićušno', '1' => 'Standardno', '2' => 'Maleno', '3' => 'Veliko', '4' => 'Golemo', ], 'tabs' => [ 'circle' => 'Krug', 'label' => 'Natpis', 'marker' => 'Marker', 'polygon' => 'Poligon', ], ]; ================================================ FILE: lang/hr/maps.php ================================================ [ 'back' => 'Povratak na :name', 'edit' => 'Uredi kartu', 'explore' => 'Istraži', ], 'create' => [ 'title' => 'Nova karta', ], 'destroy' => [], 'edit' => [], 'errors' => [ 'dashboard' => [ 'missing' => 'Ova karta treba sliku kako bi se mogla prikazati na naslovnoj ploči.', ], 'explore' => [ 'missing' => 'Dodaj sliku na kartu prije nego što ju možeš istraživati.', ], ], 'fields' => [ 'center_marker' => 'Marker', 'center_x' => 'Zadana pozicija zemljopisne dužine', 'center_y' => 'Zadana pozicija zemljopisne širine', 'centering' => 'Centriranje', 'grid' => 'Mreža', 'initial_zoom' => 'Početno povećanje', 'max_zoom' => 'Maksimalno povećanje', 'min_zoom' => 'Minimalno povećanje', 'tabs' => [ 'coordinates' => 'Koordinate', 'marker' => 'Marker', ], ], 'helpers' => [ 'center' => 'Promjena sljedećih vrijednosti kontrolira na koje područje je karta fokusirana. Ostavljanje ovih vrijednosti praznima rezultirat će se fokusom na središte karte.', 'centering' => 'Centriranje na markeru imat će prioritet nad zadanim koordinatama.', 'distance_measure' => 'Davanjem karte mjere udaljenosti omogućit će se alat za mjerenje u načinu istraživanja.', 'grid' => 'Definiraj veličinu mreže koja će biti prikazana u načinu istraživanja.', 'initial_zoom' => 'Početna razina povećanja na koju se učitava karta. Zadana vrijednost je :default, dok je najveća dopuštena vrijednost :max, a najniža dopuštena vrijednost je :min.', 'max_zoom' => 'Najviše što se karta može povećati. Zadana vrijednost je :default, dok je najviša dozvoljena vrijednost :max.', 'min_zoom' => 'Najviše što se karta može udaljiti. Zadana vrijednost je :default, dok je najniža dozvoljena vrijednost :min.', 'missing_image' => 'Spremi kartu sa slikom prije nego što možeš dodavati slojeve i markere.', ], 'index' => [], 'maps' => [], 'panels' => [ 'groups' => 'Grupe', 'layers' => 'Slojevi', 'markers' => 'Markeri', 'settings' => 'Postavke', ], 'placeholders' => [ 'center_marker' => 'Ostavi prazno za učitavanje karte u sredini', 'center_x' => 'Ostavi prazno za učitavanje karte u sredini', 'center_y' => 'Ostavi prazno za učitavanje karte u sredini', 'grid' => 'Udaljenost u pikselima između elemenata mreže. Ostavi prazno da se sakrije mreža.', 'name' => 'Naziv karte', 'type' => 'Tamnica, Grad, Galaksija', ], 'show' => [ 'tabs' => [ 'maps' => 'Karte', ], ], ]; ================================================ FILE: lang/hr/misc.php ================================================ [ 'boosting' => 'pojačavajući', ], ]; ================================================ FILE: lang/hr/notes.php ================================================ [ 'title' => 'Nova bilješka', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'Bilješka dijete', ], 'helpers' => [], 'hints' => [], 'index' => [], 'placeholders' => [ 'note' => 'Odaberite bilješku roditelja', 'type' => 'Religija, Rasa, Politički sustav', ], 'show' => [], ]; ================================================ FILE: lang/hr/notifications.php ================================================ [ 'application' => [ 'approved' => 'Odobrena je tvoja prijava na kampanju :campaign.', 'new' => 'Nova prijava za :campaign.', 'rejected' => 'Vaša prijava za kampanju :campaign je odbijena. Razlog naveden: :reason', ], 'asset_export' => 'Dostupan je izvoz sredstava kampanje. Poveznica je dostupna kroz :time minuta.', 'boost' => [ 'add' => 'Kampanju :campaign je pojačao korisnik :user.', 'remove' => 'Korisnik :user više ne pojačava kampanju :campaign.', 'superboost' => 'Kampanju :campaign podupire :user.', ], 'export' => 'Dostupan je izvoz kampanje. Veza je dostupna :time minuta.', 'export_error' => 'Došlo je do pogreške prilikom izvoza kampanje. Molimo kontaktiraj nas ako se ovaj problem nastavi.', 'join' => ':user se priključio/la :campaign.', 'leave' => ':user je napustio/la :campaign.', 'plugin' => [ 'deleted' => 'Dodatak :plugin je izbrisan s tržišta i uklonjen iz kampanje :campaign.', ], 'role' => [ 'add' => 'Dodana ti je uloga :role u kampanji :campaign.', 'remove' => 'Uklonjena ti je uloga :role iz kampanje :campaign.', ], ], 'header' => 'Imate :count obavijesti', 'index' => [ 'title' => 'Obavijesti', ], 'no_notifications' => 'Trenutno nema obavijesti.', 'subscriptions' => [ 'charge_fail' => 'Došlo je do pogreške tijekom obrade tvoje uplate. Pričekaj trenutak dok pokušavamo ponovo. Ako se ništa ne promijeni, kontaktiraj nas.', 'deleted' => 'Tvoja pretplata na Kanku je otkazana nakon previše neuspjelih pokušaja naplate tvoje kartice. Idi u postavke pretplate i pokušaj ažurirati svoje podatke o plaćanju.', 'ended' => 'Tvoja pretplata na Kanku je završila. Pojačanja tvoje kampanje i Discord uloge su uklonjene. Nadamo će te nam se uskoro vratiti!', 'failed' => 'Tvoja pretplata na Kanku je otkazana nakon previše neuspjelih pokušaja naplate tvoje kartice. Idi u postavke pretplate i pokušaj ažurirati svoje podatke o plaćanju.', 'started' => 'Tvoja pretplata na Kanku je započela.', ], ]; ================================================ FILE: lang/hr/organisations.php ================================================ [ 'title' => 'Nova organizacija', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'members' => 'Članovi', ], 'helpers' => [], 'index' => [], 'members' => [ 'destroy' => [ 'success' => 'Član uklonjen iz organizacije.', ], 'edit' => [ 'title' => 'Ažuriraj člana za :name', ], 'fields' => [ 'role' => 'Uloga', ], 'helpers' => [ 'all_members' => 'Svi likovi koji su članovi ove organizacije i njenih podorganizacija.', 'members' => 'Sljedeća lista prikazuje sve likove koji su u ovoj organizaciji i svim njenim podorganizacijama. Možeš filtrirati stranicu da prikaže samo direktne članove.', ], 'placeholders' => [ 'role' => 'Voditelj, Član, Visoki Svećenik, Majstor Špijun', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'Kult, Banda, Pobuna, Klub obožavatelja', ], 'show' => [], ]; ================================================ FILE: lang/hr/pagination.php ================================================ '« Prethodna', 'next' => 'Sljedeća »', ]; ================================================ FILE: lang/hr/partials.php ================================================ [ 'description' => 'Bilo je problema s tvojim unosom.', 'title' => 'Ups!', ], ]; ================================================ FILE: lang/hr/passwords.php ================================================ 'Lozinka je postavljena!', 'sent' => 'Poveznica za ponovono postavljanje lozinke je poslana!', 'throttled' => 'Please wait before retrying.', 'token' => 'Oznaka za ponovno postavljanje lozinke više nije važeća.', 'user' => 'Korisnik nije pronađen.', ]; ================================================ FILE: lang/hr/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Sovomedvjed', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/hr/profiles.php ================================================ [ 'success' => 'Avatar ažuriran.', ], 'edit' => [ 'success' => 'Profil ažuriran', ], 'editors' => [], 'fields' => [ 'avatar' => 'Avatar', 'email' => 'Email', 'hide_subscription' => 'Sakrij moje ime s :hall_of_fame.', 'last_login_share' => 'Dijeli s drugim članovima kampanje vrijeme moje zadnje prijave.', 'name' => 'Ime', 'new_password' => 'Nova lozinka', 'new_password_confirmation' => 'Potvrda nove lozinke', 'newsletter' => 'Želim biti ponekad kontaktiran emailom.', 'password' => 'Trenutna lozinka', 'settings' => 'Postavke', 'theme' => 'Teme', ], 'newsletter' => [ 'title' => 'Bilteni', ], 'password' => [ 'success' => 'Lozinka ažurirana', ], 'placeholders' => [ 'email' => 'Tvoja email adresa', 'name' => 'Kako se prikazuje tvoje ime', 'new_password' => 'Tvoja nova lozinka', 'new_password_confirmation' => 'Potvrdi svoju novu lozinku', 'password' => 'Unesi trenutnu lozinku za sve promjene', ], 'sections' => [ 'delete' => [ 'delete' => 'Obriši moj račun', 'helper' => 'Brisanjem računa izbrisat će se i sve kampanje čiji si jedini član. Ova je radnja trajna i ne može se poništiti.', 'title' => 'Obriši svoj račun', 'warning' => 'Brisanjem računa će se svi tvoji podaci izgubiti. Jesi li siguran/a?', ], 'password' => [ 'title' => 'Promjeni lozinku', ], ], 'settings' => [ 'success' => 'Postavke su promijenjene.', ], 'theme' => [ 'success' => 'Tema je promijenjena.', 'themes' => [ 'dark' => 'Tamna', 'default' => 'Standardna', 'future' => 'Budućnost', 'midnight' => 'Ponoćno plava', ], ], 'title' => 'Ažuriraj svoj profil', 'workflows' => [ 'created' => 'Idi na kreirani entitet', 'default' => 'Popis entiteta', ], ]; ================================================ FILE: lang/hr/quests.php ================================================ [ 'title' => 'Novi zadatak', ], 'destroy' => [], 'edit' => [], 'elements' => [ 'create' => [ 'success' => 'Entitet :entity dodan na zadatak.', 'title' => 'Novi element za :name', ], 'destroy' => [ 'success' => 'Element zadatka :entity uklonjen.', ], 'edit' => [ 'success' => 'Element zadatka :entity ažuriran.', 'title' => 'Ažuriran element zadatka za :name', ], ], 'fields' => [ 'copy_elements' => 'Kopirajte elemente pridružene zadatku', 'date' => 'Datum', 'is_completed' => 'Izvršen', 'role' => 'Uloga', ], 'helpers' => [], 'hints' => [], 'index' => [], 'placeholders' => [ 'date' => 'Stvarni datum zadatka', 'role' => 'Uloga ovog entieta u zadatku', 'type' => 'Priča o liku, Sporedni zadatak, Glavni zadatak', ], 'show' => [ 'actions' => [ 'add_element' => 'Dodaj element', ], 'tabs' => [ 'elements' => 'Elementi', ], ], ]; ================================================ FILE: lang/hr/races.php ================================================ [], 'create' => [ 'title' => 'Nova rasa', ], 'destroy' => [], 'edit' => [], 'fields' => [], 'helpers' => [], 'index' => [], 'placeholders' => [ 'type' => 'Čovjek, Vila, Borg', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/hr/redirects.php ================================================ 'Tvoja sesija je isteka. Pokušaj ponovno.', 'unknown_entity' => 'Oprosti, ne znamo što je ":entity".', ]; ================================================ FILE: lang/hr/releases.php ================================================ [ 'event' => 'Događaj', 'livestream' => 'Prijenos uživo', 'other' => 'Ostalo', 'release' => 'Izdanje', 'vote' => 'Glas zajednice', ], 'index' => [ 'description' => 'Posljednja ažuriranja za kanka.io', 'title' => 'Izdanja', ], 'post' => [ 'footer' => 'Od :name :date', ], 'show' => [ 'return' => 'Povratak na izdanja', 'title' => 'Izdanje :name', ], ]; ================================================ FILE: lang/hr/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chronicles of Darkness', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/hr/search.php ================================================ 'Nema rezultata.', 'title' => 'Pretraga', ]; ================================================ FILE: lang/hr/settings.php ================================================ [ 'actions' => [ 'social' => 'Prebaci se na prijavu u Kanku', 'update_email' => 'Ažuriraj email', 'update_password' => 'Ažuriraj lozinku', ], 'email' => 'Promjena emaila', 'email_success' => 'Email ažuriran.', 'password' => 'Promjena lozinke', 'password_success' => 'Lozinka promijenjena.', 'social' => [ 'error' => 'Već koristiš prijavu u Kanku za ovaj račun.', 'helper' => 'Tvojim računom trenutno upravlja :provider. Možeš ga prestati koristiti i prebaciti se na standardnu ​​prijavu u Kanku postavljanjem lozinke.', 'success' => 'Tvoj račun sad koristi Kanka prijavu.', 'title' => 'Društveno prema Kanki', ], 'title' => 'Račun', ], 'api' => [ 'helper' => 'Dobrodošli u Kanka API-je! Generiraj osobni pristupni žeton koji ćeš koristiti u svom API zahtjevu za prikupljanje podataka o kampanjama čijih si član.', 'link' => 'Pročitaj dokumentaciju API-ja', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Poveži', 'remove' => 'Ukloni', ], 'benefits' => 'Kanka pruža nekoliko integracija na usluge trećih strana. U budućnosti se planira više integracija trećih strana.', 'discord' => [ 'errors' => [ 'add' => 'Došlo je do pogreške u povezivanju tvog Discord računa s Kankom. Molim te pokušaj ponovno.', ], 'success' => [ 'add' => 'Tvoj Discord račun je povezan.', 'remove' => 'Veza s tvojim Discord računom je uklonjena.', ], 'text' => 'Pristupi svojim ulogama za pretplatu automatski.', ], 'title' => 'Integracija s aplikacijom', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'Kampanja :name je već pojačana.', 'exhausted_boosts' => 'Nemaš više pojačanja za pokloniti. Ukloni svoje pojačanje iz neke kampanje prije nego što ga daš drugoj.', 'exhausted_superboosts' => 'Nemaš više pojačanja. Trebaš 3 pojačanja da bi super pojačao kampanju.', ], ], 'countries' => [ 'austria' => 'Austrija', 'belgium' => 'Belgija', 'france' => 'Francuska', 'germany' => 'Njemačka', 'italy' => 'Italija', 'netherlands' => 'Nizozemska', 'spain' => 'Španjolska', ], 'invoices' => [], 'layout' => [ 'title' => 'Izgled', ], 'marketplace' => [], 'menu' => [ 'account' => 'Račun', 'api' => 'API', 'apps' => 'Aplikacije', 'other' => 'Ostalo', 'patreon' => 'Patreon', 'payment_options' => 'Mogućnosti plaćanja', 'personal_settings' => 'Osobne postavke', 'profile' => 'Profil', 'settings' => 'Postavke', 'subscription' => 'Pretplata', 'subscription_status' => 'Status pretplate', ], 'patreon' => [ 'deprecated' => 'Zastarjela značajka - ako želite podržati Kanku, učinite to putem :subscription. Patreon povezivanje je i dalje aktivno za one koji su povezali svoj račun prije našeg odlaska iz Patreona.', 'pledge' => 'Zalog: :name', 'remove' => [ 'button' => 'Prekini vezu s Patreon računom', 'success' => 'Uklonjena je poveznica na tvoj Patreon račun.', 'text' => 'Ako prekineš vezu tvog računa s Patreonom, Kanka će ukloniti tvoje bonuse, ime u kući slavnih, pojačanja kampanje, te druge značajke povezane s podrškom Kanke. Nijedan tvoj pojačani sadržaj neće biti izgubljen (npr. zaglavlja entiteta). Ako se ponovo pretplatiš, imat ćeš pristup svim svojim prethodnim podacima, uključujući mogućnost pojačanja prijašnjih pojačanih kampanja.', 'title' => 'Prekini vezu Patreon računa s Kankom', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Ažuriraj profil', ], 'avatar' => 'Profilna slika', 'success' => 'Profil ažuriran.', 'title' => 'Osobni profil', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Otkaži pretplatu', 'subscribe' => 'Pretplata', 'update_currency' => 'Spremite preferiranu valutu', ], 'billing' => [ 'helper' => 'Podaci o naplati obrađuju se i pohranjuju na sigurno putem :stripe. Ovaj način plaćanja koristi se za sve tvoje pretplate.', 'saved' => 'Spremljen način plaćanja', ], 'cancel' => [ 'text' => 'Žao nam je što odlaziš! Ako otkažeš pretplatu, bit će aktivna do sljedećeg ciklusa naplate, nakon čega ćeš izgubiti pojačanja kampanje i druge pogodnosti povezane s podrškom Kanke. Slobodno ispuni sljedeći obrazac i obavijesti nas što možemo učiniti boljim ili što je dovelo do tvoje odluke.', ], 'cancelled' => 'Tvoja pretplata je otkazana. Pretplatu možete obnoviti nakon završetka tvoje trenutne pretplate.', 'change' => [ 'text' => [ 'monthly' => 'Pretplaćuješ se na sloj :tier koji se naplaćuje mjesečno :amount.', 'yearly' => 'Pretplaćuješ se na sloj :tier koji se naplaćuje godišnje :amount.', ], 'title' => 'Promijenite razinu pretplate', ], 'currencies' => [ 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Promijenite željenu valutu naplate', ], 'errors' => [ 'callback' => 'Naš pružatelj plaćanja prijavio je pogrešku. Molimo pokušaj ponovo ili nam se obrati ako se problem nastavi.', 'subscribed' => 'Tvoju pretplatu nije moguće obraditi. Stripe je pružio sljedeći savjet.', ], 'fields' => [ 'active_since' => 'Aktivno od', 'active_until' => 'Aktivno do', 'billing' => 'Naplata', 'currency' => 'Valuta naplate', 'payment_method' => 'Način plaćanja', 'plan' => 'Trenutni plan', 'reason' => 'Razlog', ], 'helpers' => [ 'alternatives' => 'Plati svoju pretplatu pomoću :method. Na kraju pretplate ovaj se način plaćanja neće automatski obnoviti. :metoda je dostupna samo u eurima.', 'alternatives_warning' => 'Nadogradnja pretplate prilikom korištenja ove metode nije moguća. Stvori novu pretplatu kada se završi trenutna.', 'alternatives_yearly' => 'Zbog ograničenja koja se odnose na ponavljajuća plaćanja, metoda :method je dostupna samo za godišnje pretplate', ], 'manage_subscription' => 'Upravljanje pretplatom', 'payment_method' => [ 'actions' => [ 'add_new' => 'Dodajte novi način plaćanja', 'change' => 'Promjena načina plaćanja', 'save' => 'Spremi način plaćanja', 'show_alternatives' => 'Alternativni načini plaćanja', ], 'add_one' => 'Trenutno nema spremljenog načina plaćanja.', 'alternatives' => 'Možeš se pretplatiti pomoću ovih alternativnih načina plaćanja. Ova radnja će teretiti tvoj račun jednom i neće automatski obnavljati pretplatu svaki mjesec.', 'card' => 'Kartica', 'card_name' => 'Ime na kartici', 'country' => 'Zemlja prebivališta', 'ending' => 'Završava za', 'helper' => 'Ova će se kartica koristiti za sve tvoje pretplate.', 'new_card' => 'Dodaj novi način plaćanja', 'saved' => ':brand završava s :last4', ], 'placeholders' => [ 'reason' => 'Po želji nam možeš reći zašto više ne podržavaš Kanku. Nedostajala je funkcionalnost? Je li se promijenila tvoja financijska situacija?', ], 'plans' => [ 'cost_monthly' => ':currency :amount naplaćeno mjesečno', 'cost_yearly' => ':currency :amount naplaćeno godišnje', ], 'sub_status' => 'Informacije o pretplati', 'subscription' => [ 'actions' => [ 'downgrading' => 'Molimo kontaktiraj nas radi smanjenja za nižu razinu', 'rollback' => 'Promjena u Kobold', 'subscribe' => 'Promjena u :tier mjesečno', 'subscribe_annual' => 'Promjeni na :tier godišnje', ], ], 'success' => [ 'alternative' => 'Tvoja uplata je registrirana. Primit ćeš obavijest čim se obradi i tvoja pretplata postane aktivna.', 'callback' => 'Tvoja pretplata je uspješna. Tvoj račun će biti ažuriran čim nas naš pružatelj plaćanja informira o promjeni (ovo može potrajati nekoliko minuta).', 'currency' => 'Tvoja željena postavka valute je ažurirana.', 'subscribed' => 'Tvoja pretplata je bila uspješna. Ne zaboravi se pretplatiti na bilten glasanja zajednice kako bi te obavijestili kada započne novo glasanje. Postavke biltena možeš promijeniti na stranici profila.', ], 'tiers' => 'Razina pretplate', 'trial_period' => 'Godišnje pretplate imaju pravo otkaza 14 dana. Kontaktiraj nas na :email ako želiš otkazati godišnju pretplatu i dobiti povrat novca.', 'upgrade_downgrade' => [ 'button' => 'Informacije o promjeni razine', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Tvoji bonusi ostaju omogućeni do kraja razdoblja plaćanja.', 'boosts' => 'Isto se događa i s tvojim pojačanim kampanjama. Pojačane funkcionalnosti postaju nevidljive, ali se ne brišu kad se kampanja više ne pojačava.', 'kobold' => 'Za otkazivanje svoje pretplate, prijeđi na razinu Kobold.', ], 'title' => 'Prilikom otkazivanja pretplate', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Tvoja trenutna razina ostat će aktivna do kraja tvog trenutnog ciklusa naplate, nakon čega ćeš biti nadograđen na svoju novu razinu.', ], 'title' => 'Pri prelasku na niži nivo', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Tvoj način plaćanja bit će naplaćen odmah i imat ćeš pristup svom novom sloju.', 'prorate' => 'Kada nadogradiš s Owlbear na Elemental, samo će ti se naplatiti ​​razlika do tvoje nove razine.', ], 'title' => 'Pri nadogradnji na viši sloj', ], ], 'warnings' => [ 'incomplete' => 'Nismo mogli naplatiti tvoju kreditnu karticu. Ažuriraj podatke o svojoj kreditnoj kartici, a mi ćemo je pokušati ponovo naplatiti u narednih nekoliko dana. Ako opet ne uspije, pretplata će se otkazati.', 'patreon' => 'Tvoj račun je trenutno povezan s Patreonom. Prekini vezu s računom u tvojim postavkama :patreon prije prelaska na Kanka pretplatu.', ], ], ]; ================================================ FILE: lang/hr/sidebar.php ================================================ [ 'created_campaigns' => 'Tvoje kampanje', 'new_campaign' => 'Nova kampanja', 'public_campaigns' => 'Javne kampanje', 'updated' => 'Ažurirano', ], 'dashboard' => 'Naslovna ploča', 'entity-creator' => 'Kreiraj na brzinu', 'gallery' => 'Galerija', 'other' => 'Ostalo', 'world' => 'Svijet', ]; ================================================ FILE: lang/hr/starter.php ================================================ [ 'history' => 'Ovaj lik služi kao primjer. James, sin Mancea Owlchestera i Rige Dunton, odrastao je u selu Genory prije nego što se preselio u glavni grad Unria kako bi radio kao jedan od kraljevih zapisničara.', 'sex' => 'Muško', 'title' => 'Sivi lovac', ], 'character2' => [ 'history' => 'Ovaj lik služi kao primjer. Irwie je od malih nogu oduvijek bila fascinirana eksplozivima, a karijeru je posvetila zanatu.', 'sex' => 'Žensko', 'title' => 'Kraljica eksplozija', ], 'item1' => [], 'kingdom1' => [ 'description' => 'Ovo je primjer lokacije stvorene kako bi pokazala što se može učiniti s aplikacijom.', 'history' => '(primjer) Kraljevstvo Genory osnovala su Genorska plemena krajem 5. stoljeća nakon što su napravili invaziju u zemlje Hottensa.', 'type' => 'Kraljevstvo', ], 'kingdom2' => [ 'description' => '(primjer) Ulyss je glavni grad kraljevstva Genory i treći najveći grad Agagir Alliance.', 'history' => '(primjer) Ulyss je glavni grad kraljevstva Genory. Osnovao ga je Frasan Irwen i nalazi se na rijeci Unri.', 'type' => 'Glavni grad', ], 'note1' => [], ]; ================================================ FILE: lang/hr/subscriptions.php ================================================ [ 'failed' => 'Stripe nije mogao naplatiti tvojom metodom plaćanja. Tvoja pretplata je zbog toga deaktivirana.', ], ]; ================================================ FILE: lang/hr/tags.php ================================================ [ 'actions' => [ 'add' => 'Dodaj novu oznaku', ], ], 'create' => [ 'title' => 'Nova oznaka', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Djeca', ], 'helpers' => [], 'hints' => [ 'children' => 'Popis sadrži sve oznake koje su unutar trenutne oznake, a ne samo one koje su direktno ispod nje.', 'tag' => 'Ispod su prikazane sve oznake koje su izravno pod ovom oznakom.', ], 'index' => [], 'placeholders' => [ 'type' => 'Legende, Ratovi, Povijest, Religija, Veksologija', ], 'show' => [ 'tabs' => [ 'children' => 'Djeca', ], ], 'tags' => [], ]; ================================================ FILE: lang/hr/teams.php ================================================ [ 'translations' => 'Prijevodi', ], 'patreon' => [], 'people' => [ 'jay' => [ 'title' => 'Osnivač i vodeći programer', ], 'jon' => [ 'title' => 'Suosnivač i Voditelj poslovanja', ], ], ]; ================================================ FILE: lang/hr/tiers.php ================================================ 'Tvoja trenutna pretplata', 'features' => [ 'api_requests' => ':amount API zahtjeva / minuti', 'boosters' => 'Pojačivači kampanje', 'discord' => 'Discord uloge', 'feature_influence' => 'Utjecaj na nove funkcionalosti', 'file_size' => ':size veličina učitane datoteke', 'nice_image' => 'Zadane slike entiteta', 'no_ads' => 'Bez reklama', 'pagination' => ':amount maksimalna paginacija rezultata', ], 'pricing' => ':currency :amount / mjesec', ]; ================================================ FILE: lang/hr/timelines/elements.php ================================================ [ 'success' => 'Element je dodan kronologiji.', 'title' => 'Novi element kronologije', ], 'delete' => [ 'success' => 'Uklonjen element :name.', ], 'edit' => [ 'success' => 'Element je ažuriran.', 'title' => 'Uređivanje elementa kronologije', ], 'fields' => [ 'date' => 'Datum', 'era' => 'Razdoblje', 'icon' => 'Ikona', ], 'helpers' => [ 'icon' => 'Kopiraj HTML ikone iz :fontawesome ili :rpgawesome.', ], 'placeholders' => [ 'date' => 'npr 42. ožujka ili 1332-1377', 'name' => 'Obavezno ako nije odabran nijedan entitet', 'position' => 'Pozicija na popisu elemenata za razdoblje. Ostavi prazno da bi se dodalo na kraj.', ], ]; ================================================ FILE: lang/hr/timelines/eras.php ================================================ [ 'add' => 'Dodaj novo razdoblje', ], 'create' => [ 'success' => 'Kreirano razdoblje :name.', 'title' => 'Novo razdoblje', ], 'delete' => [ 'success' => 'Obrisano razdoblje :name.', ], 'edit' => [ 'success' => 'Ažurirano razdoblje :name.', 'title' => 'Uredi razdoblje :name', ], 'fields' => [ 'abbreviation' => 'Skraćenica', 'end_year' => 'Završna godina', 'start_year' => 'Početna godina', ], 'helpers' => [ 'eras' => 'Kronologiju treba stvoriti prije nego što joj se mogu dodati razdoblja.', 'primary' => 'Razdvoji kronologiju u razdolja. Kronologiji treba najmanje jedno razdoblje da bi ispravno funkcionirala.', ], 'placeholders' => [ 'abbreviation' => 'n.e., p.n.e., pr. Kr.', 'end_year' => 'Godina kada razdoblje završava. Ostavi prazno ako je ovo trenutno razdoblje.', 'name' => 'Moderno doba, Brončano doba, Galaktički Ratovi', 'start_year' => 'Godina kada razdoblje počinje. Ostavi prazno ako je ovo prvo razdoblje.', ], 'reorder' => [], ]; ================================================ FILE: lang/hr/timelines.php ================================================ [ 'add_element' => 'Dodajte element u razdoblje :era', 'back' => 'Povratak na :name', 'save_order' => 'Spremi novi redoslijed', ], 'create' => [ 'title' => 'Nova kronologija', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_eras' => 'Kopiraj razdoblja', 'eras' => 'Razdoblja', 'reverse_order' => 'Obrni redoslijed razdoblja', ], 'helpers' => [ 'reverse_order' => 'Omogući za prikaz razdoblja obrnutim kronološkim redoslijedom (prvo starije ere)', ], 'index' => [], 'placeholders' => [ 'type' => 'Primarna, Svjetska kronika, Ostavština kraljevstva', ], 'show' => [], 'timelines' => [], ]; ================================================ FILE: lang/hr/validation.php ================================================ 'Polje :attribute mora biti prihvaćeno.', 'active_url' => 'Polje :attribute nije ispravan URL.', 'after' => 'Polje :attribute mora biti datum nakon :date.', 'after_or_equal' => 'Polje :attribute mora biti datum veći ili jednak :date.', 'alpha' => 'Polje :attribute smije sadržavati samo slova.', 'alpha_dash' => 'Polje :attribute smije sadržavati samo slova, brojeve i crtice.', 'alpha_num' => 'Polje :attribute smije sadržavati samo slova i brojeve.', 'array' => 'Polje :attribute mora biti niz.', 'before' => 'Polje :attribute mora biti datum prije :date.', 'before_or_equal' => 'Polje :attribute mora biti datum manji ili jednak :date.', 'between' => [ 'numeric' => 'Polje :attribute mora biti između :min - :max.', 'file' => 'Polje :attribute mora biti između :min - :max kilobajta.', 'string' => 'Polje :attribute mora biti između :min - :max znakova.', 'array' => 'Polje :attribute mora imati između :min - :max stavki.', ], 'boolean' => 'Polje :attribute mora biti false ili true.', 'confirmed' => 'Potvrda polja :attribute se ne podudara.', 'date' => 'Polje :attribute nije ispravan datum.', 'date_equals' => 'Stavka :attribute mora biti jednaka :date.', 'date_format' => 'Polje :attribute ne podudara s formatom :format.', 'different' => 'Polja :attribute i :other moraju biti različita.', 'digits' => 'Polje :attribute mora sadržavati :digits znamenki.', 'digits_between' => 'Polje :attribute mora imati između :min i :max znamenki.', 'dimensions' => 'Polje :attribute ima neispravne dimenzije slike.', 'distinct' => 'Polje :attribute ima dupliciranu vrijednost.', 'email' => 'Polje :attribute mora biti ispravna e-mail adresa.', 'ends_with' => 'The :attribute must end with one of the following: :values.', 'exists' => 'Odabrano polje :attribute nije ispravno.', 'file' => 'Polje :attribute mora biti datoteka.', 'filled' => 'Polje :attribute je obavezno.', 'gt' => [ 'numeric' => 'Polje :attribute mora biti veće od :value.', 'file' => 'Polje :attribute mora biti veće od :value kilobajta.', 'string' => 'Polje :attribute mora biti veće od :value karaktera.', 'array' => 'Polje :attribute mora biti veće od :value stavki.', ], 'gte' => [ 'numeric' => 'Polje :attribute mora biti veće ili jednako :value.', 'file' => 'Polje :attribute mora biti veće ili jednako :value kilobajta.', 'string' => 'Polje :attribute mora biti veće ili jednako :value znakova.', 'array' => 'Polje :attribute mora imati :value stavki ili više.', ], 'image' => 'Polje :attribute mora biti slika.', 'in' => 'Odabrano polje :attribute nije ispravno.', 'in_array' => 'Polje :attribute ne postoji u :other.', 'integer' => 'Polje :attribute mora biti broj.', 'ip' => 'Polje :attribute mora biti ispravna IP adresa.', 'ipv4' => 'Polje :attribute mora biti ispravna IPv4 adresa.', 'ipv6' => 'Polje :attribute mora biti ispravna IPv6 adresa.', 'json' => 'Polje :attribute mora biti ispravan JSON string.', 'lt' => [ 'numeric' => 'Polje :attribute mora biti manje od :value.', 'file' => 'Polje :attribute mora biti manje od :value kilobajta.', 'string' => 'Polje :attribute mora biti manje od :value znakova.', 'array' => 'Polje :attribute mora biti manje od :value stavki.', ], 'lte' => [ 'numeric' => 'Polje :attribute mora biti manje ili jednako :value.', 'file' => 'Polje :attribute mora biti manje ili jednako :value kilobajta.', 'string' => 'Polje :attribute mora biti manje ili jednako :value znakova.', 'array' => 'Polje :attribute ne smije imati više od :value stavki.', ], 'max' => [ 'numeric' => 'Polje :attribute mora biti manje od :max.', 'file' => 'Polje :attribute mora biti manje od :max kilobajta.', 'string' => 'Polje :attribute mora sadržavati manje od :max znakova.', 'array' => 'Polje :attribute ne smije imati više od :max stavki.', ], 'mimes' => 'Polje :attribute mora biti datoteka tipa: :values.', 'mimetypes' => 'Polje :attribute mora biti datoteka tipa: :values.', 'min' => [ 'numeric' => 'Polje :attribute mora biti najmanje :min.', 'file' => 'Polje :attribute mora biti najmanje :min kilobajta.', 'string' => 'Polje :attribute mora sadržavati najmanje :min znakova.', 'array' => 'Polje :attribute mora sadržavati najmanje :min stavki.', ], 'not_in' => 'Odabrano polje :attribute nije ispravno.', 'not_regex' => 'Format polja :attribute je neispravan.', 'numeric' => 'Polje :attribute mora biti broj.', 'present' => 'Polje :attribute mora biti prisutno.', 'regex' => 'Polje :attribute se ne podudara s formatom.', 'required' => 'Polje :attribute je obavezno.', 'required_if' => 'Polje :attribute je obavezno kada polje :other sadrži :value.', 'required_unless' => 'Polje :attribute je obavezno osim :other je u :values.', 'required_with' => 'Polje :attribute je obavezno kada postoji polje :values.', 'required_with_all' => 'Polje :attribute je obavezno kada postje polja :values.', 'required_without' => 'Polje :attribute je obavezno kada ne postoji polje :values.', 'required_without_all' => 'Polje :attribute je obavezno kada nijedno od polja :values ne postoji.', 'same' => 'Polja :attribute i :other se moraju podudarati.', 'size' => [ 'numeric' => 'Polje :attribute mora biti :size.', 'file' => 'Polje :attribute mora biti :size kilobajta.', 'string' => 'Polje :attribute mora biti :size znakova.', 'array' => 'Polje :attribute mora sadržavati :size stavki.', ], 'starts_with' => 'Stavka :attribute mora započinjati jednom od narednih stavki: :values', 'string' => 'Polje :attribute mora biti string.', 'timezone' => 'Polje :attribute mora biti ispravna vremenska zona.', 'unique' => 'Polje :attribute već postoji.', 'uploaded' => 'Polje :attribute nije uspešno učitano.', 'url' => 'Polje :attribute nije ispravnog formata.', 'uuid' => 'Stavka :attribute mora biti valjani UUID.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ ], ]; ================================================ FILE: lang/it/abilities.php ================================================ [], 'children' => [ 'actions' => [ 'attach' => 'Allega alle entità', ], 'create' => [ 'attach_success' => '{1} Allega l\'abilità :name a :count entità.|[2,*] Allega l\'abilità :name a :count entità.', ], 'description' => 'Entità che hanno l\'abilità', 'title' => 'Abilità :name Entità', ], 'create' => [ 'title' => 'Nuova Abilità', ], 'destroy' => [], 'edit' => [], 'entities' => [], 'fields' => [ 'charges' => 'Cariche', ], 'helpers' => [], 'index' => [], 'placeholders' => [ 'charges' => 'Quantità di cariche. Fai riferimento agli attributi con {Level}*{CHA}', 'name' => 'Palla di Fuoco, Allerta, Colpo Scaltro', 'type' => 'Incantesimo, Talento, Attacco', ], 'reorder' => [ 'parentless' => 'Nessuno Genitore', 'success' => 'Abilità riordinate con successo.', 'title' => 'Riordina le abilità', ], 'show' => [ 'tabs' => [ 'reorder' => 'Riordina le abilità', ], ], ]; ================================================ FILE: lang/it/attribute_templates.php ================================================ [], 'create' => [ 'title' => 'Nuovo Modello di Attributo', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'auto_apply' => 'Applica automaticamente', ], 'hints' => [ 'automatic' => 'I seguenti attributi sono stati applicati automaticamente da :link.', 'automatic_apply' => '{1} Il seguente :count attributo è stato applicato automaticamente da :link | [2,] I seguenti :count attributi sono stati applicati automaticamente da :link.', 'entity_type' => 'Applica automaticamente gli attributi di questo modello alle nuove entità del tipo selezionato.', 'parent_attribute_template' => 'Questo modello di attributo può essere figlio di un altro modello di attributo. Quando si applica questo modello di attributo, verranno applicati anche tutti i suoi genitori.', ], 'index' => [], 'placeholders' => [ 'name' => 'Nome di un Modello di Attributo', ], 'show' => [], ]; ================================================ FILE: lang/it/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Errore', 'rendering' => 'C\'è stato un errore nel rendering del plugin del marketplace. Contatta il creatore del plugin.', ], ], 'helpers' => [], 'list' => [], 'pitch' => 'Trova e aggiungi schede del personaggio dal :marketplace in una :boosted-campaign', ]; ================================================ FILE: lang/it/auth.php ================================================ [ 'permanent' => 'Sei stato bannato permanentemente.', 'temporary' => '{1} Sei stato bannato per :days giorno|[2,*] Sei stato bannato per :days giorni', ], 'confirm' => [ 'confirm' => 'Conferma', 'error' => 'Password non valida, riprova.', 'helper' => 'Conferma la tua password prima di continuare', 'title' => 'Conferma della password', ], 'failed' => 'Credenziali non corrispondenti ai dati registrati.', 'helpers' => [ 'password' => 'Mostra / Nascondi password', ], 'login' => [ 'fields' => [ '2fa' => 'Password Monouso', 'email' => 'Email', 'password' => 'Password', ], 'or' => 'OPPURE', 'password_forgotten' => 'Password dimenticata?', 'submit' => 'Accedi', 'title' => 'Accedi', ], 'register' => [ 'already' => 'Hai già un account? :login', 'errors' => [ 'email_already_taken' => 'Un account con questa email è già stato registrato.', 'general_error' => 'C\'è stato un errore durante la registrazione del tuo account. Per favore riprova.', ], 'fields' => [ 'email' => 'Email', 'name' => 'Nome Utente', 'password' => 'Password', ], 'log-in' => 'Accedi', 'submit' => 'Registrati', 'title' => 'Registrati', 'tos' => 'Registrando un account, accetti i nostri :terms e la nostra :privacy.', ], 'reset' => [ 'fields' => [ 'email' => 'Indirizzo Email', 'password' => 'Password', 'password_confirmation' => 'Conferma la password', ], 'send' => 'Invia il Link di Reset Password', 'submit' => 'Resetta la password', 'title' => 'Resetta la password', ], 'tfa' => [ 'helper' => 'L\'Autenticazione a Due Fattori è abilitata. Inserisci la Password Monouso fornita dall\'app di autenticazione.', 'title' => 'Autenticazione a Due Fattori', ], 'throttle' => 'Troppi tentativi di accesso. Riprova tra :seconds secondi.', 'x-twitter' => 'X, conosciuto precedentemente come Twitter', ]; ================================================ FILE: lang/it/billing/invoices.php ================================================ [ 'download' => 'Scarica PDF', ], 'description' => 'Mostra le fatture degli ultimi 24 mesi.', 'empty' => 'Nessuna fattura trovata', 'fields' => [ 'amount' => 'Importo', 'date' => 'Data', 'invoice' => 'Fattura', 'status' => 'Stato', ], 'paypal' => 'Si noti che qui sono visibili solo i pagamenti effettuati tramite Stripe e non PayPal.', 'status' => [ 'paid' => 'Pagato', 'pending' => 'In attesa', ], 'title' => 'Storico della fatturazione', ]; ================================================ FILE: lang/it/billing/menu.php ================================================ 'Storico della fatturazione', 'overview' => 'Riepilogo', 'payment-method' => 'Metodo di pagamento', ]; ================================================ FILE: lang/it/billing/payment_methods.php ================================================ 'Metodo di pagamento', 'types' => [ 'card' => 'Carta', ], ]; ================================================ FILE: lang/it/bookmarks.php ================================================ [ 'customise' => 'Personalizza la barra laterale', ], 'create' => [ 'title' => 'Nuovo Collegamento Rapido', ], 'destroy' => [], 'edit' => [ 'title' => 'Collegamento Rapido :name', ], 'fields' => [ 'active' => 'Attivo', 'dashboard' => 'Pagina Principale', 'default_dashboard' => 'Pagina Principale predefinita', 'filters' => 'Filtri', 'menu' => 'Sottopagina', 'position' => 'Posizione', 'random_type' => 'Tipo di Entità Casuale', 'selector' => 'Configurazione del Collegamento Rapido', 'target' => 'Elemento', ], 'helpers' => [ 'active' => 'Collegamenti rapidi inattivi non appaiono nella barra laterale', 'dashboard' => 'Indirizza il collegamento rapido a una delle Pagine Principale personalizzate della campagna.', 'default_dashboard' => 'Collegati invece alla Pagina Principale predefinita della campagna. È ancora necessario selezionare una Pagina Principale personalizzata.', 'entity' => 'Configura questo collegamento nel menù per poter accedere direttamente ad una entità. Il campo :menu gestisce quale sotto-pagina dell\'entità sarà aperta.', 'position' => 'Utilizza questo campo per controllare in che ordine crescente i collegamenti appariranno nel menù.', 'random' => 'Utilizza questo campo per avere un collegamento rapido che punta a un\'entità a caso. È possibile filtrare il link per accedere solo a un tipo specifico di entità.', 'selector' => 'Configura la destinazione di questo collegamento rapido quando un utente fa clic su di esso nella barra laterale.', 'type' => 'Imposta questo collegamento rapido per andare direttamente a un elenco di entità. Per filtrare i risultati, copia le parti dell\'url dell\'elenco di entità filtrate dopo il segno :? nel campo :filter.', ], 'index' => [], 'placeholders' => [ 'filters' => 'location_id=15&type=city', 'menu' => 'Sottopagina del menu (utilizza l\'ultimo testo dell\'url)', 'tab' => '(disattivato)', ], 'random_no_entity' => 'Non è stata trovata alcuna entità casuale.', 'random_types' => [ 'any' => 'Qualunque entità', ], 'reorder' => [ 'success' => 'Collegamenti Rapidi riordinati.', 'title' => 'Riordina i Collegamenti Rapidi', ], 'show' => [], 'visibilities' => [ 'is_active' => 'Visualizza i Collegamenti Rapidi nella Barra Laterale', ], ]; ================================================ FILE: lang/it/bragi.php ================================================ [ 'generate' => 'Genera', 'insert' => 'Usa', ], 'errors' => [ 'invalid-sub' => 'Per accedere a questa funzione, devi possedere un abbonamento Viverna o Elementale.', 'out-of-tokens' => 'Non hai più gettoni! Li riceverei automaticamente in :date.', ], 'here' => 'qui', 'intro' => 'Ciao! Sono :name, una AI per generare background per i tuoi personaggi su Kanka. Puoi imparare di più su di me :here.', 'kankappy' => 'Sono segretamente discepoli di Kankappy.', 'loading' => 'Per favore, attendi un momento, ci sto pensando sodo e può volerci anche un minuto!', 'placeholders' => [ 'prompt' => 'Scrivi uno spunto che verrà trasformato in un background.', ], 'token-limit' => 'Attualmente hai :amount gettoni. Ogni volta che genero un background, viene utilizzato un gettone, quindi usali con saggezza!', ]; ================================================ FILE: lang/it/calendars/weather.php ================================================ [], 'create' => [ 'success' => 'Tempo meteorologico aggiunto.', 'title' => 'Nuovo evento atmosferico', ], 'destroy' => [ 'success' => 'Tempo meteorologico rimosso.', ], 'edit' => [ 'success' => 'Tempo meteorologico aggiornato.', 'title' => 'Aggiorna il Tempo meteorologico', ], 'fields' => [ 'effect' => 'Effetto', 'name' => 'Nome', 'precipitation' => 'Precipitazione', 'temperature' => 'Temperatura', 'weather' => 'Tempo Meteorologico', 'wind' => 'Vento', ], 'options' => [ 'weather' => [ 'bolt' => 'Temporale', 'cloud' => 'Nuvoloso', 'cloud-rain' => 'Piovoso', 'cloud-showers-heavy' => 'Pioggia Torrenziale', 'cloud-sun' => 'Sereno Variabile', 'cloud-sun-rain' => 'Parzialmente Nuvoloso con Precipitazioni', 'meteor' => 'Meteora', 'smog' => 'Smog', 'snowflake' => 'Neve', 'sun' => 'Soleggiato', 'wind' => 'Ventoso', ], ], 'placeholders' => [ 'effect' => 'Effetto magico o naturale', 'name' => 'Testo opzionale per il meteo', 'precipitation' => 'Quantità di acqua caduta', 'temperature' => 'Temperatura massima e minima', 'wind' => 'Velocità del vento', ], ]; ================================================ FILE: lang/it/calendars.php ================================================ [ 'add_epoch' => 'Aggiungi un\'epoca', 'add_intercalary' => 'Aggiungi giorni intercalari', 'add_month' => 'Aggiungi un mese', 'add_moon' => 'Aggiungi una luna', 'add_reminder' => 'Aggiungi un evento', 'add_season' => 'Aggiungi una stagione', 'add_weather' => 'Imposta il meteo', 'add_week' => 'Aggiungi una settimana con un nome', 'add_weekday' => 'Aggiungi un giorno della settimana', 'add_year' => 'Aggiungi un nome dell\'anno', 'set_today' => 'Imposta come giorno corrente', 'today' => 'Oggi', 'update_weather' => 'Aggiorna il meteo', ], 'checkboxes' => [ 'is_recurring' => 'Accade ogni anno', ], 'create' => [ 'title' => 'Nuovo Calendario', ], 'destroy' => [], 'edit' => [ 'today' => 'Data del calendario aggiornata.', ], 'event' => [ 'create' => [ 'success' => 'Evento del calendario creato', 'title' => 'Aggiungi un Evento del Calendario su :name', ], 'destroy' => 'Evento rimosso dal calendario \':name\'', 'edit' => [ 'success' => 'Evento del calendario aggiornato.', 'title' => 'Aggiorna un Evento del Calendario per :name', ], 'errors' => [ 'invalid_entity' => 'Selezione dell\'entità non valida', ], 'helpers' => [ 'other_calendar' => 'Stai modificando un evento che si trova sul calendario :calendar', ], 'success' => 'Evento \':event\' aggiunto al calendario.', ], 'events' => [ 'bulks' => [ 'delete' => '{1} Eliminato :count evento.|[2,*] Eliminati :count eventi.', 'patch' => '{1} Aggiorna :count evento.|[2,*] Aggiorna :count eventi.', ], 'end' => '(fine)', 'filters' => [ 'show_after' => 'Mostra oggi e dopo', 'show_all' => 'Mostra tutto', 'show_before' => 'Mostra fino a oggi', ], 'start' => '(inizio)', ], 'fields' => [ 'comment' => 'Commento', 'current_day' => 'Giorno corrente', 'current_month' => 'Mese corrente', 'current_year' => 'Anno corrente', 'date' => 'Data corrente', 'day' => 'Giorno', 'default_layout' => 'Layout predefinito', 'format' => 'Formato', 'is_incrementing' => 'Data di Avanzamento', 'is_recurring' => 'Ricorrente', 'leap_year' => 'Anni bisestili', 'leap_year_amount' => 'Aggiungi Giorni', 'leap_year_month' => 'Mese', 'leap_year_offset' => 'Ogni', 'leap_year_start' => 'Anno bisestile', 'length' => 'Durata Evento', 'length_days' => ':count giorno|:count giorni', 'month' => 'Mese', 'months' => 'Mesi', 'moons' => 'Lune', 'parameters' => 'Parametri', 'recurring_until' => 'Ricorrente fino all\'Anno', 'reset' => 'Ripristino Settimanale', 'seasons' => 'Stagioni', 'show_birthdays' => 'Mostra Compleanni', 'skip_year_zero' => 'Togli Anno Zero', 'start_offset' => 'Inizio ritardo', 'suffix' => 'Suffisso', 'week_names' => 'Nomi della Settimana', 'weekdays' => 'Giorni della Settimana', 'year' => 'Anno', ], 'helpers' => [ 'default_layout' => 'Seleziona il layout che il calendario usa in modo predefinito quando visualizzato.', 'format' => 'Aggiungi la formattazione personalizzata della data per le entità del calendario.', 'month_type' => 'I mesi intercalari non utilizzano i giorni della settimana, ma influenzano comunque le fasi lunari e le stagioni.', 'moon_offset' => 'Per impostazione predefinita, la prima luna piena appare il primo giorno dell\'anno 0. Cambiando il ritardo si modifica il momento in cui viene visualizzata la prima luna piena. Questo valore può essere negativo (fino alla lunghezza del primo mese) o positivo (fino alla lunghezza del primo mese).', 'start_offset' => 'Per impostazione predefinita, il calendario inizia col primo giorno della settimana dell\'anno 0. Cambiare questo campo influenzerà la collocazione del primo giorno del calendario.', ], 'hints' => [ 'event_length' => 'La durata prevista per l\'evento. Un evento verrà visualizzato solo nei primi due anni.', 'is_incrementing' => 'I calendari con avanzamento incrementeranno automaticamente la loro data corrente alle 00:00 UTC.', 'leap_year' => 'Imposta gli anni bisestili per il calendario.', 'months' => 'Il tuo calendario deve avere almeno 2 mesi.', 'moons' => 'Aggiungere lune le farà apparire sul calendario ad ogni luna piena e luna nuova. Se il ciclo di luna piena è maggiore di 10 giorni, saranno mostrate anche la luna calante e la luna crescente.', 'parent_calendar' => 'L\'assegnazione di un calendario genitore farà includere gli eventi e il meteo del calendario genitore.', 'reset' => 'Fai sempre coincidere l\'inizio del mese o dell\'anno col primo giorno della settimana.', 'seasons' => 'Crea stagioni per il tuo calendario specificando quando ha inizio ciascuna di esse. Kanka si occuperà del resto.', 'show_birthdays' => 'Mostra le date di nascita annuali dei personaggi che hanno un evento di compleanno su questo calendario fino alla loro data di morte.', 'skip_year_zero' => 'Per impostazione predefinita, il primo anno del calendario è l\'anno zero. Attiva questa opzione per togliere l\'anno zero.', 'weekdays' => 'Imposta i tuoi nomi dei giorni della settimana. Sono necessari almeno 2 giorni della settimana.', 'weeks' => 'Definisci alcuni nomi per le settimane più importanti del tuo calendario.', 'years' => 'Alcuni anni sono così importanti che hanno un nome specifico.', ], 'index' => [], 'layouts' => [ 'month' => 'Mese', 'monthly' => 'Mensile in modo predefinito', 'year' => 'Anno', 'yearly' => 'Annuale in modo predefinito', ], 'modals' => [ 'switcher' => [ 'title' => 'Cambio Anno', ], ], 'month_types' => [ 'intercalary' => 'Intercalari', 'standard' => 'Normali', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Luna piena', 'fullmoon_name' => ':moon piena', 'month' => 'Mensile', 'newmoon' => 'Luna nuova', 'newmoon_name' => ':moon nuova', 'none' => 'Nessuno', 'unnamed_moon' => 'Luna :number', 'year' => 'Annuale', ], ], 'resets' => [ '' => 'Nessuno', 'month' => 'Mensile', 'year' => 'Annuale', ], ], 'panels' => [ 'intercalary' => 'Giorni di intercalazione', 'leap_year' => 'Anno Bisestile', 'months' => 'Mesi', 'weeks' => 'Settimane', 'years' => 'Anni Nominati', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Durata in giorni', 'month' => 'Alla fine di quale mese', 'name' => 'Nome dell\'intercalazione', ], 'month' => [ 'alias' => 'Alias del Mese', 'length'=> 'Numero di Giorni', 'name' => 'Nome del Mese', 'type' => 'Tipo', ], 'moon' => [ 'fullmoon' => 'Luna piena ogni (giorni)', 'name' => 'Nome della Luna', 'offset' => 'Ritardo della prima luna piena', ], 'seasons' => [ 'day' => 'Giorno iniziale', 'month' => 'Mese iniziale', 'name' => 'Nome della Stagione', ], 'weeks' => [ 'name' => 'Nome della Settimana', 'number' => 'Numero', ], 'year' => [ 'name' => 'Nome dell\'Anno', 'number' => 'Anno', ], ], 'placeholders' => [ 'colour' => 'Colore', 'comment' => 'Compleanno, festa, solstizio', 'date' => 'La data corrente', 'leap_year_amount' => 'Numero di giorni aggiunti in un anno bisestile', 'leap_year_month' => 'Mese al quale i giorni sono aggiunti', 'leap_year_offset' => 'Ogni quanti anni è presente un anno bisestile', 'leap_year_start' => 'Il primo anno bisestile', 'length' => 'Durata dell\'Evento in giorni', 'months' => 'Numero di mesi in un anno', 'recurring_until' => 'Ultimo anno per la ricorrenza (lascia vuoto per ripetere per sempre)', 'seasons' => 'Numero di stagioni', 'suffix' => 'Suffisso dell\'Era corrente (AC, DC)', 'type' => 'Tipo del calendario', 'weekdays' => 'Numero di giorni in una settimana', ], 'show' => [ 'missing_details' => 'Questo calendario non può essere visualizzato. I Calendari necessitano almeno di 2 mesi e 2 giorni della settimana per essere visualizzati correttamente.', 'moon_1first_quarter' => ':moon crescente', 'moon_full' => ':moon piena', 'moon_last_quarter' => ':moon calante', 'moon_new' => ':moon nuova', 'tabs' => [ 'events' => 'Eventi del Calendario', 'weather' => 'Tempo Atmosferico', ], ], 'sorters' => [ 'after' => 'Oggi e dopo', 'before'=> 'Fino a oggi', ], 'validators' => [ 'format' => 'Il formato della data è invalido.', 'moon_offset' => 'Il ritardo della prima luna piena non può essere più lungo del primo mese del calendario.', ], 'warnings' => [ 'event_length' => 'Gli eventi che coprono più anni sono visibili solo nei primi due anni. Per saperne di più, consulta la nostra :documentation.', ], ]; ================================================ FILE: lang/it/callouts.php ================================================ [ 'actions' => [ 'boost' => 'Potenzia :campaign', 'superboost' => 'Superpotenzia :campaign', ], 'learn-more' => 'Cosa sono i potenziamenti?', 'limitation' => 'Per accedere a questa funzione, la campagna deve essere potenziata.', 'limitations' => [ 'boosted' => 'Per accedere a questa funzione, la campagna deve essere potenziata.', 'superboosted' => 'Per accedere a questa funzione, la campagna deve essere superpotenziata.', ], 'multiple' => 'Per accedere a queste funzioni, la campagna deve essere potenziata.', 'pitches' => [ 'element-class' => 'Assegna a questo elemento una classe CSS personalizzata con una :boosted-campaign', 'icon' => 'Sblocca milioni di icone personalizzate da FontAwesome con una :boosted-campaign.', ], 'titles' => [ 'boosted' => 'Funzione Potenziata', 'superboosted' => 'Funzione Superpotenziata', ], ], 'premium' => [ 'learn-more' => 'Cosa sono le campagne premium?', 'limitation' => 'Per accedere a questa funzione, le funzioni premium devono essere attivate.', 'title' => 'Funzione di campagna premium', 'unlock' => 'Sblocca funzioni premium per :campaign', ], 'subscribe' => [ 'pitch-image' => 'Abbonati per sbloccare fino a :max MB per i caricamenti di files.', 'share-booster' => 'Potenzia :campaign per aumentare le dimensioni di caricamento file per tutti i membri della campagna.', 'share-premium' => 'Aumenta le dimensioni di caricamento dei file per tutti i membri della campagna con una campagna premium.', ], ]; ================================================ FILE: lang/it/campaigns/achievements.php ================================================ 'Congratulazioni!', 'connections' => '{0} Nessun legame creato|{1} Un legame creato|[2,*] :amount di legami creati', 'created' => '{0} Nessun :plural creato|{1} Un :singular creato|[2,*] :amount :plural creati', 'dead' => '{0} Nessun romanzo giallo|{1} Un romanzo giallo|[2,*] :amount romanzi gialli', 'goal' => 'Obbiettivo :number', 'goal_reached' => 'La campagna ha sbloccato il seguente Obbiettivo:', 'level' => 'Livello :number', 'markers' => '{0} Nessun indicatore di mappa creato|{1} Un indicatore di mappa creato|[2,*] :amount indicatori di mappa creati', 'painter' => '{0} Nessun tema creato|{1} Un tema creato|[2,*] :amount temi creati', 'plugins' => '{0} Nessun plugin installato|{1} Un plugin installato|[2,*] :amount plugin installati', 'remaining' => [ 'generic' => 'Ancora un po\' e si sbloccherà il livello successivo.', ], 'tagged' => '{0} Nessun entità taggata|{1} Un\'entità taggata|[2,*] :amount entità taggate', 'titles' => [ 'calendars' => 'Maestro del Tempo', 'characters' => 'Maestro dei Nomi', 'connections' => 'Cupido', 'creatures' => 'Allevatore', 'dead' => 'Omicida', 'events' => 'Maestro delle Tradizioni', 'families' => 'Organizzatore Famigliare', 'locations' => 'Costruttore', 'markers' => 'Cartografo', 'plugins' => 'Connoisseur di Plugin', 'quests' => 'Maestro di Intrighi', 'tags' => 'Tutto Sotto Controllo', 'themes' => 'Pittore', ], ]; ================================================ FILE: lang/it/campaigns/applications.php ================================================ [ 'accept' => 'Accetta', 'reject' => 'Rifiuta', ], 'apply' => [ 'apply' => 'Candidati', 'help' => 'Questa campagna è aperta a nuovi membri. Per aderire, compila il modulo. Verrai avvisato quando gli amministratori della campagna esamineranno la tua candidatura.', 'remove_text' => 'la tua candidatura', 'success' => [ 'apply' => 'La tua candidatura è stata salvata. Puoi ancora cambiarla o cancellarla in qualunque momento. Verrai avvisato quando gli amministratori della campagna esamineranno la tua candidatura.', 'remove'=> 'La tua candidatura è stato rimossa.', 'update'=> 'La tua candidatura è stata aggiornata. Puoi ancora cambiarla o cancellarla in qualunque momento. Verrai avvisato quando gli amministratori della campagna esamineranno la tua candidatura.', ], 'title' => 'Unisciti a :name', ], 'errors' => [], 'fields' => [ 'application' => 'Candidatura', ], 'helpers' => [ 'modal' => 'Una campagna aperta alle candidature e al pubblico può avere utenti che chiedono di unirsi alla campagna.', ], 'placeholders' => [ 'note' => 'Scrivi qui la tua candidatura per unirti alla campagna', ], 'statuses' => [], 'toggle' => [ 'closed' => 'Chiuso a candidature', 'label' => 'Stato', 'open' => 'Aperto a candidature', 'success' => 'Stato di candidatura della campagna aggiornato', 'title' => 'Stato di Candidatura', ], 'update' => [ 'approve' => 'Seleziona il ruolo in cui l\'utente verrà aggiunto alla campagna.', 'approved' => 'Candidatura approvata.', 'reject' => 'Scrivi un messaggio facoltativo all\'utente per spiegare il motivo del rifiuto della candidatura.', 'rejected' => 'Candidatura rifiutata', ], ]; ================================================ FILE: lang/it/campaigns/builder.php ================================================ 'Costruisci visivamente un tema per la campagna con questa interfaccia. Scorri verso il basso per vedere l\'impatto delle modifiche sui vari elementi della campagna. Quando selezioni un colore, viene automaticamente selezionato un colore "contrastante" per la colorazione del testo. Per saperne di più sulla tematizzazione, consulta i nostri :docs.', 'pitch' => 'Hey! Abbiamo un creatore di temi se vuoi solo cambiare alcuni dei colori della campagna 😉', 'pitch-go' => 'Portami al Creatore di Temi', 'reset' => 'Ripristina lo stile del Tema', 'success' => 'Stile del Tema salvato', 'title' => 'Creatore di Temi', ]; ================================================ FILE: lang/it/campaigns/dashboard-header.php ================================================ [ 'success' => 'Intestazione della campagna modificata.', 'title' => 'Modifica l\'intestazione della campagna.', ], ]; ================================================ FILE: lang/it/campaigns/default-images.php ================================================ [ 'add' => 'Aggiungi una nuova immagine predefinita', ], 'call-to-action' => 'Carica una thumbnail personalizzata per tutti i personaggi, luoghi o altre entità della campagna. Queste immagini verranno mostrate poi in diverse liste.', 'create' => [ 'error' => 'C\'è stato un errore durante il salvataggio delle nuove immagini predefinite. Il campo :type è già impostato?', 'success' => 'Immagine predefinita per :type creata.', 'title' => 'Nuova immagine predefinita', ], 'destroy' => [ 'success' => 'Immagine predefinita per :type rimossa.', ], 'index' => [], ]; ================================================ FILE: lang/it/campaigns/export.php ================================================ [ 'download' => 'Scarica', 'export' => 'Esporta i dati della campagna', ], 'confirm' => [ 'title' => 'Conferma l\'esportazione', 'warning' => 'Stai per esportare i dati della campagna. Questo processo può richiedere molto tempo, a seconda delle dimensioni della campagna. Puoi continuare a usare Kanka mentre i nostri server generano l\'esportazione.', ], 'errors' => [ 'limit' => 'La campagna è stata già esportata una volta oggi. Per favore riprova domani.', ], 'expired' => 'Link scaduto', 'helpers' => [], 'progress' => 'Progresso', 'size' => 'Dimensione', 'status' => [ 'failed' => 'Fallito', 'finished' => 'Finito', 'running' => 'In Corso', 'scheduled' => 'Programmato', ], 'success' => 'L\'esportazione della campagna è in fase di preparazione. Riceverai una notifica su Kanka una volta che è pronta per scaricare.', 'title' => 'Esportazione della campagna', ]; ================================================ FILE: lang/it/campaigns/gallery.php ================================================ [ 'close' => 'Chiudi', 'file-link' => 'Link del file', 'focus_point' => 'Punto focus dell\'immagine', 'image-link' => 'Link dell\'immagine', 'reset_focus' => 'Ripristina punto focus dell\'immagine', 'save' => 'Salva', 'upgrade' => 'Aumenta lo spazio di archiviazione', ], 'breadcrumb' => 'Galleria', 'bulk' => [ 'destroy' => [ 'confirm' => 'Sei sicuro di voler rimuovere definitivamente gli elementi selezionati? Questa azione non può essere annullata.', 'success' => '{0}Nessun file rimosso.|{1}Un file rimosso.|{2,*} :count file rimossi.', ], ], 'cta' => 'Gestisci e riutilizza le immagini per tutta la campagna.', 'destroy' => [ 'folder' => 'Cartella :name eliminata.', 'success' => 'File :name eliminato.', ], 'errors' => [ 'max' => 'Per favore, seleziona solo fino a :count file alla volta.', 'permissions' => 'Ai ruoli della campagna manca l\'autorizzazione :permission per poter caricare le immagini nella galleria della campagna.', 'storage' => 'Lo spazio di archiviazione non è sufficiente per caricare le immagini selezionate. Spazio di archiviazione disponibile: :available.', ], 'fields' => [ 'created_by' => 'Caricata da', 'details' => 'Dettagli', 'ext' => 'Estensione', 'file_type' => 'Tipo di File', 'folder' => 'Cartella', 'image_mentioned_in' => '{0} Questa immagine non è menzionata in nessuna entità della campagna.|{1} Menzionata in una voce/post.|[2,*] menzionata in :count voci/post.', 'image_used_in' => '{0} Questa immagine non è usata in nessuna entità della campagna.|{1} Usata come immagine in un\'entità.|[2,*] Usata come immagine in :count entità.', 'link' => 'Link', 'name' => 'Nome', 'size' => 'Dimensioni', 'unused' => 'Non utilizzata da nessuna parte', 'used_in' => 'Utilizzata in', ], 'focus' => [ 'locked' => 'Per impostare il punto focus di un\'immagine è necessaria una campagna premium.', 'removed' => 'Focus dell\'immagine rimosso.', 'updated' => 'Focus dell\'immagine aggiornato.', ], 'new_folder' => [ 'title' => 'Nuova cartella', ], 'no_folder' => 'Nessuna cartella', 'pitch' => 'Carica le immagini nella galleria della campagna direttamente dall\'editor di testo.', 'placeholders' => [ 'search' => 'Cerca nome dell\'immagine...', ], 'storage' => [ 'of' => 'di', 'title' => 'Archiviazione', ], 'title' => 'Galleria della campagna :campaign', 'update' => [ 'folder' => 'Cartella modificata.', 'success' => 'Immagine modificata.', ], 'uploader' => [ 'add' => 'Aggiungi nuova', 'new_folder' => 'Nuova Cartella', 'or' => 'o', 'select_file' => 'Seleziona un file', 'well' => 'Rilascia qui un file per caricarlo', ], ]; ================================================ FILE: lang/it/campaigns/import.php ================================================ [ 'import' => 'Carica l\'esportazione', ], 'fields' => [ 'updated' => 'Ultimo aggiornamento', ], 'form' => 'Carica da', 'progress' => [ 'uploading' => 'Caricamento', ], 'status' => [ 'failed' => 'Fallito', 'finished' => 'Finito', 'queued' => 'In coda', 'running' => 'In esecuzione', ], 'title' => 'Importa', ]; ================================================ FILE: lang/it/campaigns/limits.php ================================================ 'Limite raggiunto', ]; ================================================ FILE: lang/it/campaigns/modules.php ================================================ [ 'customise' => 'Personalizza', ], 'fields' => [ 'icon' => 'Icona del Modulo', 'plural' => 'Nome plurale del modulo', 'singular' => 'Nome singolare del modulo', ], 'helpers' => [], 'pitch' => 'Rinomina e cambia l\'cona associata a questo modulo per l\'intera campagna.', 'rename' => [ 'helper' => 'Cambia il nome e l\'icona del modulo durante la campagna. Lascia vuoto per utilizzare l\'icona predefinita di Kanka.', 'success' => 'Modulo personalizzato.', 'title' => 'Personalizza il modulo :module', ], 'reset' => [ 'success' => 'I moduli della campagna sono stati ripristinati.', 'title' => 'Ripristina i nomi e le icone personalizzate del modulo', 'warning' => 'Sei sicuro di voler ripristinare i moduli della campagna con i nomi e le icone originali?', ], 'states' => [ 'disable' => 'Disattiva', 'enable' => 'Attiva', ], ]; ================================================ FILE: lang/it/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'Disattiva plugin', 'enable' => 'Attiva plugin', 'update' => 'Aggiorna plugin', ], 'changelog' => 'Registro', 'disable' => 'Disattiva plugin', 'enable' => 'Attiva plugin', 'import' => 'Importa', 'update' => 'Aggiorna plugin', 'update_available' => 'Aggiornamento disponibile!', ], 'bulks' => [ 'delete' => '{1} Rimosso :count plugin.|[2,*] Rimossi :count plugin.', 'disable' => '{1} Disattivato :count plugin.|[2,*] Disattivati :count plugin.', 'enable' => '{1} Attivato :count plugin.|[2,*] Attivati :count plugins.', 'update' => '{1} Aggiornato :count plugin.|[2,*] Aggiornati :count plugin.', ], 'destroy' => [ 'success' => 'Plugin :plugin rimosso.', ], 'disabled' => [ 'success' => 'Plugin :plugin disattivato.', ], 'empty_list' => 'La campagna non ha attualmente alcun plugin. Vai al Mercato per installarne alcuni e torna qui per attivarli.', 'enabled' => [ 'success' => 'Plugin :plugin attivato.', ], 'errors' => [ 'invalid_plugin' => 'Plugin invalido.', ], 'fields' => [ 'name' => 'Nome plugin', 'obsolete' => 'Questo plugin è stato dichiarato obsoleto dal team di Kanka, il che significa che non funziona più come previsto dal suo creatore.', 'status' => 'Stato', 'type' => 'Tipo di plugin', ], 'import' => [ 'button' => 'Importa', 'created' => 'Create le seguenti entità:', 'helper' => 'Stai per importare :count entità dal plugin :plugin. Se questo plugin è stato importato in precedenza, le modifiche apportate alle entità importate possono andare perse.', 'no_new_entities' => 'Non ci sono nuove entità da importare.', 'option_only_import' => 'Importa solo le nuove entità, saltando quelle importate in precedenza.', 'option_private' => 'Importa tutte le entità come private.', 'success' => '{1} Importata :count entità dal plugin :plugin.|[2,*] Importate :count entità dal plugin :plugin.', 'title' => 'Importa :plugin', 'updated' => 'Aggiornate le seguenti entità:', ], 'info' => [ 'helper' => 'Quando viene rilasciata una nuova versione di un plugin, puoi aggiornarlo alla versione più recente per la tua campagna.', 'title' => 'Aggiornamenti del plugin :plugin', 'updates' => 'Aggiornamenti', ], 'pitch' => 'Installa e organizza i plugin dal :marketplace', 'status' => [ 'disabled' => 'Disattivato', 'enabled' => 'Attivato', ], 'templates' => [ 'name' => ':name da :user', ], 'title' => 'Plugin - :name', 'types' => [ 'attribute' => 'Template dell\'attributo', 'pack' => 'Pacchetto di Contenuti', 'theme' => 'Tema', ], 'update' => [ 'success' => 'Plugin :plugin aggiornato.', ], ]; ================================================ FILE: lang/it/campaigns/public.php ================================================ [], 'title' => 'Cambia la visibilità della campagna', 'update' => [ 'private' => 'La campagna è ora privata e visibile solo ai suoi membri.', 'public' => 'La campagna è ora pubblica. Potrebbe volerci del tempo prima che appaia nella pagina :public-campaigns.', ], ]; ================================================ FILE: lang/it/campaigns/recovery.php ================================================ [ 'recover' => 'Recupera', ], 'error' => 'C\'è stato un errore nel tentativo di recupero delle entità.', 'fields' => [ 'deleted' => 'Rimosso', ], 'posts' => [], 'title' => 'Recupero delle Entità', 'toggle' => [], ]; ================================================ FILE: lang/it/campaigns/roles.php ================================================ [ 'status' => 'Stato :status', ], 'public' => [], 'show' => [ 'title' => 'Autorizzazioni del :role - :campaign', ], 'toggle' => [ 'disabled' => 'Membri del ruolo :role non possono più :action le :entities', 'enabled' => 'Membri del ruolo :role possono ora :action le :entities', ], 'warnings' => [ 'adding-to-admin' => 'I membri del ruolo :name hanno accesso a tutto nella campagna e non possono essere rimossi da altri membri del ruolo. Dopo :amount minuti, solo loro possono rimuovere se stessi dal ruolo.', ], ]; ================================================ FILE: lang/it/campaigns/sidebar.php ================================================ [ 'reset' => 'Reset alle impostazioni originarie', ], 'call-to-action' => 'Personalizza ordine, icone e nome degli elementi nella barra laterale della campagna', 'helpers' => [ 'reordering' => 'Riordinare la barra laterale trascinando le icone sul lato sinistro', ], 'reset' => [ 'success' => 'Reset delle impostazioni della barra laterale della campagna', 'title' => 'Reset delle impostazioni della barra laterale', 'warning' => 'Sei sicuro di voler resettare la barra laterale della tua campagna ai valori di default?', ], 'success' => 'Impostazioni della barra laterale della campagna salvate', 'title' => 'Impostazioni della barra laterale della campagna :campaign', ]; ================================================ FILE: lang/it/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Calendari', 'title' => 'Custode del Tempo', ], 'murderer' => [ 'goal' => 'Personaggi defunti', 'title' => 'Assassino', ], ], 'targets' => [], 'titles' => [ 'calendars' => 'Livello :level Custode del Tempo', 'characters'=> 'Livello :level Maestro dei Nomi', 'dead' => 'Livello :level Assassino', 'families' => 'Livello :level Genealogia', 'locations' => 'Livello :level Costruttore', 'quests' => 'Livello :level Maestro degli Intrighi', 'races' => 'Livello :level Allevatore', ], ]; ================================================ FILE: lang/it/campaigns/styles.php ================================================ [ 'current' => 'Tema attuale: :theme', 'disable' => 'Disabilita', 'enable' => 'Abilita', 'new' => 'Nuovo stile', ], 'bulks' => [ 'delete' => '{1} Rimosso :count stile.|[2,*] Rimossi :count stili.', 'disable' => '{1} Disabilitato :count stile.|[2,*] Disabilitati :count stili.', 'enable' => '{1} Abilitato :count stile.|[2,*] Abilitati :count stili.', ], 'create' => [ 'success' => 'Nuovo stile creato', 'title' => 'Nuovo stile', ], 'delete' => [ 'success' => 'Stile :name eliminato', ], 'errors' => [ 'max_content' => 'La regola CSS non può essere più lunga di :amount caratteri.', 'max_reached' => 'Massimo numero di stili (:max) raggiunto.', ], 'fields' => [ 'content' => 'Regola CSS', 'is_enabled' => 'Abilitato', 'length' => 'Lunghezza', 'modified' => 'Modificato', 'name' => 'Nome', 'order' => 'Ordine', ], 'helpers' => [ 'here' => 'sul nostro blog', 'is_enabled' => 'Attiva questo tema in ogni pagina.', 'main' => 'Puoi creare il tuo stile CSS per la tua campagna potenziata. Questi stili vengono caricati dopo i temi del Mercato che sono stati abilitati per la campagna. Puoi saperne di più :here', ], 'pitch' => 'Crea uno stile CSS personalizzato per modificare l\'aspetto e l\'ambientazione della campagna', 'placeholders' => [ 'name' => 'Nome dello stile', ], 'reorder' => [ 'save' => 'Salva il nuovo ordine', 'success' => '{1} Riordinato :count stile.|[2,*] Riordinati :count stili.', 'title' => 'Riordina stili', ], 'theme' => [ 'success' => 'Tema della campagna aggiornato.', 'title' => 'Aggiorna il tema della campagna', ], 'title' => 'Temi della Campagna', 'update' => [ 'success' => 'Stile :name aggiornato.', 'title' => 'Aggiorna stile', ], ]; ================================================ FILE: lang/it/campaigns/vanity.php ================================================ 'Il nome :vanity è disponibile!', 'rule' => 'Il campo :field deve contenere almeno un carattere alfabetico.', 'rule2' => 'Il campo :field non consente il seguente carattere: /.', 'set' => 'L\'URL di priorità della campagna è impostato in modo permanente su :vanity.', ]; ================================================ FILE: lang/it/campaigns/webhooks.php ================================================ [ 'action' => 'Cambia stato', 'add' => 'Crea webhook', 'bulks' => [ 'delete_success' => '{1} :count webhook eliminato.|[2,*] :count webhooks eliminati .', 'disable' => 'Disattiva', 'disable_success' => '{1} :count webhook disattivato.|[2,*] :count webhooks disattivati.', 'enable' => 'Attiva', 'enable_success' => '{1} :count webhook attivato.|[2,*] :count webhooks attivati.', ], 'test' => 'Testo del webhook', 'update' => 'Aggiorna webhook', ], 'create' => [ 'success' => 'Webhook creato con successo', 'title' => 'Aggiungi nuovo webhook', ], 'destroy' => [ 'success' => 'Webhook eliminato con successo', ], 'edit' => [ 'success' => 'Webhook aggiornato con successo', 'title' => 'Aggiorna webhook', ], 'fields' => [ 'enabled' => 'Attivato', 'event' => 'Evento', 'events' => [ 'deleted' => 'Entità eliminata', 'edited' => 'Entità Modificata', 'new' => 'Nuova entità', ], 'message' => 'Messaggio', 'private_entities' => [ 'helper' => 'Non attivare il webhook quando si aggiornano entità private.', 'skip' => 'Ignora entità private', ], 'type' => 'Tipo', 'types' => [ 'custom' => 'Messaggio', 'payload' => 'Carico Utile', ], 'url' => 'Url', ], 'helper' => [ 'active' => 'Se il webhook è attualmente attivo', 'message' => 'Aggiungi un messaggio personalizzato con supporto', 'status' => 'Cambia lo stato attivo del webhook', ], 'placeholders' => [ 'message' => '{chi} ha apportato delle modifiche a {nome}, controlla su {url}.', 'url' => 'Url del webhook di destinazione', ], 'test' => [ 'success' => 'Richiesta di test inviata', ], 'title' => 'Webhooks', ]; ================================================ FILE: lang/it/campaigns.php ================================================ [], 'create' => [ 'success' => 'Campagna creata.', 'title' => 'Nuova campagna', ], 'destroy' => [], 'edit' => [ 'success' => 'Campagna aggiornata.', ], 'entity_personality_visibilities' => [ 'private' => 'I nuovi personaggi hanno la loro personalità privata in maniera predefinita.', ], 'entity_visibilities' => [ 'private' => 'Le nuove entità sono private', ], 'errors' => [ 'access' => 'Non hai accesso a questa campagna.', 'premium' => 'Questa funzione è disponibile solo per le campagne premium.', 'unknown_id' => 'Campagna Sconosciuta.', ], 'export' => [], 'fields' => [ 'boosted' => 'Potenziata da', 'entity_count' => 'Numero di Entità', 'entry' => 'Descrizione della Campagna', 'followers' => 'Seguaci', 'genre' => 'Genere(i)', 'header_image' => 'Immagine di copertina della Pagina Principale', 'image' => 'Immagine della Barra Laterale', 'locale' => 'Lingua', 'name' => 'Nome', 'open' => 'Aperto a candidature', 'premium' => 'Premium sbloccato da :name', 'public' => 'Visibilità della campagna', 'public_campaign_filters' => 'Filtri Campagna Pubblica', 'superboosted' => 'Superpotenziato da', 'system' => 'Sistema', 'theme' => 'Tema', 'vanity' => 'URL personalizzato', ], 'following' => 'Che Segui', 'helpers' => [ 'boosted' => 'Alcune caratteristiche sono sbloccate perché questa campagna è stata potenziata. Scopri di più nella pagina :settings.', 'css' => 'Scrivi i tuoi CSS che saranno caricati all\'interno della pagina della tua campagna. Considera che qualsiasi abuso di questa funzionalità può portare alla rimozione dei tuoi CSS personalizzati. Le offese ripetute o gravi possono portare alla rimozione della campagna.', 'dashboard' => 'Per personalizzare la visualizzazione del widget della dashboard della campagna, compila i seguenti campi.', 'excerpt' => 'Il contenuto di questo campo sarà visualizzato sulla Pagina Princiapel nel widget dell\'intestazione della campagna, quindi scrivi qualche frase di presentazione del tuo mondo. Se questo campo è vuoto, verranno utilizzati i primi 1000 caratteri della descrizione della campagna.', 'header_image' => 'Immagine visualizzata come sfondo nel widget dell\'intestazione della campagna.', 'hide_history' => 'Se abilitato, solo i membri con il ruolo :admin della campagna avranno accesso alla cronologia (registro delle modifiche) di un\'entità.', 'hide_members' => 'Se abilitato, solo i membri del ruolo :admin della campagna avranno accesso alla lista dei membri della campagna.', 'locale' => 'La lingua in cui la tua campagna è scritta. Questa specificazione è sfruttata per generare contenuti e raggruppare le campagne pubbliche.', 'name' => 'La tua campagna/mondo può avere qualsiasi nome, a patto che che contenga almeno 4 lettere o numeri.', 'no_entry' => 'Sembra che la campagna non abbia ancora una descrizione! Risolviamo il problema.', 'premium' => 'Alcune funzioni sono disponibili perché le funzioni premium di questa campagna sono sbloccate. Per saperne di più, visita la pagina :settings', 'public_campaign_filters' => 'Aiuta altre persone a trovare la campagna tra altre campagne pubbliche fornendo le seguenti informazioni.', 'public_no_visibility' => 'Attenzione! La campagna è pubblica, ma il ruolo pubblico della campagna non può accedere a nulla. :fix.', 'system' => 'Se la tua campagna è visibile pubblicamente, il sistema sarà visualizzato nella pagina :link.', 'systems' => 'Per evitare di confondere gli utenti con una sovrabbondanza di opzioni, alcune di esse sono disponibili solamente per specifici sistemi RPG (per esempio il blocco delle statistiche dei mostri di D&D 5e). Aggiungere un sistema supportato qui abiliterà queste funzionalità.', 'theme' => 'Forza il tema della campagna, sovrascrivendo le preferenze delle utenze.', 'view_public' => 'Per visualizzare la tua campagna come farebbe uno spettatore pubblico, apri :link in una finestra di navigazione in incognito.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Copia il link nei tuoi appunti', 'link' => 'Invita persone', ], 'create' => [ 'buttons' => [ 'create' => 'Genera link', ], 'success_link' => 'Link creato: :link', 'title' => 'Invita qualcuno in :campaign', ], 'destroy' => [ 'success' => 'Invito rimosso.', ], 'error' => [ 'inactive_token' => 'Questo token è già stato utilizzato o la campagna non esiste più.', 'invalid_token' => 'Questo token non è più valido.', 'join' => 'Per favore accedi o registrati con un nuovo account per entrare in :campaign.', ], 'fields' => [ 'created' => 'Creato', 'role' => 'Ruolo', 'token' => 'Token', 'type' => 'Tipo', 'usage' => 'Scade dopo', ], 'unlimited_validity' => 'Nessun Limite', 'usages' => [ 'five' => '5 usi', 'no_limit' => 'Nessun limite', 'once' => '1 uso', 'ten' => '10 usi', ], ], 'leave' => [ 'confirm' => 'Sei sicuro di voler abbandonare la campagna :name? Non potrai più accedere, a meno che un amministratore della campagna non ti inviti nuovamente.', 'confirm-button' => 'Si, abbandona la campagna.', 'error' => 'Non puoi abbandonare la campagna.', 'fix' => 'Vai ai membri della campagna', 'no-admin-left' => 'Lasciare la campagna non è possibile perché la lascerebbe senza amministratori. Assegna il ruolo di admin prima a un altro membro.', 'success' => 'Hai abbandonato la campagna.', 'title' => 'Lasciare la campagna', ], 'members' => [ 'actions' => [ 'remove' => 'Rimuovi dalla campagna', 'switch' => 'Passa a', 'switch-back' => 'Torna al mio utente', 'switch-entity' => 'Visualizza come', ], 'fields' => [ 'banned' => 'L\'utente è bannato', 'joined' => 'Unito', 'last_login' => 'Ultimo Login', 'name' => 'Utente', 'role' => 'Ruolo', 'roles' => 'Ruoli', ], 'helpers' => [ 'switch' => 'Passa a questo utente', ], 'impersonating' => [ 'message' => 'Stai visualizzando la campagna come un altro utente. Alcune caratteristiche sono state disabilitate, ma il resto viene mostrato esattamente come lo vedrebbe quell\'utente.', 'title' => 'Stai impersonando :name', ], 'invite' => [ 'description' => 'Puoi invitare i tuoi amici nella tua campagna indicandoci i loro indirizzi e-mail. Una volta che avranno accettato il loro invito verranno aggiunti come membri nel ruolo indicato.', 'more' => 'Puoi aggiungere ulteriori ruoli su :link.', 'title' => 'Invita', ], 'removal' => 'Stai rimuovendo ":membro" dalla campagna.', 'roles' => [ 'member' => 'Membro', 'owner' => 'Amministratore', 'player' => 'Giocatore', 'public' => 'Pubblico', 'viewer' => 'Spettatore', ], 'switch_back_success' => 'Ora sei tornato al tuo utente originale.', ], 'modules' => [], 'overview' => [ 'entity-count' => '{0} Nessuna entità|{1} :amount entità|[2,] :amount entità', 'follower-count' => '{0} Nessun seguace|{1} :amount seguace|[2,] :amount seguaci', ], 'panels' => [ 'dashboard' => 'Pagina Principale', 'setup' => 'Configurazione', 'sharing' => 'Condivisione', 'systems' => 'Sistemi', 'ui' => 'Interfaccia', ], 'placeholders' => [ 'locale' => 'Codice di lingua', 'name' => 'Il nome della tua campagna', 'system' => 'D&D 5e, Pathfinder, Fate, Gurps, DSA', ], 'privacy' => [ 'hidden' => 'Nascosto', 'private' => 'Privato', 'visible' => 'Visibile', ], 'public' => [ 'helpers' => [ 'introduction' => 'Le campagne sono private per impostazione predefinita e possono essere rese pubbliche. Questo permette a chiunque di accedervi e le rende disponibili nella pagina :public-campaigns se hanno entità visibili al ruolo :public-role. Una campagna pubblica è visibile a tutti, ma affinché il suo contenuto sia visibile, il ruolo :public-role ha bisogno di permessi adeguati.', ], ], 'roles' => [ 'actions' => [ 'add' => 'Aggiungi un ruolo', 'duplicate' => 'Duplica ruolo', 'permissions' => 'Gestire le autorizzazioni', 'rename' => 'Rinomina ruolo', 'save' => 'Salva ruolo', ], 'admin_role' => 'Ruolo di amministratore', 'bulks' => [ 'delete' => '{1} Rimosso :count ruolo.|[2,*] Rimossi :count ruoli.', 'edit' => '{1} Aggiornato :count ruolo.|[2,*] Aggiornati :count ruoli.', ], 'create' => [ 'success' => 'Ruolo :name creato.', 'title' => 'Crea un nuovo ruolo', ], 'destroy' => [ 'success' => 'Ruolo :name rimosso.', ], 'edit' => [ 'success' => 'Ruolo :name aggiornato.', 'title' => 'Modifica il ruolo :name', ], 'fields' => [ 'copy_permissions' => 'Copia autorizzazioni', 'name' => 'Nome', 'permissions' => 'Permessi', 'type' => 'Tipo', 'users' => 'Utenti', ], 'helper' => [ '1' => 'Una campagna può avere tanti ruoli quanti ne desideri. Il ruolo :admin ti dà automaticamente accesso a tutto nella campagna, ma ogni altro ruolo può avere permessi specifici su diversi tipi di entità (personaggio, luogo, ecc).', '2' => 'I permessi delle entità possono essere perfezionati utilizzando la tabella "Permessi" di unl\'entità. Questa tabella appare quando la tua campagna ha più ruoli o membri.', '3' => 'Puoi usare un sistema "opt-out", dove ai ruoli è dato il permesso di vedere tutte le entità, e usare la spunta "Privato" sull\'entità per nasconderla. Oppure puoi dare ai ruoli pochi permessi, ma impostare ogni entità come visibile.', '4' => 'Campagne Potenziate possono avere un numero illimitato di ruoli', 'permissions_helper' => 'Duplica tutte le autorizzazioni del ruolo, sia dei moduli che delle entità', ], 'hints' => [ 'campaign_not_public' => 'Il ruolo pubblico ha dei permessi, ma la campagna è privata. Puoi cambiare questa impostazione sulla tabella Condivisione mentre modifichi la campagna.', 'empty_role' => 'Il ruolo non ha ancora nessun membro all\'interno.', 'role_admin' => 'Il ruolo :name garantisce automaticamente ai suoi membri l\'accesso a tutto ciò che è presente nella campagna.', 'role_permissions' => 'Abilita il ruolo :name per le seguenti funzioni su tutte le entità.', ], 'members' => 'Membri', 'modals' => [ 'details' => [ 'campaign' => 'Le autorizzazioni della campagna consentono quanto segue.', 'entities' => 'Ecco un breve riepilogo di ciò che i membri di questo ruolo ottengono quando viene impostata un\'autorizzazione.', 'more' => 'Per maggiori dettagli, guarda il nostro video tutorial su Youtube', 'title' => 'Dettagli di autorizzazione', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Crea', 'dashboard' => 'Pagina Principale', 'delete' => 'Elimina', 'edit' => 'Modifica', 'gallery' => [ 'browse' => 'Ricerca', 'manage' => 'Pieno controllo', 'upload' => 'Aggiorna', ], 'manage' => 'Gestisci', 'members' => 'Membri', 'permission'=> 'Autorizzazioni', 'read' => 'Visualizza', 'toggle' => 'Cambia per tutte', ], 'helpers' => [ 'add' => 'Consente la creazione di entità di questo tipo. Gli utenti saranno automaticamente autorizzati a visualizzare e modificare le entità che creano, se non hanno l\'autorizzazione di visualizzazione o modifica.', 'dashboard' => 'Consente la modifica della Pagina Principale e dei widget della Pagina Principale.', 'delete' => 'Consente la rimozione di tutte le entità di questo tipo.', 'edit' => 'Consente la modifica di tutte le entità di questo tipo.', 'gallery' => [ 'browse' => 'Consente di visualizzare la galleria e di impostare l\'immagine di un\'entità dalla galleria.', 'manage' => 'Consente tutto ciò che è possibile fare nella galleria come un amministratore, compresa la modifica e l\'eliminazione delle immagini.', 'upload' => 'Consente di caricare immagini nella galleria. Se non è abbinato al permesso di cercare immagini, l\'utente vedrà solo le immagini che ha caricato.', ], 'manage' => 'Consente la modifica della campagna come farebbe l\'amministratore di una campagna, senza permettere ai membri di cancellare la campagna.', 'members' => 'Consente di invitare nuovi membri alla campagna.', 'not_public'=> 'La campagna non è pubblica. Le autorizzazioni per il ruolo pubblico possono essere impostate, ma saranno ignorate. Modifica la campagna per renderla pubblica.', 'permission'=> 'Consente di impostare le autorizzazioni sulle entità di questo tipo che possono modificare.', 'read' => 'Consente di visualizzare tutte le entità di questo tipo che non sono private.', ], ], 'placeholders' => [ 'name' => 'Nome del ruolo', ], 'title' => 'Ruoli della campagna :name', 'types' => [ 'owner' => 'Amministratore', 'public' => 'Pubblico', 'standard' => 'Predefinito', ], 'users' => [ 'actions' => [ 'add' => 'Aggiungi membro', 'remove' => ':user dal ruolo :role', 'remove_user' => 'Rimuovi l\'utente dal ruolo', ], 'create' => [ 'success' => ':user aggiunto al ruolo :role.', 'title' => 'Aggiungi un membro al ruolo :name', ], 'destroy' => [ 'success' => ':user rimosso dal ruolo :role.', ], 'errors' => [ 'cant_kick_admins' => 'Per evitare eventuali abusi, non è possibile rimuovere altri membri dal ruolo di :admin della campagna. In caso di problemi, contattataci su :discord o all\'indirizzo :email.', 'needs_more_roles' => 'È necessario aggiungersi a un altro ruolo nella campagna prima di potersi rimuovere dal ruolo :admin.', ], 'fields' => [ 'name' => 'Nome', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Abilita', ], 'boosted' => 'Questa funzionalità è in beta e al momento è disponibile solo per :boosted.', 'deprecated' => [ 'help' => 'Questo modulo è obsoleto, il che significa che non viene più mantenuto e che i bug non vengono risolti a ogni nuovo aggiornamento. Utilizza questo modulo sapendo che alla fine verrà rimosso da Kanka.', 'title' => 'Obsoleto', ], 'disabled' => 'Il modulo :module è disabilitato.', 'enabled' => 'Il modulo :module è abilitato.', 'errors' => [ 'module-disabled' => 'Il modulo richiesto è attualmente disabilitato nelle impostazioni della campagna. :fix.', ], 'helpers' => [ 'abilities' => 'Crea abilità, siano esse talenti, incantesimi o poteri che possono essere assegnati alle entità.', 'assets' => 'Carica file, imposta link e crea alias per le singole entità.', 'bookmarks' => 'Crea segnalibri alle entità o agli elenchi filtrati che appaiono nella barra laterale.', 'calendars' => 'Un\'area dove definire i calendari del tuo mondo.', 'characters' => 'Le persone che abitano il tuo mondo.', 'conversations' => 'Conversazioni fittizie tra i personaggi o gli utenti della campagna.', 'creatures' => 'Crea le creature, gli animali e i mostri del tuo mondo con il modulo delle creature.', 'dice_rolls' => 'Per quelli che utilizzano Kanka per una campagna GDR, un modo per gestire i tiri di dado.', 'entity_attributes' => 'Tieni traccia degli attributi delle entità della campagna, ad esempio PF o VELOCITÀ.', 'events' => 'Vacanze, feste, disastri, compleanni, guerre.', 'families' => 'Clan o famiglie, le loro relazioni e i loro membri.', 'inventories' => 'Gestisci gli inventari delle tue entità', 'items' => 'Armi, veicoli, reliquie, pozioni.', 'journals' => 'Osservazioni scritte dai personaggi, o preparazione per le sessioni del dungeon master.', 'locations' => 'Pianeti, piani, continenti, fiumi, nazioni, insediamenti, templi, taverne.', 'maps' => 'Carica mappe con livelli e indicatori che portano ad altre entità nella campagna.', 'notes' => 'Tradizioni, religioni, storia, magia, culture.', 'organisations' => 'Culti, religioni, fazioni, gilde.', 'quests' => 'Per tener traccia di varie missioni con personaggi e luoghi.', 'races' => 'Traccia le origini, le etnie e i tratti di specie dei personaggi del mondo con il modulo della stirpe.', 'tags' => 'Ogni entità può avere diversi tag. I tag possono appartenere ad altri tag e le entità possono essere filtrate per tag.', 'timelines' => 'Narra la storia del tuo mondo con le linee temporali', ], ], 'sharing' => [ 'filters' => 'Le campagne pubbliche sono visibili nella pagina :public-campaigns. La compilazione di questi campi aiuta la gente a trovare la campagna.', 'language' => 'La lingua in cui sono scritti i contenuti della campagna.', 'system' => 'Se giochi a un GDR, il sistema utilizzato per giocare nella campagna.', ], 'show' => [ 'actions' => [ 'edit' => 'Modifica Campagna', ], 'tabs' => [ 'achievements' => 'Obbiettivi', 'customisation' => 'Personalizzazione', 'data' => 'Dati', 'default-images' => 'Anteprime predefinite', 'export' => 'Esporta', 'import' => 'Importa', 'management' => 'Gestione', 'members' => 'Membri', 'plugins' => 'Plugin', 'recovery' => 'Recupero', 'roles' => 'Ruoli', 'sidebar' => 'Configurazione della barra laterale', 'styles' => 'Tema', 'webhooks' => 'Webhooks', ], 'title' => 'Panoramica - :name', ], 'themes' => [ 'none' => 'Nessuno (predefinito alle impostazioni utente)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Visibile solo agli amministratori della campagna', 'visible' => 'Visibile ai membri', ], 'fields' => [ 'entity_history' => 'Cronologia dell\'entità', 'member_list' => 'Lista dei membri della campagna', ], 'helpers' => [ 'entity-history' => 'Controlla chi può vedere le modifiche recenti apportate alle singole entità della campagna.', 'member-list' => 'Controlla chi può vedere chi partecipa alla campagna.', 'theme' => 'Visualizza la campagna nel tema dell\'utente o usa uno dei temi seguenti per tutti gli utenti.', ], 'members' => [ 'hidden' => 'Visibile solo agli amministratori della campagna', 'visible' => 'Visibile ai membri', ], ], 'visibilities' => [ 'private' => 'Campagna Privata', 'public' => 'Campagna Pubblica', ], 'warning' => [], ]; ================================================ FILE: lang/it/characters.php ================================================ [ 'add_appearance' => 'Aggiungi un dettaglio dell\'aspetto fisico', 'add_personality' => 'Aggiungi un tratto della personalità', ], 'conversations' => [], 'create' => [ 'title' => 'Nuovo Personaggio', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'families' => [ 'reorder' => [ 'success' => 'Famiglie del personaggio aggiornate con successo.', ], ], 'fields' => [ 'age' => 'Età', 'is_appearance_pinned' => 'Aspetto fissato', 'is_dead' => 'Morto', 'is_personality_pinned' => 'Personalità fissata', 'is_personality_visible' => 'Personalità visibile', 'life' => 'Vita', 'physical' => 'Caratteristiche fisiche', 'pronouns' => 'Pronomi', 'sex' => 'Genere', 'title' => 'Titolo', 'traits' => 'Tratti', ], 'helpers' => [ 'age' => 'Puoi collegare questa entità con un calendario della tua campagna per calcolare automaticamente l\'età. :more', ], 'hints' => [ 'is_appearance_pinned' => 'Se selezionati, i tratti fisici del personaggio appariranno sotto la voce principale della pagina.', 'is_dead' => 'Questo personaggio è morto', 'is_personality_visible' => 'I tratti della personalità sono visibili a tutti, non solo ai membri del ruolo di :admin.', 'personality_not_visible' => 'Solo gli amministratori possono vedere i tratti caratteriali di questo personaggio.', 'personality_visible' => 'Tutti possono vedere i tratti carattieriali di questo personaggio.', ], 'index' => [], 'items' => [], 'journals' => [], 'labels' => [ 'appearance' => [ 'entry' => 'Descrizione dell\'aspetto', 'name' => 'Nome dell\'aspetto', ], 'personality' => [ 'entry' => 'Descrizione dei tratti della personalità', 'name' => 'Nome dei tratti della personalità', ], ], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => ':character aggiunto a :organisation.', 'title' => 'Nuova organizzazione per :name', ], 'destroy' => [ 'success' => 'Organizzazione del personaggio rimossa.', ], 'edit' => [ 'success' => 'Organizzazione del personaggio aggiornata.', 'title' => 'Organizzazione aggiornata per :name', ], 'fields' => [ 'role' => 'Ruolo', ], ], 'placeholders' => [ 'age' => 'Età', 'appearance_entry' => 'Descrizione', 'appearance_name' => 'Capelli, Occhi, Carnagione, Altezza', 'name' => 'Nome del personaggio', 'personality_entry' => 'Dettagli', 'personality_name' => 'Obbiettivi, Vezzi, Paure, Legami', 'physical' => 'Caratteristiche Fisiche', 'pronouns' => 'Lui, Lei, Loro', 'sex' => 'Genere', 'title' => 'Titolo', 'traits' => 'Tratti', 'type' => 'PNG, Personaggio Giocante, Divinità', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Missioni per cui il personaggio è il committente.', 'quest_member' => 'Missioni di cui il personaggio fa parte.', ], ], 'races' => [ 'reorder' => [ 'success' => 'Stirpi del personaggio aggiornate con successo', ], ], 'sections' => [ 'appearance' => 'Aspetto', 'personality' => 'Personalità', ], 'show' => [], 'warnings' => [ 'personality_hidden' => 'Non puoi modificare i tratti della personalità di questo personaggio.', ], ]; ================================================ FILE: lang/it/colours.php ================================================ 'Acqua', 'black' => 'Nero', 'blue' => 'Blu', 'brown' => 'Marrone', 'green' => 'Verde', 'grey' => 'Grigio', 'light-blue' => 'Azzurro', 'maroon' => 'Bordeaux', 'navy' => 'Navy', 'none' => 'Nessuno', 'orange' => 'Arancione', 'pink' => 'Rosa', 'purple' => 'Viola', 'red' => 'Rosso', 'teal' => 'Verde Acqua', 'white' => 'Bianco', 'yellow' => 'Giallo', ]; ================================================ FILE: lang/it/concept.php ================================================ 'campagna potenziata', 'premium-campaign' => 'campagna premium', 'premium-campaign-count' => '{0} Nessuna Campagna Premium |{1} 1 Campagna Premium |[2,*] :count Campagne Premium', 'premium-campaigns' => 'campagne premium', 'premium-feature' => 'Funzione premium', 'superboosted-campaign' => 'campagna superpotenziata', ]; ================================================ FILE: lang/it/confirm/editing.php ================================================ 'Torna indietro', 'description' => 'Sembra che qualcun altro stia modificando questa pagina! Vuoi tornare indietro o ignorare questo avviso, con il rischio di perdere i dati?', 'ignore' => 'Modifica comunque', 'members' => 'Membri che stanno modificando questa pagina:', 'title' => 'Attenzione', 'user' => ':user da :since', ]; ================================================ FILE: lang/it/conversations.php ================================================ [ 'title' => 'Nuova conversazione', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_closed' => 'Chiusa', 'messages' => 'Messaggi', 'participants' => 'Partecipanti', ], 'hints' => [ 'empty' => 'Non ci sono partecipanti in questa conversazione.', 'participants' => 'Per favore aggiungi partecipanti alla tua conversazione premendo l\'icona :icon in altro a destra.', ], 'index' => [], 'messages' => [ 'destroy' => [ 'success' => 'Messaggio rimosso.', ], 'is_updated' => 'Aggiornata', 'load_previous' => 'Carica i messaggi precedenti', 'placeholders' => [ 'message' => 'Il tuo messaggio', ], ], 'participants' => [ 'create' => [ 'success' => 'Partecipante :entity aggiunto alla conversazione.', ], 'destroy' => [ 'success' => 'Partecipante :entity rimosso dalla conversazione.', ], 'modal' => 'Partecipanti', 'title' => 'Partecipanti di :name', ], 'placeholders' => [ 'name' => 'Nome della conversazione', 'type' => 'In Gioco, Preparazione, Trama', ], 'show' => [ 'is_closed' => 'Conversazione chiusa.', ], 'tabs' => [ 'participants' => 'Partecipanti', ], 'targets' => [ 'characters' => 'Personaggi', 'members' => 'Membri', ], ]; ================================================ FILE: lang/it/cookieconsent.php ================================================ 'Accetta i cookies', 'dismiss' => 'Rifiuta i cookies', 'header' => 'Consenso ai cookie', 'link' => 'Ulteriori informazioni', 'message' => 'Kanka utilizza i cookie per garantire la migliore esperienza sul nostro sito web.', 'policy' => 'Informativa sui cookies', 'reject' => 'Rifiuta', ]; ================================================ FILE: lang/it/creatures.php ================================================ [ 'title' => 'Nuova Creatura', ], 'fields' => [ 'is_extinct' => 'Estinto', ], 'helpers' => [], 'hints' => [ 'is_dead' => 'Questa creatura è morta.', 'is_extinct' => 'Questa creatura è estinta.', ], 'placeholders' => [ 'type' => 'Erbivoro, Acquatico, Mitologico', ], ]; ================================================ FILE: lang/it/crud.php ================================================ [ 'actions' => 'Azioni', 'apply' => 'Applica', 'back' => 'Indietro', 'change' => 'Cambia', 'copy' => 'Copia', 'copy_mention' => 'Copia [ ] menzione', 'copy_to_campaign' => 'Copia nella campagna', 'disable' => 'Disattiva', 'enable' => 'Attiva', 'explore_view' => 'Visualizzazione Annidata', 'export' => 'Esporta (PDF)', 'find_out_more' => 'Per saperne di più', 'go_to' => 'Vai a :name', 'help' => 'Aiuto', 'json-export' => 'Esporta (JSON)', 'markdown-export' => 'Esporta (Markdown)', 'move' => 'Muovi', 'new' => 'Nuovo', 'new_child' => 'Nuovo figlio', 'new_post' => 'Nuovo post', 'next' => 'Prossimo', 'open' => 'Apri', 'print' => 'Stampa', 'reorder' => 'Riordina', 'reset' => 'Ripristina', 'transform' => 'Trasforma', ], 'add' => 'Aggiungi', 'alerts' => [ 'copy_attribute' => 'La menzione dell\'attributo è stata copiata nei tuoi appunti.', 'copy_invite' => 'Il link di invito della campagna è stato copiato negli appunti.', 'copy_mention' => 'La menzione avanzata dell\'entità è stata copiata negli appunti.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Modifica e tagga', 'permissions' => 'Cambia permessi', ], 'age' => [ 'helper' => 'Puoi utilizzare i simboli + e - prima del numero per aggiornare l\'età di quel valore.', ], 'buttons' => [ 'label' => 'Per i selezionati', ], 'edit' => [ 'locations' => 'Azione per i luoghi', 'tagging' => 'Azione per le etichette', 'tags' => [ 'add' => 'Aggiungi', 'remove' => 'Rimuovi', ], 'title' => 'Modifica molteplici entità', ], 'errors' => [ 'admin' => 'Solo gli amministratori della campagna possono cambiare lo stato privato delle entità.', 'general' => 'Si è verificato un errore nell\'elaborazione dell\'azione. Riprova e contattaci se il problema persiste. Messaggio di errore: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Sovrascrivi', ], 'helpers' => [ 'override' => 'Se selezionato, le autorizzazioni delle entità selezionate verranno sovrascritte con queste. Se non è selezionato, le autorizzazioni selezionate verranno aggiunte a quelle esistenti.', ], 'title' => 'Cambia autorizzazioni per diverse entità', ], ], 'bulk_templates' => [ 'bulk_title' => 'Applica un modello a entità multiple', ], 'cancel' => 'Cancella', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Copia le entità a un\'altra campagna', 'panel' => 'Copia', 'title' => 'Copia \':name\' in un\'altra campagna', ], 'create' => 'Crea', 'datagrid' => [ 'empty' => 'Non c\'è ancora nulla da mostrare.', ], 'delete_modal' => [ 'callout' => 'Hey!', 'confirm' => 'Conferma la cancellazione', 'permanent' => 'Questa azione è permanente.', 'recoverable' => 'Le entità possono essere recuperate per un massimo di :days con una :boosted-campaign.', 'title' => 'Conferma della rimozione', ], 'destroy_many' => [], 'edit' => 'Modifica', 'errors' => [ 'boosted_campaigns' => 'Questa funzione è disponibile solo per :boosted.', 'unavailable_feature' => 'Funzione non disponibile', ], 'events' => [], 'fields' => [ 'calendar_date' => 'Data del Calendario', 'child' => 'Figlio', 'closed' => 'Chiuso', 'colour' => 'Colore', 'copy_abilities' => 'Copia Abilità', 'copy_inventory' => 'Copia Inventario', 'copy_links' => 'Copia Link', 'copy_permissions' => 'Copia Autorizzazioni (questo sovrascriverà i valori impostati nella scheda Autorizzazioni)', 'copy_posts' => 'Copia Post (questo include anche le autorizzazioni dei post)', 'copy_reminders' => 'Copia Eventi', 'creator' => 'Creatore', 'date_range' => 'Intervallo di date', 'excerpt' => 'Estratto', 'has_entity_files' => 'Ha file di entità', 'has_image' => 'Ha un\'immagine', 'has_posts' => 'Ha post', 'header_image' => 'Intestazione dell\'Immagine', 'image' => 'Immagine', 'is_closed' => 'La conversazione verrà chiusa e non accetterà più nuovi messaggi.', 'is_private' => 'Privato', 'is_private_v3' => 'Mostra solo ai membri del ruolo :admin-role. Questo sovrascrive qualsiasi altra autorizzazione.', 'is_star' => 'Fissato', 'locations' => ':first in :second', 'name' => 'Nome', 'parent' => 'Genitore', 'position' => 'Posizione', 'replace_mentions' => 'Sostituire le menzioni degli attributi nella voce con quelle della nuova entità.', 'template' => 'Modello', 'tooltip' => 'Tooltip', 'type' => 'Tipo', 'visibility' => 'Visibilità', ], 'files' => [ 'errors' => [ 'max' => 'Hai raggiunto il numero massimo (:max) di file per questa entità.', 'max_size' => 'La campagna ha raggiunto la capacità massima di archiviazione dei file.', 'no_files' => 'Nessun File.', ], 'hints' => [ 'limit' => 'Ciascuna entità può avere un massimo di :max file caricati.', 'limitations' => 'Formati supportati: :formats. Dimensioni massime di file: :size.', ], ], 'filter' => 'Filtro', 'filters' => [ 'all' => 'Filtra a tutti i discendenti', 'clear' => 'Azzera i Filtri', 'copy_helper' => 'Utilizza i filtri copiati negli appunti come valori per i filtri dei widget della Pagina Principale e dei collegamenti rapidi.', 'copy_to_clipboard' => 'Copia i filtri negli appunti', 'direct' => 'Filtra ai discendenti diretti', 'filtered' => 'Mostra :count di :total :entity.', 'lists' => [ 'desktop' => [ 'all' => 'Mostra tutti i discendenti (:count)', 'filtered' => 'Mostra discendenti diretti (:count)', ], ], 'mobile' => [ 'clear' => 'Azzera', 'copy' => 'Appunti', ], 'options' => [ 'children' => 'Corrisponde a questo o ai suoi discendenti', 'exclude' => 'Non corrisponde', 'hide' => 'Nascondi', 'include' => 'Corrisponde', 'none' => 'Vuoto', 'show' => 'Mostra', ], 'show' => 'Mostra Filtri', 'sorting' => [ 'asc' => ':field Crescente', 'desc' => ':field Decrescente', 'helper' => 'Controlla in che ordine appaiono i risultati.', ], 'title' => 'Filtri Avanzati', ], 'fix-this-issue' => 'Risolvi questo problema', 'forms' => [ 'actions' => [ 'calendar' => 'Aggiungi una data del calendario', ], 'copy_options' => 'Copia Opzioni', ], 'helpers' => [ 'copy_options' => 'Copia i seguenti elementi correlati alla fonte nella nuova entità.', 'linking' => 'Collega ad altre entità', ], 'hidden' => 'Nascosto', 'hints' => [ 'calendar_date' => 'La data del calendario consente di filtrare facilmente gli elenchi e di mantenere un promemoria nel calendario selezionato.', 'image_dimension' => 'Dimensioni raccomandate: :dimension pixels.', 'image_limitations' => 'Formati supportati: :formats. Dimensioni Massime del file: :size', 'image_recommendation' => 'Dimensioni raccomandate :width per :height px.', 'is_star' => 'Elementi fissati appariranno nella panoramica della pagina dell\'entità.', 'tooltip' => 'Sostituisci il tooltip generato automaticamente con il seguente contenuto. Il codice HTML verrà eliminato, ma si potranno comunque citare altre entità utilizzando le menzioni avanzate.', ], 'history' => [ 'created_clean' => 'Creata da :name :date', 'created_date_clean' => 'Creata :date', 'unknown' => 'Sconosciuto', 'updated_clean' => 'Ultima modifica da parte di :name :date', 'updated_date_clean' => 'Ultima modifica :date', 'view' => 'Visualizza il registro dell\'entità', ], 'image' => [ 'error' => 'Non siamo riusciti a ottenere l\'immagine richiesta. Potrebbe essere che il sito web non ci permetta di scaricare l\'immagine (come in genere Squarespace e DeviantArt), oppure che il link non sia più valido. Assicurati inoltre che l\'immagine non sia più grande di :size.', ], 'is_private' => 'Questa entità è privata e visibile solamente ai membri del ruolo "Amministratore".', 'keyboard-shortcut' => 'Scorciatoia di tastiera :code', 'navigation' => [ 'cancel' => 'cancella', 'or_cancel' => 'o :cancel', 'skip_to_content' => 'Salta navigazione', ], 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Consenti', 'deny' => 'Rifiuta', 'ignore' => 'Salta', 'remove' => 'Rimuovi', ], 'bulk_entity' => [ 'allow' => 'Consenti', 'deny' => 'Rifiuta', 'inherit' => 'Eredita', ], 'delete' => 'Cancella', 'edit' => 'Modifica', 'toggle' => 'Attiva', ], 'fields' => [ 'member' => 'Membro', 'role' => 'Ruolo', ], 'helpers' => [ 'setup' => 'Utilizza questa interfaccia per regolare con precisione il modo in cui i ruoli e gli utenti possono interagire con questa entità. :allow consente all\'utente o al ruolo di eseguire questa azione. :deny nega l\'azione. :inherit utilizza il ruolo dell\'utente o il permesso del ruolo principale. Un utente impostato su :allow è in grado di eseguire l\'azione, anche se il suo ruolo è impostato su :deny.', ], 'success' => 'Autorizzazioni salvate.', 'title' => 'Autorizzazioni', 'too_many_members' => 'Questa campagna ha troppi membri (>:number) per poterli mostrare tutti in questa interfaccia. Ti preghiamo di usare il tasto Permessi sulla pagine dell\'entità per poter verificare i permessi nel dettaglio.', ], 'placeholders' => [ 'calendar' => 'Seleziona un calendario', 'entry' => 'Usa @ seguito da tre lettere per menzionare altre entità di altre campagne.', 'fallback' => 'Seleziona un :module', 'gallery_image' => 'Seleziona un\'immagine dalla galleria della campagna', 'image_url' => 'Carica invece un\'immagine da un URL', 'journal' => 'Seleziona un diario', 'location' => 'Seleziona un luogo', 'multiple' => 'Scegli uno o più', 'name' => 'Nome dell\'entità', 'organisation' => 'Seleziona un\'organizzazione', 'parent' => 'Seleziona un genitore', 'tag' => 'Seleziona un tag', 'timeline' => 'Seleziona una Linea Temporale', 'user' => 'Seleziona un utente', ], 'relations' => [], 'remove' => 'Rimuovi', 'reorder' => [ 'empty' => 'Nessun elemento da riordinare', ], 'save' => 'Salva', 'save_and_close' => 'Salva e Chiudi', 'save_and_copy' => 'Salva e Copia', 'save_and_new' => 'Salva e Crea Nuovo', 'save_and_update' => 'Salva e Modifica', 'save_and_view' => 'Salva e Visualizza', 'search' => 'Cerca', 'select' => 'Seleziona', 'tabs' => [ 'abilities' => 'Abilità', 'inventory' => 'Inventario', 'mentions' => 'Menzioni', 'overview' => 'Panoramica', 'permissions' => 'Autorizzazioni', 'premium' => 'Premium', 'profile' => 'Profilo', 'reminders' => 'Eventi', ], 'titles' => [ 'editing' => 'Modifica :name', 'new' => 'Nuovo :module', ], 'tooltips' => [], 'update' => 'Modifica', 'users' => [ 'unknown' => 'Sconosciuto', ], 'view' => 'Visualizza', 'visibilities' => [ 'admin' => 'Amministratori', 'admin-self' => 'Te Stesso & Amministratori', 'all' => 'Tutto', 'members' => 'Membri della campagna', 'self' => 'Te Stesso', ], ]; ================================================ FILE: lang/it/dashboard.php ================================================ [ 'follow' => 'Segui', 'join' => 'Unisciti', 'unfollow' => 'Smetti di seguire', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Modifica nome & autorizzazioni', 'new' => 'Nuova Pagina Principale', ], 'create' => [ 'success' => 'Nuova Pagina Principale della campagna :name creata.', 'title' => 'Nuova Pagina Principale della Campagna', ], 'custom' => [ 'text' => 'Stai modificando la Pagina Principale :name della campagna.', ], 'default' => [ 'text' => 'Stai modificando la Pagina Principale predefinita della campagna.', 'title' => 'Pagina Principale Predefinita', ], 'delete' => [ 'success' => 'Pagina Principale :name rimossa.', ], 'fields' => [ 'copy_widgets' => 'Copia widgets', 'name' => 'Nome Pagina Principale', 'visibility' => 'Visibilità', ], 'helpers' => [ 'copy_widgets' => 'Duplica i widgets dalla Pagina Principale :name a questa nuova.', ], 'pitch' => 'Crea multiple Pagine Principali con autorizzazioni personalizzate per ogni ruolo all\'interno della campagna.', 'placeholders' => [ 'name' => 'Nome della Pagina Principale', ], 'update' => [ 'success' => 'Pagina Principale della campagna :name aggiornata.', 'title' => 'Aggiorna la Pagina Principale della campagna :name.', ], 'visibility' => [ 'default' => 'Predefinito', 'none' => 'Nessuna', 'visible' => 'Visibile', ], ], 'helpers' => [ 'follow' => 'Seguendo una campagna, questa apparirà nel selettore delle campagne sotto le vostre campagne.', 'join' => 'Questa campagna è aperta a nuovi membri. Clicca per unirti.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Aggiungi un widget', 'back_to_dashboard' => 'Torna alla Pagina Principale', 'edit' => 'Modifica un widget', 'new' => 'Nuovo widget :type', ], 'reorder' => [ 'success' => 'Widget riordinati.', ], 'title' => 'Impostazioni della Pagina Principale della Campagna', 'tutorial' => [ 'blog' => 'nostro tutorial', 'text' => 'Hai bisogno di aiuto per configurare la Pagina Principale della tua campagna? Leggi :blog per trovare aiuto e ispirazione.', ], ], 'title' => 'Pagina Principale', 'widgets' => [ 'advanced_options_boosted' => 'Abilita altre opzioni, come mostrare i pin con una campagna :boosted_campaign.', 'calendar' => [ 'actions' => [ 'next' => 'Cambia la data al giorno successivo', 'previous' => 'Cambia la data al giorno precedente', ], 'previous_events' => 'Precedente', 'upcoming_events' => 'Prossimi', ], 'campaign' => [ 'helper' => 'Questo widget mostra l\'intestazione della campagna. Il widget è sempre visibile nella Pagina Principale predefinita.', ], 'create' => [ 'success' => 'Widget aggiunto alla Pagina Principale.', ], 'delete' => [ 'success' => 'Widget rimosso dalla Pagina Principale.', ], 'fields' => [ 'class' => 'classe CSS', 'dashboard' => 'Pagina Principale', 'name' => 'Nome personalizzato di widget', 'optional-entity' => 'Link all\'entità', 'order' => 'Ordinamento', 'size' => 'Dimensione', 'width' => 'Larghezza', ], 'helpers' => [ 'class' => 'Definisci una classe personalizzata CSS aggiunto al widget.', 'filters' => 'Clicca per imparare di più sulle opzioni di filtro disponibili.', ], 'orders' => [ 'name_asc' => 'Nome crescente', 'name_desc' => 'Nome decrescente', 'oldest' => 'Non modificato da molto tempo', 'recent' => 'Modificato recentemente', ], 'preview' => [ 'displays' => [ 'expand' => 'Voce espandibile', 'full' => 'Voce completa', ], 'fields' => [ 'display' => 'Visualizza', ], ], 'random' => [ 'helpers' => [ 'name' => 'Puoi fare riferimento al nome dell\'entità casuale con {name}', ], 'type' => [ 'all' => 'Tutti', ], ], 'recent' => [ 'advanced_filter' => 'Filtro avanzato', 'advanced_filters' => [ 'mentionless' => 'Senza menzioni (entità che non menziona altre entità)', 'unmentioned' => 'Non menzionata (entità che non è menzionata da nessun altra)', ], 'entity-header' => 'Usa l\'intestazione dell\'entità come immagine', 'filters' => 'Filtri', 'help' => 'Visualizza solamente l\'ultima entità aggiornata, ma visualizza un\'anteprima completa per la stessa.', 'helpers' => [ 'entity-header' => 'Se l\'entità ha un\'intestazione dell\'entità (caratteristica della campagna potenziata), imposta questo widget in modo che utilizzi quell\'immagine invece dell\'immagine dell\'entità.', 'show_attributes' => 'Mostra gli attributi appuntati dell\'entità sotto la voce.', 'show_members' => 'Se l\'entità è una famiglia o un\'organizzazione, indica i suoi membri sotto la voce.', 'show_relations' => 'Mostra le relazioni appuntate dell\'entità sotto la voce.', ], 'show_attributes' => 'Mostra gli attributi appuntati', 'show_members' => 'Mostra membri', 'show_relations' => 'Mostra relazioni fissate', 'singular' => 'Anteprima', 'tags' => 'Filtra la lista di entità con etichette specifiche.', 'title' => 'Lista di entità', ], 'tabs' => [ 'advanced' => 'Avanzato', 'setup' => 'Impostazione', ], 'unmentioned' => [ 'title' => 'Entità non menzionate', ], 'update' => [ 'success' => 'Widget modificato.', ], 'widths' => [ '0' => 'Automatica', '12'=> 'Intera (100%)', '3' => 'Minuscola (25%)', '4' => 'Piccola (33%)', '6' => 'Media (50%)', '8' => 'Larga (66%)', '9' => 'Grande (75%)', ], ], ]; ================================================ FILE: lang/it/dashboards/widgets/welcome.php ================================================ [], 'focus' => [ 'text' => 'Ecco, sono io!', 'title' => 'Hey', ], 'intros' => [ '1' => 'Saluta il tuo nuovo laboratorio di worldbuilding, :user! Abbiamo impostato la tua prima campagna e incluso due esempi di :characters e :locations. Sono visibili anche qui, nella Pagina Principale della campagna.', '2' => 'Per iniziare, fai clic sul grande pulsante :new-entity (o premi :letter sulla tua tastiera) e fai clic su :characters per creare il tuo primo personaggio. È davvero così facile! Puoi trovare tutti i tuoi personaggi, luoghi e altre :entities nella barra laterale a sinistra della pagina.', '3' => 'Ecco i nostri 5 migliori trucchi per utilizzare Kanka', ], 'title' => 'Benvenuto a :kanka! 🎉', 'tricks' => [ '1' => 'Quando scrivi descrizioni, non riscrivere i nomi degli elementi della campagna. Digita invece :code e tre lettere per :mention altre entità della campagna. Queste menzioni si aggiorneranno automaticamente quando cambierai i loro nomi.', '2' => 'Per modificare il nome, il tema o l\'immagine della campagna, fai clic su :world nella barra laterale, seguito dal pulsante :edit.', '3' => 'Scrivi informazioni segrete sulle entità come :posts invece che nel campo di testo principale.', '4' => 'Invita i tuoi amici alla campagna cliccando su :world e :members. Da qui, è possibile creare link di invito.', '5' => 'Puoi rimuovere questo messaggio di benvenuto e mostrare altre informazioni in questa pagina (chiamata Pagina Principale). Scorri verso il basso e fai clic sul pulsante :button.', 'mention' => 'menzione', ], ]; ================================================ FILE: lang/it/datagrids.php ================================================ [ 'back_to' => 'Ritorna a :name', ], 'modes' => [ 'flatten' => 'Passa a un layout appiattito', 'grid' => 'Passa alla visualizzazione a griglia', 'nested' => 'Passa a un layout annidato', 'table' => 'Passa alla visualizzazione a tabella', ], 'tooltips' => [ 'nested' => 'Questa entità non ha figli. Clicca sull\'immagine per visualizzarli.', ], ]; ================================================ FILE: lang/it/datetime.php ================================================ 'giorno', 'days' => 'giorni', 'elapsed_ago' => ':duration fa', 'hour' => 'ora', 'hours' => 'ore', 'just_now' => 'proprio adesso', 'minute' => 'minuto', 'minutes' => 'minuti', 'month' => 'mese', 'months' => 'mesi', 'second' => 'secondo', 'seconds' => 'secondi', 'week' => 'settimana', 'weeks' => 'settimane', 'year' => 'anno', 'years' => 'anni', ]; ================================================ FILE: lang/it/default.php ================================================ 'Titolo della pagina', ]; ================================================ FILE: lang/it/dice_roll_results.php ================================================ [ 'title' => 'Risultato del tiro di dado', ], ]; ================================================ FILE: lang/it/dice_rolls.php ================================================ [ 'title' => 'Nuovo Tiro di Dado', ], 'destroy' => [ 'dice_roll' => 'Tiro di dado rimosso.', ], 'edit' => [], 'fields' => [ 'created_at' => 'Tirato il', 'parameters' => 'Parametri', 'results' => 'Risultati', 'rolls' => 'Tiri', ], 'hints' => [ 'parameters' => 'Quali sono le opzioni per i miei dadi?', ], 'index' => [ 'actions' => [ 'results' => 'Risultati', ], ], 'placeholders' => [ 'name' => 'Nome del Tiro di Dado', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Tira', ], 'error' => 'Tiro di dado fallito. Impossibile analizzare i parametri.', 'fields' => [ 'creator' => 'Creatore', 'date' => 'Data', 'result' => 'Risultato', ], 'hint' => 'Tutti i tiri effettuati per questo template di tiro di dado.', 'success' => 'Dado tirato.', ], 'show' => [ 'tabs' => [ 'results' => 'Risultati', ], ], ]; ================================================ FILE: lang/it/emails/purge/first.php ================================================ 'Se utilizzi regolarmente il tuo account, non preoccuparti: stiamo eliminando solo gli account e le campagne che non vengono utilizzati attivamente.', 'help' => 'Serve aiuto nell\'usare Kanka? Entra nel nostro :discord o contattaci via :email', 'intro_account' => 'Ti informiamo che il tuo account sarà cancellato tra :amount giorni, poiché non lo hai utilizzato negli ultimi :duration months.', 'intro_campaigns' => 'Ti informiamo che il tuo account e le seguenti campagne saranno cancellate tra :amount giorni, poiché non sono stati utilizzati negli ultimi :duration mesi.', 'keep' => 'Se desideri mantenere attivo il proprio account, effettua il login entro i prossimi :amount giorni.', 'title' => 'Il tuo account Kanka sarà cancellato tra :amount giorni.', 'warning' => [ 'account' => 'Una volta effettuata questa operazione, tutti i dati associati al tuo account, sotto la voce :email, saranno eliminati in modo permanente.', 'campaigns' => 'Una volta effettuata questa operazione, tutti i dati associati al tuo account, sotto la voce email :email, nonché le seguenti campagne saranno definitivamente cancellati.', ], ]; ================================================ FILE: lang/it/emails/purge/second.php ================================================ 'Questo è un ultimo promemoria per ricordarti che l\'account Kanka all\'indirizzo :email sarà cancellato tra :amount giorni, poiché non lo hai utilizzato negli ultimi :amount mesi.', 'title' => 'Ultimo avvertimento: Il tuo account Kanka sarà cancellato tra :amount giorni', ]; ================================================ FILE: lang/it/emails/subscriptions/expiring.php ================================================ 'aggiorna i dettagli della carta', 'primary' => 'Questo è un avviso automatico che il tuo :brand **** :last sta per scadere.', 'title' => 'Carta in scadenza per l\'abbonamento', 'valid' => 'Se desideri mantenere l\'abbonamento, prego :action.', ]; ================================================ FILE: lang/it/emails/subscriptions/upcoming.php ================================================ 'Se non desideri rinnovare l\'abbonamento, accedi per favore al tuo account Kanka e :link.', 'closing' => 'Cordiali Saluti,', 'dear' => 'Caro :name', 'link' => 'cancella il tuo abbonamento', 'notice' => 'Avviso importante sui rinnovi:', 'primary' => 'Questo è un promemoria automatico del fatto che addebiteremo automaticamente il tuo :brand **** :last alla :date, per il tuo abbonamento Kanka.', 'title' => 'Pagamento annuale per il tuo abbonamento Kanka', 'valid' => 'Assicurati che la carta di credito sia valida alla data del pagamento.', ]; ================================================ FILE: lang/it/emails/subscriptions/validation.php ================================================ 'Convalida dell\'e-mail dell\'account Kanka.', ]; ================================================ FILE: lang/it/emails/validation.php ================================================ 'Convalida fallita, riprova per favore.', 'modal' => 'Per abbonarsi è necessario un indirizzo e-mail convalidato. Controlla per favore la posta in arrivo per trovare un link di conferma dell\'e-mail prima di continuare il processo di abbonamento.', 'success' => 'Email convalidata con successo.', ]; ================================================ FILE: lang/it/emails/welcome/2024.php ================================================ 'Buon worldbuilding e grazie per aver partecipato a questo viaggio con noi,', 'header' => 'Benvenuti nel posto migliore per creare la tua campagna, :name!', 'lead_1' => 'Kanka nasce dall\'idea di appassionati giocatori di ruolo che volevano un approccio semplice e collaborativo alla costruzione del mondo, senza rinunciare a funzioni di qualità.', 'lead_2' => 'Kanka è ciò che ne è uscito fuori. Siamo qui per aiutarti a organizzare la tua campagna e lasciarti concentrare sulle parti migliori della realizzazione del tuo mondo.', 'ps' => 'PS: Se vuoi metterti in contatto con noi, puoi trovarci su :discord, o in :email.', 'what_1' => 'Per aiutarti a iniziare, abbiamo creato la tua prima campagna e abbiamo incluso due personaggi e due luoghi di esempio. Puoi :start quando vuoi.', 'what_2' => 'Consigliamo anche di consultare queste risorse:', 'what_3' => 'Il nostro :kb se hai domande di base sulle funzioni di Kanka e sul tuo account.', 'what_4' => 'La :doc tratta tutto in modo più approfondito.', 'what_5' => 'E infine :campaigns mostra ciò che altri hanno fatto con Kanka.', 'what_new' => 'Inizia', 'what_now' => 'E adesso?', 'why' => 'Hai ricevuto questa e-mail perché ti sei iscritto al nostro sito web.', ]; ================================================ FILE: lang/it/emails/welcome.php ================================================ [ 'basics' => [ 'text_1' => 'Con uno strumento così esteso come Kanka, può essere difficile sapere da dove cominciare o cosa fare. Il nostro :kb copre le domande più elementari che potresti avere, mentre per un ulteriore aiuto puoi consultare il nostro :doc.', 'title' => 'Le basi', ], 'chat' => [ 'text_1' => 'Ci piace ascoltare i nostri utenti! Siamo molto attivi su :discord, dove troverai molti dei nostri dedicati utenti, un team di arruolamento e i fondatori di Kanka, che potranno rispondere a tutte le tue domande. Puoi anche scriverci all\'indirizzo :email.', 'title' => 'Vuoi chattare?', ], 'intro' => [ 'header' => 'Benvenuto nella migliore comunità di worldbuilding, :name!', 'link' => 'Vai al tuo mondo!', 'text_1' => 'Saluta il tuo nuovo laboratorio di worldbuilding, :name! La comunità è nel nostro DNA e siamo lieti che ti unisca a noi. Kanka nasce dall\'idea di appassionati giocatori di ruolo che credono che bisogna approcciarsi alla costruzione di un mondo in modo semplificato e comunitario, senza complicare alcuna funzione.', 'text_2' => 'Abbiamo impostato la tua prima campagna e incluso due esempi di personaggi e luoghi per aiutarti a iniziare.', ], 'preview' => 'Entra a far parte della migliore comunità di worldbuilding, :name!', ], 'header' => 'Benvenuto su Kanka, :name!', 'header_sub' => 'Congratulazioni, hai mosso i primi passi per la creazione del tuo mondo su :kanka!', 'pricing' => 'prezzi', 'section_1' => 'E ora dove si va?', 'section_11' => 'Crea il tuo mondo,', 'section_2' => 'La risorsa più importante è :discord, dove troverai tanti nostri utenti volenterosi, un team di accoglienza, così come il fondatore di Kanka, che potrà rispondere a qualsiasi domanda che potresti voler fare.', 'section_4' => 'Il nostro canale :youtube contiene video che trattano le basi di Kanka. Sebbene non tutti gli argomenti siano ancora stati trattati, noi aggiungiamo regolarmente nuovi video.', 'section_4_v2' => 'La nostra :knowledge-base copre le domande più elementari che potresti avere, mentre per un aiuto più completo puoi consultare la nostra :documentation !', 'section_6' => 'Contattaci', 'section_7' => 'Se non hai trovato una risposta alle tue domande, o desideri semplicemente contattarci, puoi trovarci su :facebook, o puoi inviarci una email a :email. Siamo un piccolo team di due amici, ma ci assicuriamo di rispondere a ciascuna email che riceviamo, quindi non esitare!', 'section_8' => 'Un\'ultima cosa', 'section_9_v2' => 'Abbiamo fatto in modo che tutte le funzioni principali di Kanka siano gratuite. Tuttavia, se vuoi sostenerci in questo progetto e ottenere l\'accesso a funzionalità aggiuntive e alla nostra gratitudine, puoi diventare abbonato. Per saperne di più, consulta la pagina :princing.', 'social_account' => 'Se hai problemi ad accedere al vostro account, ti ricordiamo che stai utilizzando un login :provider. È possibile modificarlo nelle impostazioni dell\'account.', 'title' => 'Iniziare con Kanka', ]; ================================================ FILE: lang/it/entities/abilities.php ================================================ [ 'add' => 'Aggiungi abilità', 'reset' => 'Ripristina gli usi dell\'abilità', 'sync' => 'Aggiungi alle stirpi', ], 'charges' => [ 'left' => ':amount rimaste', ], 'create' => [ 'success' => 'Abilità :ability aggiunta a :entity.', 'success_multiple' => 'Abilità :abilities aggiunte a :entity', 'title' => 'Aggiungi abilità a :name', ], 'fields' => [ 'note' => 'Nota', 'position' => 'Posizione', ], 'groups' => [ 'unorganised' => 'Disorganizzato', ], 'helpers' => [ 'note' => 'Puoi citare un\'entità usando la menzione avanzata (ex :code) e gli attributi dell\'entità (ex :attr) in questo campo.', 'recharge' => 'Azzeramento di tutte le cariche delle abilità utilizzate.', 'sync' => 'Importa le abilità definite nelle stirpi del personaggio.', ], 'import' => [ 'errors' => [ 'no_race' => 'Il personaggio non ha una stirpe.', 'not_character' => 'Questa entità non è un personaggio.', ], 'success' => '{1} :count abilità importata.|[2,*] :count abilità importate.', ], 'recharge' => [ 'success' => 'Tutte le cariche sono state azzerate.', ], 'reorder' => [ 'parentless' => 'Nessun Genitore', 'success' => 'Abiità riordinate con successo', ], 'show' => [ 'helper' => 'Collega le abilità a questa entità. È sempre possibile modificare la visibilità o rimuovere un\'abilità. Le abilità che appartengono alla stessa abilità genitore verranno visualizzate come caselle di filtro.', 'reorder' => 'Riordina', 'title' => 'Abilità di :name', ], 'types' => [ 'unorganised' => 'Le abilità sono raggruppate in base al loro campo genitore, e si trovano qui.', ], 'update' => [ 'success' => 'Abilità dell\'entità :ability aggiornata.', 'title' => 'Abilità dell\'entità per :name', ], ]; ================================================ FILE: lang/it/entities/actions.php ================================================ [ 'set' => 'Imposta come modello', 'success' => [ 'set' => 'Entità :name impostata come modello.', 'unset' => 'L\'entità :name non è più impostata come modello.', ], 'toggle' => 'Stato del modello modificato.', 'unset' => 'Rimuovi come modello', ], ]; ================================================ FILE: lang/it/entities/aliases.php ================================================ [ 'add' => 'Aggiungi un pseudonimo', ], 'create' => [ 'success' => 'Pseudonimo :name aggiunto a :entity', 'title' => 'Aggiungi pseudonimo a :name', ], 'destroy' => [ 'success' => 'Pseudonimo :name rimosso.', ], 'fields' => [ 'name' => 'Nome', ], 'helpers' => [ 'primary' => 'Impostando uno o più pseudonimi sull\'entità, la si potrà trovare nella ricerca globale (barra superiore) e attraverso le menzioni.', ], 'pitch' => 'Crea dei pseudonimi per questa entità per trovarla facilmente attraverso la ricerca e le menzioni.', 'placeholders' => [ 'name' => 'Nuovo Pseudonimo', ], 'update' => [ 'success' => 'Pseudonimo :name aggiornato per :entity.', 'title' => 'Aggiorna pseudonimo per :name', ], ]; ================================================ FILE: lang/it/entities/assets.php ================================================ [ 'alias' => 'Pseudonimo', 'file' => 'File', 'link' => 'Link', ], 'copy_alias' => [ 'success' => 'Menzione dello pseudonimo copiato negli appunti.', ], 'show' => [ 'title' => 'Assets :name', ], ]; ================================================ FILE: lang/it/entities/attributes.php ================================================ [ 'load' => 'Carica', 'manage' => 'Gestisci', 'more' => 'Altro', 'remove_all' => 'Elimina tutto', 'save_and_edit' => 'Applica e Modifica', 'save_and_story'=> 'Applica e Visualizza', 'show_hidden' => 'Mostra attributi nascosti', 'toggle_privacy'=> 'Privato/Pubblico', ], 'errors' => [ 'loop' => 'Il calcolo di questo attributo è un ciclo infinito!', 'no_attribute_selected' => 'Seleziona prima uno o più attributi.', 'too_many_v2' => 'Campi massimi raggiunti (:count/:max). Elimina alcuni attributi prima di poterne aggiungere altri.', ], 'fields' => [ 'community_templates' => 'Modelli della Comunità', 'is_private' => 'Attributi Privati', 'is_star' => 'Fissato', 'preferences' => 'Preferenze', 'value' => 'Valore', ], 'filters' => [ 'name' => 'Nome dell\'attributo', 'value' => 'Valore dell\'attributo', ], 'helpers' => [ 'delete_all' => 'Sei sicuro di voler cancellare tutti gli attributi di questa entità?', 'is_private' => 'Consenti solo ai membri del ruolo :admin-role di vedere gli attributi di questa entità.', 'setup' => 'Puoi rappresentare elementi come Punti Ferita o l\'Intelligenza di un\'entità con degli attributi. È possibile aggiungere manualmente gli attributi facendo clic sul pulsante :manage, oppure applicare automaticamente quelli di un modello di attributo.', ], 'hints' => [], 'index' => [ 'success' => 'Attributi per :entity aggiornati.', 'title' => 'Attributi per :name', ], 'labels' => [ 'checkbox' => 'Nome del Checkbox', 'name' => 'Nome dell\'attributo', 'section' => 'Nome della sezione', 'value' => 'Valore dell\'attributo', ], 'live' => [ 'success' => 'Attributo :attribute aggiornato.', 'title' => 'Aggiornando :attribute', ], 'placeholders' => [ 'attribute' => 'Numero di Conquiste, Grado di Sfida, Iniziativa, Popolazione', 'block' => 'Blocca nome', 'checkbox' => 'Nome del Checkbox', 'icon' => [ 'class' => 'Classe: fas fa-users di FontAwesome o RPG Awesome', 'name' => 'Nome dell\'icona', ], 'number' => 'Valore numerico', 'random' => [ 'name' => 'Nome dell\'attributo', 'value' => '1-100 o lista di valori separati da una virgola', ], 'section' => 'Nome della sezione', 'value' => 'Valore dell\'attributo', ], 'ranges' => [ 'text' => 'Opzioni disponibili :options', ], 'sections' => [ 'unorganised' => 'Disorganizzato', ], 'show' => [ 'hidden' => 'Attributi Nascosti', 'title' => 'Attributi di :name', ], 'template' => [ 'load' => [ 'success' => 'Modello caricato', 'title' => 'Carica dal Modello', ], 'success' => 'Il Modello di Attributi :name è stato applicato a :entity', 'title' => 'Applica un Modello degli Attributi per :name', ], 'title' => 'Attributi', 'toasts' => [ 'bulk_deleted' => 'Attributi eliminati', 'bulk_privacy' => 'Privacy degli attributi attivata', 'lock' => 'Attributo bloccato', 'pin' => 'Attributo fissato', 'unlock' => 'Attributo sbloccato', 'unpin' => 'Attributo non fissato', ], 'types' => [ 'attribute' => 'Attributo', 'block' => 'Blocca', 'checkbox' => 'Checkbox', 'icon' => 'Icona', 'number' => 'Numero', 'random' => 'Casuale', 'section' => 'Sezione', 'text' => 'Testo Multilinea', ], 'update' => [ 'success' => 'Attributi per :entity aggiornati', ], 'visibility' => [ 'entry' => 'Gli attributi sono visualizzati nel menu dell\'entità.', 'private' => 'Attributo visibile solo ai membri del ruolo "Amministratore".', 'public' => 'Attributo visibile a tutti i membri.', 'tab' => 'L\'attributo viene visualizzato solo nella scheda Attributi.', ], ]; ================================================ FILE: lang/it/entities/events.php ================================================ [ 'type' => 'Tipo di Evento', ], 'helpers' => [ 'characters' => 'Impostando il tipo come data di nascita o di morte di questo personaggio, si calcolerà automaticamente la sua età. :more.', 'founding' => 'Impostando il tipo come :type si calcolerà automaticamente l\'età dell\'entità dalla fondazione.', ], 'show' => [ 'actions' => [ 'add' => 'Aggiungi evento', ], 'title' => 'Evento :name', ], 'types' => [ 'birth' => 'Nascita', 'birthday' => 'Compleanno', 'death' => 'Morte', 'founded' => 'Fondazione', 'primary' => 'Primario', ], 'years-ago' => '{1} :count anno fa|[2,*] :count anni fa', ]; ================================================ FILE: lang/it/entities/files.php ================================================ [], 'create' => [ 'success_plural' => '{1} File :name aggiunto.|[2,*] :count file aggiunti.', 'title' => 'Nuovo file per :entity', ], 'destroy' => [ 'success' => 'File :name rimosso.', ], 'fields' => [ 'file' => 'File', 'name' => 'Nome del file', ], 'max' => [ 'title' => 'Limite raggiunto', ], 'update' => [ 'success' => 'File :name aggiornato.', ], ]; ================================================ FILE: lang/it/entities/image.php ================================================ [ 'change_focus' => 'Cambia punto focus', 'replace_image' => 'Sostituisci immagine', 'save-replace' => 'Sostituisci immagine', 'save_focus' => 'Salva punto focus', 'view' => 'Visualizza immagine', ], 'call-to-action' => 'Fai clic sull\'immagine dell\'entità per impostarne il punto focus, invece di utilizzare il sistema di riconoscimento automatico.', 'focus' => [ 'breadcrumb' => 'Focus dell\'immagine', 'helper' => 'Clicca sull\'immagine per impostarne il punto focus. Clicca sul punto focus per rimuoverlo.', 'panel_title' => 'Focus dell\'Immagine', 'success' => 'Focus dell\'immagine aggiornato', 'title' => 'Focus dell\'immagine per :name', 'unboosted' => 'L\'impostazione di un punto focus dell\'immagine è riservata a :boosted-campaigns.', 'warning' => 'Il punto focus delle immagini della :gallery è condivisa da tutte le entità che usano la stessa immagine.', ], 'gallery_permissions' => [ 'admin' => 'La galleria di immagini è visibile solo ai membri della campagna con il ruolo :admin.', 'adminself' => 'La galleria di immagini è visibile solo a :creator e ai membri della campagna con il ruolo :admin.', 'member' => 'La galleria di immagini è visibile solo ai membri della campagna.', 'self' => 'La galleria di immagini è visibile solo da te.', ], 'replace' => [ 'breadcrumb' => 'Sostituzione dell\'immagine', 'panel_title' => 'Sostituzione dell\'Immagine dell\'Entità', 'success' => 'Immagine sostituita.', 'title' => 'Sostituzione dell\'immagine dell\'entità :name', ], ]; ================================================ FILE: lang/it/entities/inventories.php ================================================ [ 'copy_inventory' => 'Copia inventario', ], 'copy' => [], 'create' => [ 'success' => 'L\'oggetto :item è stato aggiunto ad :entity.', 'success_bulk' => '{0} Nessun oggetto aggiunto a :entity.|{1} Aggiunto :count oggetto a :entity.|[2,*] Aggiunti :count oggetti a :entity.', 'title' => 'Aggiungi un oggetto a :name', ], 'default_position' => 'Disorganizzato', 'destroy' => [ 'success' => 'L\'oggetto :item è stato rimosso da :entity.', 'success_position' => 'Oggetti in :position rimossi da :entity.', ], 'fields' => [ 'amount' => 'Quantità', 'copy_entity_entry_v2' => 'Utilizza la voce dell\'oggetto', 'description' => 'Descrizione', 'is_equipped' => 'Equipaggiato', 'name' => 'Nome', 'position' => 'Posizione', 'qty' => 'Quantità', ], 'helpers' => [ 'amount' => 'Numero degli oggetti', 'copy_entity_entry_v2' => 'Visualizza la voce dell\'oggetto invece della descrizione personalizzata.', 'description' => 'Aggiungi una descrizione personalizzata all\'oggetto', 'is_equipped' => 'Contrassegna questo oggetto come equipaggiato.', 'name' => 'Assegna il nome all\'oggetto. Il nome è richiesto se non è stato selezionato alcun oggetto', ], 'placeholders' => [ 'amount' => 'Qualsiasi quantità', 'description' => 'Utilizzato, Danneggiato, In Sintonia', 'name' => 'Richiesto se nessun oggetto è stato selezionato', 'position' => 'Equipaggiato, Zaino, Magazzino, Banca', ], 'show' => [ 'helper' => 'Le entità possono avere oggetti collegati ad esse per creare degli inventari.', 'title' => 'Inventario dell\'Entità :name', 'unsorted' => 'Non classificato', ], 'tooltips' => [ 'equipped' => 'Questo oggetto è equipaggiato', ], 'update' => [ 'success' => 'L\'oggetto :item è stato aggiornato per :entity.', 'title' => 'Aggiorna un oggetto per :name', ], ]; ================================================ FILE: lang/it/entities/links.php ================================================ [ 'add' => 'Aggiungi un link', ], 'call-to-action' => 'Aggiungendo collegamenti a risorse esterne a questa entità, come ad esempio a DnDBeyond, questi verranno visualizzati direttamente nella panoramica dell\'entità.', 'create' => [ 'success' => 'Link :name aggiunto a :entity.', 'title' => 'Aggiunto un link a :name', ], 'destroy' => [ 'success' => 'Link :name rimosso.', ], 'fields' => [ 'icon' => 'Icona', 'name' => 'Nome', 'position' => 'Posizione', 'url' => 'URL', ], 'go' => [ 'actions' => [ 'confirm' => 'Sono sicuro', 'trust' => 'Non chiedermelo di nuovo', ], 'description' => 'Questo link esterno ti porterà a :link. Sei sicuro di volerci andare?', 'title' => 'Stai lasciando Kanka', ], 'helpers' => [ 'icon' => 'Puoi personalizzare l\'icona visualizzata per il collegamento. Puoi utilizzare una delle icone di :fontawesome, :rpgawesome o lasciare questo campo vuoto per il valore predefinito. Per saperne di più, consulta i nostri :docs.', 'parent' => 'Visualizza questo collegamento rapido dopo un elemento della barra laterale, anziché nella sezione dei collegamenti rapidi della barra laterale.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Le campagne premium possono aggiungere link a siti esterni.', 'title' => 'Links per :name', ], 'update' => [ 'success' => 'Link :name aggiornato per :entity', 'title' => 'Modifica link per :name', ], ]; ================================================ FILE: lang/it/entities/logs.php ================================================ [ 'create' => 'Creazione', 'create_post' => 'Creato post ":post"', 'delete' => 'Cancellazione', 'delete_post' => 'Post eliminato', 'reorder_post' => 'Post riordinato', 'restore' => 'Recupero', 'update' => 'Aggiornamento', 'update_post' => 'Post aggiornato ":post"', 'view' => 'Visualizza cambiamenti', ], 'call-to-action' => 'I log completi delle modifiche fino a :amount giorni sono disponibili per le campagne superpotenziate.', 'fields' => [ 'action' => 'Azione', 'date' => 'Data', ], 'impersonated' => 'Impersonato da :name', 'show' => [ 'title' => 'Logs dell\'Entità :name', ], ]; ================================================ FILE: lang/it/entities/map-points.php ================================================ 'Questa entità è appuntata sulle seguenti mappe.', 'title' => 'Punti per la Mappa :name', ]; ================================================ FILE: lang/it/entities/mentions.php ================================================ [ 'element' => 'Elemento', 'type' => 'Tipo', ], 'helper' => 'Di seguito la lista delle entità che menzionano questa entità in uno dei loro campi.', 'mentioned_in_v2' => 'Questa entità viene menzionata in :count entità, post o campagne. :more.', 'see_more' => 'Vedi dettagli', 'show' => [ 'title' => 'Menzioni dell\'Entità :name', ], 'title' => 'Entità menzionata', ]; ================================================ FILE: lang/it/entities/move.php ================================================ [ 'copy' => 'Copia', ], 'errors' => [ 'permission' => 'Non puoi creare entità di questo tipo nella campagna di destinazione.', 'permission_update' => 'Non puoi muovere questa entità.', 'same_campaign' => 'Devi selezionare un\'altra campagna per muoverci l\'entità.', 'unknown_campaign' => 'Campagna sconosciuta.', ], 'fields' => [ 'campaign' => 'Campagna di destinazione', 'copy' => 'Crea una copia', 'select_one' => 'Seleziona una campagna', ], 'helpers' => [ 'copy' => 'Crea una copia dell\'entità nella campagna di destinazione.', ], 'panel' => [ 'description' => 'Muovi questa entità a un\'altra campagna, o crea una copia di questa in un\'altra campagna.', 'description_bulk_copy' => 'Seleziona una campagna in cui vuoi copiare le entità selezionate.', 'title' => 'Muovi o copia un\'entità a un\'altra campagna', ], 'success' => 'Entità :name mossa alla campagna :campaign.', 'success_copy' => 'Entità :name copiata alla campagna :campaign.', 'title' => 'Muovi :name', ]; ================================================ FILE: lang/it/entities/notes.php ================================================ [ 'add' => 'Aggiungi un nuovo Post', 'add_role' => 'Aggiungi un ruolo', 'add_user' => 'Aggiungi un utente', ], 'collapsed' => [ 'closed' => 'Il post è ridotto alla sola intestazione', 'open' => 'Il post è esteso', ], 'copy_mention' => [ 'copy' => 'Copia la menzione avanzata', 'copy_with_name' => 'Copia la menzione avanzata con il nome del post', 'success' => 'La menzione avanzata al post è stata copiata negli appunti.', ], 'create' => [ 'success' => 'Post \':name\' aggiunto a :entity', ], 'destroy' => [ 'success' => 'Post :name per :entity rimosso.', ], 'edit' => [ 'success' => 'Post :name per :entity modificato.', ], 'fields' => [ 'creator' => 'Creatore', 'display' => 'Visualizza', 'name' => 'Nome', 'position' => 'Posizione', ], 'footer' => [ 'created' => 'Creato da :user il :date', 'updated' => 'Aggiornato da :user il :date', ], 'hint' => 'Le informazioni che non si adattano abbastanza ai campi standard di un\'entità o che devono essere mantenute private possono essere aggiunte come Post.', 'hints' => [ 'reorder' => 'Puoi riordinare i post di un\'entità cliccando sull\'icona :icon nell\'intestazione dell\'entità.', ], 'index' => [], 'move' => [ 'copy' => 'Crea una copia sull\'entità di destinazione', 'copy_success' => 'Post :name copiato in :entity con successo.', 'copy_title' => 'Conserva una copia', 'description' => 'Seleziona un\'entità in cui desideri spostare o creare una copia di questo post.', 'entity' => 'Entità di destinazione', 'move_success' => 'Post :name mosso in :entity con successo.', ], 'placeholders' => [ 'name' => 'Nome del post, della nota o del commento.', ], 'show' => [ 'advanced' => 'Autorizzazioni Avanzate', 'title' => 'Post dell\'entità :name per :entity', ], 'states' => [ 'collapsed' => 'Ridotto', 'expanded' => 'Espanso', ], 'warning' => [], ]; ================================================ FILE: lang/it/entities/permissions.php ================================================ [ 'text' => 'Questa entità è impostata come privata. È ancora possibile definire autorizzazioni personalizzate, ma finché l\'entità è privata, queste saranno ignorate e solo i membri del ruolo :admin della campagna potranno vedere l\'entità.', 'warning' => 'Attenzione', ], 'quick' => [ 'empty-permissions' => 'Nessun ruolo o utente fuori dagli amministratori della campagna hanno accesso a questa entità.', 'manage' => 'Gestisci i Permessi', 'screen-reader' => 'Apri impostazioni della privacy', 'success' => [ 'private' => ':entity è attualmente nascosto.', 'public' => ':entity è attualmente visibile.', ], 'title' => 'Panoramica dei Permessi', 'viewable-by' => 'Visibile da', ], ]; ================================================ FILE: lang/it/entities/pins.php ================================================ 'Link', 'title' => 'Pin', ]; ================================================ FILE: lang/it/entities/profile.php ================================================ [ 'edit_profile' => 'Modifica profilo', ], 'history' => 'Cronologia delle Modifiche', 'show' => [ 'tab_name' => 'Profilo', 'title' => 'Profilo :name', ], ]; ================================================ FILE: lang/it/entities/quests.php ================================================ 'Questa entità è parte delle seguenti missioni.', 'title' => 'Missioni :name', ]; ================================================ FILE: lang/it/entities/relations.php ================================================ [ 'mode-map' => 'Strumento di Esplorazione di Legami', 'mode-table' => 'Tabella dei legami e degli elementi correlati', ], 'bulk' => [ 'delete' => '{0} Rimossi :count legami|{1} Rimosso :count legame.|[2,*] Rimossi :count legami.', 'fields' => [ 'delete_mirrored' => 'Elimina i speculari', 'unmirror' => 'Scollega i speculari', 'update_mirrored' => 'Aggiorna i speculari', ], 'helpers' => [ 'delete_mirrored' => 'Elimina anche i legami speculari.', 'unmirror' => 'Scollega i legami speculari.', 'update_mirrored' => 'Aggiorna i legami speculari.', ], 'success' => [ 'editing' => '{0} :count legami sono stati aggiornati|{1} :count legame è stato aggiornato.|[2,*] :count legami sono stati aggiornati.', 'editing_partial' => '{0} :count/:total legami sono stati aggiornati|{1} :count/:total legame è stato aggiornato.|[2,*] :count/:total legami sono stati aggiornati.', ], ], 'call-to-action' => 'Esplora visivamente i legami di questa entità e il modo in cui è collegata al resto della campagna.', 'connections' => [ 'map_point' => 'Punto mappa', 'mention' => 'Menzione', 'quest_element' => 'Elemento di Missione', 'timeline_element' => 'Elemento di Linea Temporale', ], 'create' => [ 'new_title' => 'Nuovo legame', 'success_bulk' => '{1} Aggiunto :count legame a :entity.|[2,*] Aggiunti :count legami a :entity.', ], 'delete_mirrored' => [ 'helper' => 'Questo legame è speculare nell\'entità bersaglio. Selezionare questa opzione rimuoverà anche il legame speculare.', 'option' => 'Elimina il legame speculare', ], 'destroy' => [ 'mirrored' => 'Eliminerai il legame speculare in modo permanente.', 'success' => 'Legame :target rimosso per :name.', ], 'fields' => [ 'attitude' => 'Attitudine', 'is_pinned' => 'Fissato', 'owner' => 'Fonte', 'target' => 'Entità Bersaglio', 'two_way' => 'Crea anche il legame speculare', 'unmirror' => 'Togli l\'impostazione speculare di questo legame.', ], 'filters' => [ 'connection' => 'Relazione del legame', 'name' => 'Bersaglio del legame', ], 'helper' => 'Imposta i legami fra due entità con atteggiamento e visibilità. I legami possono anche essere fissati nel menù dell\'entità.', 'helpers' => [ 'no_relations' => 'Questa entità non ha attualmente nessun legame ad altre entità nella campagna.', ], 'hints' => [ 'attitude' => 'Questo campo opzionale può essere utilizzato per definire l\'ordine predefinito di visualizzazione dei legami in ordine decrescente.', 'two_way' => 'Se scegli di creare un legame speculare, il medesimo legame sarà creato per l\'entità bersaglio: se ne modificherai uno, tuttavia, l\'altro non verrà aggiornato.', ], 'index' => [ 'title' => 'Legami', ], 'options' => [ 'mentions' => 'Predefinito + correlato + menzioni', 'only_relations' => 'Solo legami diretti', 'related' => 'Predefinito + correlato', 'relations' => 'Predefinito', 'show' => 'Mostra', ], 'panels' => [ 'related' => 'Correlato', ], 'placeholders' => [ 'attitude' => '-100 fino a 100, in cui 100 rappresenta un attitudine molto positiva', ], 'show' => [ 'title' => 'Legami per :name', ], 'types' => [ 'family_member' => 'Membro di Famiglia', 'organisation_member' => 'Membro di Organizzazione', ], 'update' => [ 'success' => 'Legame :target aggiornato per :name.', 'title' => 'Aggiorna i legami per :name', ], ]; ================================================ FILE: lang/it/entities/story.php ================================================ [ 'collapse_all' => 'Comprimi tutto', 'expand_all' => 'Espandi tutto', 'load_more' => 'Carica altro', 'login_for_more' => 'Accedi per visualizzare più post', ], 'reorder' => [ 'icon_tooltip' => 'Riordina posts', 'panel_title' => 'Riordina posts', 'save' => 'Salva il nuovo ordine', 'success' => 'Posts riordinati.', ], 'update' => [ 'title' => 'Aggiorna :entity inserimento', ], 'warning' => [], ]; ================================================ FILE: lang/it/entities/timelines.php ================================================ 'Le linee temporali che hanno elementi collegati a questa entità sono mostrate di seguito.', 'show' => [ 'title' => 'Linee temporali :name', ], ]; ================================================ FILE: lang/it/entities/transform.php ================================================ [], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Tipo di entità sconosciuto o non valido.', ], 'success' => '{1} :count entità trasformata a nuovo tipo :type.|[2,*] :count entità trasformate al nuovo tipo: :type.', ], 'fields' => [ 'select_one' => 'Seleziona uno', 'target' => 'Nuovo tipo di entità', ], 'panel' => [ 'bulk_description' => 'Cambia il tipo di entità in molteplici entità. Tieni presente che alcuni dati potrebbero andare persi a causa dei diversi campi tra i tipi di entità.', 'bulk_title' => 'Trasforma le entità in blocco', 'title' => 'Trasforma un entità', ], 'success' => 'Entità :name trasformata.', 'title' => 'Trasforma :name', ]; ================================================ FILE: lang/it/entities.php ================================================ 'Abilità', 'ability' => 'Abilità', 'attribute_template' => 'Modello di Attributi', 'attribute_templates' => 'Modelli di Attributi', 'bookmark' => 'Segnalibro', 'bookmarks' => 'Segnalibri', 'calendar' => 'Calendario', 'calendars' => 'Calendari', 'campaign' => 'Campagna', 'campaigns' => 'Campagne', 'character' => 'Personaggio', 'characters' => 'Personaggi', 'conversation' => 'Conversazione', 'conversations' => 'Conversazioni', 'creator' => [ 'actions' => [ 'create' => 'Crea :type', 'full' => 'Vai al modulo completo', 'more' => 'Aggiungi dettagli', ], 'back' => 'Ritorna alla selezione', 'bulk_names' => 'Aggiungi un nome per riga', 'duplicate' => 'Attenzione! Ci sono altre entità di questo tipo con lo stesso nome.', 'helper_v2' => 'Crea velocemente le basi di una nuova entità senza interrompere il flusso di pensieri.', 'missing_v2' => 'Solo i moduli abilitati e che hai il permesso di creare sono mostrati in questa interfaccia. :learn-more.', 'modes' => [ 'bulk' => 'Aggiungi in blocco', 'default' => 'Aggiungi veloce', ], 'name' => [ 'new' => 'Nuovo nome', 'remove' => 'Rimuovi', ], 'success_multiple' => '{1} Nuova entità :link creata.|[2,*] Nuove entità :link create.', 'success_multiple_posts' => '{1} Nuovo post :link creato.|[2,*] Nuovi posts :link creati.', 'title' => 'Nuova Entità', 'titles' => [ 'everything' => 'Tutto', 'quick-access' => 'Accesso veloce', ], 'tooltip' => 'Crea una nuova entità senza lasciare la pagina corrente.', 'tooltips' => [ 'create' => 'Crea un\'entità e torna alla schermata di selezione delle entità', 'create_more' => 'Crea un\'entità e comincia a crearne un\'altra dello stesso tipo', 'edit' => 'Crea un\'entità e comincia a modificarla', ], ], 'creature' => 'Creatura', 'creatures' => 'Creature', 'dice_roll' => 'Tiro di Dadi', 'dice_rolls' => 'Tiri di Dadi', 'event' => 'Evento', 'events' => 'Eventi', 'families' => 'Famiglie', 'family' => 'Famiglia', 'inventories' => 'Inventari', 'item' => 'Oggetto', 'items' => 'Oggetti', 'journal' => 'Diario', 'journals' => 'Diari', 'location' => 'Luogo', 'locations' => 'Luoghi', 'map' => 'Mappa', 'maps' => 'Mappe', 'new' => [], 'note' => 'Nota', 'notes' => 'Note', 'organisation' => 'Organizzazione', 'organisations' => 'Organizzazioni', 'quest' => 'Missione', 'quest_element' => 'Elemento della missione', 'quests' => 'Missioni', 'race' => 'Stirpe', 'races' => 'Stirpi', 'relation' => 'Legame', 'relations' => 'Legami', 'tag' => 'Tag', 'tags' => 'Tag', 'timeline' => 'Linea Temporale', 'timeline_element' => 'Elemento della linea temporale', 'timelines' => 'Linee Temporali', ]; ================================================ FILE: lang/it/errors.php ================================================ [ 'body' => 'Sembra che tu non abbia il permesso per accedere a questa pagina!', 'title' => 'Permesso Negato', ], '403-form' => [ 'help' => 'Forse la tua sessione è scaduta. Prova ad accedere di nuovo in un\'altra finestra prima di salvare.', ], '404' => [ 'body' => 'Ci dispiace, la pagina che stavi cercando non può essere trovata.', 'title' => 'Pagina non trovata', ], '500' => [ 'body' => [ '1' => 'Ops, sembra che qualcosa non abbia funzionato.', '2' => 'Un report con l\'errore riscontrato ci è appena stato inviato ma ogni tanto ci può essere d\'aiuto sapere qualcosa in più su cosa stessi facendo.', ], 'title' => 'Errore', ], '503' => [ 'body' => [ '1' => 'Kanka è attualmente in manutenzione che normalmente significa che c\'è un aggiornamento in corso!', '2' => 'Ci dispiace per l\'inconveniente. Tutto tornerà alla normalità in pochi minuti.', ], 'json' => 'Kanka è al momento in manutenzione, riprova fra qualche minuto.', 'title' => 'Manutenzione', ], '503-form' => [], 'footer' => 'Se necessiti di ulteriore assistenza per favore contattaci a hello@kanka.io oppure su :discord', 'post_layout' => 'Layout del post non valido.', ]; ================================================ FILE: lang/it/events.php ================================================ [ 'title' => 'Nuovo Evento', ], 'destroy' => [], 'edit' => [], 'events' => [ 'helper' => 'Gli eventi che hanno questa entità come evento genitore sono visualizzati qui.', ], 'fields' => [ 'date' => 'Data', ], 'helpers' => [ 'date' => 'Questo campo può contenere qualsiasi cosa e non è collegato ai calendari della campagna. Per collegare questo evento a un calendario, aggiungilo al calendario o alla sottopagina dei promemoria di questo evento.', ], 'index' => [], 'placeholders' => [ 'date' => 'Una data per il tuo evento', 'type' => 'Cerimonia, Festival, Disastro, Battaglia, Nascita', ], 'show' => [], 'tabs' => [ 'calendars' => 'Elementi del Calendario', ], ]; ================================================ FILE: lang/it/families/trees.php ================================================ [ 'clear' => 'Cancella tutto', 'first' => 'Aggiungi un fondatore', 'founder' => 'Aggiungi un nuovo fondatore', 'rename-relation' => 'Rinomina la relazione', 'reset' => 'Scarta i cambiamenti', 'save' => 'Salva', ], 'modal' => [ 'first-title' => 'Seleziona un\'entità', 'helper' => 'Sostituisci l\'entità con un\'altra della stessa campagna', 'relation' => 'Relazione', 'title' => 'Sostituisci l\'entità', ], 'modals' => [ 'clear' => [ 'confirm' => 'Sei sicuro di voler reinizializzare tutti i dati dell\'albero genealogico?', ], 'entity' => [ 'add' => [ 'founder' => 'Fondatore', 'member' => 'Membro', 'success' => 'Entità aggiunta.', 'title' => 'Aggungi un\'entità', ], 'child' => [ 'success' => 'Figlio aggiunto.', 'title' => 'Aggiungi un figlio', ], 'edit' => [ 'helper' => 'Seleziona questa opzione se la relazione è sconosciuta. Un personaggio può essere aggiunto in seguito.', 'success' => 'Entità aggiornata.', 'title' => 'Aggiorna un\'entità', ], 'founder' => [ 'title' => 'Aggiungi un nuovo fondatore', ], 'remove' => [ 'confirm' => 'Sei sicuro di voler rimuovere questa entità dall\'albero genealogico?', 'success' => 'Entità rimossa.', ], ], 'relations' => [ 'add' => [ 'success' => 'Relazione aggiunta.', 'title' => 'Aggiungi una relazione', ], 'edit' => [ 'success' => 'Relazione aggiornata.', 'title' => 'Aggiorna una relazione', ], 'unknown' => 'Sconosciuto', ], 'reset' => [ 'confirm' => 'Sei sicuro di voler eliminare tutte le modifiche apportate all\'albero genealogico?', ], ], 'pitch' => 'Crea un dettagliato albero genealogico per le famiglie della campagna.', 'success' => [ 'cleared' => 'Albero genealogico cancellato.', 'reseted' => 'L\'albero genealogico è stato ripristinato.', 'saved' => 'L\'albero genealogico è stato salvato.', ], 'title' => 'Albero Genealogico :name', 'unknown' => 'non stabilito', ]; ================================================ FILE: lang/it/families.php ================================================ [ 'title' => 'Nuova Famiglia', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Questa famiglia è estinta.', 'members' => 'I membri di una famiglia sono mostrati qui. Un personaggio può essere aggiunto alla famiglia modificando il personaggio, usando il selettore "Famiglia".', ], 'index' => [], 'members' => [ 'create' => [ 'success' => '{0} Nessun membro è stato aggiunto.|{1} 1 membro è stato aggiunto.|[2,*] :count membri sono stati aggiunti.', 'title' => 'Nuovi Membri', ], ], 'placeholders' => [ 'name' => 'Nome della famiglia', 'type' => 'Famiglia Reale, Nobile, Estinta', ], 'show' => [ 'tabs' => [ 'tree' => 'Albero Genealogico', ], ], ]; ================================================ FILE: lang/it/faq.php ================================================ [ 'answer' => <<<'TEXT' Il miglior modo in cui possiamo spiegarti i Template di Attributi è con un esempio. Immaginiamo per esempio che il tuo mondo abbia molti Luoghi e su molti di questi luoghi vuoi ricordarti di creare degli attributi personalizzati per "Popolazione", "Clima", "Livello di Criminalità". Ora, puoi facilmente farlo su ogni Luogo ma può diventare tedioso e potresti dimenticarti qualche volta di reare l'attributo "Livello di Criminalità". Qui è dove i Template di Attributi entrano in gioco. Puoi creare un Template di Attributi con questi attributi (Popolazione, Clima, Livello di Criminalità, etc.), e successivamente applicare questo template sui tuoi luoghi. Questo creerà gli attributi del template nel tuo luogo quindi tutto quello che dovrai fare sarà cambiarne i valori e non ricordarti degli attributi da creare! TEXT , 'question' => 'Templates di Attributi, che cosa sono?', ], 'backup' => [ 'answer' => 'Una volta al giorno, puoi esportare tutti i dati della tua campagna in un file ZIP. Nnell\'app, clicca su "Campagna" nel menù a sinistra, poi clicca su "Esporta". Questa azione creerà un file esportato che sarà disponibile per 30 minuti. Non puoi caricare questo file esportato su Kanka, è inteso solamente per tua tranquillità o se non pensi più di usare l\'app.', 'question' => 'Come posso fare un backup della mia campagna o esportarla?', ], 'bugs' => [ 'answer' => 'Puoi semplicemente unirti al nostro server :discord e riferire il tuo bug nel canale #error-and-bugs (in lingua inglese).', 'question' => 'Come posso riferire un bug?', ], 'campaign-sync' => [ 'answer' => 'Kanka non ha questa funzionalità. In ogni caso, se stai cercando di gestire vari gruppi di gioco nel medesimo mondo, puoi provare a usare la stessa campagna e a separare i tuoi gruppi per mezzo di una combinazione di missioni, tags e permessi.', 'question' => 'Posso sincronizzare entità per campagne multiple?', ], 'custom' => [ 'answer' => 'Kanka parte con un set di tipi predefiniti di entità che interagiscono l\'uno con l\'altro. Permettere la creazione di tipi di entità personalizzati richiederebbe la ricostruzione dell\'app da zero, rendendo vano lo scopo di uno strumento dotato di tipi predefiniti per aiutare persone a creare mondi, piuttosto che a cercare di capire come organizzare cose. Inoltre, Kanka è flessibile con i Tags, che possono rappresentare la maggior parte dei probabili tipi di entità personalizzati.', 'question' => 'Posso creare tipi di entità personalizzati?', ], 'delete-campaign' => [ 'answer' => 'Vai alla bacheca della tua campagna, e clicca su "Campagna" nel menù a sinistra. Apparirà un pulsante "Rimuovi" se sei l\'ultimo membro della campagna. Cancellare una campagna è un\'azione permanente che eliminerà tutti i dati presenti sui nostri server, incluse le immagini.', 'question' => 'Come posso eliminare una campagna?', ], 'entity-notes' => [ 'answer' => 'Tutte le entità hanno le "Note dell\'Entità" che sono piccoli testi che possono essere impostati per essere visibili solamente da te (perfetto quando co-amministrata), solo agli amministratori della campagna o a tutti. Puoi anche dare i permessi ai tuoi membri per creare e modificare le note senza il bisogno di abilitarli alla modifica completa dell\'entità.', 'question' => 'Come Kanka gestisce le informazioni parzialmente nascoste?', ], 'fields' => [ 'answer' => 'Risposta', 'category' => 'Categoria', 'locale' => 'Locale', 'order' => 'Ordine', 'question' => 'Domanda', ], 'free' => [ 'answer' => <<<'TEXT' Sì! Noij crediamo nel fatto che il tuo stato finanziario non deve impattare la gioia del giocare di ruolo o di creare nuovi mondi e così manterremo sempre l'app gratuita. Grazie ai nostri generosi Patrons su Patreon siamo in grado di coprire il costo mensile dei server e mantenere l'app senza pubblicità! Supportarci su Patreon però ti permette di incrementare il limite di dimensione dei file che puoi caricare, aggiunge il tuo nome al wall of fame dei Patreon, avere delle icone di default più curate, votare per prioritizzare cosa verrà sviluppato ed altro ancora! TEXT , 'question' => 'L\'app resterà gratuita?', ], 'gods-and-religions' => [ 'answer' => 'Consigliamo la creazione di Divinità come Personaggi, e di Religioni come Organizzazioni. Se vuoi trovare velocemente le tue divinità, ti consigliamo di taggarle con un Tag e/o un tipo appropriato.', 'question' => 'Dove posso creare Dei e religioni?', ], 'help' => [ 'answer' => 'Prima di tutto grazie per volerci aiutare! Siamo sempre interessati in persone che possono aiutarci con le traduzioni, nel testare le nuove funzionalità o che possano aiutare i nuovi utenti. Adoriamo anche quando le persone promuovono Kanka per raggiungere nuove utenze in posti a cui non avevamo pensato. La miglior cosa che puoi fare è unirti a noi su Discord dove un canale è dedicato all\'aiuto degli utenti. Adoriamo anche i nostri patrons su Patreon se vuoi supportarci e ottenere l\'accesso a qualche vantaggio!', 'question' => 'Voglio aiutare! Cosa posso fare?', ], 'map' => [ 'answer' => <<<'TEXT' Ogno luogo può contenere una mappa (png, jpg o svg) con all'interno dei "punti della mappa" che possono essere posizionati controllandone la dimensione, la forma, l'icona ed il colore impostandoli come collegamenti ad altre entità o semplici etichette. Per favore considerate che le mappe prodotte da tool popolari come Azgaar e Medieval Fantasy Town Generator comprimono iol file generato rendendoli incompatibili con Kanka. Un fix è quello di aprire il file con Inkscape o Photoshop e salvarlo nuovamente prima di caricarlo su Kanka. TEXT , 'question' => 'Posso caricare delle mappe su Kanka?', ], 'mobile' => [ 'answer' => 'Attualmente non vi è nessuna app mobile per Kanka ma la maggior parte delle funzionalità funzionano anche su di un dispositivo mobile. Una limitazione è lo strumento di menzione che non funziona nell\'editor testuale. Se il supporto su Patreon lo permetterà, spero un giorno di riuscire a pagare qualcuno per fare l\'app mobile ma non prevedo che succederà a breve.', 'question' => 'C\'è un\'app mobile? È pianificata?', ], 'multiworld' => [ 'answer' => 'No non ne hai bisogno! Potrai creare tutte le "campagne" che vuoi e fare in modo che rappresentino dei mondi, settings o quello che vorrai. Quando avrai diverse campagne potrai comodamente passare da una all\'altra', 'question' => 'Io sto creando diversi mondi in sistemi differenti, necessiterò di un account differente per ogni mondo?', ], 'permissions' => [ 'answer' => 'Assolutamente, questo è il perché noi abbiamo creato Kanka! Potrai invitare tutti i tuoi giocatori nella tua campagna ed assegnargli dei ruoli e dei permessi. Abbiamo costruito il tutto per essere estremamente flessibile (tu potrai utilizzare sia sia una configurazione opt-in che una opt-out) per coprire il maggiorn numero possibile di bisogni e situazioni.', 'question' => 'Io voglio utilizzare Kanka per costruire il mondo del mio RPG, ma voglio che i miei giocatori abbiano accesso ad alcune delle entità e possano modificare i loro personaggi. È possibile?', ], 'plans' => [ 'answer' => <<<'TEXT' I piani a lungo termine per Kanka sono di creare un versatile strumento di gestione delle campagne e di creazione di mondi che sia indipendente dal sistema utilizzato con contenuti specifici per il systema gestiti dalla comunità nella forma di "Template della Comunità". Un traguardo successivo sarà quello di realezzare uno strumento che possa integrarsi con altre piattaforma come Virtual Table Top per collegarle con i mondi su Kanka. Per quanto riguarda la seconda parte la maggior parte dei progetti obbistici finiscono per mancanza di fondi e con il creatore che li abbandona. Il Patreon è stato pensato per fare in modo che possa ridurre le mie ore lavorative per dedicare più tempo a Kanka senza sacrificare la sicurezza finanziaria della mia famiglia e per coprire i costi del server. Il progetto è anche open source e potrà essere gestito dalla comunità se quelcosa dovesse mai succedermi. I dati di ogni campagna possono essere esportati dall'amministratore della stessa una volta al giorno in caso fossi preoccupato per la possibilità di perdere tutti i tuoi contenuti. TEXT , 'question' => 'Quali sono i piani a lungo termine? Cosa succederebbe se Ilestis si stufasse di lavorare su Kanka?', ], 'public-campaigns' => [ 'answer' => 'Puoi dare uno sguardo alla pagina :public-campaigns per osservare come gli altri sfruttano Kanka per le loro campagne.', 'question' => 'Gli altri come usano Kanka?', ], 'sections' => [ 'community' => 'Community', 'general' => 'Generale', 'other' => 'Altro', 'permissions' => 'Permessi', 'pricing' => 'Prezzi', 'worldbuilding' => 'Worldbuilding', ], 'show' => [ 'return' => 'Ritorna alle FAQ', 'timestamp' => 'Ultimo aggiornamento: date', 'title' => 'FAQ :name', ], 'user-switch' => [ 'answer' => 'I permessi possono diventare complicati, specialmente con grandi campagne. Come amministratore di una campagna puoi navigare alla lista dei membri della campagna e premere sul pulsante "Cambia" che apparirà accanto ai membri non amministratori. Facendo così effettuerai l\'accesso con quell\'utente e vedrai la campagna come sarà vista da lui. Questo è il modo più semplice per controllare i permessi della tua campagna.', 'question' => 'I permessi della mia campagna sono stati impostati, come posso testarli?', ], 'visibility' => [ 'answer' => 'Solo le persone che hai invitato alla tua campagna potranno vedere ed interagire con quello che hai creato. I tuoi dati sono provati e sempre sotto il tuo controllo.', 'question' => 'Chiunque può vedere il mio mondo?', ], ]; ================================================ FILE: lang/it/fields.php ================================================ [ 'placeholder' => 'Scegli un\'immagine dalla galleria della campagna', ], 'gallery-header' => [ 'description' => 'Se l\'entità non ha un\'immagine di intestazione, visualizza invece un\'immagine dalla galleria della campagna.', ], 'gallery-image' => [ 'description' => 'Se l\'entità non ha un\'immagine, visualizza invece un\'immagine dalla galleria della campagna.', ], 'header-image' => [ 'boosted-description' => 'Visualizza un\'immagine di sfondo nell\'intestazione dell\'entità con una :boosted-campaign.', 'description' => 'Visualizza un\'immagine di sfondo nell\'intestazione dell\'entità. Per ottenere risultati migliori, utilizza un\'immagine molto grande.', 'title' => 'Immagine di Intestazione', ], 'tooltip' => [], ]; ================================================ FILE: lang/it/filters.php ================================================ [ 'copy' => 'I filtri sono stati copiati negli appunti.', ], 'helpers' => [ 'guest' => 'Accedi al tuo account per filtrare i risultati.', ], ]; ================================================ FILE: lang/it/footer.php ================================================ 'Chi siamo?', 'blog' => 'Blog', 'boosters' => 'Potenziamenti', 'community' => 'Comunità', 'company' => 'Società', 'contact' => 'Contattaci', 'copyright' => 'Copyright :copy :year Owlchester SNC', 'documentation' => 'Documentazione', 'features' => 'Funzioni', 'kb' => 'Conoscenza base', 'language-switcher' => [ 'other' => 'Altre Lingue', 'title' => 'Seleziona la tua lingua', ], 'newsletter' => 'Notizie', 'platform' => 'Piattaforma', 'premium' => 'Campagne Premium', 'press-kit' => 'Cartella Stampa', 'pricing' => 'Prezzi', 'privacy' => 'Privacy', 'public-campaigns' => 'Campagne pubbliche', 'resources' => 'Risorse', 'roadmap' => 'Roadmap', 'security' => 'Sicurezza', 'status' => 'Stato del servizio', 'terms' => 'Termini', 'translator_call' => 'Kanka è tradotto in altre lingue grazie alla nostra fantastica comunità. Se vuoi aiutare a tradurre Kanka nella tua lingua, contattaci sul nostro :discord!', 'whats-new' => 'Cosa c\'è di nuovo?', ]; ================================================ FILE: lang/it/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Voti della Comunità', ]; ================================================ FILE: lang/it/front/hall-of-fame.php ================================================ 'Sala delle Glorie', ]; ================================================ FILE: lang/it/front/kb.php ================================================ [], 'show' => [], 'title' => 'Conoscenza di base', ]; ================================================ FILE: lang/it/front/newsletter.php ================================================ [ 'learn_more' => 'Scopri di più', 'subscribe' => 'Iscriviti', ], 'fields' => [ 'firstname' => 'Nome', 'lastname' => 'Cognome', 'notifications' => 'Notifiche', ], 'groups' => [ 'all' => 'Ricevi aggiornamenti occasionali su nuove funzionalità, votazioni ed eventi della comunità, ecc.', 'newsletter' => 'Newsletter', ], 'headline' => 'Iscriviti ad una o a tutte le nostre newsletter per rimanere aggiornato con Kanka.', 'title' => 'Aggiornamenti Email', ]; ================================================ FILE: lang/it/front.php ================================================ [], 'actions' => [], 'campaigns' => [ 'public' => [ 'filters' => [ 'is-premium' => 'Questa è una campagna premium!', ], ], ], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => 'Capito!', 'link' => 'Ulteriori informazioni', 'message' => 'Questo sito web utilizza i cookie per garantirti la migliore esperienza sul nostro sito web.', ], 'faq' => [], 'featured_campaigns' => [], 'features' => [ 'api' => [ 'link' => 'Documentazione API', ], 'patreon' => [ 'api_calls' => 'Aumento delle chiamate API (90 al minuto)', 'boosts' => 'Potenziamenti della Campagna', 'default_image' => 'Immagini predefinite più belle per le entità negli elenchi', 'discord' => 'Canale Privato di :discord', 'free' => 'Gratuito', 'hall_of_fame' => 'Nome nel :link', 'impact' => 'Influenza le funzionalità future (tramite Discord)', 'monthly_vote' => 'Partecipa alle future votazioni della comunità', 'pagination' => 'Risultati per pagina aumentati', 'upload_limit' => 'Dimensioni di caricamento', 'upload_limit_map' => 'Dimensioni di caricamento delle mappe', ], ], 'first_block' => [], 'footer' => [], 'goodbye' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Sei un game master, un worldbuilder o uno scrittore? Offriamo un gestore di campagne per giochi di ruolo e uno strumento di worldbuilding che rende facile organizzare, pianificare e divertirsi con le tue campagne GDR. Siamo spinti e ispirati dalla nostra comunità e, soprattutto, le nostre funzioni principali sono gratuite!', ], ], 'master' => [], 'menu' => [ 'dashboard' => 'Pagina Principale', 'login' => 'Accedi', 'register' => 'Registrati', 'register_free' => 'Registrati gratuitamente', ], 'meta' => [ 'description' => ':kanka è un flessibile costruttore di mondi e gestore on-line di campagne GDR', 'title' => ':kanka - Gestore di campagne Online per GDR e strumento per la creazione di mondi', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Gratis', 'month' => 'mese', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Worldbuilding, Giochi di Ruolo da Tavolo, Gestione di Campagne GDR', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/it/gallery.php ================================================ [ 'gallery' => 'Dalla galleria', 'url' => 'Carica un\'immagine da un URL', ], 'browse' => [ 'layouts' => [ 'large' => 'Anteprime grandi', 'small' => 'Anteprime piccole', ], 'search' => [ 'placeholder' => 'Cerca un\'immagine nella galleria', ], 'title' => 'Galleria', 'unauthorized' => 'Nessuno dei tuoi ruoli ha l\'autorizzazione per sfogliare la galleria.', ], 'delete' => [ 'success' => '[0] Eliminati 0 elementi|[1] Eliminato un elemento|{2,*} Eliminati :count elementi', ], 'download' => [ 'errors' => [ 'copy_failed' => 'I nostri server non sono riusciti a scaricare l\'immagine indicata.', 'gallery_full_free' => 'Lo spazio di archiviazione della galleria è esaurito. Attiva le funzioni premium per avere più spazio di archiviazione.', 'gallery_full_premium' => 'Lo spazio di archiviazione della galleria è pieno. Rimuovi prima i file inutilizzati.', 'invalid_format' => 'Il file non è di formato valido.', 'too_big' => 'Il file è troppo grande.', 'unauthorized' => 'Nessuno dei tuoi ruoli ha l\'autorizzazione per caricare nella galleria.', ], ], 'file' => [ 'saved' => 'Salvato', ], 'filters' => [ 'only_unused' => 'Mostra solo i file non utilizzati', ], 'move' => [ 'success' => '[0] Mossi 0 elementi|[1] Mosso un elemento|{2,*} Mossi :count elementi', ], 'update' => [ 'home' => 'Cartella Principale', 'success' => '[0] Aggiornati 0 elementi|[1] Aggiornato un elemento|{2,*} Aggiornati :count elementi', ], ]; ================================================ FILE: lang/it/general.php ================================================ 'Deseleziona Tutto', 'no' => 'No', 'required' => 'Necessario', 'select_all' => 'Seleziona Tutto', 'success' => [ 'created' => ':name creato.', 'deleted' => ':name rimosso.', 'deleted-cancel' => ':name rimosso. :cancel.', 'updated' => ':name aggiornato.', ], 'yes' => 'Sì', ]; ================================================ FILE: lang/it/genres.php ================================================ 'Ucronia', 'cyberpunk' => 'Cyberpunk', 'fantasy' => 'Fantasy', 'historical' => 'Storico', 'many_worlds' => 'Multiverso', 'modern' => 'Moderno', 'occult' => 'Occulto', 'post_apocalyptic' => 'Post-Apocalittico', 'pulp' => 'Pulp', 'science_fantasy' => 'Fantasy Scientifico', 'science_fiction' => 'Fantascientifico', 'space_opera' => 'Epopea Spaziale', 'steampunk' => 'Steampunk', 'superhero' => 'Supereroi', 'urban_fantasy' => 'Fantasy Urbano', 'western' => 'Western', ]; ================================================ FILE: lang/it/header.php ================================================ [ 'title' => 'Novità di Kanka', ], 'notifications' => [ 'dismiss' => 'Chiudi', 'no-unread' => 'Nessuna notifica non letta', 'read_all' => 'Visualizza tutto', ], 'toggle_navigation' => 'Navigazione', 'user' => [ 'settings' => 'Impostazioni', 'sign-out' => 'Disconnetti', 'upgrade' => 'Aggiorna', 'your-profile' => 'Il tuo Profilo', ], ]; ================================================ FILE: lang/it/helpers.php ================================================ [ 'description' => 'Per l\'endpoint API :name sono disponibili i seguenti filtri.', 'title' => 'Filtri API', ], 'attributes' => [ 'link' => 'Opzioni di Attributo', ], 'calendar-widget' => [ 'info' => 'Perché vengono mostrati questi messaggi?', 'title' => 'Widget del Calendario', ], 'dice' => [], 'entity_templates' => [], 'filters' => [ 'title' => 'Come usare i filtri', ], 'link' => [ 'description' => 'Puoi collegarti facilmente ad altre entità della propria campagna utilizzando le seguenti abbreviazioni.', ], 'map' => [], 'public' => 'Guarda un video tutorial su Youtube che spiega le campagne pubbliche.', 'troubleshooting' => [ 'description' => 'Un membro del team di Kanka ti ha inviato a questa pagina. Seleziona una campagna dal menu a tendina per generare un token che ci permetta di entrare temporaneamente nella tua campagna come amministratore.', 'errors' => [ 'token_exists' => 'Un token esiste già per :campaign.', ], 'save_btn' => 'Genera token', 'select_campaign' => 'Seleziona una campagna', 'subtitle' => 'Per favore, invia un aiuto!', 'success' => 'Per favore, copia il seguente token e invialo a qualcuno del team di Kanka.', 'title' => 'Risoluzione dei Problemi', ], 'widget-filters' => [ 'description' => 'Puoi filtrare le entità visualizzate nel widget delle ultime modifiche fornendo un elenco di campi dell\'entità e dei valori. Ad esempio, si può usare :example per filtrare i personaggi morti di tipo PNG.', 'link' => 'filtri dei widget', 'title' => 'Filtri dei Widget della Pagina Principale', ], ]; ================================================ FILE: lang/it/history.php ================================================ [ 'show-old' => 'Cambiamenti', ], 'cta' => 'Visualizza un registro di tutte le modifiche recenti apportate alla campagna.', 'empty' => 'Nessun dato', 'filters' => [ 'all-actions' => 'Tutte le azioni', 'all-users' => 'Tutti i membri', 'no-results' => 'Nessun risultato da visualizzare. Prova altri filtri o torna dopo aver apportato modifiche alle entità della campagna.', ], 'helpers' => [ 'base' => 'Questa interfaccia contiene le modifiche recenti alle entità della campagna per un periodo massimo di :amount mesi, mostrando prima le modifiche più recenti.', 'changes' => 'I seguenti campi avevano in precedenza questi dati.', ], 'log' => [ 'create' => ':user ha creato :entity', 'create_post' => ':user ha creato il post ":post" su :entity', 'delete' => ':user ha eliminato :entity', 'delete_post' => ':user ha eliminato un post su :entity', 'reorder_post' => ':user ha riordinato i post di :entity', 'restore' => ':user ha recuperato :entity', 'update' => ':user ha modificato :entity', 'update_post' => ':user ha modificato il post ":post" su :entity', ], 'title' => 'Cronologia', 'unknown' => [ 'entity' => 'un\'entità sconosciuta', ], ]; ================================================ FILE: lang/it/items.php ================================================ [ 'title' => 'Nuovo Oggetto', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_equipped' => 'Equipaggiato', 'price' => 'Prezzo', 'size' => 'Taglia', ], 'helpers' => [], 'hints' => [], 'index' => [], 'inventories' => [], 'placeholders' => [ 'price' => 'Prezzo dell\'oggetto', 'size' => 'Taglia, Peso, Dimensioni', 'type' => 'Arma, Pozione, Artefatto', ], 'show' => [ 'tabs' => [ 'inventories' => 'Inventari', ], ], ]; ================================================ FILE: lang/it/journals.php ================================================ [ 'title' => 'Nuovo Diario', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Autore', 'date' => 'Data', ], 'helpers' => [], 'index' => [], 'placeholders' => [ 'author' => 'Chi ha scritto il diario', 'date' => 'Data del diario', 'type' => 'Sessione, One Shot, Bozza', ], 'show' => [], ]; ================================================ FILE: lang/it/languages.php ================================================ [ 'ca' => 'Catalano', 'cs' => 'Ceco', 'de' => 'Tedesco', 'el' => 'Greco', 'en' => 'Inglese', 'en-US' => 'Inglese Americano', 'es' => 'Spagnolo', 'fr' => 'Francese', 'gl' => 'Galiziano', 'he' => 'Ebraico', 'hr' => 'Croato', 'hu' => 'Ungherese', 'it' => 'Italiano', 'nb' => 'Norvegese (Bokmal)', 'nl' => 'Olandese', 'pl' => 'Polacco', 'pt-BR' => 'Portoghese-Brasiliano', 'ru' => 'Russo', 'sk' => 'Slovacco', 'tr' => 'Turco', ], 'header'=> 'Lingue', ]; ================================================ FILE: lang/it/locations.php ================================================ [], 'create' => [ 'title' => 'Nuovo Luogo', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [ 'is_destroyed' => 'Distrutto', ], 'helpers' => [ 'characters' => 'Visualizza tutti i personaggi in questo luogo e nei luoghi discendenti, o semplicemente quelli che si trovano qui.', ], 'hints' => [ 'is_destroyed' => 'Questo luogo è distrutto.', ], 'index' => [], 'items' => [], 'journals' => [], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'type' => 'Città, Regno, Rovina', ], 'show' => [], ]; ================================================ FILE: lang/it/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Entra nella modalità di modifica', 'exit-edit-mode' => 'Esci dalla modalità di modifica', 'finish-drawing' => 'Finisci di disegnare il poligono', ], 'notifications' => [ 'start-drawing' => 'Fai clic sulla mappa per iniziare a disegnare un poligono', ], 'toggle' => 'Apri/chiudi tutti i gruppi', ]; ================================================ FILE: lang/it/maps/groups.php ================================================ [ 'add' => 'Aggiungi un nuovo gruppo', ], 'bulks' => [ 'delete' => '{1} Rimosso :count gruppo.|[2,*] Rimossi :count gruppi.', 'patch' => '{1} Modificato :count gruppo.|[2,*] Modificati :count gruppi.', ], 'create' => [ 'success' => 'Gruppo :name creato.', 'title' => 'Nuovo Gruppo', ], 'delete' => [ 'success' => 'Gruppo :name eliminato.', ], 'edit' => [ 'success' => 'Gruppo :name modificato.', 'title' => 'Modifica il Gruppo :name', ], 'fields' => [ 'is_shown' => 'Mostra gli indicatori del gruppo', 'position' => 'Posizione', ], 'helper' => [ 'amount_v3' => 'Gli indicatori possono essere raggruppati utilizzando i gruppi. Ogni gruppo può essere cliccato durante l\'esplorazione di una mappa per mostrare o nascondere rapidamente tutti gli indicatori in esso contenuti.', ], 'hints' => [ 'is_shown' => 'Se questa opzione è selezionata, gli indicatori di gruppo saranno visualizzati sulla mappa per impostazione predefinita.', ], 'index' => [ 'title' => 'Gruppi di :name', ], 'pitch' => [], 'placeholders' => [ 'name' => 'Negozi, Tesori, PNGs', 'position' => 'Primo', 'position_list' => 'Dopo :name', ], 'reorder' => [ 'save' => 'Salva il nuovo ordine', 'success' => '{1} Riordinato :count gruppo.|[2,*] Riordinati :count gruppi.', 'title' => 'Riordina gruppi', ], ]; ================================================ FILE: lang/it/maps/layers.php ================================================ [ 'add' => 'Aggiungi un nuovo livello', ], 'base' => 'Livello Base', 'bulks' => [ 'delete' => '{1} Rimosso :count livello.|[2,*] Rimossi :count livelli.', 'patch' => '{1} Modificato :count livello.|[2,*] Modificati :count livelli.', ], 'create' => [ 'success' => 'Livello :name creato.', 'title' => 'Nuovo Livello', ], 'delete' => [ 'success' => 'Livello :name eliminato.', ], 'edit' => [ 'success' => 'Livello :name modificato.', 'title' => 'Modifica Livello :name', ], 'fields' => [ 'position' => 'Posizione', 'type' => 'Tipo di Livello', ], 'helper' => [ 'amount_v2' => 'Carica i livelli su una mappa per cambiare l\'immagine di sfondo visualizzata sotto gli indicatori o come sovrapposizione sopra la mappa ma sotto gli indicatori.', 'is_real' => 'I livelli non sono disponibili mentre usi OpenStreetMaps.', ], 'index' => [ 'title' => 'Livelli di :name', ], 'pitch' => [], 'placeholders' => [ 'name' => 'Sotterranei, Livello 2, Relitto', 'position' => 'Primo', 'position_list' => 'Dopo :name', ], 'reorder' => [ 'save' => 'Salva il nuovo ordine', 'success' => '{1} Riordinato :count livello.|[2,*] Riordinati :count livelli.', 'title' => 'Riordina livelli', ], 'short_types' => [ 'overlay' => 'Sovrapposizione', 'overlay_shown' => 'Sovrapposizione (mostra automaticamente)', 'standard' => 'Predefinita', ], 'types' => [ 'overlay' => 'Sovrapposizione (visualizzata sopra il livello attivo)', 'overlay_shown' => 'Sovrapposizione visibile per impostazione predefinita', 'standard' => 'Livello predefinito (per passare da un livello all\'altro)', ], ]; ================================================ FILE: lang/it/maps/markers.php ================================================ [ 'entry' => 'Scrivi una voce personalizzata per questo indicatore.', 'remove' => 'Rimuovi indicatore', 'reset-polygon' => 'Ripristina posizioni', 'save_and_explore' => 'Salva e Esplora', 'start-drawing' => 'Inizia a disegnare', 'update' => 'Modifica Indicatori', ], 'bulks' => [ 'delete' => '{1} Rimosso :count indicatore.|[2,*] Rimossi :count indicatori.', 'patch' => '{1} Aggiornato :count indicatore.|[2,*] Aggiornati :count indicatori.', ], 'circle_sizes' => [ 'custom' => 'Personalizzata', 'huge' => 'Enorme', 'large' => 'Grande', 'small' => 'Piccola', 'standard' => 'Predefinito', 'tiny' => 'Minuscola', ], 'create' => [ 'success' => 'Indicatore :name creato.', 'title' => 'Nuovo Indicatore', ], 'delete' => [ 'success' => 'Indicatore :name rimosso.', ], 'edit' => [ 'success' => 'Indicatore :name aggiornato.', 'title' => 'Modifica Indicatore :name', ], 'fields' => [ 'bg_colour' => 'Colore di sfondo', 'circle_radius' => 'Raggio del cerchio', 'copy_elements' => 'Copia elementi', 'custom_icon' => 'Icona personalizzata', 'custom_shape' => 'Forma Personalizzata a Poligono', 'font_colour' => 'Colore dell\'icona', 'group' => 'Gruppo di indicatori', 'icon' => 'Icona', 'is_draggable' => 'Trascinabile', 'latitude' => 'Latitudine', 'longitude' => 'Longitudine', 'opacity' => 'Opacità', 'pin_size' => 'Dimensioni dell\'Indicatore', 'polygon_style' => [ 'stroke' => 'Colore del tratto', 'stroke-opacity' => 'Opacità del tratto', 'stroke-width' => 'Larghezza del tratto', ], 'popupless' => 'Popup del Tooltip', 'size' => 'Dimensioni', ], 'helpers' => [ 'base' => 'Aggiungi indicatori alla mappa cliccando su un qualsiasi punto della stessa.', 'copy_elements' => 'Copia gruppi, livelli e indicatori.', 'copy_elements_to_campaign' => 'Copia gruppi, livelli e indicatori delle mappe. Gli indicatori collegati a un\'entità saranno convertiti in un indicatore standard.', 'custom_icon_v2' => 'Usa le icone da :fontawesome, :rpgawesome, o da un\'icona SVG personalizzata. Scopri di più in :docs.', 'custom_radius' => 'Seleziona l\'opzione dimensione personalizzata dal menu a tendina per definire una dimensione.', 'draggable' => 'Attivalo per permettere lo spostamento di un indicatore in modalità Esplora.', 'is_popupless' => 'Disabilita la visualizzazione del tooltip dell\'indicatore al passaggio del mouse.', 'label' => 'Un\'etichetta viene visualizzata come un blocco di testo sulla mappa. Il contenuto sarà il nome dell\'indicatore o dell\'entità.', 'polygon' => [ 'edit' => 'Modifica il poligono trascinando i suoi bordi e i suoi nodi.', ], ], 'hints' => [ 'entry' => 'Modifica l\'icona per scrivere una voce personalizzata.', ], 'icons' => [ 'custom' => 'Icona personalizzata', 'entity' => 'Immagine dell\'entità', 'exclamation' => 'Icona con Punto di Esclamazione', 'marker' => 'Icona dell\'Indicatore', 'question' => 'Icona con Punto Interrogativo', ], 'index' => [ 'title' => 'Indicatore di :name', ], 'pitches' => [ 'poly' => 'Disegna forme poligonali personalizzate per rappresentare bordi e altre forme irregolari.', ], 'placeholders' => [ 'custom_icon' => 'Prova :example1 o :example2', 'custom_shape' => '100, 100 200, 240 340, 110', 'name' => 'Richiesto se non è stata selezionata alcuna entità', ], 'presets' => [ 'helper' => 'Fai clic su una preimpostazione per caricarla o crearne una nuova.', ], 'shapes' => [ '0' => 'Cerchio', '1' => 'Quadrato', '2' => 'Triangolo', '3' => 'Personalizzato', ], 'sizes' => [ '0' => 'Minuscolo', '1' => 'Normale', '2' => 'Piccolo', '3' => 'Grande', '4' => 'Enorme', ], 'tabs' => [ 'circle' => 'Cerchio', 'label' => 'Etichetta', 'marker' => 'Indicatore', 'polygon' => 'Poligono', 'preset' => 'Preimpostazione', ], ]; ================================================ FILE: lang/it/maps.php ================================================ [ 'back' => 'Torna a :name', 'edit' => 'Modifica mappa', 'explore' => 'Esplora', ], 'create' => [ 'title' => 'Nuova Mappa', ], 'edit' => [], 'errors' => [ 'chunking' => [ 'error' => 'Si è verificato un errore durante il raggruppamento della mappa. Contatta il team su :discord per ricevere supporto.', 'running' => [ 'edit' => 'La mappa non può essere modificata mentre si sta raggruppando.', 'explore' => 'La mappa non può essere visualizzata mentre si sta raggruppando.', 'time' => 'Questa operazione può richiedere da alcuni minuti a diverse ore, a seconda delle dimensioni della mappa.', ], ], 'dashboard' => [ 'missing' => 'Questa mappa necessita di un immagine per poterla visualizzare nella Pagina Principale.', ], 'explore' => [ 'missing' => 'Per favore, aggiungi un immagine alla mappa prima di poterla esplorare.', ], ], 'fields' => [ 'center_marker' => 'Indicatore', 'center_x' => 'Posizione Longitudinale Predefinita', 'center_y' => 'Posizione Latitudinale Predefinita', 'centering' => 'Centratura', 'distance_measure' => 'Misura di distanza', 'distance_name' => 'Etichetta dell\'unità di distanza', 'grid' => 'Griglia', 'has_clustering' => 'Raggruppa gli indicatori', 'initial_zoom' => 'Zoom iniziale', 'is_real' => 'Usa OpenStreetMaps', 'max_zoom' => 'Zoom massimo', 'min_zoom' => 'Zoom minimo', 'tabs' => [ 'coordinates' => 'Coordinate', 'marker' => 'Indicatore', ], ], 'helpers' => [ 'center' => 'Modificando i seguenti valori si controlla l\'area della mappa su cui si concentra l\'attenzione. Se questi valori vengono lasciati vuoti, verrà focalizzato il centro della mappa.', 'centering' => 'La centratura su un marcatore avrà la priorità sulle coordinate predefinite.', 'chunked_zoom' => 'Raggruppa automaticamente gli indicatori quando sono vicini.', 'distance_measure' => 'Se si assegna alla mappa una misurazione della distanza, si abilita lo strumento di misurazione nella modalità di esplorazione.', 'distance_measure_2' => 'Per 100 pixel che misurano 1 chilometro, inserire un valore di 0,0041.', 'grid' => 'Definisce la dimensione della griglia che verrà visualizzata nella modalità di esplorazione.', 'has_clustering' => 'Raggruppa automaticamente gli indicatori quando sono vicini.', 'initial_zoom' => 'Il livello di zoom iniziale con cui viene caricata la mappa. Il valore predefinito è :default, mentre il valore massimo consentito è :max e il valore minimo consentito è :min.', 'is_real' => 'Selezionare questa opzione se desideri utilizzare una mappa del mondo reale invece dell\'immagine caricata. Questa opzione disabilita i livelli.', 'max_zoom' => 'Il massimo ingrandimento possibile per una mappa. Il valore predefinito è :default, mentre il valore massimo consentito è :max.', 'min_zoom' => 'Il massimo ingrandimento possibile per una mappa. Il valore predefinito è :default, mentre il valore minimo consentito è :min.', 'missing_image' => 'Salva la mappa con un\'immagine prima di poter aggiungere livelli e marcatori.', ], 'index' => [], 'maps' => [], 'panels' => [ 'groups' => 'Gruppi', 'layers' => 'Livelli', 'legend' => 'Legenda', 'markers' => 'Indicatori', 'settings' => 'Impostazioni', ], 'placeholders' => [ 'center_marker' => 'Lascia vuoto per caricare la mappa nel mezzo', 'center_x' => 'Lascia vuoto per caricare la mappa nel mezzo', 'center_y' => 'Lascia vuoto per caricare la mappa nel mezzo', 'distance_name' => 'Km, miglia, piedi, hamburgers', 'grid' => 'Distanza in pixel tra gli elementi della griglia. Lascia vuoto per nascondere la griglia.', 'name' => 'Nome della mappa', 'type' => 'Sotterraneo, Città, Galassia', ], 'show' => [ 'tabs' => [ 'maps' => 'Mappe', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'La mappa è in fase di raggruppamento. Questo processo può richiedere da alcuni minuti a qualche ora.', ], ], ]; ================================================ FILE: lang/it/misc.php ================================================ [ 'premium' => 'attiva le funzioni premium per', 'remove_v4' => 'Rimuovi gli annunci :subscribing a Kanka o :premium per :currency :price al mese.', 'subscribing' => 'abbonandoti', ], ]; ================================================ FILE: lang/it/notes.php ================================================ [ 'title' => 'Nuova Nota', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'Sottonote', ], 'helpers' => [], 'hints' => [], 'index' => [], 'placeholders' => [ 'note' => 'Scegli una nota sovraordinata', 'type' => 'Religione, Razza, Systema Politico', ], 'show' => [], ]; ================================================ FILE: lang/it/notifications.php ================================================ [ 'application' => [ 'approved' => 'La tua candidatura alla campagna :campaign è stata approvata.', 'approved_message' => 'La tua candidatura alla campagna :campaign è stata approvata. Messaggio allegato: :reason', 'new' => 'Nuova candidatura per :campaign.', 'rejected' => 'La tua candidatura alla campagna :campaign è stata rifiutata. Motivo dichiarato: :reason', 'rejected_no_message' => 'La tua candidatura alla campagna :campaign è stata rifiutata.', ], 'asset_export' => 'È disponibile l\'esportazione degli asset di una campagna. Il link è disponibile in :time minuti.', 'boost' => [ 'add' => 'La campagna :campaign viene potenziata da :user.', 'remove' => ':user non sta più potenziando la campagna :campaign.', 'superboost' => 'La campagna :campaign sta venendo supportata da :user.', ], 'deleted' => 'La campagna :campaign è stata eliminata.', 'export' => 'Un\'esportazione di una campagna è disponibile. Il link sarà disponibile in :time minuti.', 'export_error' => 'Abbiamo riscontrato un errore nell\'esportazione delle entità della campagna. Per favore contattaci se questo problema dovesse persistere.', 'hidden' => 'La campagna :campaign è ora nascosta dalla pagina delle campagne pubbliche.', 'import' => [ 'failed' => 'L\'importazione della campagna :campaign non è riuscita.', 'success' => 'La campagna :campaign ha terminato l\'importazione.', ], 'join' => ':user si è unito alla campagna :campaign.', 'leave' => ':user ha lasciato la campagna :campaign.', 'plugin' => [ 'deleted' => 'Il plugin :plugin è stato eliminato dal marketplace e rimosso dalla tua campagna :campagna.', ], 'premium' => [ 'add' => 'Le funzionalità premium sono state sbloccate per la campagna :campaign da :user.', 'remove' => 'L\'utente :user non sblocca più le funzioni premium per la campagna :campaign.', ], 'removed-image' => 'L\'immagine o l\'intestazione di :entity è stata rimossa a causa di una violazione del copyright.', 'role' => [ 'add' => 'Sei stato aggiunto al ruolo :role nella campagna :campaign.', 'remove' => 'Sei stato rimosso dal ruolo :role nella campagna :campaign.', ], 'troubleshooting' => [ 'joined' => 'Il membro del team Kanka :user ha aderito alla campagna :campaign.', ], ], 'clear' => [ 'action' => 'Pulisci tutto', 'success' => 'Notifiche rimosse.', 'title' => 'Pulisci le notifiche', ], 'features' => [ 'approved' => 'La tua idea :feature è stata approvata.', 'finished' => 'La tua idea :feature è ora disponibile su Kanka!', 'rejected' => 'La tua idea :feature è stata rifutata, motivo: :reason.', ], 'header' => 'Hai :count notifiche.', 'index' => [ 'title' => 'Notifiche', ], 'map' => [ 'chunked' => 'La mappa :name ha terminato il raggruppamento ed è ora utilizzabile.', ], 'no_notifications' => 'Attualmente non ci sono notifiche. Le notifiche appariranno qui una volta che ne avrete ricevuto qualcuna.', 'subscriptions' => [ 'charge_fail' => 'Si è verificato un errore durante l\'elaborazione del pagamento. Per favore, attendi un momento mentre proviamo di nuovo. Se non cambia nulla, contattaci.', 'deleted' => 'L\'abbonamento a Kanka è stato annullato automaticamente dopo troppi tentativi falliti di addebito sulla carta. Per favore, accedi alle impostazioni dell\'abbonamento e prova ad aggiornare i tuoi dati di pagamento.', 'ended' => 'Il tuo abbonamento a Kanka è terminato. Le tue campagne premium e i tuoi ruoli su Discord sono stati disattivati. Speriamo di rivederti presto!', 'failed' => 'L\'abbonamento a Kanka è stato annullato automaticamente dopo troppi tentativi falliti di addebito sulla carta. Per favore, accedi alle impostazioni dell\'abbonamento e prova ad aggiornare i tuoi dati di pagamento.', 'started' => 'Il tuo abbonamento a Kanka è appena iniziato.', ], 'unread' => 'Nuova Notifica', ]; ================================================ FILE: lang/it/organisations.php ================================================ [ 'title' => 'Nuova Organizzazione', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_defunct' => 'Dismessa', 'members' => 'Membri', ], 'helpers' => [], 'hints' => [ 'is_defunct' => 'Questa organizzazione è dismessa.', ], 'index' => [], 'members' => [ 'destroy' => [ 'success' => 'Membro rimosso da :name.', ], 'edit' => [ 'title' => 'Membro Aggiornato per :name', ], 'fields' => [ 'parent' => 'Superiore', 'pinned' => 'Fissato', 'role' => 'Ruolo', 'status' => 'Stato di affiliazione', ], 'helpers' => [ 'all_members' => 'Tutti i personaggi che sono membri di questa organizzazione e delle sue sotto-organizzazioni.', 'members' => 'Tutti i personaggi che fanno parte di questa organizzazione.', 'pinned' => 'Scegli se questo membro deve essere mostrato nella sezione fissata della panoramica delle entità associate.', ], 'pinned' => [ 'both' => 'Fissata a entrambe', 'none' => 'Fissata da nessuna parte', ], 'placeholders' => [ 'parent' => 'Chi è il superiore di questo membro', 'role' => 'Leader, Membro, Alto Septon, Maestro di Spionaggio', ], 'status' => [ 'active' => 'Membro Attivo', 'inactive' => 'Membro Inattivo', 'unknown' => 'Stato Sconosciuto', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'Culto, Gang, Ribellione, Fandom', ], 'show' => [], ]; ================================================ FILE: lang/it/pagination.php ================================================ '« Precedente', 'next' => 'Successivo »', ]; ================================================ FILE: lang/it/partials.php ================================================ [ 'description' => 'Ci sono stati dei problemi con i dati inseriti.', 'title' => 'Ops!', ], ]; ================================================ FILE: lang/it/passwords.php ================================================ 'Le password devono essere di almeno 6 caratteri e devono coincidere.', 'reset' => 'La password è stata reimpostata!', 'sent' => 'Promemoria della password inviato!', 'token' => 'Questo token per la reimpostazione della password non è valido.', 'user' => 'Non esiste un utente associato a questo indirizzo e-mail.', ]; ================================================ FILE: lang/it/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/it/pins.php ================================================ 'Impara di più sui pin nella nostra documentazione.', 'options' => [ 'no' => 'Non pinnato', 'yes' => 'Pinnato sulla pagina di riepilogo dell\'entità', ], ]; ================================================ FILE: lang/it/post_layouts.php ================================================ 'Organizzazioni del personaggio', 'connection_map' => 'Mappa dei Legami', 'helper' => 'Questo post è impostato per visualizzare la sottopagina :subpage dell\'entità.', 'location_characters' => 'Personaggi del luogo', 'location_events' => 'Eventi del luogo', 'quest_elements' => 'Elementi della Missione', ]; ================================================ FILE: lang/it/posts.php ================================================ [ 'title' => 'Nuovo Post', ], 'fields' => [ 'name' => 'Nome', ], 'placeholders' => [ 'name' => 'Nome del post', ], 'position' => [ 'dont_change' => 'Non cambiare', 'first' => 'Primo', 'last' => 'Ultimo', ], ]; ================================================ FILE: lang/it/presets.php ================================================ [ 'create' => 'Crea una nuova preimpostazione', ], 'create' => [ 'success' => 'Preimpostazione :nome creata.', 'title' => 'Nuova preimpostazione', ], 'destroy' => [ 'success' => 'Preimpostazione :nome cancellata.', ], 'edit' => [ 'success' => 'Preimpostazione :nome modificata.', 'title' => 'Modifica :nome preimpostazione', ], 'fields' => [ 'name' => 'Nome della preimpostazione', ], 'lists' => [ 'empty' => 'Al momento non sono presenti preimpostazioni nella tua campagna.', ], 'placeholders' => [ 'name' => 'Il nome della preimpostazione', ], ]; ================================================ FILE: lang/it/profiles.php ================================================ [ 'success' => 'Avatar aggiornato.', ], 'campaign_switcher_order_by' => [], 'edit' => [ 'success' => 'Profilo aggiornato', ], 'fields' => [ 'avatar' => 'Avatar', 'bio' => 'Biografia', 'email' => 'Email', 'hide_subscription' => 'Nascondi il mio nome dalla :hall_of_fame.', 'last_login_share' => 'Condividi con gli altri membri della campagna quando ho effettuato l\'ultimo accesso.', 'login_sharing' => 'Condivisione dell\'ultimo login', 'name' => 'Nome', 'new_password' => 'Nuova Password', 'new_password_confirmation' => 'Conferma della Nuova Password', 'newsletter' => 'Desidero essere contattato qualche volta via email.', 'password' => 'Password attuale', 'profile-name' => 'Nome del profilo', 'settings' => 'Impostazioni', 'subscription_hiding' => 'Nascondere l\'abbonamento', 'theme' => 'Tema', ], 'helpers' => [ 'profile-name' => 'Cambia il modo in cui il tuo nome appare sul tuo :profile e sul :marketplace. Se lasciato vuoto, verrà utilizzato il nome del proprio account.', ], 'newsletter' => [ 'helpers' => [ 'header' => 'Iscriviti alle seguenti newsletter per essere informato sulle novità di Kanka.', ], 'options' => [ 'monthly' => 'Newsletter di Kanka', ], 'title' => 'Newsletter', 'updated' => 'Preferenze della Newsletter aggiornate.', ], 'password' => [ 'success' => 'Password aggiornata', ], 'placeholders' => [ 'bio' => 'Una piccola biografia di te stesso visualizzata sul tuo profilo pubblico.', 'email' => 'Il tuo indirizzo email', 'name' => 'Il tuo nome come visualizzato', 'new_password' => 'La tua nuova password', 'new_password_confirmation' => 'Conferma la tua nuova password', 'password' => 'Fornisci la tua password attuale per eventuali modifiche', ], 'sections' => [ 'dangerzone' => 'Zona Pericolosa', 'delete' => [ 'confirm' => 'Elimina il mio account ora', 'delete' => 'Elimina il mio account', 'goodbye' => 'In caso affermativo, scrivi :code nella casella sottostante.', 'helper' => 'L\'eliminazione dell\'account comporta anche l\'eliminazione di qualsiasi campagna di cui sei l\'unico membro. Questa azione è permanente e non può essere annullata.', 'subscribed' => 'Cancella il tuo :subscription prima di poter cancellare il proprio account.', 'title' => 'Elimina il tuo account', 'warning' => 'Eliminando il tuo account, tutti i tuoi dati saranno perduti. Sei sicuro di voler proseguire?', ], 'password' => [ 'title' => 'Cambia la tua password', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'La biografia è ora visibile nel tuo :link.', 'profile' => 'profilo pubblico', ], 'success' => 'Impostazioni cambiate.', ], 'theme' => [ 'success' => 'Tema cambiato.', 'themes' => [ 'dark' => 'Scuro', 'default' => 'Predefinito', 'future' => 'Futuristico', 'midnight' => 'Blu Notte', ], ], 'title' => 'Modifica il tuo profilo', 'workflows' => [ 'created' => 'Visualizza l\'entità appena creata', 'default' => 'Visualizza la lista di entità', ], ]; ================================================ FILE: lang/it/quests.php ================================================ [ 'title' => 'Nuova Missione', ], 'destroy' => [], 'edit' => [], 'elements' => [ 'create' => [ 'success' => 'Elemento :entity aggiunto alla missione.', 'title' => 'Nuovo elemento per :name', ], 'destroy' => [ 'success' => 'Elemento :entity rimosso.', ], 'edit' => [ 'success' => 'Elemento :entity aggiornato.', 'title' => 'Modifica elemento per :name', ], 'fields' => [ 'entity_or_name' => 'Seleziona un\'entità della campagna o dai un nome a questo elemento.', ], ], 'fields' => [ 'copy_elements' => 'Copia gli elementi collegati alla missione', 'date' => 'Data', 'element_role' => 'Ruolo', 'instigator' => 'Iniziatore', 'is_completed' => 'Completato', 'role' => 'Ruolo', ], 'helpers' => [ 'is_completed' => 'Seleziona se la missione è considerata completata.', ], 'hints' => [], 'index' => [], 'placeholders' => [ 'date' => 'Data del mondo reale per la missione', 'entity' => 'Nome di un elemento dalla missione', 'role' => 'Il ruolo di questa entità nella missione', 'type' => 'Arco Narrativo, Missione Secondaria, Missione Principale', ], 'show' => [ 'actions' => [ 'add_element' => 'Aggiungi un elemento', ], 'tabs' => [ 'elements' => 'Elementi', ], ], ]; ================================================ FILE: lang/it/races.php ================================================ [], 'create' => [ 'title' => 'Nuova Stirpe', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'members' => 'Membri', ], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Questa stirpe è estinta.', ], 'index' => [], 'members' => [ 'create' => [ 'submit' => 'Aggiungi membri', 'success' => '{0} Nessun membro è stato aggiunto.|{1} 1 membro è stato aggiunto.|[2,*] :count sono stati aggiunti.', 'title' => 'Nuovi Membri', ], ], 'placeholders' => [ 'type' => 'Umano, Fata, Borg', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/it/redirects.php ================================================ 'La tua session è scaduta. Per favore ritenta.', 'unknown_entity' => 'Ci dispiace, non sappiamo cosa sia \':entity\'.', ]; ================================================ FILE: lang/it/releases.php ================================================ [ 'event' => 'Evento', 'livestream' => 'Diretta streaming', 'other' => 'Altro', 'release' => 'Rilascio', 'vote' => 'Voto della comunità', ], 'index' => [ 'description' => 'Gli ultimi aggiornamenti di kanka.io', 'title' => 'Rilasci', ], 'post' => [ 'footer' => 'Da :name :date', ], 'show' => [ 'return' => 'Torna ai rilasci', 'title' => 'Rilascio :name', ], ]; ================================================ FILE: lang/it/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chronicles of Darkness', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/it/search/fulltext.php ================================================ 'Ricerca di entità, post, attributi e altro per termine :term.', 'title' => 'Ricerca completa', ]; ================================================ FILE: lang/it/search.php ================================================ 'Cerca ovunque', 'lookup' => [ 'empty' => 'Nessun risultato', 'hint' => 'Scrivi almeno tre lettere per effettuare una ricerca fra le entità di una campagna.', 'keyboard' => 'Premi :k per effettuare una ricerca, :esc per cessarla', 'recents' => 'Recenti', 'results' => 'Risultati', ], 'no_results' => 'Nessun risultato', 'placeholder' => 'CERCA', 'preview' => [ 'links' => 'Link', 'no-connections' => 'Non ci sono legami fissati da mostrare', ], 'title' => 'Cerca', ]; ================================================ FILE: lang/it/settings/appearance.php ================================================ [ 'learn-more' => 'Per ulteriori informazioni su questa impostazione, consulta la nostra documentazione.', 'save' => 'Salva le impostazioni', ], 'campaign-switcher' => [ 'alphabetical' => 'In ordine alfabetico (A-Z)', 'date_created' => 'Data di creazione (prima la più vecchia)', 'date_joined' => 'Data di adesione (prima la più vecchia)', 'r_alphabetical' => 'In ordine alfabetico inverso (Z-A)', 'r_date_created' => 'Data di creazione (prima la più nuova)', 'r_date_joined' => 'Data di adesione (prima la più vecchia)', ], 'dismissible' => [ 'main' => 'Controlla l\'aspetto e il feel di Kanka. Nota che le campagne possono sovrascrivere alcune di queste impostazioni.', ], 'editors' => [ 'default' => 'Predefinito (:name)', 'legacy' => 'Legacy (:name)', ], 'explore' => [ 'grid' => 'Griglia (predefinito)', 'table' => 'Tabella', ], 'fields' => [ 'campaign-order' => 'Ordine dell\'elenco delle campagne', 'date-format' => 'Formato della data', 'editor' => 'Editor di testo', 'entity-explore' => 'Liste di Entità', 'mentions' => 'Menzioni mentre modifichi', 'new-entity-workflow' => 'Flusso di lavoro della nuova entità', 'pagination' => 'Risultati per pagina', 'theme' => 'Preferenze del tema', ], 'helpers' => [ 'advanced-mentions' => 'Quando modifichi i testi, puoi controllare se le menzioni vengono rese come nome dell\'entità o come menzione avanzata.', 'campaign-order' => 'Modificare l\'ordine in cui le campagne sono elencate nel selettore di campagne.', 'date-format' => 'Se disponibile, controlla il formato in cui vengono visualizzate le date del mondo reale.', 'entity-explore' => 'Controlla il modo in cui gli elenchi di entità vengono visualizzati nelle campagne.', 'new-entity-workflow' => 'Controlla l\'interfaccia a cui si accede dopo la creazione di una nuova entità.', 'overridable' => 'Le singole campagne possono ignorare questa preferenza.', 'pagination' => 'Per gli elenchi che si estendono su più pagine, definisci quanti sono visibili in ogni pagina.', 'theme' => 'Scegli l\'aspetto di Kanka come vuoi tu', ], 'mentions' => [ 'advanced' => 'Visualizza menzioni avanzate :code', 'default' => 'Menzioni come nome dell\'entità', ], 'nested' => [], 'success' => 'Opzioni di aspetto salvati', 'values' => [ 'pagination' => ':amount risultati per pagina', 'pagination-sub' => ':amount (disponibili per abbonati)', ], ]; ================================================ FILE: lang/it/settings/boosters.php ================================================ [ 'boost_name' => 'Potenzia :name', ], 'available' => 'Potenziamenti disponibili :amount/:total', 'benefits' => [ 'boosted' => 'Potenziando una campagna con :one potenziamento si sblocca l\'accesso al :marketplace, alle opzioni di tematizzazione, ai caricamenti più grandi per tutti i membri, al recupero delle entità cancellate e :more ancora.', 'more' => 'altre funzioni pazzesche', 'superboosted' => 'Superpotenziando una campagna con :amount potenziamenti si sbloccano tutti i vantaggi di una campagna potenziata, oltre a una galleria della campagna, log completi delle modifiche apportate alle entità e :more ancora.', ], 'boost' => [ 'actions' => [ 'confirm' => 'Potenziala!', 'remove' => 'Smetti di potenziare :campaign', 'subscribe' => 'Abbonati a Kanka', 'upgrade' => 'Aggiorna il tuo abbonamento', ], 'confirm' => 'Che emozione! Stai per potenziare :campaign. Questo assegnerà uno (:cost) dei tuoi potenziamenti di campagna disponibili.', 'duration' => 'I potenziamenti assegnati rimangono tali fino a quando non vengono rimossi manualmente o fino al termine dell\'abbonamento.', 'errors' => [ 'boosted' => 'Oh oh, sembra che la :campaign sia già potenziata!', 'out-of-boosters' => 'Oh no! Non hai abbastanza potenziamenti disponibili. Hai :available e hai bisogno di :cost. O smetti di potenziare altre campagne o :upgrade.', ], 'pitch' => 'Diventa un abbonato per sbloccare i potenziamenti di campagna.', 'success' => 'La campagna :campaign è ora potenziata. Goditi tutte le nuove fantastiche funzioni!', 'title' => 'Potenzia :campaign', 'upgrade' => 'aggiorna il tuo abbonamento', ], 'campaign' => [ 'boosted' => 'Potenziata da :user da :time', 'premium' => 'Premium grazie a :user da :time', 'standard' => 'Standard', 'superboosted' => 'Superpotenziata da :user da :time', 'unboosted' => 'Depotenziata', ], 'intro' => [ 'anyone' => 'Non sei limitato a potenziare solo le campagne che hai creato. Puoi potenziare qualsiasi campagna di cui fai parte o che puoi vedere. Questo include le campagne in cui sei un giocatore o una :public che ti piace.', 'data' => 'Quando una campagna non viene più potenziata, l\'accesso alle funzioni potenziate viene rimosso. Tuttavia, non viene eliminato alcun contenuto, per cui se la campagna viene nuovamente incrementata in futuro, l\'accesso viene ripristinato.', 'first' => 'Le funzioni avanzate si sbloccano assegnando i potenziamenti per potenziare o superpotenziare una campagna. La quantità di potenziamenti di cui disponi è determinata dal tuo :subscription. Questo numero è disponibile in ogni momento quando si è abbonati. Il potenziamento di una campagna le assegna uno dei vostri potenziamenti, mentre il superpotenziamenti di una campagna ne assegna tre.', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Recupera un\'entità precedentemente cancellata per un periodo massimo di :amount giorni.', 'customisable' => 'Personalizzazione completa dell\'aspetto di una campagna', 'icons' => 'Accedi a migliaia di bellissime icone per mappe e linee temporali', 'title' => 'Le campagne potenziate prendono', 'upload' => 'Dimensioni di caricamento maggiori per tutti i membri delle campagne', ], 'description' => 'Assegna i potenziamenti alle campagne e contribuisci a sbloccare funzioni straordinarie per tutti i partecipanti. Non sei impressionato dalle campagne potenziate? Ci pensiamo noi con le campagne superpotenziate!', 'more' => 'Consulta l\'elenco completo dei vantaggi sulla nostra pagina :boosters.', 'title' => 'Porta una campagna a un livello superiore con la personalizzazione e i vantaggi per tutti i suoi membri', ], 'ready' => [ 'available' => 'I tuoi potenziamenti di campagna disponibili.', 'pricing' => 'Tutti i nostri livelli di abbonamento includono almeno un potenziamento di campagna e iniziano con :amount al mese.', 'pricing-amount' => ':currency:amount', 'title' => 'Potenzia una campagna', ], 'superboost'=> [ 'actions' => [ 'confirm' => 'Superpotenziala!', 'instead' => 'Superpotenziala per :count!', 'remove' => 'Smetti di superpotenziare :campaign', ], 'confirm' => 'Che emozione! Stai per superpotenziare :campaign. Questo assegnerà tre (:cost) dei tuoi potenziamenti di campagna disponibili.', 'errors' => [ 'boosted' => 'Oh oh, sembra che la :campaign sia già superpotenziata!', ], 'success' => 'La campagna :campaign è ora superpotenziata. Goditi tutte le nuove fantastiche funzioni!', 'title' => 'Superpotenzia :campaign', 'upgrade' => 'Pronti per l\'esperienza Kanka definitiva? Superpotenziare :campaign assegnerà :cost ulteriori potenziamenti di campagna.', ], 'title' => 'Potenziamenti di Campagna', 'unboost' => [ 'confirm' => 'Sì, sono sicuro', 'status' => [ 'boosting' => 'Potenziare', 'superboosting' => 'Superpotenziare', ], 'success' => 'La campagna :campaign è ora non più potenziata, e i tuoi potenziamenti sono ora disponibili di nuovo.', 'title' => 'Depotenzia una campagna', 'warning' => 'Sei sicuro di voler interrompere :action :campaign? Questo rilascerà i potenziamenti assegnati e nasconderà tutti i contenuti e le funzionalità relative ai vantaggi fino a quando la campagna non verrà nuovamente potenziata.', ], ]; ================================================ FILE: lang/it/settings/premium.php ================================================ [ 'remove' => 'Rimuovi premium', 'unlock' => 'Diventa premium', ], 'create' => [ 'actions' => [ 'confirm' => 'Diventa Premium!', ], 'confirm' => 'Che emozione! Stai per sbloccare le funzioni premium per :campaign. Questa operazione utilizzerà una delle campagne premium disponibili.', 'duration' => 'Le campagne premium rimangono tali fino a quando non vengono rimosse manualmente o fino alla scadenza dell\'abbonamento.', 'success' => 'La campagna :campaign è ora premium. Goditi tutte le nuove fantastiche funzionalità!', ], 'exceptions' => [ 'already' => 'Le funzioni premium sono già state sbloccate per questa campagna.', 'out-of-stock' => 'Non sono disponibili abbastanza campagne premium per sbloccare questa campagna. Rimuovi lo stato premium da un\'altra campagna oppure :upgrade.', ], 'pitch' => [ 'description' => 'Diventa premium e contribuisci a sbloccare funzioni straordinarie per tutti i partecipanti.', 'title' => 'Le campagne premium ricevono', ], 'ready' => [ 'available' => 'Le tue campagne premium disponibili', 'pricing' => 'Tutti i nostri livelli di abbonamento includono almeno una campagna premium e partono da :amount al mese.', 'pricing-amount' => ':currency:amount', 'title' => 'Diventa premium', ], 'remove' => [ 'confirm' => 'Sì, sono sicuro', 'cooldown' => 'Questa funzione premium di :campaign può essere rimossa dopo :date.', 'success' => 'Le funzioni premium sono state rimosse dalla campagna :campaign. È ora possibile sbloccare le funzioni premium in un\'altra campagna.', 'title' => 'Rimuovendo le funzioni premium', 'warning' => 'Sei sicuro di voler rimuovere le funzionalità premium da :campaign? In questo modo sarà possibile sbloccare un\'altra campagna e nascondere tutti i contenuti e le funzionalità relative ai vantaggi fino a quando lo stato premium della campagna non sarà nuovamente abilitato.', ], ]; ================================================ FILE: lang/it/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Disattiva l\'autenticazione a due fattori', 'finish' => 'Termina la configurazione e accedi', ], 'activation_helper' => 'Per completare la configurazione dell\'autenticazione a due fattori del tuo account, segui queste istruzioni.', 'disable' => [ 'helper' => 'Se desideri disattivare l\'autenticazione a due fattori, fai clic sul pulsante sottostante. Tieni presente che in questo modo il tuo account sarà vulnerabile a chiunque conosca i tuoi dati di accesso.', 'title' => 'Disattiva l\'autenticazione a due fattori', ], 'enable_instructions' => 'Per avviare il processo di attivazione, genera il codice QR di autenticazione, poi scansionalo nell\'app Google Authenticator (:ios, :android) o in un\'altra app simile di autenticazione.', 'enabled' => 'L\'autenticazione a due fattori è attualmente abilitata sul tuo account.', 'error_enable' => 'Codice Invalido, prova di nuovo', 'fields' => [ 'otp' => 'Inserisci la Password Una Tantum (OTP) fornita dall\'app di autenticazione', 'qrcode' => 'Scansiona il seguente codice QR con l\'app di autenticazione per generare una Password Una Tantum (OTP).', ], 'generate_qr' => 'Genera codice QR', 'helper' => 'L\'autenticazione a due fattori (2FA) rafforza la sicurezza dell\'accesso richiedendo due metodi (detti anche fattori) per verificare la tua identità a ogni accesso.', 'learn_more' => 'Per saperne di più sull\'autenticazione a due fattori.', 'social' => 'L\'autenticazione a due fattori di Kanka è abilitata solo per gli utenti che effettuano il login utilizzando l\'e-mail e la password. Modifica il metodo di accesso nelle impostazioni dell\'account prima di poter abilitare questa opzione.', 'success_disable' => 'L\'autenticazione a due fattori è stata disattivata con successo.', 'success_enable' => 'L\'autenticazione a due fattori è stata attivata con successo. Accedi nuovamente per completare la configurazione.', 'success_key' => 'Il codice QR sicuro è stato generato con successo. Completa la configurazione per attivare l\'autenticazione a due fattori.', 'title' => 'Autenticazione a Due Fattori', ], 'actions' => [ 'social' => 'Passa al login di Kanka', 'update_email' => 'Aggiorna email', 'update_password' => 'Aggiorna password', ], 'email' => 'Cambia email', 'email_success' => 'Email aggiornata.', 'password' => 'Cambia password', 'password_success' => 'Password modificata.', 'social' => [ 'error' => 'Stai già utilizzando il login Kanka per questo account.', 'helper' => 'Il tuo account è attualmente gestito da :provider. Puoi smettere di usarlo e passare al login standard di Kanka impostando una password.', 'success' => 'Il tuo account ora utilizza il login di Kanka.', 'title' => 'Social per Kanka', ], 'title' => 'Account', ], 'api' => [ 'helper' => 'Benvenuto nelle API di Kanka. Genera un token di accesso personale da utilizzare nella tua richiesta API per raccogliere informazioni sulle campagne di cui fai parte.', 'link' => 'Leggi la documentazione API', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Connetti', 'remove' => 'Rimuovi', ], 'benefits' => 'Kanka offre alcune integrazioni con servizi di terze parti. Altre integrazioni di terze parti sono previste per il futuro.', 'discord' => [ 'confirm' => 'Sei sicuro di voler disconnettere il tuo account da Discord? Questo rimuoverà tutti i ruoli con cui sei stato sincronizzato.', 'errors' => [ 'add' => 'Si è verificato un errore nel collegamento del tuo account Discord con Kanka. Riprova. Se continua a verificarsi questo problema, tieni presente che Discord ha un limite di 100 server collegati quando si utilizzano le sue API.', ], 'success' => [ 'add' => 'Il tuo account Discord è stato collegato.', 'remove' => 'Il tuo account Discord è stato scollegato.', ], 'text' => 'Collega il tuo account Discord a Kanka per ottenere automaticamente l\'accesso ai ruoli di abbonamento e ai canali privati.', 'unlock' => 'Sblocca ruoli Discord', ], 'title' => 'Integrazione App', ], 'billing' => [ 'placeholder' => 'Se desideri aggiungere ulteriori informazioni di contatto o fiscali alle tue ricevute (indirizzo dell\'azienda, numero di partita IVA, ecc.), inseriscile qui sotto e appariranno su tutte le tue ricevute.', 'save' => 'Salva le informazioni di fattura', 'title' => 'Informazioni di Fattura', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'La campagna :name è stata già potenziata.', 'exhausted_boosts' => 'Hai finito i potenziamenti da dare. Rimuovi il potenziamento da una campagna prima di darlo a un\'altra.', 'exhausted_superboosts' => 'Hai finito i potenziamenti. Sono necessari 3 potenziamenti per superpotenziare una campagna.', ], ], 'countries' => [ 'austria' => 'Austria', 'belgium' => 'Belgio', 'france' => 'Francia', 'germany' => 'Germania', 'italy' => 'Italia', 'netherlands' => 'Paesi Bassi', 'spain' => 'Spagna', ], 'layout' => [ 'title' => 'Configurazione', ], 'menu' => [ 'account' => 'Account', 'api' => 'API', 'appearance' => 'Aspetto', 'apps' => 'App', 'boosters' => 'Potenziamenti', 'notifications' => 'Notifiche', 'other' => 'Altro', 'patreon' => 'Patreon', 'payment_options' => 'Opzioni di Pagamento', 'personal_settings' => 'Impostazioni Personali', 'premium' => 'Campagne Premium', 'profile' => 'Profilo Pubblico', 'settings' => 'Impostazioni', 'subscription' => 'Abbonamento', 'subscription_status' => 'Stato di Abbonamento', ], 'patreon' => [ 'deprecated' => 'Funzionalità disattivata - se desideri supportare Kanka, per favore fallo tramite un :subscription. Il collegamento con Patreon è ancora attivo per coloro che lo avevano collegato prima dell\'abbandono di Patreon.', 'pledge' => 'Contributo :name', 'remove' => [ 'button' => 'Scollega il tuo account Patreon', 'success' => 'Il tuo account Patreon è stato scollegato.', 'text' => 'Scollegare il tuo account Patreon da Kanka rimuoverà i tuoi bonus, il tuo nome nella Sala delle Glorie, i potenziamenti delle campagne e altre caratteristiche legate al supporto di Kanka. Nessuno dei tuoi contenuti potenziati andrà perduto (ad esempio, le intestazioni delle entità). Iscrivendosi di nuovo, avrai accesso a tutti i dati precedenti, compresa la possibilità di sbloccare le campagne precedentemente premium.', 'title' => 'Scollega il tuo account Patreon da Kanka', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Aggiorna profilo', ], 'avatar' => 'Immagine del Profilo', 'success' => 'Profilo aggiornato.', 'title' => 'Profilo Pubblico', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Cancella abbonamento', 'subscribe' => 'Abbonati', 'update_currency' => 'Salva la valuta di fatturazione', ], 'billing' => [ 'helper' => 'I dati di fatturazione vengono elaborati e conservati in modo sicuro tramite :stripe. Questo metodo di pagamento viene utilizzato per tutti gli abbonamenti.', 'saved' => 'Metodo di pagamento salvato', ], 'cancel' => [ 'options' => [ 'competitor' => 'Passo a un sito concorrente', 'financial' => 'L\'abbonamento è troppo costoso', 'missing_features' => 'Funzioni mancanti', 'not_for' => 'L\'abbonamento non fa per me', 'not_playing' => 'Non gioco/scrivo più o la campagna è in pausa', 'not_using' => 'Non uso Kanka al momento', 'other' => 'Altro', ], 'text' => 'Siamo spiacenti di vederti andare via! L\'annullamento dell\'abbonamento lo manterrà attivo fino a :date, dopodiché perderà i potenziamenti delle campagne e gli altri vantaggi legati al sostegno di Kanka. Non esitare a compilare il seguente modulo per informarci su cosa possiamo fare di meglio o su cosa ti ha portato a prendere questa decisione.', ], 'cancelled' => 'L\'abbonamento è stato annullato. Puoi rinnovare l\'abbonamento una volta che l\'abbonamento attuale scade dopo la data :date.', 'change' => [ 'text' => [ 'monthly' => 'Stai sottoscrivendo l\'abbonamento per il grado :tier, da pagare mensilmente in cifra pari a :amount.', 'upgrade_monthly' => 'Passi al livello :tier per :upgrade, poi fatturi mensilmente per :amount.', 'upgrade_paypal' => 'Passi al livello :tier per :upgrade fino a :date.', 'upgrade_yearly' => 'Passi al livello :tier per :upgrade, poi fatturi annualmente per :amount.', 'yearly' => 'Stai sottoscrivendo l\'abbonamento per il grado :tier, da pagare annualmente in cifra pari a :amount.', ], 'title' => 'Cambia Grado di Abbonamento', ], 'coupon' => [ 'check' => 'controlla il codice promozionale', 'invalid' => 'Codice promozionale invalido', 'label' => 'Codice promozionale', 'percent_off' => 'Sconteremo il tuo primo abbonamento annuale del :percent%!', ], 'currencies' => [ 'brl' => 'BRL', 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Cambia la valuta di fatturazione preferita', ], 'errors' => [ 'callback' => 'Il nostro fornitore di pagamenti ha segnalato un errore. Riprova per favore, o contattaci se il problema persiste.', 'failed' => 'Attualmente si stanno verificando problemi con il nostro sistema di fatturazione. Contattaci all\'indirizzo :email per ricevere assistenza.', 'subscribed' => 'Non è stato possibile elaborare l\'abbonamento. Stripe ha fornito il seguente suggerimento.', ], 'fields' => [ 'active_since' => 'Attivo da', 'active_until' => 'Attivo fino', 'billing' => 'Fatturazione', 'currency' => 'Valuta di Fatturazione', 'payment_method' => 'Metodo di pagamento', 'plan' => 'Piano attuale', 'reason' => 'Motivazione', 'reset' => 'Ripristina dati di fatturazione', 'reset_billing' => 'Sono consapevole che cambiando valuta perderò la mia cronologia di fatturazione e dovrò inserire nuovamente il mio metodo di pagamento.', ], 'helpers' => [ 'alternatives' => 'Paga l\'abbonamento con il metodo :method. Questo metodo di pagamento non si rinnova automaticamente alla fine dell\'abbonamento. :method è disponibile solo in euro.', 'alternatives-2' => 'Paga il tuo abbonamento usando :method. Si tratta di un pagamento unico che non si rinnova automaticamente alla fine dell\'abbonamento.', 'alternatives_warning' => 'Non è possibile aggiornare l\'abbonamento utilizzando questo metodo. Sottoscrivi nuovamente l\'abbonamento al termine di quello attuale.', 'alternatives_yearly' => 'Accettiamo abbonamenti annuali solo con :method', 'currency_block' => 'Non è possibile cambiare la valuta mentre si ha un abbonamento attivo a Kanka; è possibile cambiare la valuta una volta terminato l\'abbonamento attuale.', 'currency_reset' => 'La modifica della valuta scelta cancellerà la cronologia di fatturazione e richiederà di inserire nuovamente un metodo di pagamento.', 'paypal_v3' => 'Paga in tutta sicurezza il tuo abbonamento annuale con PayPal.', 'stripe' => 'I dati di fatturazione vengono elaborati e conservati in modo sicuro tramite :stripe.', ], 'manage_subscription' => 'Gestisci abbonamento', 'payment_method' => [ 'actions' => [ 'add' => 'Aggiungi', 'add_new' => 'Aggiungi nuovo metodo di pagamento', 'change' => 'Cambia metodo di pagamento', 'save' => 'Salva metodo di pagamento', 'show_alternatives' => 'Metodi di pagamento alternativi', ], 'add_one' => 'Non hai attualmente metodi di pagamento salvati.', 'alternatives' => 'Puoi abbonarti utilizzando queste opzioni di pagamento alternative. Questa azione addebiterà il conto una volta sola e non rinnoverà automaticamente l\'abbonamento ogni mese.', 'card' => 'Carta', 'card_name' => 'Nome sulla carta', 'country' => 'Nazione di residenza', 'ending' => 'Che finisce con', 'helper' => 'Questa carta verrà usata per tutti i tuoi abbonamenti.', 'new_card' => 'Aggiungi un nuovo metodo di pagamento', 'saved' => ':brand **** :last4', ], 'periods' => [ 'monthly' => 'Mensilmente', 'yearly' => 'Annualmente', ], 'placeholders' => [ 'downgrade_reason' => 'Indica facoltativamente il motivo del declassamento dell\'abbonamento.', 'reason' => 'Indica facoltativamente il motivo per cui non supporti più Kanka. Mancava una funzione? La tua situazione finanziaria è cambiata?', ], 'plans' => [ 'cost_monthly' => ':currency :amount fatturati mensilmente', 'cost_yearly' => ':currency :amount fatturati annualmente', ], 'sub_status' => 'Informazioni di abbonamento', 'subscription' => [ 'actions' => [ 'cancel' => 'Cancella abbonamento', 'downgrading' => 'Si prega di contattarci per il declassamento', 'rollback' => 'Cambia a Coboldo', 'subscribe' => 'Cambia a :tier mensilmente', 'subscribe_annual' => 'Cambia a :tier annualmente', ], ], 'success' => [ 'alternative' => 'Il pagamento è stato registrato. Riceverai una notifica non appena il pagamento sarà stato elaborato e l\'abbonamento sarà attivo.', 'callback' => 'La sottocrizione dell\'abbonamento è andata a buon fine. L\'account verrà aggiornato non appena il nostro fornitore di pagamenti ci informerà della modifica (potrebbero essere necessari alcuni minuti).', 'currency' => 'L\'impostazione della valuta preferita è stata aggiornata.', 'subscribed' => 'La tua iscrizione è andata a buon fine! Non dimenticarti di iscriverti alla newsletter del Community Vote per essere avvisato quando una votazione viene effettuata. Inoltre, puoi dare un\'occhiata al nostro discord e diventare parte della comunità', ], 'tiers' => 'Gradi di Abbonamento', 'trial_period' => 'Gli abbonamenti annuali prevedono una politica di cancellazione di 14 giorni. Contattaci all\'indirizzo :email se desideri cancellare il vostro abbonamento annuale e ottenere un rimborso.', 'upgrade_downgrade' => [ 'button' => 'Informazioni su Potenziamenti e Declassamenti', 'cancel' => [ 'bullets' => [ 'bonuses' => 'I bonus rimangono abilitati fino alla fine del periodo di pagamento.', 'boosts' => 'Lo stesso accade per le campagne potenziate. Le funzioni potenziate diventano invisibili, ma non vengono eliminate quando una campagna non è più potenziata.', 'kobold' => 'Per annullare l\'abbonamento passa al grado Coboldo.', 'premium' => 'Lo stesso accade per le campagne premium. Le funzioni premium diventano invisibili, ma non vengono eliminate quando una campagna non è più premium.', ], 'title' => 'Quando cancelli il tuo abbonamento', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Il livello attuale rimarrà attivo fino alla fine del ciclo di fatturazione in corso, dopodiché verrai declassato al nuovo livello.', ], 'provide_reason' => 'Se è possibile, spiegaci il motivo del declassamento dell\'abbonamento.', 'title' => 'Quando declassi a un grado inferiore', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Il metodo di pagamento verrà addebitato immediatamente e avrai accesso al nuovo grado.', 'prorate' => 'Quando passi da Orsogufo a Elementale, ti verrà addebitata solo la differenza rispetto al nuovo grado.', ], 'title' => 'Quando passi a un grado superiore', ], ], 'warnings' => [ 'incomplete' => 'Non è stato possibile addebitare la carta di credito. Ti preghiamo di aggiornare i dati della tua carta di credito e riproveremo ad addebitarla nei prossimi giorni. Se l\'operazione non dovesse andare a buon fine, l\'abbonamento verrà annullato.', 'patreon' => 'Il tuo account è attualmente collegato a Patreon. Per favore, scollega il tuo account nelle impostazioni di :patreon prima di passare a un abbonamento a Kanka.', ], ], ]; ================================================ FILE: lang/it/sidebar.php ================================================ [ 'count' => 'Membro di :member', 'created_campaigns' => 'Le tue campagne', 'follow_more' => 'Trova le campagne', 'followed_campaigns'=> 'Campagne seguite', 'new_campaign' => 'Nuova campagna', 'public_campaigns' => 'Campagne pubbliche', 'reorder' => 'Riordina', 'updated' => 'Aggiorna', ], 'dashboard' => 'Dashboard', 'entity-creator' => 'Creazione Rapida', 'gallery' => 'Galleria', 'game' => 'Gioco', 'other' => 'Altro', 'recent' => 'Cambiamenti recenti', 'relations' => 'Legami', 'settings' => 'Impostazioni', 'time' => 'Tempo', 'world' => 'Mondo', ]; ================================================ FILE: lang/it/starter.php ================================================ [ 'name' => 'Mondo di :user', ], 'character1' => [], 'character2' => [], 'item1' => [], 'kingdom1' => [], 'kingdom2' => [], 'note1' => [], ]; ================================================ FILE: lang/it/subscription.php ================================================ [ 'main' => 'Abbonati a Kanka per sbloccare un maggior numero di immagini caricate, un\'esperienza senza pubblicità, :booster e :more. Utilizziamo :stripe per gestire la fatturazione, senza che i dati della carta di credito vengano memorizzati o transitino sui nostri server.', 'more' => 'altre meravigliose funzioni', ], 'errors' => [ 'grace' => 'Il tuo attuale abbonamento finisce il :date, poi potrai riabbonarti.', 'invalid_card_country' => [ 'brl' => 'Siamo spiacenti, ma attualmente accettiamo solo pagamenti in BRL per i clienti con carta di credito brasiliana. Se ritieni che si tratti di un errore, contattaci a :email.', ], 'invalid_currency' => 'L\'abbonamento in :old non ti permette di avere un nuovo abbonamento in :new. Per favore, cambia la tua valuta in :old, o contattaci all\'indirizzo :email se desideri cambiare valuta.', ], ]; ================================================ FILE: lang/it/subscriptions/promos.php ================================================ [ 'inactive' => 'Questa promozione non è più attiva.', 'invalid' => 'Promozione sconosciuta.', 'only-new' => 'Questa promozione è disponibile solo ai nuovi iscritti.', ], ]; ================================================ FILE: lang/it/subscriptions.php ================================================ [ 'failed' => 'Stripe non è stato in grado di verificare il tuo metodo di pagamento. Conseguentemente a ciò, il tuo abbonamento è stato disattivato.', ], ]; ================================================ FILE: lang/it/tags.php ================================================ [ 'actions' => [ 'add' => 'Aggiungi un nuovo tag', 'add_entity' => 'Aggiungi all\'entità', ], 'create' => [ 'attach_success' => '{1} Aggiunto :count entità al tag :name.|[2,*] Aggiunte :count entità al tag :name.', 'attach_success_entity' => 'Tag aggiornati con successo per :name.', 'entity' => 'Aggiungi tag a :name', ], ], 'create' => [ 'title' => 'Nuovo Tag', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Discendenti', 'is_auto_applied' => 'Applica automaticamente alle nuove entità', 'is_hidden' => 'Nascosto dall\'intestazione e dal tooltip', ], 'helpers' => [ 'no_children' => 'Al momento non ci sono entità con questo tag.', ], 'hints' => [ 'children' => 'Questo elenco contiene tutte le entità assegnate a questo tag o ai suoi discendenti.', 'is_auto_applied' => 'Seleziona questa opzione per applicare automaticamente questo tag alle entità appena create.', 'is_hidden' => 'Se selezionato, questo tag non sarà visualizzato nell\'intestazione o nel tooltip di un\'entità.', 'tag' => 'Questo elenco contiene tutti i tag discendenti di questo tag o dei suoi tag discendenti.', ], 'index' => [], 'placeholders' => [ 'type' => 'Tradizioni, Guerre, Storia, Religione, Araldica', ], 'show' => [ 'tabs' => [ 'children' => 'Discendente', ], ], 'tags' => [], 'transfer' => [ 'fail' => 'Impossibile trasferire le entità da :tag a :newTag', 'success' => 'Spostate con successo le entità da :tag a :newTag', 'transfer' => 'Sposta', ], ]; ================================================ FILE: lang/it/teams.php ================================================ [ 'lead' => 'Rendere la creazione del tuo mondo divertente e affidabile', 'translations' => 'Traduzioni', ], 'leads' => [ 'translators' => 'Kanka è tradotto in molte lingue grazie a questi eccezionali collaboratori.', ], 'people'=> [ 'itzamna' => [ 'title' => 'Sviluppatore Junior', ], 'jay' => [ 'title' => 'Fondatore e Capo Sviluppatore', ], 'jon' => [ 'title' => 'Co-fondatore e Business Manager', ], 'kaz' => [ 'title' => 'Distruttore di Bug', ], 'laura' => [ 'title' => 'Social Media', ], ], ]; ================================================ FILE: lang/it/tiers.php ================================================ [ 'subscribe' => [ 'choose' => 'Scegli :tier', 'monthly' => ':tier al mese', 'yearly' => ':tier all\'anno', ], ], 'current' => 'Il tuo attuale abbonamento', 'features' => [ 'api_requests' => ':amount richieste APi/minuto', 'boosters' => 'Potenziamenti Campagna', 'discord' => 'Ruoli e canali :discord unici', 'feature_influence' => 'Dai la tua opinione sulle funzioni future', 'file_size' => ':size per i caricamenti di files', 'nice_image' => 'Immagini predefinite per le entità', 'no_ads' => 'Nessun annuncio.', 'pagination' => ':amount risultati per pagina (massimo di entità mostrate per pagina)', 'roadmap' => 'Vota le nuove idee', ], 'periods' => [ 'billed_monthly' => 'Fatturato mensilmente', 'billed_yearly' => 'Fatturato annualmente', ], 'pricing' => ':currency :amount / mese', 'ribbons' => [ 'best-value' => 'Più conveniente', 'current' => 'Abbonamento attuale', ], 'toggle' => [], ]; ================================================ FILE: lang/it/timelines/elements.php ================================================ [ 'copy_with_name' => 'Copia la menzione avanzata con il nome dell\'elemento', 'success' => 'Menzione avanzata dell\'elemento copiata negli appunti.', ], 'create' => [ 'success' => 'Elemento aggiunto alla linea temporale', 'title' => 'Nuovo elemento cronologico', ], 'delete' => [ 'success' => 'Elemento :element rimosso.', ], 'edit' => [ 'success' => 'Elemento aggiornato.', 'title' => 'Modifica dell\'elemento cronologico', ], 'fields' => [ 'date' => 'Data', 'era' => 'Era', 'icon' => 'Icona', 'use_entity_entry' => 'Visualizza la voce dell\'entità allegata di seguito. Il testo di questo elemento sarà visualizzato per primo se presente.', 'use_event_date' => 'Usa la data dell\'evento collegata.', ], 'helpers' => [ 'date' => 'Se l\'elemento è collegato a un\'entità evento, visualizza la data dell\'evento.', 'entity_is_private' => 'L\'entità dell\'elemento è privata.', 'icon' => 'Copia il codice CSS di un\'icona da :fontawesome o :rpgawesome.', 'is_collapsed' => 'L\'immagine dell\'elemento è ridotta a icona per impostazione predefinita.', ], 'placeholders' => [ 'date' => 'e.g. 42 Marzo o 1332-1337', 'name' => 'Richiesto se nessuna entità è selezionata', 'position' => 'Posizione nella lista degli elementi dell\'era. Lasciare vuoto per aggiungere alla fine.', ], 'warning' => [], ]; ================================================ FILE: lang/it/timelines/eras.php ================================================ [ 'add' => 'Aggiungi una nuova era', ], 'bulks' => [ 'delete' => '{0} Rimosse :count ere.|{1} Rimossa :count era.|[2,*] Rimosse :count ere.', ], 'create' => [ 'success' => 'Era :name creata.', 'title' => 'Nuova era', ], 'delete' => [ 'success' => 'Era :name rimossa.', ], 'edit' => [ 'success' => 'Era :name aggiornata.', 'title' => 'Modifica Era :name', ], 'fields' => [ 'abbreviation' => 'Abbreviazione', 'end_year' => 'Anno finale', 'is_collapsed' => 'Ridotta', 'start_year' => 'Anno iniziale', ], 'helpers' => [ 'eras' => 'La linea temporale deve essere creata prima delle ere.', 'is_collapsed' => 'L\'era è ridotta per impostazione predefinita.', 'primary' => 'Separa la tua linea temporale in ere. Una liena temporale deve avere almeno una era per funzionare correttamente.', ], 'index' => [ 'title' => 'Ere di :name', ], 'placeholders' => [ 'abbreviation' => 'a.C., d.C., BCE', 'end_year' => 'Anno in cui l\'era finisce. Lasciare vuoto se l\'era è in corso.', 'name' => 'Età Moderna, Età del Bronzo, Guerre Galattiche', 'start_year' => 'Anno in cui l\'era comincia. Lasciare vuoto se questa è la prima era.', ], 'reorder' => [], ]; ================================================ FILE: lang/it/timelines.php ================================================ [ 'add_element' => 'Aggiungi all\'era :era', 'back' => 'Torna a :nome', 'save_order' => 'Salva nuovo ordine', ], 'create' => [ 'title' => 'Nuova linea temporale', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_elements' => 'Copia elementi', 'copy_eras' => 'Copia ere', 'eras' => 'Ere', 'reverse_order' => 'Inverti l\'ordine delle ere', ], 'helpers' => [ 'no_era_v2' => 'Questa linea temporale non ha attualmente ere. Aggiungi una o più ere, dopodiché potrai aggiungere elementi alle ere qui.', 'reverse_order' => 'Abilita per visualizzare le ere in ordine cronologico inverso (prima l\'era più antica)', ], 'index' => [], 'placeholders' => [ 'type' => 'Principale, Cronache del mondo, Storia del Regno', ], 'reorder' => [ 'empty' => 'Aggiungi ere e altri elementi alla tua Linea Temporale per poterla riordinare.', 'success' => 'Linea temporale riordinata con successo.', 'title' => 'Riordina la linea temporale', ], 'show' => [], 'timelines' => [], ]; ================================================ FILE: lang/it/users/profile.php ================================================ [ 'wordsmith' => 'Un vero e proprio paroliere! Vincitore di un prompt di worldbuilding.', ], 'fields' => [ 'achievements' => 'Traguardi', 'banned' => 'Questo utente è stato bannato', 'entities_created' => 'Entità create :help :count', 'member_since' => 'Membro da :date', 'public_campaigns' => 'Campagne pubbliche', 'subscriber_since' => 'Iscritto da :date', ], 'helpers' => [ 'entities_created' => 'Questo valore viene ricalcolato quotidianamente', ], 'title' => ':name Profilo', ]; ================================================ FILE: lang/it/validation.php ================================================ ':attribute deve essere accettato.', 'active_url' => ':attribute non è un URL valido.', 'after' => ':attribute deve essere una data successiva al :date.', 'after_or_equal' => ':attribute deve essere una data successiva o uguale al :date.', 'alpha' => ':attribute può contenere solo lettere.', 'alpha_dash' => ':attribute può contenere solo lettere, numeri e trattini.', 'alpha_num' => ':attribute può contenere solo lettere e numeri.', 'array' => ':attribute deve essere un array.', 'before' => ':attribute deve essere una data precedente al :date.', 'before_or_equal' => ':attribute deve essere una data precedente o uguale al :date.', 'between' => [ 'numeric' => ':attribute deve trovarsi tra :min - :max.', 'file' => ':attribute deve trovarsi tra :min - :max kilobyte.', 'string' => ':attribute deve trovarsi tra :min - :max caratteri.', 'array' => ':attribute deve avere tra :min - :max elementi.', ], 'boolean' => 'Il campo :attribute deve essere vero o falso.', 'confirmed' => 'Il campo di conferma per :attribute non coincide.', 'date' => ':attribute non è una data valida.', 'date_equals' => ':attribute deve essere una data e uguale a :date.', 'date_format' => ':attribute non coincide con il formato :format.', 'different' => ':attribute e :other devono essere differenti.', 'digits' => ':attribute deve essere di :digits cifre.', 'digits_between' => ':attribute deve essere tra :min e :max cifre.', 'dimensions' => "Le dimensioni dell'immagine di :attribute non sono valide.", 'distinct' => ':attribute contiene un valore duplicato.', 'email' => ':attribute non è valido.', 'exists' => ':attribute selezionato non è valido.', 'file' => ':attribute deve essere un file.', 'filled' => 'Il campo :attribute deve contenere un valore.', 'gt' => [ 'numeric' => ':attribute deve essere maggiore di :value.', 'file' => ':attribute deve essere maggiore di :value kilobyte.', 'string' => ':attribute deve contenere più di :value caratteri.', 'array' => ':attribute deve contenere più di :value elementi.', ], 'gte' => [ 'numeric' => ':attribute deve essere uguale o maggiore di :value.', 'file' => ':attribute deve essere uguale o maggiore di :value kilobyte.', 'string' => ':attribute deve contenere un numero di caratteri uguale o maggiore di :value.', 'array' => ':attribute deve contenere un numero di elementi uguale o maggiore di :value.', ], 'image' => ":attribute deve essere un'immagine.", 'in' => ':attribute selezionato non è valido.', 'in_array' => 'Il valore del campo :attribute non esiste in :other.', 'integer' => ':attribute deve essere un numero intero.', 'ip' => ':attribute deve essere un indirizzo IP valido.', 'ipv4' => ':attribute deve essere un indirizzo IPv4 valido.', 'ipv6' => ':attribute deve essere un indirizzo IPv6 valido.', 'json' => ':attribute deve essere una stringa JSON valida.', 'lt' => [ 'numeric' => ':attribute deve essere minore di :value.', 'file' => ':attribute deve essere minore di :value kilobyte.', 'string' => ':attribute deve contenere meno di :value caratteri.', 'array' => ':attribute deve contenere meno di :value elementi.', ], 'lte' => [ 'numeric' => ':attribute deve essere minore o uguale a :value.', 'file' => ':attribute deve essere minore o uguale a :value kilobyte.', 'string' => ':attribute deve contenere un numero di caratteri minore o uguale a :value.', 'array' => ':attribute deve contenere un numero di elementi minore o uguale a :value.', ], 'max' => [ 'numeric' => ':attribute non può essere superiore a :max.', 'file' => ':attribute non può essere superiore a :max kilobyte.', 'string' => ':attribute non può contenere più di :max caratteri.', 'array' => ':attribute non può avere più di :max elementi.', ], 'mimes' => ':attribute deve essere del tipo: :values.', 'mimetypes' => ':attribute deve essere del tipo: :values.', 'min' => [ 'numeric' => ':attribute deve essere almeno :min.', 'file' => ':attribute deve essere almeno di :min kilobyte.', 'string' => ':attribute deve contenere almeno :min caratteri.', 'array' => ':attribute deve avere almeno :min elementi.', ], 'not_in' => 'Il valore selezionato per :attribute non è valido.', 'not_regex' => 'Il formato di :attribute non è valido.', 'numeric' => ':attribute deve essere un numero.', 'present' => 'Il campo :attribute deve essere presente.', 'regex' => 'Il formato del campo :attribute non è valido.', 'required' => 'Il campo :attribute è richiesto.', 'required_if' => 'Il campo :attribute è richiesto quando :other è :value.', 'required_unless' => 'Il campo :attribute è richiesto a meno che :other sia in :values.', 'required_with' => 'Il campo :attribute è richiesto quando :values è presente.', 'required_with_all' => 'Il campo :attribute è richiesto quando :values sono presenti.', 'required_without' => 'Il campo :attribute è richiesto quando :values non è presente.', 'required_without_all' => 'Il campo :attribute è richiesto quando nessuno di :values è presente.', 'same' => ':attribute e :other devono coincidere.', 'size' => [ 'numeric' => ':attribute deve essere :size.', 'file' => ':attribute deve essere :size kilobyte.', 'string' => ':attribute deve contenere :size caratteri.', 'array' => ':attribute deve contenere :size elementi.', ], 'starts_with' => ':attribute deve iniziare con uno dei seguenti: :values', 'string' => ':attribute deve essere una stringa.', 'timezone' => ':attribute deve essere una zona valida.', 'unique' => ':attribute è stato già utilizzato.', 'uploaded' => ':attribute non è stato caricato.', 'url' => 'Il formato del campo :attribute non è valido.', 'uuid' => ':attribute deve essere un UUID valido.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ 'name' => 'nome', 'username' => 'nome utente', 'first_name' => 'nome', 'last_name' => 'cognome', 'password_confirmation' => 'conferma password', 'city' => 'città', 'country' => 'paese', 'address' => 'indirizzo', 'phone' => 'telefono', 'mobile' => 'cellulare', 'age' => 'età', 'sex' => 'sesso', 'gender' => 'genere', 'day' => 'giorno', 'month' => 'mese', 'year' => 'anno', 'hour' => 'ora', 'minute' => 'minuto', 'second' => 'secondo', 'title' => 'titolo', 'content' => 'contenuto', 'description' => 'descrizione', 'excerpt' => 'estratto', 'date' => 'data', 'time' => 'ora', 'available' => 'disponibile', 'size' => 'dimensione', ], ]; ================================================ FILE: lang/it/visibilities.php ================================================ 'Aggiorna Visibilità', 'toast' => 'Visibilità aggiornata con successo.', 'tooltip' => 'Fai clic qui per conoscere le varie opzioni di visibilità.', ]; ================================================ FILE: lang/nl/abilities.php ================================================ [], 'create' => [ 'title' => 'Nieuwe Vaardigheid', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'charges' => 'Ladingen', ], 'helpers' => [], 'index' => [], 'placeholders' => [ 'charges' => 'Hoeveelheid ladingen. Referentie kenmerken met {Level}*{CHA}', 'name' => 'Vuurbal, Waarschuwing, Sluwe Aanval', 'type' => 'Spreuk, Prestatie, Aanval', ], 'show' => [], ]; ================================================ FILE: lang/nl/attribute_templates.php ================================================ [], 'create' => [ 'title' => 'Nieuw attribuutsjabloon', ], 'destroy' => [], 'edit' => [], 'fields' => [], 'hints' => [ 'automatic' => 'Attributen worden automatisch toegepast vanuit de :link Attribuutsjabloon', 'entity_type' => 'Indien ingesteld, zal bij het maken van een nieuwe entiteit van dit type automatisch deze attribuutsjabloon worden toegepast.', 'parent_attribute_template' => 'Dit attribuutsjabloon kan gerelateerd zijn aan een ander attribuutsjabloon. Bij het toepassen van deze attribuutsjabloon worden deze en al zijn bovenliggende entiteiten toegepast.', ], 'index' => [], 'placeholders' => [ 'name' => 'Naam van de attribuutsjabloon', ], 'show' => [], ]; ================================================ FILE: lang/nl/auth.php ================================================ 'Deze combinatie van e-mailadres en wachtwoord is niet geldig.', 'helpers' => [ 'password' => 'Wachtwoord weergeven / verbergen', ], 'login' => [ 'fields' => [ 'email' => 'E-mail', 'password' => 'Wachtwoord', ], 'or' => 'OF', 'password_forgotten' => 'Wachtwoord vergeten?', 'submit' => 'Log in', 'title' => 'Log in', ], 'register' => [ 'already' => 'Heb je al een account? :login', 'errors' => [ 'email_already_taken' => 'Er is al een account met dit e-mailadres geregistreerd.', 'general_error' => 'Er is een fout opgetreden bij het registreren van uw account. Probeer het a.u.b. opnieuw.', ], 'fields' => [ 'email' => 'E-mail', 'name' => 'Gebruikersnaam', 'password' => 'Wachtwoord', ], 'submit' => 'Registreren', 'title' => 'Registreren', ], 'reset' => [ 'fields' => [ 'email' => 'E-mailadres', 'password' => 'Wachtwoord', 'password_confirmation' => 'Bevestig uw wachtwoord', ], 'send' => 'Stuur Wachtwoord Reset Link', 'submit' => 'Wachtwoord opnieuw instellen', 'title' => 'Wachtwoord opnieuw instellen', ], 'throttle' => 'Te veel mislukte loginpogingen. Probeer het over :seconds seconden nogmaals.', ]; ================================================ FILE: lang/nl/bookmarks.php ================================================ [ 'title' => 'Nieuwe Snelle Link', ], 'destroy' => [], 'edit' => [ 'title' => 'Snelle Link :name', ], 'fields' => [ 'dashboard' => 'Dashboard', 'filters' => 'Filters', 'menu' => 'Menu', 'position' => 'Positie', 'random_type' => 'Willekeurig Entiteit Type', 'selector' => 'Quick Link Configuratie', ], 'helpers' => [ 'dashboard' => 'Laat de snelkoppeling een van de custom dashboards van de campaign targeten.', 'entity' => 'Stel deze snelle link in om rechtstreeks naar een entiteit te gaan. Het :tab veld bepaalt welke van de tabbladen de focus heeft. Het :menu veld bepaalt welke subpagina van de entiteit wordt geopend.', 'position' => 'Gebruik dit veld om te bepalen in welke oplopende volgorde de links in het menu verschijnen.', 'random' => 'Gebruik dit veld om een snelle link naar een willekeurige entiteit te laten verwijzen. Je kunt de link filteren om alleen naar een specifiek entiteit type te gaan.', 'selector' => 'Configureer waar deze snelle link naartoe gaat wanneer een gebruiker erop klikt in de zijbalk.', 'type' => 'Stel deze snelle link in om rechtstreeks naar een lijst met entiteiten te gaan. Om de resultaten te filteren, kopieer je delen van de url in de gefilterde entiteitenlijst na het :? teken op het :filter veld.', ], 'index' => [], 'placeholders' => [ 'filters' => 'location_id=15&type=city', 'menu' => 'Menu subpagina (gebruik de laatste tekst van de url)', 'tab' => 'invoer, relaties, notities', ], 'random_types' => [ 'any' => 'Elke entiteit', ], 'show' => [], ]; ================================================ FILE: lang/nl/calendars/weather.php ================================================ [ 'success' => 'Weer toegevoegd.', 'title' => 'Nieuw Weer Effect', ], 'destroy' => [ 'success' => 'Weer verwijderd.', ], 'edit' => [ 'success' => 'Weer bijgewerkt.', 'title' => 'Werk Weer bij', ], 'fields' => [ 'effect' => 'Effect', 'name' => 'Naam', 'precipitation' => 'Neerslag', 'temperature' => 'Temperatuur', 'weather' => 'Weer', 'wind' => 'Wind', ], 'options' => [ 'weather' => [ 'bolt' => 'Donder', 'cloud' => 'Bewolkt', 'cloud-rain' => 'Regenachtig', 'cloud-showers-heavy' => 'Zware Regen', 'cloud-sun' => 'Bewolkt en Zonnig', 'cloud-sun-rain' => 'Wolk, Zon en Regen', 'meteor' => 'Meteoor', 'smog' => 'Smog', 'snowflake' => 'Sneeuw', 'sun' => 'Zonnig', 'wind' => 'Winderig', ], ], 'placeholders' => [ 'effect' => 'Magisch of natuurlijk effect', 'name' => 'Optionele aangepaste weertekst', 'precipitation' => 'Hoeveelheid water', 'temperature' => 'Dagelijks hoog en laag', 'wind' => 'Windsnelheden', ], ]; ================================================ FILE: lang/nl/calendars.php ================================================ [ 'add_epoch' => 'Voeg een tijdperk toe', 'add_intercalary' => 'Voeg schrikkeldagen toe', 'add_month' => 'Voeg een maand toe', 'add_moon' => 'Voeg een maan toe', 'add_season' => 'Voeg een seizoen toe', 'add_week' => 'Voeg een benoemde week toe', 'add_weekday' => 'Voeg een weekdag toe', 'add_year' => 'Voeg een jaarnaam toe', 'set_today' => 'Instellen als huidige dag', 'today' => 'Vandaag', ], 'checkboxes' => [ 'is_recurring' => 'Vindt elk jaar plaats', ], 'create' => [ 'title' => 'Nieuwe Kalender', ], 'destroy' => [], 'edit' => [ 'today' => 'Kalender datum bijgewerkt', ], 'event' => [ 'create' => [ 'success' => 'Kalender gebeurtenis gemaakt.', 'title' => 'Voeg een Kalender Gebeurtenis toe aan :name', ], 'destroy' => 'Gebeurtenis verwijderd uit kalender \':name\'.', 'edit' => [ 'success' => 'Kalender gebeurtenis bijgewerkt.', 'title' => 'Werk Kalender Gebeurtenis bij voor :naam', ], 'helpers' => [ 'other_calendar' => 'Je bewerkt een herinnering die in de kalender :calendar staat.', ], 'success' => 'Gebeurtenis \':event\' toegevoegd aan de kalender', ], 'events' => [], 'fields' => [ 'comment' => 'Opmerking', 'current_day' => 'Huidige Dag', 'current_month' => 'Huidige Maand', 'current_year' => 'Huidig Jaar', 'date' => 'Huidige Datum', 'is_incrementing' => 'Vooruitgaande Datum', 'is_recurring' => 'Terugkerend', 'leap_year_amount' => 'Voeg Dagen toe', 'leap_year_month' => 'Maand', 'leap_year_offset' => 'Elke', 'leap_year_start' => 'Schrikkeljaar', 'length' => 'Gebeurtenis Duur', 'length_days' => ':count dag|:count dagen', 'months' => 'Maanden', 'moons' => 'Manen', 'parameters' => 'Parameters', 'recurring_until' => 'Terugkerend tot jaar', 'reset' => 'Wekelijkse reset', 'seasons' => 'Seizoenen', 'start_offset' => 'Begin offset', 'suffix' => 'Suffix', 'week_names' => 'Week Namen', 'weekdays' => 'Week Dagen', ], 'helpers' => [ 'month_type' => 'Schrikkelmaanden gebruiken geen weekdagen, maar hebben nog steeds invloed op manen en seizoenen.', 'start_offset' => 'Standaard begint de kalender op de eerste weekdag van jaar 0. Het wijzigen van dit veld heeft invloed op de plaats van de eerste dag van de kalender.', ], 'hints' => [ 'is_incrementing' => 'Bij vooruitgaande kalenders wordt de huidige datum automatisch verhoogd om 00:00 UTC.', 'months' => 'Je kalender moet minimaal 2 maanden bevatten.', 'moons' => 'Door manen toe te voegen, verschijnen ze bij elke volle en nieuwe maan in de kalender. Als de periode van volle maan langer is dan 10 dagen, worden ook afnemende en wassende manen weergegeven.', 'parent_calendar' => 'Als je de kalender een bovenliggende kalender geeft, worden de herinneringen en weerseffecten van de bovenliggende kalender meegenomen.', 'reset' => 'Begin altijd aan het begin van de maand of het jaar op de eerste weekdag.', 'seasons' => 'Maak seizoenen voor je kalender door aan te geven wanneer ze beginnen. Kanka zorgt voor de rest.', 'weekdays' => 'Stel je weekdag namen in. Er zijn minimaal 2 werkdagen vereist.', 'weeks' => 'Definieer een aantal namen voor de belangrijkste weken van je kalender.', 'years' => 'Sommige jaren zijn zo belangrijk dat ze hun eigen naam hebben.', ], 'index' => [], 'layouts' => [ 'month' => 'Maand', 'year' => 'Jaar', ], 'modals' => [ 'switcher' => [ 'title' => 'Jaar Wisselaar', ], ], 'month_types' => [ 'intercalary' => 'Intercalary', 'standard' => 'Standaard', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'month' => 'Maandelijks', 'year' => 'Jaarlijks', ], ], 'resets' => [ '' => 'Geen', 'month' => 'Maandelijks', 'year' => 'Jaarlijks', ], ], 'panels' => [ 'intercalary' => 'Schrikkeldagen', 'leap_year' => 'Schrikkeljaar', 'months' => 'Maanden', 'weeks' => 'Weken', 'years' => 'Genoemde Jaren', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Duur in dagen', 'month' => 'Aan het einde van welke maand', 'name' => 'Naam van intercalatie', ], 'month' => [ 'alias' => 'Maand Alias', 'length'=> 'Dagen', 'name' => 'Maand Naam', 'type' => 'Type', ], 'moon' => [ 'fullmoon' => 'Volle maan elke (dagen)', 'name' => 'Maan Naam', 'offset' => 'Eerste volle maan offset', ], 'seasons' => [ 'day' => 'Dag start', 'month' => 'Maand start', 'name' => 'Seizoen Naam', ], 'weeks' => [ 'name' => 'Week Naam', 'number' => 'Nummer', ], 'year' => [ 'name' => 'Jaar Naam', 'number' => 'Jaar', ], ], 'placeholders' => [ 'colour' => 'Kleur', 'comment' => 'Geboortedag, festival, zonnestilstand', 'date' => 'De huidige datum', 'leap_year_amount' => 'Aantal dagen toegevoegd aan een schrikkeljaar', 'leap_year_month' => 'Maand waarop dagen worden opgeteld', 'leap_year_offset' => 'Elke hoeveel jaar is een schrikkeljaar', 'leap_year_start' => 'Eerste jaar dat een schrikkeljaar is', 'length' => 'Gebeurtenis duur in dagen', 'months' => 'Aantal maanden in een jaar', 'recurring_until' => 'Laatste terugkerend jaar (leeg laten voor altijd terugkerend)', 'seasons' => 'Aantal seizoenen', 'suffix' => 'Huidig Tijdperk Suffix (nC, vC)', 'type' => 'Type van de kalender', 'weekdays' => 'Aantal dagen in een week', ], 'show' => [ 'missing_details' => 'Deze kalender kan niet worden weergegeven. Kalenders hebben minimaal 2 maanden en 2 weekdagen nodig om correct te worden weergegeven.', 'tabs' => [ 'events' => 'Kalender Gebeurtenissen', 'weather' => 'Weer', ], ], ]; ================================================ FILE: lang/nl/campaigns/applications.php ================================================ [ 'accept' => 'Accepteren', 'reject' => 'Afwijzen', ], 'apply' => [ 'apply' => 'Toepassen', 'help' => 'Deze campaign staat open voor nieuwe leden. Meld je aan om mee te doen door het formulier in te vullen. Je ontvangt een melding wanneer de campaign beheerders je aanvraag beoordelen.', 'remove_text' => 'jouw inzending', 'success' => [ 'apply' => 'Je aanvraag is opgeslagen. Je kunt het op elk moment wijzigen of annuleren. Je krijgt een melding wanneer de campaign beheerders het beoordelen.', 'remove'=> 'Je aanvraag is verwijderd.', 'update'=> 'Je aanvraag is bijgewerkt. Je kunt het op elk moment wijzigen of annuleren. Je krijgt een melding wanneer de campaign beheerders het beoordelen.', ], 'title' => 'Join :name', ], 'errors' => [], 'fields' => [ 'application' => 'Aanvraging', ], 'helpers' => [], 'placeholders' => [ 'note' => 'Schrijf je aanvraag op om mee te doen aan de campaign', ], 'update' => [ 'approve' => 'Selecteer de rol waaraan de gebruiker zal worden toegevoegd zoals in je campaign.', 'approved' => 'Aanvraag goedgekeurd.', 'reject' => 'Schrijf een optioneel bericht aan de gebruiker waarom je zijn aanvraag afwijst.', 'rejected' => 'Aanvraag afgewezen', ], ]; ================================================ FILE: lang/nl/campaigns/default-images.php ================================================ [ 'add' => 'Voeg een nieuwe standaard afbeelding toe', ], 'create' => [ 'error' => 'Fout bij het opslaan van de nieuwe standaard entiteitsafbeeldingen. Is :type al ingesteld?', 'success' => 'Standaard entiteitsafbeelding voor :type gemaakt.', 'title' => 'Nieuwe standaard entiteitsafbeelding', ], 'destroy' => [ 'success' => 'Standaard entiteitsafbeelding voor :type verwijderd.', ], 'index' => [], ]; ================================================ FILE: lang/nl/campaigns/gallery.php ================================================ [ 'close' => 'Sluiten', 'save' => 'Opslaan', ], 'destroy' => [ 'success' => 'Afbeelding :name verwijderd.', ], 'fields' => [ 'created_by' => 'Geupload door', 'ext' => 'Ext', 'folder' => 'Map', 'image_used_in' => '{1}Gebruikt als afbeelding van één entiteit.|[2,*]Gebruikt als afbeelding van :count entiteiten.', 'name' => 'Naam', 'size' => 'Grootte', ], 'new_folder' => [ 'title' => 'Nieuwe map', ], 'no_folder' => 'Geen map', 'placeholders' => [ 'search' => 'Naam afbeelding zoeken...', ], 'title' => 'Campaign :campaign Galerij', 'update' => [ 'success' => 'Afbeelding gewijzigd.', ], 'uploader' => [ 'add' => 'Voeg nieuwe toe', 'new_folder' => 'Nieuwe Map', 'or' => 'of', 'select_file' => 'Selecteer een bestand', 'well' => 'Drop bestand om te uploaden', ], ]; ================================================ FILE: lang/nl/campaigns/plugins.php ================================================ [ 'disable' => 'Schakel plugin uit', 'enable' => 'Schakel plugin in', 'update' => 'Werk plugin bij', 'update_available' => 'Update beschikbaar!', ], 'destroy' => [ 'success' => 'Plugin :plugin verwijderd.', ], 'disabled' => [ 'success' => 'Plugin :plugin uitgeschakeld', ], 'empty_list' => 'De campaign heeft momenteel geen plugins. Ga naar de marktplaats om er een paar te installeren en kom terug om ze te activeren.', 'enabled' => [ 'success' => 'Plugin :plugin ingeschakeld', ], 'errors' => [ 'invalid_plugin' => 'Ongeldige plugin', ], 'fields' => [ 'name' => 'Plugin naam', 'status' => 'Status', 'type' => 'Plugin type', ], 'info' => [ 'helper' => 'Wanneer er een nieuwe versie van een plugin wordt uitgebracht, kan je deze bijwerken naar de nieuwste versie voor je campaign.', 'title' => 'Plugin :plugin updates', 'updates' => 'Updates', ], 'status' => [ 'disabled' => 'Uitgeschakeld', 'enabled' => 'Ingeschakeld', ], 'templates' => [ 'name' => ':name door :user', ], 'title' => 'Campaign :name Plugins', 'types' => [ 'attribute' => 'Attribuutsjabloon', 'pack' => 'Content Pack', 'theme' => 'Thema', ], 'update' => [ 'success' => 'Plugin :plugin bijgewerkt.', ], ]; ================================================ FILE: lang/nl/campaigns/recovery.php ================================================ [ 'recover' => 'Herstellen', ], 'error' => 'Er is een fout opgetreden bij het herstellen van entiteiten.', 'fields' => [ 'deleted' => 'Verwijderd', ], 'title' => 'Entiteitsherstel voor :campaign', ]; ================================================ FILE: lang/nl/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Kalenders', 'title' => 'Tijdsbewaarder', ], 'murderer' => [ 'goal' => 'Overleden personages', 'title' => 'Moordenaar', ], ], 'targets' => [], 'titles' => [ 'calendars' => 'Tijdsbewaarder level :level', 'characters'=> 'Naam Gever level :level', 'dead' => 'Moordenaar level :level', 'families' => 'Familie planning level :level', 'locations' => 'Bouwer level :level', 'quests' => 'Mastermind level :level', 'races' => 'Fokker level :level', ], ]; ================================================ FILE: lang/nl/campaigns.php ================================================ [ 'success' => 'Campaign gemaakt.', 'title' => 'Nieuwe Campaign', ], 'destroy' => [], 'edit' => [ 'success' => 'Campaign bijgewerkt.', ], 'entity_note_visibility' => [], 'entity_personality_visibilities' => [ 'private' => 'Nieuwe personages hebben standaard hun persoonlijkheid privé.', ], 'entity_visibilities' => [ 'private' => 'Nieuwe entiteiten zijn privé', ], 'errors' => [ 'access' => 'Je hebt geen toegang tot deze campaign.', 'unknown_id' => 'Onbekende Campaign.', ], 'export' => [], 'fields' => [ 'boosted' => 'Boosted door', 'entity_count' => 'Entiteit Telling', 'entry' => 'Campaign beschrijving', 'followers' => 'Volgers', 'header_image' => 'Header Afbeelding', 'image' => 'Afbeelding', 'locale' => 'Lokale', 'name' => 'Naam', 'open' => 'Open voor sollicitaties', 'public_campaign_filters' => 'Openbare Campaign Filters', 'superboosted' => 'Superboosted door', 'system' => 'Systeem', 'theme' => 'Thema', ], 'following' => 'Volgend', 'helpers' => [ 'boosted' => 'Sommige functies zijn ontgrendeld omdat deze campaign een boost krijgt. Lees meer op de :settings pagina.', 'css' => 'Schrijf je eigen CSS die in de pagina\'s van je campaign wordt geladen. Houd er rekening mee dat elk misbruik van deze functie kan leiden tot het verwijderen van je aangepaste CSS. Herhaalde of ernstige overtredingen kunnen ertoe leiden dat je campaign wordt verwijderd.', 'excerpt' => 'Het campaign excerpt wordt op het dashboard weergegeven, dus schrijf een paar zinnen die je wereld introduceren. Houd het kort voor het beste resultaat.', 'hide_history' => 'Schakel deze optie in om de geschiedenis van entiteiten te verbergen voor niet-beheerders van de campaign.', 'hide_members' => 'Schakel deze optie in om de lijst met campaign leden te verbergen voor niet-beheerders.', 'locale' => 'De taal waarin je campaign is geschreven. Dit wordt gebruikt voor het genereren van inhoud en het groeperen van openbare campaigns.', 'name' => 'Je campaign / wereld kan elke naam hebben, zolang deze maar minimaal 4 letters of cijfers bevat.', 'public_campaign_filters' => 'Help anderen de campaign te vinden naast andere openbare campaigns door de volgende informatie te verstrekken.', 'system' => 'Als je campaign openbaar zichtbaar is, wordt het systeem weergegeven op de :link pagina.', 'systems' => 'Om te voorkomen dat gebruikers volgestopt raken met opties, zijn sommige functies van Kanka alleen beschikbaar met specifieke RPG-systemen (dwz het D & D 5e monster stat-blok). Als je hier ondersteunde systemen toevoegt, worden deze functies ingeschakeld.', 'theme' => 'Forceer het thema voor de campaign, waarbij de voorkeur van een gebruiker wordt overschreven.', 'view_public' => 'Om je campaign te bekijken zoals een openbare kijker dat zou doen, open je :link in een incognitovenster.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Kopieer de link naar je klembord', 'link' => 'Nieuwe Link', ], 'create' => [ 'buttons' => [ 'create' => 'Maak uitnodiging', ], 'title' => 'Nodig iemand uit voor je campaign', ], 'destroy' => [ 'success' => 'Uitnodiging verwijderd.', ], 'error' => [ 'inactive_token' => 'Deze token is al gebruikt of de campaign bestaat niet meer.', 'invalid_token' => 'Dit token is niet meer geldig.', ], 'fields' => [ 'created' => 'Verstuurd', 'role' => 'Rol', 'type' => 'Type', ], 'unlimited_validity' => 'Onbeperkt', ], 'leave' => [ 'confirm' => 'Weet je zeker dat je de :name campaign wilt verlaten? Je hebt er geen toegang meer toe, tenzij een beheerder van de campagne je opnieuw uitnodigt.', 'error' => 'Kan de campaign niet verlaten.', 'success' => 'Je hebt de campaign verlaten.', ], 'members' => [ 'actions' => [ 'switch' => 'Wissel', 'switch-back' => 'Terug naar mijn gebruiker', ], 'fields' => [ 'joined' => 'Aangesloten', 'last_login' => 'Laatste Login', 'name' => 'Gebruiker', 'role' => 'Rol', 'roles' => 'Rollen', ], 'helpers' => [ 'switch' => 'Wissel naar deze gebruiker', ], 'impersonating' => [ 'message' => 'Je bekijkt de campaign als een andere gebruiker. Sommige functies zijn uitgeschakeld, maar de rest werkt precies zoals de gebruiker het zou zien. Om terug te schakelen naar jouw gebruiker, gebruik je de knop Wissel Terug op de plaats waar de knop Uitloggen zich gewoonlijk bevindt.', 'title' => ':name aan het imiteren', ], 'invite' => [ 'description' => 'Je kunt vrienden uitnodigen om deel te nemen aan je campaign door hen een Uitnodiging Link te geven. Na het accepteren van hun uitnodiging, worden ze toegevoegd als lid in de gevraagde rol. Je kunt ze ook een verzoek per e-mail sturen.', 'more' => 'Je kunt meer rollen toevoegen via de :link.', 'title' => 'Uitnodiging', ], 'roles' => [ 'member' => 'Lid', 'owner' => 'Beheerder', 'player' => 'Speler', 'public' => 'Openbaar', 'viewer' => 'Kijker', ], 'switch_back_success' => 'Je bent nu terug bij je oorspronkelijke gebruiker.', ], 'open_campaign' => [], 'panels' => [ 'dashboard' => 'Dashboard', 'setup' => 'Opstelling', 'sharing' => 'Delen', 'systems' => 'Systemen', 'ui' => 'Interface', ], 'placeholders' => [ 'locale' => 'Taal code', 'name' => 'Jouw campaign naam', 'system' => 'D&D, Pathfinder, Fate, DSA', ], 'roles' => [ 'actions' => [ 'add' => 'Voeg een rol toe', ], 'admin_role' => 'Beheerder rol', 'create' => [ 'success' => 'Rol gemaakt.', 'title' => 'Maak een nieuwe rol aan voor :name', ], 'destroy' => [ 'success' => 'Rol verwijderd.', ], 'edit' => [ 'success' => 'Rol bijgewerkt.', 'title' => 'Wijzig Rol :name', ], 'fields' => [ 'name' => 'Naam', 'permissions' => 'Permissies', 'type' => 'Type', 'users' => 'Gebruikers', ], 'helper' => [ '1' => 'Een campaign kan zoveel rollen hebben als je wilt. De rol "Beheerder" heeft automatisch toegang tot alles in een campaign, maar elke andere rol kan specifieke rechten hebben voor verschillende soorten entiteiten (personage, locatie, enz.).', '2' => 'Entiteiten kunnen meer verfijnde machtigingen hebben door het tabblad "Machtigingen" van een entiteit te bekijken. Dit tabblad wordt weergegeven zodra je campaign meerdere rollen of leden heeft.', '3' => 'Men kan ofwel kiezen voor een "opt-out" systeem, waarbij rollen toegang krijgen om alle entiteiten te bekijken, en het selectievakje "Privé" op entiteiten gebruiken om ze te verbergen. Of men kan rollen niet veel machtigingen geven, maar elke entiteit zo instellen dat deze afzonderlijk zichtbaar is.', ], 'hints' => [ 'campaign_not_public' => 'De openbare rol heeft machtigingen, maar de campaign is privé. Je kunt deze instelling wijzigen op het tabblad Delen wanneer je de campaign bewerkt.', 'role_permissions' => 'Schakel de rol \':name\' in om de volgende acties op alle entiteiten uit te voeren.', ], 'members' => 'Leden', 'permissions' => [ 'actions' => [ 'add' => 'Maak', 'dashboard' => 'Dashboard', 'delete' => 'Verwijder', 'edit' => 'Wijzig', 'manage' => 'Beheer', 'members' => 'Leden', 'permission'=> 'Permissies', 'read' => 'Bekijk', 'toggle' => 'Verander voor alle', ], ], 'placeholders' => [ 'name' => 'Naam van de rol', ], 'title' => 'Campaign :name Rollen', 'types' => [ 'owner' => 'Beheerder', 'public' => 'Openbaar', 'standard' => 'Standaard', ], 'users' => [ 'actions' => [ 'add' => 'Voeg een lid toe', ], 'create' => [ 'success' => 'Gebruiker toegevoegd aan de rol.', 'title' => 'Voeg een lid toe aan de :name rol', ], 'destroy' => [ 'success' => 'Gebruiker verwijderd van de rol.', ], 'fields' => [ 'name' => 'Naam', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Inschakelen', ], 'boosted' => 'Deze functie is in vroege toegang en momenteel alleen beschikbaar voor :boosted.', 'helpers' => [ 'abilities' => 'Maak vaardigheden, of het nu gaat om prestaties, spreuken of krachten die aan entiteiten kunnen worden toegewezen.', 'calendars' => 'Een plek om de kalenders van je wereld te definiëren.', 'characters' => 'De mensen die jouw wereld bewonen.', 'conversations' => 'Fictieve conversaties tussen personages of tussen campaign gebruikers. Deze module is verouderd.', 'dice_rolls' => 'Voor degenen die Kanka gebruiken voor RPG campaigns, een manier om met dobbelstenen te werken. Deze module is verouderd.', 'events' => 'Feestdagen, festivals, rampen, verjaardagen, oorlogen.', 'families' => 'Clans of families, hun relaties en hun leden.', 'items' => 'Wapens, voertuigen, relikwieën, potions.', 'journals' => 'Observaties geschreven door personages of sessie voorbereiding voor de dungeon master.', 'locations' => 'Planeten, vliegtuigen, continenten, rivieren, staten, nederzettingen, tempels, herbergen.', 'maps' => 'Upload kaarten met lagen en markeringen die naar andere entiteiten in de campaign verwijzen.', 'notes' => 'Lore, natuur, geschiedenis, magie, culturen.', 'organisations' => 'Sekten, religies, facties, gilden.', 'quests' => 'Om verschillende quests bij te houden met personages en locaties.', 'races' => 'Als je campaign meer dan één ras heeft, maakt dit het bijhouden van het overzicht gemakkelijk.', 'tags' => 'Elke entiteit kan meerdere tags hebben. Tags kunnen bij andere tags horen en invoeringen kunnen op tag worden gefilterd.', 'timelines' => 'Geef de geschiedenis van je wereld weer met tijdlijnen.', ], ], 'show' => [ 'actions' => [ 'edit' => 'Wijzig Campaign', ], 'tabs' => [ 'achievements' => 'Prestaties', 'default-images' => 'Standaard Afbeeldingen', 'export' => 'Exporteer', 'members' => 'Leden', 'plugins' => 'Plugins', 'recovery' => 'Herstel', 'roles' => 'Rollen', ], 'title' => 'Campaign :name', ], 'superboosted' => [], 'ui' => [], 'visibilities' => [ 'private' => 'Privé', 'public' => 'Openbaar', ], ]; ================================================ FILE: lang/nl/characters.php ================================================ [ 'add_appearance' => 'Voeg een uiterlijk toe', 'add_personality' => 'Voeg een persoonlijkheid toe', ], 'conversations' => [], 'create' => [ 'title' => 'Nieuw Personage', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'fields' => [ 'age' => 'Leeftijd', 'is_dead' => 'Dood', 'is_personality_visible' => 'Persoonlijkheid zichtbaar', 'life' => 'Leven', 'physical' => 'Fysiek', 'sex' => 'Geslacht', 'title' => 'Titel', 'traits' => 'Eigenschappen', ], 'helpers' => [ 'age' => 'Je kunt deze entiteit koppelen aan een kalender van je campaign om in plaats daarvan automatisch hun leeftijd te berekenen. :more.', ], 'hints' => [ 'is_dead' => 'Dit personage is dood', 'is_personality_visible' => 'Schakel deze optie uit om het hele persoonlijkheidsgedeelte te verbergen voor niet-beheerders.', 'personality_not_visible' => 'Persoonlijkheidskenmerken van dit personage zijn momenteel alleen zichtbaar voor Beheerders', 'personality_visible' => 'Persoonlijkheidskenmerken van dit personage zijn voor iedereen zichtbaar.', ], 'index' => [], 'items' => [], 'journals' => [], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => 'Personage toegevoegd aan organisatie.', 'title' => 'Nieuwe Organisatie voor :name', ], 'destroy' => [ 'success' => 'Personage organisatie verwijderd.', ], 'edit' => [ 'success' => 'Personage organisatie bijgewerkt.', 'title' => 'Werk organisatie voor :name bij', ], 'fields' => [ 'role' => 'Rol', ], ], 'placeholders' => [ 'age' => 'Leeftijd', 'appearance_entry' => 'Beschrijving', 'appearance_name' => 'Haar, Ogen, Huid, Lengte', 'personality_entry' => 'Details', 'personality_name' => 'Doelen, Manieren, Angsten, Verplichtingen', 'physical' => 'Fysiek', 'sex' => 'Geslacht', 'title' => 'Titel', 'traits' => 'Eigenschappen', 'type' => 'NPC, Speler Personage, Godheid', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Quests waarvan het personage een quest gever is.', 'quest_member' => 'Quests waarvan het personage lid is.', ], ], 'sections' => [ 'appearance' => 'Uiterlijk', 'personality' => 'Persoonlijkheid', ], 'show' => [], 'warnings' => [ 'personality_hidden' => 'Het is niet toegestaan om persoonlijkheidskenmerken van dit personage te bewerken.', ], ]; ================================================ FILE: lang/nl/colours.php ================================================ 'Aqua', 'black' => 'Zwart', 'blue' => 'Blauw', 'brown' => 'Bruin', 'green' => 'Groen', 'grey' => 'Grijs', 'light-blue' => 'Licht Blauw', 'maroon' => 'Kastanjebruin', 'navy' => 'Marineblauw', 'none' => 'Geen', 'orange' => 'Oranje', 'pink' => 'Roze', 'purple' => 'Paars', 'red' => 'Rood', 'teal' => 'Groenblauw', 'white' => 'Wit', 'yellow' => 'Geel', ]; ================================================ FILE: lang/nl/conversations.php ================================================ [ 'title' => 'Nieuwe Conversatie', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'messages' => 'Berichten', 'participants' => 'Deelnemers', ], 'hints' => [ 'participants' => 'Voeg deelnemers aan je conversatie toe door op het :icon pictogram in de rechterbovenhoek te drukken.', ], 'index' => [], 'messages' => [ 'destroy' => [ 'success' => 'Bericht verwijderd.', ], 'is_updated' => 'Bijgewerkt', 'load_previous' => 'Laad vorige berichten', 'placeholders' => [ 'message' => 'Jouw berichten', ], ], 'participants' => [ 'create' => [ 'success' => 'Deelnemer :entity toegevoegd aan de conversatie.', ], 'destroy' => [ 'success' => 'Deelnemer :entity verwijderd uit de conversatie.', ], 'modal' => 'Deelnemers', 'title' => 'Deelnemers van :name', ], 'placeholders' => [ 'name' => 'Naam van de conversatie', 'type' => 'In Game, Prep, Plot', ], 'show' => [], 'tabs' => [ 'participants' => 'Deelnemers', ], 'targets' => [ 'characters' => 'Personages', 'members' => 'Leden', ], ]; ================================================ FILE: lang/nl/crud.php ================================================ [ 'actions' => 'Acties', 'apply' => 'Toepassen', 'back' => 'Terug', 'copy' => 'Kopieer', 'copy_mention' => 'Kopieer [ ] opmerking', 'copy_to_campaign' => 'Kopieer naar Campaign', 'explore_view' => 'Geneste weergave', 'export' => 'Exporteer (PDF)', 'find_out_more' => 'Meer te weten komen', 'go_to' => 'Ga naar :name', 'json-export' => 'Exporteer (JSON)', 'move' => 'Veranderen of Verplaatsen', 'new' => 'Nieuw', 'next' => 'Volgende', 'reset' => 'Reset', ], 'add' => 'Toevoegen', 'alerts' => [ 'copy_mention' => 'De geavanceerde vermelding van de entiteit is naar je klembord gekopieerd.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Bulk Bewerken en Taggen', ], 'age' => [ 'helper' => 'Je kunt + en - voor het nummer gebruiken om de leeftijd met dat aantal bij te werken.', ], 'edit' => [ 'tagging' => 'Acties voor tags', 'tags' => [ 'add' => 'Toevoegen', 'remove' => 'Verwijderen', ], 'title' => 'Meerdere entiteiten bewerken', ], 'errors' => [ 'admin' => 'Alleen campaign beheerders kunnen de privéstatus van entiteiten wijzigen.', 'general' => 'Er is een fout opgetreden bij het verwerken van je actie. Probeer het opnieuw en neem contact met ons op als het probleem zich blijft voordoen. Foutmelding :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Overschrijven', ], 'helpers' => [ 'override' => 'Indien geselecteerd, worden permissies van de geselecteerde entiteiten hiermee overschreven. Indien niet aangevinkt, worden de geselecteerde permissies toegevoegd aan de bestaande.', ], 'title' => 'Wijzig permissies voor verschillende entiteiten', ], ], 'bulk_templates' => [ 'bulk_title' => 'Pas een sjabloon toe op meerdere entiteiten', ], 'cancel' => 'Annuleer', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Kopieer entiteiten naar andere campaign', 'panel' => 'Kopieer', 'title' => 'Kopieer \':name\' naar andere campaign', ], 'create' => 'Maak', 'datagrid' => [ 'empty' => 'Nog niets te laten zien.', ], 'delete_modal' => [ 'title' => 'Bevestiging verwijderen', ], 'destroy_many' => [], 'edit' => 'Wijzig', 'errors' => [ 'boosted_campaigns' => 'Deze functie is alleen beschikbaar voor :boosted.', 'unavailable_feature' => 'Functie niet beschikbaar', ], 'events' => [], 'fields' => [ 'calendar_date' => 'Kalender Datum', 'colour' => 'Kleur', 'copy_abilities' => 'Kopieer Vaardigheden', 'copy_inventory' => 'Kopieer Inventory', 'copy_links' => 'Kopieer Entiteit Links', 'creator' => 'Maker', 'excerpt' => 'Excerpt', 'has_entity_files' => 'Heeft entiteit bestanden', 'has_image' => 'Heeft een afbeelding', 'header_image' => 'Header Afbeeldingen', 'image' => 'Afbeelding', 'is_private' => 'Privé', 'is_star' => 'Vastgemaakt', 'name' => 'Naam', 'position' => 'Positie', 'tooltip' => 'Tooltip', 'type' => 'Type', 'visibility' => 'Zichtbaarheid', ], 'files' => [ 'errors' => [ 'max' => 'Je hebt het maximale aantal (: max) bestanden voor deze entiteit bereikt.', 'no_files' => 'Geen bestanden.', ], 'hints' => [ 'limit' => 'Elke entiteit kan maximaal :max bestanden hebben geüpload.', 'limitations' => 'Ondersteunde formaten: :formats. Max Bestandsgrootte: :size', ], ], 'filter' => 'Filter', 'filters' => [ 'all' => 'Filter op alle afstammelingen', 'clear' => 'Wis Filters', 'direct' => 'Filter naar directe afstammelingen', 'filtered' => ':count van :total :entity weergegeven.', 'options' => [ 'exclude' => 'Uitsluiten', 'include' => 'Omvatten', 'none' => 'Geen', ], 'show' => 'Toon Filters', 'sorting' => [ 'asc' => ':field Oplopend', 'desc' => ':field Aflopend', 'helper' => 'Bepaal in welke volgorde de resultaten worden weergegeven.', ], 'title' => 'Filters', ], 'forms' => [ 'actions' => [ 'calendar' => 'Voeg een kalender datum toe', ], 'copy_options' => 'Kopieer Opties', ], 'helpers' => [ 'copy_options' => 'Kopieer de volgende gerelateerde elementen van de bron naar de nieuwe entiteit.', ], 'hidden' => 'Verborgen', 'hints' => [ 'calendar_date' => 'Een kalender datum maakt eenvoudig filteren in lijsten mogelijk en houdt ook een kalender gebeurtenis bij in de geselecteerde kalender.', 'image_limitations' => 'Ondersteunde formaten: :formats. Max Bestandsgrootte: :size.', 'is_star' => 'Vastgezette elementen verschijnen in het menu van de entiteit', 'tooltip' => 'Vervang de automatisch gegenereerde tooltip door de volgende inhoud.', ], 'history' => [ 'unknown' => 'Onbekend', 'view' => 'Bekijk entiteit log', ], 'image' => [ 'error' => 'We kunnen de door jou aangevraagde afbeelding niet ophalen. Het kan zijn dat de website ons niet toestaat om de afbeelding te downloaden (meestal voor Squarespace en DeviantArt), of dat de link niet langer geldig is. Let er ook op dat de afbeelding niet groter is dan :size.', ], 'is_private' => 'Deze entiteit is privé en alleen zichtbaar voor leden van de Beheerder rol.', 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Toestaan', 'deny' => 'Weigeren', 'ignore' => 'Overslaan', 'remove' => 'Verwijder', ], 'bulk_entity' => [ 'allow' => 'Toestaan', 'deny' => 'Weigeren', 'inherit' => 'Erven', ], 'delete' => 'Verwijder', 'edit' => 'Wijzig', 'toggle' => 'Wissel', ], 'fields' => [ 'member' => 'Lid', 'role' => 'Rol', ], 'helpers' => [ 'setup' => 'Gebruik deze interface om te verfijnen hoe rollen en gebruikers kunnen communiceren met deze entiteit. :allow staat de gebruiker of rol toe om deze actie uit te voeren. :deny zal hen die actie ontzeggen. :inherit gebruikt de gebruiker\'s rol of de toestemming van de hoofd rol. Een gebruiker die is ingesteld op :allow, kan de actie uitvoeren, zelfs als hun rol is ingesteld op :deny.', ], 'success' => 'Permissies opgeslagen.', 'title' => 'Permissies', 'too_many_members' => 'Deze campaign heeft te veel leden (>10) om in deze interface weer te geven. Gebruik de Permissie knop in de entiteit weergave om permissies in detail te beheren.', ], 'placeholders' => [ 'calendar' => 'Kies een kalender', 'gallery_image' => 'Kies een afbeelding uit de campaign galerij', 'image_url' => 'Je kunt in plaats daarvan een afbeelding uploaden vanaf een URL', 'journal' => 'Kies een logboek', 'location' => 'Kies een locatie', 'organisation' => 'Kies een organisatie', 'tag' => 'Kies een tag', 'timeline' => 'Kies een tijdlijn', ], 'relations' => [], 'remove' => 'Verwijder', 'save' => 'Opslaan', 'save_and_close' => 'Opslaan en Afsluiten', 'save_and_copy' => 'Opslaan en Kopiëren', 'save_and_new' => 'Opslaan en Nieuwe', 'save_and_update' => 'Opslaan en Wijzig', 'save_and_view' => 'Opslaan en Bekijken', 'search' => 'Zoeken', 'select' => 'Selecteren', 'tabs' => [ 'abilities' => 'Vaardigheden', 'inventory' => 'Inventory', 'permissions' => 'Permissies', 'reminders' => 'Herinneringen', ], 'update' => 'Update', 'users' => [ 'unknown' => 'Onbekend', ], 'view' => 'Bekijk', 'visibilities' => [ 'admin' => 'Beheerder', 'admin-self' => 'Zelf & Beheerder', 'all' => 'Alle', 'members' => 'Leden', 'self' => 'Zelf', ], ]; ================================================ FILE: lang/nl/dashboard.php ================================================ [ 'follow' => 'Volg', 'join' => 'Join', 'unfollow' => 'Ontvolg', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Wijzig', 'new' => 'Nieuw Dashboard', ], 'create' => [ 'success' => 'Nieuw campaign dashboard :name gemaakt.', 'title' => 'Nieuw Campaign Dashboard', ], 'custom' => [ 'text' => 'Je bewerkt momenteel het :name dashboard van de campaign.', ], 'default' => [ 'text' => 'Je bewerkt momenteel het standaard dashboard van de campaign.', 'title' => 'Standaard Dashboard', ], 'delete' => [ 'success' => 'Dashboard :name verwijderd.', ], 'fields' => [ 'copy_widgets' => 'Kopieer widgets', 'name' => 'Dashboard naam', 'visibility' => 'Zichtbaarheid', ], 'helpers' => [ 'copy_widgets' => 'Dupliceer de widgets van het :name dashboard naar deze nieuwe.', ], 'placeholders' => [ 'name' => 'Naam van het dashboard', ], 'update' => [ 'success' => 'Campaign dashboard :name bijgewerkt.', 'title' => 'Werk campaign dashboard :name bij', ], 'visibility' => [ 'default' => 'Standaard', 'none' => 'Geen', 'visible' => 'Zichtbaar', ], ], 'helpers' => [ 'follow' => 'Als je een campaign volgt, wordt deze weergegeven in de campaign wisselaar (linksboven) onder je campaigns.', 'join' => 'Deze campaign staat open voor nieuwe leden. Klik om aan te vragen om er lid van te worden.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Voeg een widget toe', 'back_to_dashboard' => 'Terug naar dashboard', 'edit' => 'Wijzig een widget', ], 'title' => 'Campaign Dashboard Setup', ], 'title' => 'Dashboard', 'widgets' => [ 'calendar' => [ 'actions' => [ 'next' => 'Wijzig de datum naar de volgende dag', 'previous' => 'Wijzig de datum naar de vorige dag', ], 'previous_events' => 'Vorige', 'upcoming_events' => 'Aankomend', ], 'campaign' => [ 'helper' => 'Deze widget geeft de campaign header weer. Deze widget wordt altijd weergegeven op het standaard dashboard.', ], 'create' => [ 'success' => 'Widget toegevoegd aan het dashboard.', ], 'delete' => [ 'success' => 'Widget verwijderd van het dashboard', ], 'fields' => [ 'name' => 'Aangepaste widget naam', 'width' => 'Breedte', ], 'recent' => [ 'entity-header' => 'Gebruik de entiteit header als afbeelding', 'help' => 'Toon alleen de laatst bijgewerkte entiteit, maar toon een hele preview van de entiteit', 'helpers' => [ 'entity-header' => 'Als je entiteit een entiteit header heeft (functie voor boosted campagnes), stel je deze widget in om die afbeelding te gebruiken in plaats van de afbeelding van de entiteit.', ], 'singular' => 'Enkelvoud', 'tags' => 'Filter de lijst met onlangs gewijzigde entiteiten op gespecificeerde tags.', 'title' => 'Onlangs gewijzigd', ], 'unmentioned' => [ 'title' => 'Niet-genoemde entiteiten', ], 'update' => [ 'success' => 'Widget aangepast.', ], 'widths' => [ '0' => 'Auto', '12'=> 'Volledig (100%)', '3' => 'Mini (25%)', '4' => 'Klein (33%)', '6' => 'Half (50%)', '8' => 'Wijd (66%)', ], ], ]; ================================================ FILE: lang/nl/datetime.php ================================================ 'dag', 'days' => 'dagen', 'elapsed_ago' => ':duration geleden', 'hour' => 'uur', 'hours' => 'uren', 'just_now' => 'zo net', 'minute' => 'minuut', 'minutes' => 'minuten', 'month' => 'maand', 'months' => 'maanden', 'second' => 'seconde', 'seconds' => 'secondes', 'week' => 'week', 'weeks' => 'weken', 'year' => 'jaar', 'years' => 'jaren', ]; ================================================ FILE: lang/nl/default.php ================================================ 'Pagina Titel', ]; ================================================ FILE: lang/nl/dice_roll_results.php ================================================ [ 'title' => 'Dobbelsteen Worp Resultaten', ], ]; ================================================ FILE: lang/nl/dice_rolls.php ================================================ [ 'title' => 'Nieuwe Dobbelsteen Worp', ], 'destroy' => [ 'dice_roll' => 'Dobbelsteen worp verwijderd.', ], 'edit' => [], 'fields' => [ 'created_at' => 'Gerold Bij', 'parameters' => 'Parameters', 'results' => 'Resultaten', 'rolls' => 'Worpen', ], 'hints' => [ 'parameters' => 'Wat zijn mijn dobbelsteen opties?', ], 'index' => [ 'actions' => [ 'results' => 'Resultaten', ], ], 'placeholders' => [ 'name' => 'Naam van de Dobbelsteen Worp', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Worp', ], 'error' => 'Dobbelsteen worp mislukt. Kan de parameters niet parseren.', 'fields' => [ 'creator' => 'Maker', 'date' => 'Datum', 'result' => 'Resultaat', ], 'hint' => 'Alle worpen gedaan voor deze dobbelsteen worp sjabloon.', 'success' => 'Dobbelstenen gegooid.', ], 'show' => [ 'tabs' => [ 'results' => 'Resultaten', ], ], ]; ================================================ FILE: lang/nl/emails/welcome.php ================================================ 'Welkom bij Kanka :name!', 'header_sub' => 'Gefeliciteerd, je hebt de eerste stap gezet in de creatie van jouw wereld op :kanka!', 'pricing' => 'prijzen', 'section_1' => 'Waar nu naartoe?', 'section_11' => 'Creëer je wereld,', 'section_2' => 'De belangrijkste bron is :discord, waar je veel van onze toegewijde gebruikers, een onboarding-team en de oprichter van Kanka zult vinden, die al je vragen kan beantwoorden.', 'section_4' => 'Onze :youtube heeft video\'s over de basisprincipes van Kanka. Hoewel nog niet alle onderwerpen aan bod komen, voegen we regelmatig nieuwe video\'s toe.', 'section_6' => 'Neem contact op', 'section_7' => 'Als je geen antwoord op je vragen hebt gevonden, of gewoon contact met ons wilt opnemen, kan je ons vinden op :facebook, of je kunt ons een e-mail sturen op :email. We zijn een klein team van 2 vrienden, maar we zorgen ervoor dat we elke e-mail beantwoorden die we ontvangen, dus aarzel niet!', 'section_8' => 'Nog een ding', 'section_9_v2' => 'We hebben ervoor gezorgd dat alle kernfuncties in Kanka gratis zijn, en dat zullen we altijd zo houden. Als je ons echter bij dit project wilt steunen, kan je abonnee worden en toegang krijgen tot extra functies, evenals onze eeuwige dankbaarheid. Lees meer op de :pricing pagina.', 'title' => 'Aan de slag met Kanka', ]; ================================================ FILE: lang/nl/entities/abilities.php ================================================ [ 'add' => 'Voeg vaardigheden toe', 'reset' => 'Gebruik van vaardigheden opnieuw instellen', ], 'create' => [ 'success' => 'Vaardigheid :ability toegevoegd aan :entity.', 'success_multiple' => 'Vaardigheden :abilities toegevoegd aan :entity.', 'title' => 'Voeg vaardigheden toe aan :name', ], 'fields' => [ 'note' => 'Notitie', 'position' => 'Positie', ], 'helpers' => [ 'note' => 'Je kunt verwijzen naar entiteiten met behulp van geavanceerde vermeldingen (bijv. :code) en attributen van de entiteit (bijv. :attr) in dit veld.', ], 'import' => [ 'errors' => [ 'no_race' => 'Het personage heeft geen ras.', 'not_character' => 'De entiteit is geen personage.', ], 'success' => '{1} :count geïmporteerd.|[2, *] :count geïmporteerd.', ], 'show' => [ 'helper' => 'Koppel vaardigheden aan deze entiteit. Je kunt altijd de zichtbaarheid bewerken of een vaardigheid verwijderen. Vaardigheden die tot dezelfde bovenliggende vaardigheid behoren, worden weergegeven als filtervakken.', 'title' => 'Entiteit Vaardigheden voor :name', ], 'update' => [ 'title' => 'Entiteit Vaardigheid voor :name', ], ]; ================================================ FILE: lang/nl/entities/actions.php ================================================ [ 'set' => 'Instellen als sjabloon', 'success' => [ 'set' => 'Entiteit :name ingesteld als sjabloon.', 'unset' => 'Entiteit :name is niet langer ingesteld als sjabloon.', ], 'unset' => 'Verwijder als sjabloon', ], ]; ================================================ FILE: lang/nl/entities/attributes.php ================================================ [ 'manage' => 'Beheer', 'more' => 'Meer opties', 'remove_all' => 'Verwijder Alles', ], 'fields' => [ 'community_templates' => 'Community Sjablonen', 'is_private' => 'Privé Attributen', 'is_star' => 'Vastgemaakt', 'value' => 'Waarde', ], 'helpers' => [ 'delete_all' => 'Weet je zeker dat je alle attributen van deze entiteit wilt verwijderen?', ], 'hints' => [], 'index' => [ 'success' => 'Attributen voor :entity bijgewerkt.', 'title' => 'Attributen voor :name', ], 'placeholders' => [ 'attribute' => 'Aantal Conquests, Challenge Ratings, Initiatives, Populaties', 'block' => 'Blokkeer naam', 'checkbox' => 'Selectievak naam', 'icon' => [ 'class' => 'FontAwesome of RPG Awesome klasse: fas fa-gebruikers', 'name' => 'Pictogram naam', ], 'section' => 'Sectie naam', 'value' => 'Waarde van het attribuut', ], 'template' => [ 'success' => 'Attribuutsjabloon :name toegepast op :entity', 'title' => 'Pas een attribuutsjabloon toe voor :name', ], 'types' => [ 'attribute' => 'Attribuut', 'block' => 'Blokkeer', 'checkbox' => 'Selectievak', 'icon' => 'Pictogram', 'section' => 'Sectie', 'text' => 'Multiline Tekst', ], 'visibility' => [ 'entry' => 'Attribuut wordt weergegeven in het entiteit menu.', 'private' => 'Attribuut alleen zichtbaar voor leden van de rol "Beheerder".', 'public' => 'Attribuut zichtbaar voor alle leden.', 'tab' => 'Attribuut wordt alleen weergegeven op het tabblad Attributen.', ], ]; ================================================ FILE: lang/nl/entities/events.php ================================================ [ 'type' => 'Gebeurtenis Type', ], 'helpers' => [ 'characters' => 'Als je het type instelt als geboortedatum of overlijdensdatum voor dit personage, wordt automatisch hun leeftijd berekend. :more.', ], 'types' => [ 'birth' => 'Geboorte', 'death' => 'Overlijden', 'primary' => 'Primair', ], ]; ================================================ FILE: lang/nl/entities/inventories.php ================================================ [], 'create' => [ 'success' => 'Voorwerp :item toegevoegd aan :entity.', 'title' => 'Voeg een Voorwerp toe aan :name', ], 'destroy' => [ 'success' => 'Voorwerp :item verwijderd van :entity', ], 'fields' => [ 'amount' => 'Aantal', 'description' => 'Beschrijving', 'is_equipped' => 'Uitgerust', 'name' => 'Naam', 'position' => 'Positie', ], 'placeholders' => [ 'amount' => 'Elk aantal', 'description' => 'Gebruikt, Beschadigd, Attuned', 'name' => 'Vereist als er geen voorwerp is geselecteerd', 'position' => 'Uitgerust, Rugzak, Opslag, Bank', ], 'show' => [ 'helper' => 'Entiteiten kunnen voorwerpen hebben die eraan zijn gekoppeld om een inventory te maken.', 'title' => 'Entiteit :name Inventory', 'unsorted' => 'Ongesorteerd', ], 'update' => [ 'success' => 'Voorwerp :item bijgewerkt voor :entity.', 'title' => 'Werk een voorwerp bij op :name', ], ]; ================================================ FILE: lang/nl/entities/links.php ================================================ [ 'add' => 'Voeg een link toe', ], 'create' => [ 'success' => 'Link :name toegevoegd aan :entity.', 'title' => 'Voeg een link toe aan :name', ], 'destroy' => [ 'success' => 'Link :name verwijderd van :entity.', ], 'fields' => [ 'icon' => 'Pictogram', 'name' => 'Naam', 'position' => 'Positie', 'url' => 'URL', ], 'helpers' => [ 'icon' => 'Je kunt het pictogram dat voor de link wordt weergegeven, aanpassen. Gebruik een van de gratis pictogrammen van :fontawesome of laat dit veld leeg als standaard.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Boosted campaigns kunnen links toevoegen aan entiteiten die naar externe websites verwijzen.', 'title' => 'Links voor :name', ], 'update' => [ 'success' => 'Link :name bijgewerkt voor :entity', 'title' => 'Werk link bij voor :name', ], ]; ================================================ FILE: lang/nl/entities/logs.php ================================================ [ 'create' => 'Maak', 'delete' => 'Verwijder', 'restore' => 'Herstel', 'update' => 'Werk bij', ], 'fields' => [ 'action' => 'Actie', 'date' => 'Datum', ], 'impersonated' => 'Geïmiteerd door :name', 'show' => [ 'title' => 'Entiteit :name Logs', ], ]; ================================================ FILE: lang/nl/entities/map-points.php ================================================ 'Deze entiteit is vastgemaakt op de volgende kaarten.', 'title' => ':name Kaart Punten', ]; ================================================ FILE: lang/nl/entities/mentions.php ================================================ [], 'helper' => 'Het volgende is een lijst met entiteiten die deze entiteit vermelden in hun "Invoer" veld.', 'show' => [ 'title' => 'Entiteit :name Vermeldingen', ], ]; ================================================ FILE: lang/nl/entities/notes.php ================================================ [ 'add' => 'Nieuwe Entieit Notitie', 'add_user' => 'Voeg gebruiker toe', ], 'create' => [ 'success' => 'Entiteit Notitie \':name\' toegevoegd aan :entity', ], 'destroy' => [ 'success' => 'Entiteit Notitie \':name\' voor :entity verwijderd.', ], 'edit' => [ 'success' => 'Entiteit Notitie \':name\' voor :entity bijgewerkt.', ], 'fields' => [ 'creator' => 'Maker', 'name' => 'Naam', ], 'hint' => 'Informatie die niet helemaal in de standaardvelden van een entiteit past of die privé moet worden gehouden, kan worden toegevoegd als Entiteit Notitie.', 'hints' => [], 'index' => [], 'placeholders' => [ 'name' => 'Naam van de entiteit notitie, observatie of opmerking', ], 'show' => [ 'advanced' => 'Geavanceerde Machtigingen', 'title' => 'Entiteit Notitie :name voor :entity', ], ]; ================================================ FILE: lang/nl/entities/relations.php ================================================ [], 'destroy' => [ 'success' => 'Relatie :target verwijderd voor :entity', ], 'fields' => [ 'attitude' => 'Attitude', 'target' => 'Doel', 'two_way' => 'Maak spiegelrelatie', ], 'helper' => 'Zet relaties op tussen entiteiten met attitudes en zichtbaarheid. Relaties kunnen ook worden vastgemaakt aan het menu van de entiteit.', 'hints' => [ 'attitude' => 'Dit optionele veld kan worden gebruikt om de standaardvolgorde in relaties in aflopende volgorde te definiëren.', 'two_way' => 'Als je ervoor kiest om een spiegelrelatie te maken, wordt dezelfde relatie op het doel gemaakt. Als je er echter een bewerkt, wordt de spiegel niet bijgewerkt.', ], 'placeholders' => [ 'attitude' => '-100 tot 100, waarbij 100 zeer positief is', ], 'show' => [ 'title' => 'Relaties voor :name', ], 'types' => [ 'family_member' => 'Familielid', 'organisation_member' => 'Organisatie Lid', ], 'update' => [ 'success' => 'Relatie :target bijgewerkt voor :entity.', 'title' => 'Werk relaties bij voor :name', ], ]; ================================================ FILE: lang/nl/entities/timelines.php ================================================ 'Tijdlijnen met elementen die aan deze entiteit zijn gekoppeld, worden hieronder weergegeven.', 'show' => [ 'title' => 'Tijdlijnen van :name', ], ]; ================================================ FILE: lang/nl/entities.php ================================================ 'Vaardigheden', 'ability' => 'Vaardigheid', 'attribute_template' => 'Attribuutsjabloon', 'attribute_templates' => 'Attribuutsjablonen', 'calendar' => 'Kalender', 'calendars' => 'Kalenders', 'campaign' => 'Campaign', 'campaigns' => 'Campaigns', 'character' => 'Personage', 'characters' => 'Personages', 'conversation' => 'Conversatie', 'conversations' => 'Conversaties', 'creator' => [ 'back' => 'Terug naar selectie', 'duplicate' => 'Er zijn andere entiteiten van dit type met dezelfde naam.', 'title' => 'Nieuwe Entiteit', ], 'dice_roll' => 'Dobbelsteen Worp', 'dice_rolls' => 'Dobbelsteen Worpen', 'event' => 'Gebeurtenis', 'events' => 'Gebeurtenissen', 'families' => 'Families', 'family' => 'Familie', 'item' => 'Voorwerp', 'items' => 'Voorwerpen', 'journal' => 'Logboek', 'journals' => 'Logboeken', 'location' => 'Locatie', 'locations' => 'Locaties', 'map' => 'Kaart', 'maps' => 'Kaarten', 'new' => [], 'note' => 'Notitie', 'notes' => 'Notities', 'organisation' => 'Organisatie', 'organisations' => 'Organisaties', 'quest' => 'Quest', 'quests' => 'Quests', 'race' => 'Ras', 'races' => 'Rassen', 'tag' => 'Tag', 'tags' => 'Tags', 'timeline' => 'Tijdlijn', 'timelines' => 'Tijdlijnen', ]; ================================================ FILE: lang/nl/errors.php ================================================ [ 'body' => 'Het lijkt erop dat je geen toestemming hebt om deze pagina te openen!', 'title' => 'Toestemming geweigerd', ], '403-form' => [ 'help' => 'Dit kan komen door de time-out van je sessie. Probeer opnieuw in te loggen in een ander venster voordat je opslaat.', ], '404' => [ 'body' => 'Sorry, de pagina die je zoekt, is niet gevonden.', 'title' => 'Pagina niet gevonden', ], '500' => [ 'body' => [ '1' => 'Oeps, het ziet ernaar uit dat er iets is misgegaan.', '2' => 'Er is een rapport met de aangetroffen fout naar ons gestuurd, maar soms helpt het als we wat meer kunnen weten over wat je aan het doen was.', ], 'title' => 'Fout', ], '503' => [ 'body' => [ '1' => 'Kanka is momenteel in onderhoud, wat meestal betekent dat er een update aan de gang is!', '2' => 'Excuses voor het ongemak. Alles zal binnen enkele minuten weer normaal worden.', ], 'title' => 'Onderhoud', ], '503-form' => [], 'footer' => 'Als je meer hulp nodig hebt, neem dan contact met ons op via hello@kanka.io of via :discord', ]; ================================================ FILE: lang/nl/events.php ================================================ [ 'title' => 'Nieuwe gebeurtenis', ], 'destroy' => [], 'edit' => [], 'events' => [], 'fields' => [ 'date' => 'Datum', ], 'helpers' => [ 'date' => 'Dit veld kan alles bevatten en is niet gekoppeld aan de kalenders van de campaign. Om deze gebeurtenis aan een kalender te koppelen, voeg je deze toe aan de kalender of op het tabblad herinneringen van deze gebeurtenissen.', ], 'index' => [], 'placeholders' => [ 'date' => 'Een datum voor je gebeurtenis', 'type' => 'Ceremonie, Festival, Ramp, Veldslag, Geboorte', ], 'show' => [], 'tabs' => [ 'calendars' => 'Kalender Invoeren', ], ]; ================================================ FILE: lang/nl/families.php ================================================ [ 'title' => 'Nieuwe Familie', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'members' => 'Leden van een familie worden hier vermeld. Een personage kan aan een familie worden toegevoegd door het gewenste personage te bewerken en de vervolgkeuzelijst "Familie" te gebruiken.', ], 'index' => [], 'members' => [], 'placeholders' => [ 'name' => 'Naam van de familie', 'type' => 'Koninklijk, Adel, Uitgestorven', ], 'show' => [], ]; ================================================ FILE: lang/nl/faq.php ================================================ [ 'answer' => 'We maken twee back-ups per dag om dataverlies te voorkomen. Onze eigen campaigns staan op de server, dus we willen geen enkel risico nemen!', 'question' => 'Hoe vaak wordt er een back-up gemaakt van de gegevens op Kanka?', ], 'attribute-templates' => [ 'answer' => <<<'TEXT' De beste manier waarop we attribuutsjablonen kunnen uitleggen, is met een voorbeeld. Stel dat je wereld veel locaties heeft en dat je op veel van die locaties een aangepast Attribuut wilt maken voor "Bevolking", "Klimaat", "Misdaadniveau". Nu zou je dat gemakkelijk op elke locatie kunnen doen, maar het kan vervelend worden, en je zou soms kunnen vergeten om het attribuut "Misdaadniveau" aan te maken. Dit is waar attribuutsjablonen in het spel komen. Je kunt een Attribuutsjabloon maken met die attributen (bevolking, klimaat, misdaadniveau, enz.), En die sjabloon later op je locaties toepassen. Hiermee worden de attributen van de sjabloon op de locaties gemaakt, dus het enige wat je hoeft te doen is de waarden te wijzigen en de attributen niet te onthouden! TEXT , 'question' => 'Attribuutsjablonen, wat zijn dat?', ], 'backup' => [ 'answer' => 'Een keer per dag kun je al je campaign gegevens exporteren als een ZIP-bestand. Klik in de app op "Campaign" in het linkermenu en klik op "Exporteren". Hierdoor wordt een export gemaakt die 30 minuten beschikbaar is. Je kunt deze export niet uploaden naar Kanka, het is alleen bedoeld voor je eigen gemoedsrust of als je niet langer van plan bent de app te gebruiken.', 'question' => 'Hoe kan ik een back-up maken van mijn campaign of deze exporteren?', ], 'bugs' => [ 'answer' => 'Word gewoon lid van onze :discord server en meld je bug in het #error-and-bugs kanaal.', 'question' => 'Hoe kan ik een bug melden?', ], 'campaign-sync' => [ 'answer' => 'Kanka heeft deze functie niet. Als je echter meerdere speelgroepen in dezelfde wereld probeert te hebben, overweeg dan om dezelfde campaign te gebruiken en je groepen te scheiden door een combinatie van speurtochten, tags en permissies', 'question' => 'Kan ik entiteiten over meerdere campagnes synchroniseren?', ], 'custom' => [ 'answer' => 'Kanka wordt geleverd met een set vooraf gedefinieerde entiteit typen die met elkaar communiceren. Om aangepaste entiteit typen toe te staan, zou de app helemaal opnieuw moeten worden opgebouwd en zou het doel van een tool met vooraf gedefinieerde typen om mensen te helpen met worldbuilding teniet doen, in plaats van uit te zoeken hoe ze dingen moeten organiseren. Bovendien is Kanka flexibel met Tags die de meeste scenario\'s van het type entiteit kunnen vertegenwoordigen.', 'question' => 'Kan ik aangepaste entiteit typen maken?', ], 'delete-campaign' => [ 'answer' => 'Ga naar je campaign dashboard en klik op \'Campaign\' in het linkermenu. Er verschijnt een campaign knop "Verwijderen" als je het laatste lid van de campaign bent. Het verwijderen van een campaign is een permanente actie waarbij alle gegevens die op onze servers zijn opgeslagen, inclusief afbeeldingen, worden verwijderd.', 'question' => 'Hoe kan ik een campaign verwijderen?', ], 'early-access' => [ 'answer' => 'Early Access is voor ons een manier om onze geweldige abonnees te belonen door ze een exclusieve periode van 30 dagen te geven waarin ze de nieuwste modules kunnen uitproberen voor iemand anders.', 'question' => 'Wat is Early Access?', ], 'entity-notes' => [ 'answer' => 'Alle entiteiten hebben een tabblad \'Entiteit Notities\'. Dit zijn kleine tekstfragmenten die kunnen worden ingesteld om alleen zichtbaar te zijn voor jou (handig bij co-dming), alleen voor leden van de beheerdersrol of zichtbaar voor iedereen. Je kunt je spelers ook toestemming geven om entiteit notities voor entiteiten te maken en te bewerken zonder dat ze een hele entiteit hoeven te bewerken.', 'question' => 'Hoe gaat Kanka om met gedeeltelijk verborgen informatie?', ], 'fields' => [ 'answer' => 'Antwoord', 'category' => 'Categorie', 'locale' => 'Locale', 'order' => 'Volgorde', 'question' => 'Vraag', ], 'free' => [ 'answer' => <<<'TEXT' Ja! We zijn ervan overtuigd dat je financiële situatie geen invloed mag hebben op je plezier in RPG's of worldbuilding en we zullen de kernapp altijd gratis houden. Als je echter een actievere rol op deze reis wilt spelen, ons wilt steunen en wilt stemmen over de functies die voor jou het belangrijkst zijn, kun je dit doen via onze abonnementen. Naast het stemmen over de richting die Kanka inslaat, kun je door ons te steunen toegang krijgen tot :boosters, uploadlimieten voor bestandsgrootte verhogen, je naam toevoegen aan de hall of fame, mooiere standaard pictogrammen en meer! TEXT , 'question' => 'Blijft de app gratis?', ], 'gods-and-religions' => [ 'answer' => 'We raden aan om Goden als Parakters te creëren en religies te creëren als Organisaties. Als je snel je goden wilt vinden, raden we je aan ze te taggen met een geschikte tag en / of type.', 'question' => 'Waar kun je goden en religies creëren?', ], 'help' => [ 'answer' => 'Allereerst bedankt dat je wilt helpen! We zijn altijd geïnteresseerd in mensen die kunnen helpen met vertalingen, nieuwe functies kunnen testen of die nieuwe gebruikers kunnen helpen. We vinden het ook geweldig als mensen Kanka promoten om nieuwe gebruikers te bereiken op plaatsen waar we niet aan hadden gedacht. Je beste manier van handelen is om je bij ons aan te sluiten op de :discord, waar een kanaal is toegewijd om te helpen.', 'question' => 'Hoe kan ik helpen?', ], 'map' => [ 'answer' => 'De Kaarten module ondersteunt PNG-, JPG- en SVG-afbeeldingen. Deze kaarten kunnen lagen, groepen en markeringen hebben die verschillende vormen en maten hebben die naar andere entiteiten in een campaign verwijzen.', 'question' => 'Kan ik kaarten uploaden naar Kanka?', ], 'mobile' => [ 'answer' => 'Er is momenteel geen speciale mobiele app voor Kanka, maar de meeste functies van de app werkt op een mobiel apparaat. We hopen dat we dankzij de ondersteuning via abonnementen ooit iemand kunnen betalen om een mobiele app te bouwen, maar voorzien dat niet in de nabije toekomst.', 'question' => 'Is er een mobiele app? Is er een gepland?', ], 'monsters' => [ 'answer' => 'We raden aan om de Rassen module te gebruiken voor mensen, soorten, monsters en alles wat leeft dat geen personage is.', 'question' => 'Waar monsters te maken?', ], 'multiworld' => [ 'answer' => 'Je kunt deelnemen aan zoveel campaigns als je wilt, inclusief de campaigns die je hebt gemaakt. Om over te schakelen of een nieuwe campaign te maken, ga je naar je campaign dashboard en kun je rechtsboven op je huidige campaign klikken om de interface van de campaign wisselaar weer te geven.', 'question' => 'Kan ik meer dan één campaign hebben?', ], 'nested' => [ 'answer' => 'Als je jouw entiteiten liever standaard in een geneste weergave bekijkt (in bijvoorbeeld de knop Geneste Weergave in de lijst met locaties), kun je dit doen door naar je Profiel- en Lay-out opties te gaan. Daar kun je de optie Geneste Weergave controleren. Dit is alleen voor jouw account en niet voor je campaigns.', 'question' => 'Kan ik instellen dat de lijsten standaard worden genest?', ], 'permissions' => [ 'answer' => 'Absoluut, daarom hebben we Kanka gebouwd! Je kunt al je spelers voor je campaigns uitnodigen en hen rollen en permissies geven. We hebben het systeem zo gebouwd dat het uiterst flexibel is (je kunt zowel een opt-in- als een opt-out-configuratie gebruiken) om in zoveel mogelijk behoeften en situaties te voorzien.', 'question' => 'Kan ik de informatie beperken die mijn spelers in mijn campagne zien?', ], 'plans' => [ 'answer' => <<<'TEXT' Het langetermijnplan voor Kanka is om een veelzijdige tool voor worldbuilding en campaign beheer te bouwen die systeemonafhankelijk is met inhoud die wordt beheerd door de gemeenschap in de vorm van "Community Sjablonen". Een ander doel van ons is om tools te bouwen die kunnen worden geïntegreerd met andere platforms, zoals Virtual Tabletop apps. We gebruiken Kanka zelf, dus we hebben geen plannen om ooit te stoppen met het ontwikkelen en verbeteren ervan. Voor de zekerheid is het project echter ook open source en kan het worden opgepikt door de community als er ooit iets met ons zou gebeuren. TEXT , 'question' => 'Wat zijn de langetermijnplannen?', ], 'public-campaigns' => [ 'answer' => 'Je kunt bladeren op de :public-campaigns pagina om te zien hoe anderen Kanka gebruiken voor hun campaigns.', 'question' => 'Hoe gebruiken anderen Kanka?', ], 'renaming-modules' => [ 'answer' => 'Standaard staat Kanka je niet toe de naam van modules te wijzigen. Dit komt door de grammaticale correctheid en gebruikerservaringen voor talen die gendergerelateerde woorden gebruiken. Een boosted campaign kan echter de naam van modules in de zijbalk wijzigen door Aangepaste CSS te gebruiken.', 'question' => 'Kan ik modules hernoemen? Bijvoorbeeld Families in Clans, of Organisaties in Facties?', ], 'sections' => [ 'community' => 'Community', 'general' => 'Algemeen', 'other' => 'Andere', 'permissions' => 'Permissies', 'pricing' => 'Prijzen', 'worldbuilding' => 'Worldbuilding', ], 'show' => [ 'return' => 'Ga terug naar de FAQ', 'timestamp' => 'Laatst bijgewerkt :date', 'title' => 'FAQ :name', ], 'unboost' => [ 'answer' => 'Als je een campaign boost ongedaan maakt, worden geen gegevens verwijderd die tijdens de boost zijn gemaakt, maar worden alleen de informatie en functies verborgen. Als je een campaign opnieuw een boost geeft, zijn de informatie en functies weer beschikbaar met dezelfde instellingen als voordat je een campaign boost ongedaan maakte.', 'question' => 'Wat gebeurt er als een campaign niet langer wordt ge-boost?', ], 'user-switch' => [ 'answer' => 'Permissies kunnen lastig worden, vooral bij grote campaigns. Als campaign beheerder kun je naar de ledenpagina van de campaign navigeren en op de knop "Schakelen" klikken die wordt weergegeven naast niet-beheerders van de campaign. Als je dit doet, log je in als die gebruiker en kun je de campaign zien zoals zij dat zouden doen. Dit is de gemakkelijkste manier om de permissies van je campaign te controleren.', 'question' => 'Mijn campaign permissies zijn ingesteld, hoe kan ik ze testen?', ], 'visibility' => [ 'answer' => 'Alleen de mensen die je voor je campaign uitnodigt, kunnen de door jou gecreëerde zien en ermee communiceren. Je gegevens zijn privé en altijd onder jouw controle. Je kunt je campaign ook instellen op openbaar, zodat niet-geregistreerde gebruikers deze kunnen bekijken.', 'question' => 'Kan iedereen mijn wereld zien?', ], ]; ================================================ FILE: lang/nl/footer.php ================================================ 'Kanka is vertaald in andere talen dankzij onze geweldige community. Als je wilt helpen met het vertalen van Kanka in jouw taal, neem dan contact met ons op via :discord!', ]; ================================================ FILE: lang/nl/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Community Stemming', ]; ================================================ FILE: lang/nl/front/hall-of-fame.php ================================================ 'Hall of Fame', ]; ================================================ FILE: lang/nl/front/newsletter.php ================================================ [ 'learn_more' => 'Leer meer', 'subscribe' => 'Abboneer', ], 'fields' => [ 'firstname' => 'Voornaam', 'lastname' => 'Achternaam', 'notifications' => 'Notificaties', ], 'groups' => [ 'newsletter' => 'Nieuwsbrief', ], 'headline' => 'Abonneer je op een of al onze nieuwsbrieven om op de hoogte te blijven van Kanka.', 'title' => 'E-mailupdates', ]; ================================================ FILE: lang/nl/front.php ================================================ [], 'campaigns' => [], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => 'Begrepen!', 'link' => 'Leer meer', 'message' => 'Deze website gebruikt cookies om ervoor te zorgen dat je de beste ervaring op onze website krijgt.', ], 'faq' => [], 'features' => [ 'api' => [ 'link' => 'API docs', ], 'patreon' => [ 'api_calls' => 'Verhoogde API calls (90 per minuut)', 'boosts' => 'Campaign Boosters', 'default_image' => 'Custom standaard afbeeldingen voor entiteiten', 'discord' => 'Privé Discord kanaal', 'free' => 'Gratis', 'hall_of_fame' => 'Naam in de :link', 'impact' => 'Impact toekomstige functies', 'monthly_vote' => 'Deelname aan de stemming over de community functie', 'pagination' => 'Verhoogde paginering resultaten', 'upload_limit' => 'Upload formaten', 'upload_limit_map' => 'Uploadformaten voor kaarten', ], ], 'first_block' => [], 'footer' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Ben je een game master, worldbuilder of een verhalenverteller? We bieden een tabletop campaign manager en een tool voor worldbuilding waarmee je eenvoudig jouw TTRPG-campaigns kunt organiseren, plannen en ervan kunt genieten. We zijn community-driven en het beste van alles is dat onze kernfuncties gratis zijn!', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Dashboard', 'login' => 'Inloggen', 'register' => 'Registreren', ], 'meta' => [ 'description' => 'Kanka is een flexibele digitale world builder en online rpg campaign manager', 'title' => 'Kanka - Online tabletop RPG campaign manager en worldbuilding tool', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Gratis', 'month' => 'Maand', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Worldbuilding, Tabletop RPG, RPG Campaign Manager', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/nl/header.php ================================================ [ 'read_all' => 'Alles lezen', ], 'toggle_navigation' => 'Schakel navigatie in/uit', ]; ================================================ FILE: lang/nl/helpers.php ================================================ [], 'attributes' => [], 'dice' => [], 'filters' => [ 'title' => 'Filters gebruiken', ], 'link' => [ 'description' => 'Je kunt eenvoudig koppelen naar andere entiteiten in je campaign met behulp van de volgende afkortingen.', ], 'map' => [], 'public' => 'Bekijk een instructievideo op YouTube waarin openbare campaigns worden uitgelegd.', ]; ================================================ FILE: lang/nl/items.php ================================================ [ 'title' => 'Nieuw Voorwerp', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'price' => 'Prijs', 'size' => 'Grootte', ], 'index' => [], 'inventories' => [], 'placeholders' => [ 'price' => 'Prijs van het voorwerp', 'size' => 'Grootte, Gewicht, Afmeting', 'type' => 'Wapen, Potion, Artefact', ], 'show' => [ 'tabs' => [ 'inventories' => 'Inventories', ], ], ]; ================================================ FILE: lang/nl/journals.php ================================================ [ 'title' => 'Nieuw Logboek', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Auteur', 'date' => 'Datum', ], 'helpers' => [], 'index' => [], 'journals' => [], 'placeholders' => [ 'author' => 'Wie schreef het logboek', 'date' => 'Echte werelddatum van het logboek', 'type' => 'Sessie, One Shot, Concept', ], 'show' => [], ]; ================================================ FILE: lang/nl/languages.php ================================================ [ 'ca' => 'Catalaans', 'cs' => 'Tsjechisch', 'de' => 'Duits', 'el' => 'Grieks', 'en' => 'Engels', 'en-US' => 'Amerikaans Engels', 'es' => 'Spaans', 'fr' => 'Frans', 'gl' => 'Galicisch', 'he' => 'Hebreeuws', 'hr' => 'Kroatisch', 'hu' => 'Hongaars', 'it' => 'Italiaans', 'nb' => 'Noors (Bokmal)', 'nl' => 'Nederlands', 'pl' => 'Pools', 'pt-BR' => 'Braziliaans Portugees', 'ru' => 'Russisch', 'sk' => 'Slowaaks', 'tr' => 'Turks', ], 'header'=> 'Talen', ]; ================================================ FILE: lang/nl/locations.php ================================================ [], 'create' => [ 'title' => 'Nieuwe Locatie', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [], 'helpers' => [ 'characters' => 'Bekijk alle personages op deze locatie en de gerelateerde locaties, of alleen degenen die zich hier direct bevinden.', ], 'hints' => [], 'index' => [], 'items' => [], 'journals' => [], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'type' => 'Stad, Koninkrijk, Ruïne', ], 'show' => [], ]; ================================================ FILE: lang/nl/maps/groups.php ================================================ [ 'add' => 'Voeg een nieuwe groep toe', ], 'create' => [ 'success' => 'Groep :name gemaakt', 'title' => 'Nieuwe Groep', ], 'delete' => [ 'success' => 'Groep :name verwijderd', ], 'edit' => [ 'success' => 'Groep :name bijgewerkt', 'title' => 'Wijzig Groep :name', ], 'fields' => [ 'is_shown' => 'Toon groepsmarkeringen', 'position' => 'Positie', ], 'helper' => [], 'hints' => [ 'is_shown' => 'Indien aangevinkt, worden de groepsmarkeringen standaard op de kaart getoond.', ], 'placeholders' => [ 'name' => 'Winkels, Schatten, NPCs', 'position' => 'Optioneel veld om de volgorde in te stellen waarin de groepen verschijnen.', ], ]; ================================================ FILE: lang/nl/maps/layers.php ================================================ [ 'add' => 'Voeg een nieuw laag toe', ], 'base' => 'Basis Laag', 'create' => [ 'success' => 'Laag :name gemaakt.', 'title' => 'Nieuwe Laag', ], 'delete' => [ 'success' => 'Laag :name verwijderd.', ], 'edit' => [ 'success' => 'Laag :name bijgewerkt.', 'title' => 'Wijzig Laag :name', ], 'fields' => [ 'position' => 'Positie', 'type' => 'Laag type', ], 'helper' => [], 'placeholders' => [ 'name' => 'Ondergronds, Niveau 2, Schipbreuk', 'position' => 'Optioneel veld om de volgorde in te stellen waarin de lagen verschijnen.', ], 'short_types' => [ 'overlay' => 'Overlay', 'overlay_shown' => 'Overlay (automatisch tonen)', 'standard' => 'Standaard', ], 'types' => [ 'overlay' => 'Overlay (weergegeven boven de actieve laag)', 'overlay_shown' => 'Overlay wordt standaard weergegeven', 'standard' => 'Standaard laag (schakelen tussen lagen)', ], ]; ================================================ FILE: lang/nl/maps/markers.php ================================================ [ 'entry' => 'Schrijf een custom invoer veld voor deze markering.', 'remove'=> 'Verwijder markering', 'update'=> 'Wijzig markering', ], 'create' => [ 'success' => 'Markering :name gemaakt.', 'title' => 'Nieuwe Markering', ], 'delete' => [ 'success' => 'Markering :name verwijderd.', ], 'edit' => [ 'success' => 'Markering :name bijgewerkt.', 'title' => 'Wijzig Markering :name', ], 'fields' => [ 'circle_radius' => 'Cirkel straal', 'copy_elements' => 'Kopieer elementen', 'custom_icon' => 'Aangepast Pictogram', 'custom_shape' => 'Aangepaste Vorm', 'font_colour' => 'Pictogram Kleur', 'group' => 'Markering Groep', 'is_draggable' => 'Sleepbaar', 'latitude' => 'Breedtegraad', 'longitude' => 'Lengtegraad', 'opacity' => 'Doorzichtigheid', 'pin_size' => 'Pin Grootte', 'polygon_style' => [ 'stroke' => 'Lijn kleur', 'stroke-opacity' => 'Lijn doorzichtigheid', 'stroke-width' => 'Lijn breedte', ], ], 'helpers' => [ 'base' => 'Voeg markeringen toe aan de kaart door op een willekeurige plek te klikken.', 'copy_elements' => 'Kopieer groepen, lagen en markeringen.', 'copy_elements_to_campaign' => 'Kopieer groepen, lagen en markeringen van de kaarten. Markeringen die aan een entiteit zijn gekoppeld, worden geconverteerd naar een standaard markering.', 'custom_radius' => 'Selecteer de optie voor custom formaat in de vervolgkeuzelijst om een formaat te definiëren.', 'draggable' => 'Schakel in om het verplaatsen van een markering in de verkenning modus toe te staan.', 'label' => 'Een label wordt als een tekstblok op de kaart weergegeven. De inhoud is de naam van de markering van de entiteit.', 'polygon' => [ 'edit' => 'Klik op de kaart om die positie toe te voegen aan de coördinaten van de polygoon.', ], ], 'icons' => [ 'custom' => 'Aangepast', 'entity' => 'Entiteit', 'exclamation' => 'Uitroep', 'marker' => 'Markering', 'question' => 'Vraag', ], 'placeholders' => [ 'custom_shape' => '100,100 200,240 340,110', 'name' => 'Vereist als er geen entiteit is geselecteerd', ], 'shapes' => [ '0' => 'Cirkel', '1' => 'Vierkant', '2' => 'Driehoek', '3' => 'Aangepast', ], 'sizes' => [ '0' => 'Mini', '1' => 'Standaard', '2' => 'Klein', '3' => 'Groot', '4' => 'Zeer Groot', ], 'tabs' => [ 'circle' => 'Cirkel', 'label' => 'Label', 'marker' => 'Markering', 'polygon' => 'Polygoon', ], ]; ================================================ FILE: lang/nl/maps.php ================================================ [ 'back' => 'Terug naar :name', 'edit' => 'Wijzig Kaart', 'explore' => 'Verkennen', ], 'create' => [ 'title' => 'Nieuwe Kaart', ], 'destroy' => [], 'edit' => [], 'errors' => [ 'dashboard' => [ 'missing' => 'Deze kaart heeft een afbeelding nodig om op het dashboard te kunnen weergeven.', ], 'explore' => [ 'missing' => 'Voeg een afbeelding toe aan de kaart voordat je deze kunt verkennen.', ], ], 'fields' => [ 'center_x' => 'Standaard Lengtegraad Positie', 'center_y' => 'Standaard Breedtegraad Positie', 'grid' => 'Raster', 'initial_zoom' => 'Initiële zoom', 'max_zoom' => 'Maximale zoom', 'min_zoom' => 'Minimale zoom', ], 'helpers' => [ 'center' => 'Als je de volgende waarden wijzigt, wordt er bepaald op welk gebied van de kaart de focus is gericht. Als je deze waarden leeg laat, wordt er op het midden van de kaart gefocust.', 'distance_measure' => 'Door de kaart een afstandsmeting te geven, wordt het meetinstrument in de verkenning modus ingeschakeld.', 'grid' => 'Definieer een raster grootte die in de verkenning modus wordt weergegeven.', 'initial_zoom' => 'Het aanvankelijke zoomniveau waarmee een kaart is geladen. De standaardwaarde is :default, terwijl de hoogst toegestane waarde :max is en de laagst toegestane waarde :min.', 'max_zoom' => 'Op een kaart kan maximaal worden ingezoomd. De standaardwaarde is :default, terwijl de hoogst toegestane waarde :max is.', 'min_zoom' => 'Op een kaart kan maximaal worden uitgezoomd. De standaardwaarde is :default, terwijl de laagst toegestane waarde :min is.', 'missing_image' => 'Sla de kaart met een afbeelding op voordat je lagen en markeringen kunt toevoegen.', ], 'index' => [], 'maps' => [], 'panels' => [ 'groups' => 'Groepen', 'layers' => 'Lagen', 'markers' => 'Markeringen', 'settings' => 'Instellingen', ], 'placeholders' => [ 'center_x' => 'Laat leeg om de kaart in het midden te laden', 'center_y' => 'Laat leeg om de kaart in het midden te laden', 'grid' => 'Afstand in pixel tussen raster elementen. Laat leeg om het raster te verbergen.', 'name' => 'Naam van de kaart', 'type' => 'Dungeon, Stad, Sterrestelsel', ], 'show' => [ 'tabs' => [ 'maps' => 'Kaarten', ], ], ]; ================================================ FILE: lang/nl/misc.php ================================================ [ 'boosting' => 'Boosten', ], ]; ================================================ FILE: lang/nl/notes.php ================================================ [ 'title' => 'Nieuwe Notitie', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'Sub Notities', ], 'helpers' => [], 'hints' => [], 'index' => [], 'placeholders' => [ 'note' => 'Kies een bovenliggende notitie', 'type' => 'Religie, Ras, Politiek systeem', ], 'show' => [], ]; ================================================ FILE: lang/nl/notifications.php ================================================ [ 'application' => [ 'approved' => 'Je aanvraag voor de campaign :campaign is goedgekeurd.', 'new' => 'Nieuwe aanvraag voor :campaign.', 'rejected' => 'Je aanvraag voor de :campaign campaign is afgewezen. Reden opgegeven: :reason', ], 'asset_export' => 'Er is een export van campaign assets beschikbaar. De link is beschikbaar voor :time minuten.', 'boost' => [ 'add' => 'Campaign :campaign wordt ge-boost door :user.', 'remove' => ':user boost niet langer de campaign :campaign.', 'superboost' => 'Campaign :campaign wordt ge-superboost door :user', ], 'export' => 'Een export van een campaign is beschikbaar. De link is beschikbaar voor :time minutes.', 'export_error' => 'Er is een fout opgetreden bij het exporteren van je campaign. Neem contact met ons op als dit probleem zich blijft voordoen.', 'join' => ':user heeft zich aangesloten bij de campaign :campaign.', 'leave' => ':user heeft de campaign :campaign verlaten.', 'role' => [ 'add' => 'Je bent toegevoegd aan de :role rol in de :campaign campaign.', 'remove' => 'Je bent verwijderd van de :role rol in de :campaign campaign.', ], ], 'header' => 'Je hebt :count notificaties', 'index' => [ 'title' => 'Notificaties', ], 'no_notifications' => 'Er zijn momenteel geen notificaties.', 'subscriptions' => [ 'charge_fail' => 'Er is een fout opgetreden bij het verwerken van uw betaling. Wacht even terwijl we het opnieuw proberen. Als er niets verandert, neem dan contact met ons op.', 'deleted' => 'Uw abonnement op Kanka is geannuleerd na te veel mislukte pogingen om uw kaart op te laden. Ga naar uw abonnementsinstellingen en probeer uw betalingsgegevens bij te werken.', 'ended' => 'Je abonnement op Kanka is beëindigd. Je campaign boosts en Discord rollen zijn verwijderd. We hopen je snel weer te zien!', 'failed' => 'We konden uw betalingsgegevens niet in rekening brengen. Werk ze bij in uw betalingsmethode-instellingen.', 'started' => 'Je abonnement op Kanka is begonnen.', ], ]; ================================================ FILE: lang/nl/organisations.php ================================================ [ 'title' => 'Nieuwe Organisatie', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'members' => 'Leden', ], 'helpers' => [], 'index' => [], 'members' => [ 'destroy' => [ 'success' => 'Lid verwijderd van de organisatie', ], 'edit' => [ 'title' => 'Werk Lid bij voor :name', ], 'fields' => [ 'role' => 'Rol', ], 'helpers' => [ 'all_members' => 'Alle personages die lid zijn van deze organisaties en zijn suborganisaties.', 'members' => 'Alle personages die lid zijn van deze organisatie.', ], 'placeholders' => [ 'role' => 'Leider, Lid, Hoog-lid, Spymaster', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'Sekte, Gang, Rebellie, Fandom', ], 'show' => [], ]; ================================================ FILE: lang/nl/pagination.php ================================================ '« Vorige', 'next' => 'Volgende »', ]; ================================================ FILE: lang/nl/partials.php ================================================ [ 'description' => 'Er waren enkele problemen met je invoer.', 'title' => 'Oeps!', ], ]; ================================================ FILE: lang/nl/passwords.php ================================================ 'Wachtwoord moet minimaal zes tekens lang zijn en de wachtwoorden moeten overeenkomen.', 'reset' => 'Het wachtwoord van uw account is gewijzigd.', 'sent' => 'We hebben een e-mail verstuurd met instructies om een nieuw wachtwoord in te stellen.', 'token' => 'Dit wachtwoord herstel token is niet geldig.', 'user' => 'Geen gebruiker bekend met het e-mailadres.', ]; ================================================ FILE: lang/nl/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/nl/profiles.php ================================================ [ 'success' => 'Avatar bijgewerkt.', ], 'edit' => [ 'success' => 'Profiel bijgewerkt', ], 'editors' => [], 'fields' => [ 'avatar' => 'Avatar', 'email' => 'E-mail', 'hide_subscription' => 'Verberg mijn naam voor de :hall_of_fame.', 'last_login_share' => 'Deel met andere campaign leden wanneer ik voor het laatst heb ingelogd.', 'name' => 'Naam', 'new_password' => 'Nieuw Wachtwoord', 'new_password_confirmation' => 'Nieuw Wachtwoord Bevestiging', 'newsletter' => 'Ik wens soms per e-mail gecontacteerd te worden.', 'password' => 'Huidig wachtwoord', 'settings' => 'Instellingen', 'theme' => 'Thema', ], 'newsletter' => [ 'title' => 'Nieuwsbrieven', ], 'password' => [ 'success' => 'Wachtwoord bijgewerkt', ], 'placeholders' => [ 'email' => 'Jouw e-mailadres', 'name' => 'Je naam zoals weergegeven', 'new_password' => 'Je nieuwe wachtwoord', 'new_password_confirmation' => 'Bevestig je nieuwe wachtwoord', 'password' => 'Geef je huidige wachtwoord op voor eventuele wijzigingen', ], 'sections' => [ 'delete' => [ 'delete' => 'Verwijder mijn account', 'title' => 'Verwijder je account', 'warning' => 'Door je account te verwijderen, gaan al je gegevens verloren. Weet je het zeker?', ], 'password' => [ 'title' => 'Wijzig je wachtwoord', ], ], 'settings' => [ 'success' => 'Instellingen gewijzigd.', ], 'theme' => [ 'success' => 'Thema gewijzigd.', 'themes' => [ 'dark' => 'Donker', 'default' => 'Standaard', 'future' => 'Futuristisch', 'midnight' => 'Middernacht Blauw', ], ], 'title' => 'Werk je profiel bij', 'workflows' => [ 'created' => 'Ga naar gemaakte entiteiten', 'default' => 'Lijst van entiteiten', ], ]; ================================================ FILE: lang/nl/quests.php ================================================ [ 'title' => 'Nieuwe Quest', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_elements' => 'Kopieer elementen die aan de quest zijn gekoppeld', 'date' => 'Datum', 'is_completed' => 'Voltooid', 'role' => 'Rol', ], 'helpers' => [], 'hints' => [], 'index' => [], 'placeholders' => [ 'date' => 'Echte werelddatum voor de quest', 'role' => 'De rol van deze entiteit in de quest', 'type' => 'Karakter Boog, Sidequest, Hoofd', ], 'show' => [], ]; ================================================ FILE: lang/nl/races.php ================================================ [], 'create' => [ 'title' => 'Nieuw Ras', ], 'destroy' => [], 'edit' => [], 'fields' => [], 'helpers' => [], 'index' => [], 'placeholders' => [ 'type' => 'Mens, Fey, Borg', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/nl/redirects.php ================================================ 'Je sessie is verlopen. Probeer het a.u.b. opnieuw.', 'unknown_entity' => 'Sorry, we weten niet wat een \':entity\' is.', ]; ================================================ FILE: lang/nl/releases.php ================================================ [ 'event' => 'Gebeurtenis', 'other' => 'Andere', 'release' => 'Release', 'vote' => 'Community stemming', ], 'index' => [ 'description' => 'De laatste updates voor kanka.io', 'title' => 'Releases', ], 'post' => [ 'footer' => 'Door :name :date', ], 'show' => [ 'return' => 'Terug naar Releases', 'title' => 'Release :name', ], ]; ================================================ FILE: lang/nl/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chronicles of Darkness', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/nl/search.php ================================================ 'Geen resultaten.', 'title' => 'Zoeken', ]; ================================================ FILE: lang/nl/settings.php ================================================ [ 'actions' => [ 'social' => 'Schakel over naar Kanka Login', 'update_email' => 'Werk e-mail bij', 'update_password' => 'Vernieuw wachtwoord', ], 'email' => 'E-mailadres wijzigen', 'email_success' => 'E-mail bijgwerkt.', 'password' => 'Wijzig wachtwoord', 'password_success' => 'Wachtwoord bijgewerkt.', 'social' => [ 'error' => 'Je gebruikt de Kanka-login al voor dit account.', 'helper' => 'Je account wordt momenteel beheerd door :provider. Je kunt stoppen met het gebruik en overschakelen naar de standaard Kanka-login door een wachtwoord in te stellen.', 'success' => 'Je account gebruikt nu de Kanka-login.', 'title' => 'Sociaal voor Kanka', ], 'title' => 'Account', ], 'api' => [ 'helper' => 'Welkom bij de Kanka API\'s. Genereer een Persoonlijke Toegangstoken om in je API verzoek te gebruiken om informatie te verzamelen over de campaigns waarvan jij deel uitmaakt.', 'link' => 'Lees de API documentatie', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Verbind', 'remove' => 'Verwijder', ], 'benefits' => 'Kanka biedt enkele integratie met services van derden. Voor de toekomst zijn er meer integraties van derden gepland.', 'discord' => [ 'errors' => [ 'add' => 'Er is een fout opgetreden bij het koppelen van je Discord-account aan Kanka. Probeer het a.u.b. opnieuw.', ], 'success' => [ 'add' => 'Je Discord account is gekoppeld.', 'remove' => 'Je Discord account is ontkoppeld.', ], 'text' => 'Krijg automatisch toegang tot je abonnement rollen.', ], 'title' => 'App Integratie', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'Campaign :name is al boosted', 'exhausted_boosts' => 'Je hebt geen boosts meer om te geven. Haal je boost uit een campaign voordat je deze aan een andere geeft.', 'exhausted_superboosts' => 'Je hebt geen boosts meer. Je hebt 3 boosters nodig om een campaign een superboost te geven.', ], ], 'countries' => [ 'austria' => 'Oostenrijk', 'belgium' => 'België', 'france' => 'Frankrijk', 'germany' => 'Duitsland', 'italy' => 'Italië', 'netherlands' => 'Nederland', 'spain' => 'Spanje', ], 'invoices' => [], 'layout' => [ 'title' => 'Lay-out', ], 'marketplace' => [], 'menu' => [ 'account' => 'Account', 'api' => 'API', 'apps' => 'Apps', 'other' => 'Andere', 'patreon' => 'Patreon', 'payment_options' => 'Betalingsmogelijkheden', 'personal_settings' => 'Persoonlijke Instellingen', 'profile' => 'Profiel', 'settings' => 'Instellingen', 'subscription' => 'Abbonement', 'subscription_status' => 'Abbonement Status', ], 'patreon' => [ 'deprecated' => 'Verouderde functie - als je Kanka wilt steunen, doe dit dan met een :subscription. Patreon-koppeling is nog steeds actief voor onze klanten die hun account hebben gekoppeld voordat ze weggingen van Patreon.', 'pledge' => 'Toezegging: :name', 'remove' => [ 'button' => 'Ontkoppel je Patreon-account', 'success' => 'Je Patreon-account is ontkoppeld.', 'text' => 'Als je je Patreon-account met Kanka ontkoppelt, worden je bonussen, naam in de hall of fame, campaign boosts en andere functies verwijderd die zijn gekoppeld aan het ondersteunen van Kanka. Geen van je boosted inhoud gaat verloren (bijv. entiteit headers). Door je opnieuw te abonneren, heb je toegang tot al je eerdere gegevens, inclusief de mogelijkheid om je eerder boosted campaigns een boost te geven.', 'title' => 'Ontkoppel je Patreon-account met Kanka', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Profiel bijwerken', ], 'avatar' => 'Profiel Foto', 'success' => 'Profiel bijgewerkt.', 'title' => 'Persoonlijk profiel', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Annuleer abonnement', 'subscribe' => 'Abboneer', 'update_currency' => 'Bewaar de gewenste valuta', ], 'billing' => [ 'helper' => 'Je factuurgegevens worden veilig verwerkt en opgeslagen via :stripe. Deze betaalmethode wordt gebruikt voor al je abonnementen.', 'saved' => 'Opgeslagen betaalmethode', ], 'cancel' => [ 'text' => 'Spijtig om je te zien gaan! Als je jouw abonnement opzegt, blijft het actief tot je volgende betalingscyclus, waarna je jouw campaign boosts en andere voordelen met betrekking tot het ondersteunen van Kanka kwijtraakt. Vul gerust het volgende formulier in om ons te laten weten wat we beter kunnen doen, of wat tot je beslissing heeft geleid.', ], 'cancelled' => 'Je abonnement is opgezegd. Je kunt een abonnement verlengen zodra je huidige abonnement afloopt.', 'change' => [ 'text' => [ 'monthly' => 'Je abonneert je op de :tier tier, maandelijks gefactureerd voor :amount.', 'yearly' => 'Je abonneert je op de :tier tier, jaarlijks gefactureerd voor :amount.', ], 'title' => 'Wijzig Abonnement Tier', ], 'currencies' => [ 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Wijzig de valuta van je voorkeur voor facturering', ], 'errors' => [ 'callback' => 'Onze betalingsprovider heeft een fout gemeld. Probeer het opnieuw of neem contact met ons op als het probleem zich blijft voordoen.', 'subscribed' => 'Kan je abonnement niet verwerken. Stripe gaf de volgende hint.', ], 'fields' => [ 'active_since' => 'Actief sinds', 'active_until' => 'Actief tot', 'billing' => 'Facturering', 'currency' => 'Facturering Valuta', 'payment_method' => 'Betalingsmiddel', 'plan' => 'Huidige plan', 'reason' => 'Reden', ], 'helpers' => [ 'alternatives' => 'Betaal je abonnement met :method. Deze betaalmethode wordt aan het einde van je abonnement niet automatisch verlengd. :method is alleen beschikbaar in euro\'s.', 'alternatives_warning' => 'Het is niet mogelijk om je abonnement op te waarderen wanneer je deze methode gebruikt. Maak een nieuw abonnement aan wanneer je huidige afloopt.', 'alternatives_yearly' => 'Vanwege de beperkingen rond terugkerende betalingen, is de :method alleen beschikbaar voor jaarlijkse abonnementen', ], 'manage_subscription' => 'Beheer abonnement', 'payment_method' => [ 'actions' => [ 'add_new' => 'Voeg een nieuwe betaalmethode toe', 'change' => 'Verander de betaalmethode', 'save' => 'Bewaar betaalmethode', 'show_alternatives' => 'Alternatieve betalingsmogelijkheden', ], 'add_one' => 'Je hebt momenteel geen betalingsmethode opgeslagen.', 'alternatives' => 'Je kunt je abonneren met behulp van deze alternatieve betalingsopties. Met deze actie wordt je account eenmaal in rekening gebracht en wordt je abonnement niet elke maand automatisch verlengd.', 'card' => 'Kaart', 'card_name' => 'Naam op kaart', 'country' => 'Land van verblijf', 'ending' => 'Eindigend in', 'helper' => 'Deze kaart wordt gebruikt voor al je abonnementen.', 'new_card' => 'Voeg een nieuwe betaalmethode toe', 'saved' => ':brand eindigend met :last4', ], 'placeholders' => [ 'reason' => 'Vertel ons desgewenst waarom je Kanka niet langer steunt. Ontbreekt er een functie? Is je financiële situatie veranderd?', ], 'plans' => [ 'cost_monthly' => ':currency :amount maandelijks gefactureerd', 'cost_yearly' => ':currency :amount jaarlijks gefactureerd', ], 'sub_status' => 'Abonnementsgegevens', 'subscription' => [ 'actions' => [ 'downgrading' => 'Neem contact met ons op voor het downgraden', 'rollback' => 'Schakel over naar Kobold', 'subscribe' => 'Verander naar :tier maandelijks', 'subscribe_annual' => 'Verander naar :tier jaarlijks', ], ], 'success' => [ 'alternative' => 'Je betaling is geregistreerd. Je krijgt een melding zodra deze is verwerkt en je abonnement actief is.', 'callback' => 'Je inschrijving is gelukt. Je account wordt bijgewerkt zodra onze betalingsprovider ons op de hoogte stelt van de wijziging (dit kan enkele minuten duren).', 'currency' => 'De valuta-instelling van je voorkeur is bijgewerkt.', 'subscribed' => 'Je inschrijving is gelukt. Vergeet niet om je te abonneren op de Community Vote-nieuwsbrief om op de hoogte te worden gehouden wanneer een stemming live gaat. Je kunt je nieuwsbriefinstellingen wijzigen op je profielpagina.', ], 'tiers' => 'Abonnement Tiers', 'trial_period' => 'Jaarabonnementen hebben een annuleringsbeleid van 14 dagen. Neem contact met ons op via :email als je jouw jaarabonnement wilt annuleren en een terugbetaling wilt ontvangen.', 'upgrade_downgrade' => [ 'button' => 'Upgrade- en downgrade-informatie', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Je bonussen blijven geactiveerd tot het einde van je betalingsperiode.', 'boosts' => 'Hetzelfde gebeurt voor je boosted campaigns. Boosted functies worden onzichtbaar, maar worden niet verwijderd wanneer een campaign niet langer wordt ge-boost.', 'kobold' => 'Om je abonnement te annuleren, ga je naar het Kobold tier.', ], 'title' => 'Bij het opzeggen van je abonnement', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Je huidige niveau blijft actief tot het einde van je huidige factureringscyclus, waarna je wordt gedowngraded naar je nieuwe tier.', ], 'title' => 'Bij het downgraden naar een lager niveau', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Uw betalingsmethode wordt onmiddellijk gefactureerd en u krijgt toegang tot uw nieuwe niveau.', 'prorate' => 'Bij het upgraden van Owlbear naar Elemental, wordt alleen het verschil met je nieuwe niveau in rekening gebracht.', ], 'title' => 'Bij het upgraden naar een hoger niveau', ], ], 'warnings' => [ 'incomplete' => 'We konden uw creditcard niet belasten. Werk uw creditcardgegevens bij en we zullen proberen deze de komende dagen opnieuw in rekening te brengen. Als het opnieuw mislukt, wordt uw abonnement opgezegd.', 'patreon' => 'Uw account is momenteel gekoppeld aan Patreon. Ontkoppel uw account in uw: patreon-instellingen voordat u overschakelt naar een Kanka-abonnement.', ], ], ]; ================================================ FILE: lang/nl/sidebar.php ================================================ [ 'created_campaigns' => 'Jouw Campaigns', 'new_campaign' => 'Nieuwe Campaign', 'public_campaigns' => 'Openbare Campaigns', 'updated' => 'Bijgewerkt', ], 'dashboard' => 'Dashboard', 'entity-creator' => 'Quick Creator', 'gallery' => 'Galerij', 'other' => 'Andere', 'world' => 'Wereld', ]; ================================================ FILE: lang/nl/starter.php ================================================ [ 'history' => 'Dit is een voorbeeld personage. James werd geboren als zoon van Mance Owlchester en Rige Dunton en groeide op op het platteland van Genory voordat hij naar de hoofdstad Unria verhuisde om als schrijver voor de koning te werken.', 'sex' => 'Mannelijk', 'title' => 'Grijze Jager', ], 'character2' => [ 'history' => 'Dit is een voorbeeld personage. Van jongs af aan is Irwie altijd gefascineerd geweest door explosieven en heeft ze haar carrière gewijd aan het vak.', 'sex' => 'Vrouwelijk', 'title' => 'Koningin van Explosies', ], 'item1' => [], 'kingdom1' => [ 'description' => 'Dit is een voorbeeld locatie die is gemaakt om je te laten zien wat je met de app kunt doen.', 'history' => '(voorbeeld) Het Koninkrijk van Genory werd gesticht door Genoriaanse stamleden aan het einde van de 5e eeuw nadat ze het land waren binnengevallen vanuit de Hottens.', 'type' => 'Koninkrijk', ], 'kingdom2' => [ 'description' => '(voorbeeld) Ulyss is de hoofdstad van het koninkrijk Genory, en de derde grootste stad van Agagir Alliance.', 'history' => '(voorbeeld) Ulyss is de hoofdstad van het koninkrijk Genory. Het werd opgericht door Frasan Irwen en is gelegen aan de rivier de Unri.', 'type' => 'Hoofdstad', ], 'note1' => [], ]; ================================================ FILE: lang/nl/subscriptions.php ================================================ [ 'failed' => 'Stripe kan je betaalmethode niet belasten. Je abonnement is daarom gedeactiveerd.', ], ]; ================================================ FILE: lang/nl/tags.php ================================================ [ 'actions' => [ 'add' => 'Voeg een nieuwe tag toe', ], ], 'create' => [ 'title' => 'Nieuwe Tag', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Gerelateerden', ], 'helpers' => [], 'hints' => [ 'children' => 'Deze lijst bevat alle entiteiten rechtstreeks in deze tag en in alle geneste tags.', 'tag' => 'Hieronder worden alle tags weergegeven die direct onder deze tag staan.', ], 'index' => [], 'placeholders' => [ 'type' => 'Lore, Oorlogen, Geschiedenis, Religie, Vexillologie', ], 'show' => [ 'tabs' => [ 'children' => 'Gerelateerden', ], ], 'tags' => [], ]; ================================================ FILE: lang/nl/teams.php ================================================ [ 'translations' => 'Vertalingen', ], 'people'=> [ 'jay' => [ 'title' => 'Oprichter en hoofdontwikkelaar', ], 'jon' => [ 'title' => 'Medeoprichter & Business Manager', ], ], ]; ================================================ FILE: lang/nl/tiers.php ================================================ 'Je huidige abbonement', 'features' => [ 'api_requests' => ':amount API requests / min', 'boosters' => 'Campaign Boosters', 'discord' => 'Discord rollen', 'feature_influence' => 'Nieuwe feature-invloed', 'file_size' => ':size Bestand grootte uploads', 'nice_image' => 'Standaard entiteit afbeeldingen', 'no_ads' => 'Geen advertenties', 'pagination' => ':amount Maximaal gepagineerde resultaten (entiteiten weergegeven per pagina)', ], 'pricing' => ':currency :amount / maand', ]; ================================================ FILE: lang/nl/timelines/elements.php ================================================ [ 'success' => 'Element toegevoegd aan de tijdlijn.', 'title' => 'Nieuw Tijdlijn Element', ], 'delete' => [ 'success' => 'Element :name verwijderd', ], 'edit' => [ 'success' => 'Element bijgewerkt.', 'title' => 'Wijzig Tijdlijn Element', ], 'fields' => [ 'date' => 'Datum', 'era' => 'Tijdperk', 'icon' => 'Pictogram', ], 'helpers' => [ 'icon' => 'Kopieer de HTML van een pictogram van :fontawesome of :rpgawesome', ], 'placeholders' => [ 'date' => 'Bijv. 42 Maart of 1332-1337', 'name' => 'Vereist als er geen entiteit is geselecteerd', 'position' => 'Positie in de lijst met elementen voor het tijdperk. Laat leeg om aan het einde toe te voegen.', ], ]; ================================================ FILE: lang/nl/timelines/eras.php ================================================ [ 'add' => 'Voeg een nieuw tijdperk toe', ], 'create' => [ 'success' => 'Tijdperk :name gemaakt', 'title' => 'Nieuw Tijdperk', ], 'delete' => [ 'success' => 'Tijdperk :name verwijderd.', ], 'edit' => [ 'success' => 'Tijdperk :name bijgewerkt.', 'title' => 'Wijzig Tijdperk :name', ], 'fields' => [ 'abbreviation' => 'Afkorting', 'end_year' => 'Einde jaar', 'start_year' => 'Start Jaar', ], 'helpers' => [ 'eras' => 'De tijdlijn moet worden gemaakt voordat er tijdperken aan kunnen worden toegevoegd.', 'primary' => 'Scheid je tijdlijn in tijdperken. Een tijdlijn heeft minstens één tijdperk nodig om goed te kunnen werken.', ], 'placeholders' => [ 'abbreviation' => 'AD, BC, BCE', 'end_year' => 'Jaar waarin het tijdperk eindigt. Laat leeg als dit het huidige tijdperk is.', 'name' => 'Moderne Tijdperk, Bronze Tijdperk, Galactische Oorlogen', 'start_year' => 'Jaar waarin het tijdperk begint. Laat leeg als dit het eerste tijdperk is.', ], 'reorder' => [], ]; ================================================ FILE: lang/nl/timelines.php ================================================ [ 'add_element' => 'Voeg toe aan tijdperk :era', 'back' => 'Terug naar :name', 'save_order' => 'Nieuwe volgorde opslaan', ], 'create' => [ 'title' => 'Nieuwe Tijdlijn', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_eras' => 'Kopieer tijdperken', 'eras' => 'Tijdperken', 'reverse_order' => 'Keer tijdperk volgorde om', ], 'helpers' => [ 'reverse_order' => 'Schakel in om tijdperken in omgekeerde chronologische volgorde weer te geven (eerst het oudere tijdperk)', ], 'index' => [], 'placeholders' => [ 'type' => 'Primair, Wereldkroniek, Koninkrijk geschiedenis', ], 'show' => [], 'timelines' => [], ]; ================================================ FILE: lang/nl/validation.php ================================================ ':attribute moet geaccepteerd zijn.', 'active_url' => ':attribute is geen geldige URL.', 'after' => ':attribute moet een datum na :date zijn.', 'after_or_equal' => ':attribute moet een datum na of gelijk aan :date zijn.', 'alpha' => ':attribute mag alleen letters bevatten.', 'alpha_dash' => ':attribute mag alleen letters, nummers, underscores (_) en streepjes (-) bevatten.', 'alpha_num' => ':attribute mag alleen letters en nummers bevatten.', 'array' => ':attribute moet geselecteerde elementen bevatten.', 'before' => ':attribute moet een datum voor :date zijn.', 'before_or_equal' => ':attribute moet een datum voor of gelijk aan :date zijn.', 'between' => [ 'array' => ':attribute moet tussen :min en :max items bevatten.', 'file' => ':attribute moet tussen :min en :max kilobytes zijn.', 'numeric' => ':attribute moet tussen :min en :max zijn.', 'string' => ':attribute moet tussen :min en :max karakters zijn.', ], 'boolean' => ':attribute moet ja of nee zijn.', 'confirmed' => ':attribute bevestiging komt niet overeen.', 'date' => ':attribute moet een datum bevatten.', 'date_format' => ':attribute moet een geldig datum formaat bevatten.', 'different' => ':attribute en :other moeten verschillend zijn.', 'digits' => ':attribute moet bestaan uit :digits cijfers.', 'digits_between' => ':attribute moet bestaan uit minimaal :min en maximaal :max cijfers.', 'dimensions' => ':attribute heeft geen geldige afmetingen voor afbeeldingen.', 'distinct' => ':attribute heeft een dubbele waarde.', 'email' => ':attribute is geen geldig e-mailadres.', 'exists' => ':attribute bestaat niet.', 'file' => ':attribute moet een bestand zijn.', 'filled' => ':attribute is verplicht.', 'image' => ':attribute moet een afbeelding zijn.', 'in' => ':attribute is ongeldig.', 'in_array' => ':attribute bestaat niet in :other.', 'integer' => ':attribute moet een getal zijn.', 'ip' => ':attribute moet een geldig IP-adres zijn.', 'ipv4' => ':attribute moet een geldig IPv4-adres zijn.', 'ipv6' => ':attribute moet een geldig IPv6-adres zijn.', 'json' => ':attribute moet een geldige JSON-string zijn.', 'max' => [ 'array' => ':attribute mag niet meer dan :max items bevatten.', 'file' => ':attribute mag niet meer dan :max kilobytes zijn.', 'numeric' => ':attribute mag niet hoger dan :max zijn.', 'string' => ':attribute mag niet uit meer dan :max karakters bestaan.', ], 'mimes' => ':attribute moet een bestand zijn van het bestandstype :values.', 'mimetypes' => ':attribute moet een bestand zijn van het bestandstype :values.', 'min' => [ 'array' => ':attribute moet minimaal :min items bevatten.', 'file' => ':attribute moet minimaal :min kilobytes zijn.', 'numeric' => ':attribute moet minimaal :min zijn.', 'string' => ':attribute moet minimaal :min karakters zijn.', ], 'not_in' => 'Het formaat van :attribute is ongeldig.', 'numeric' => ':attribute moet een nummer zijn.', 'present' => ':attribute moet bestaan.', 'regex' => ':attribute formaat is ongeldig.', 'required' => ':attribute is verplicht.', 'required_if' => ':attribute is verplicht indien :other gelijk is aan :value.', 'required_unless' => ':attribute is verplicht tenzij :other gelijk is aan :values.', 'required_with' => ':attribute is verplicht i.c.m. :values', 'required_with_all' => ':attribute is verplicht i.c.m. :values', 'required_without' => ':attribute is verplicht als :values niet ingevuld is.', 'required_without_all' => ':attribute is verplicht als :values niet ingevuld zijn.', 'same' => ':attribute en :other moeten overeenkomen.', 'size' => [ 'array' => ':attribute moet :size items bevatten.', 'file' => ':attribute moet :size kilobyte zijn.', 'numeric' => ':attribute moet :size zijn.', 'string' => ':attribute moet :size karakters zijn.', ], 'string' => ':attribute moet een tekenreeks zijn.', 'timezone' => ':attribute moet een geldige tijdzone zijn.', 'unique' => ':attribute is al in gebruik.', 'uploaded' => 'Het uploaden van :attribute is mislukt.', 'url' => ':attribute is geen geldige URL.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ 'address' => 'adres', 'age' => 'leeftijd', 'available' => 'beschikbaar', 'city' => 'stad', 'content' => 'inhoud', 'country' => 'land', 'date' => 'datum', 'day' => 'dag', 'description' => 'omschrijving', 'email' => 'e-mailadres', 'excerpt' => 'uittreksel', 'first_name' => 'voornaam', 'gender' => 'geslacht', 'hour' => 'uur', 'last_name' => 'achternaam', 'message' => 'boodschap', 'minute' => 'minuut', 'mobile' => 'mobiel', 'month' => 'maand', 'name' => 'naam', 'password' => 'wachtwoord', 'password_confirmation' => 'wachtwoordbevestiging', 'phone' => 'telefoonnummer', 'second' => 'seconde', 'sex' => 'geslacht', 'size' => 'grootte', 'subject' => 'onderwerp', 'time' => 'tijd', 'title' => 'titel', 'username' => 'gebruikersnaam', 'year' => 'jaar', ], ]; ================================================ FILE: lang/pl/abilities.php ================================================ [], 'children' => [ 'actions' => [ 'attach' => 'Dodaj elementom', ], 'create' => [ 'attach_success' => '{1} Zdolność :name dodano :count elementowi. |[2,*] Zdolność :name dodano :count elementom.', 'helper' => 'Dodaje :name jednemu lub wielu elementom.', 'title' => 'Dodawanie elementom', ], 'description' => 'Elementy posiadające tę zdolność', 'title' => 'Elementy zdolności :name', ], 'create' => [ 'title' => 'Nowa zdolność', ], 'destroy' => [], 'edit' => [], 'entities' => [], 'fields' => [ 'charges' => 'Ładunki', ], 'helpers' => [], 'index' => [], 'lists' => [ 'empty' => 'Dodaj moce, czary i talenty. Wielu twórców używa ich do modelowania klas z D&D.', ], 'placeholders' => [ 'charges' => 'Liczba ładunków zdolności. Możesz wpisać wartość cechy jako {Level}*{CHA}', 'name' => 'Kula ognia, alarm, podstępny atak', 'type' => 'Czar, umiejętność, technika bojowa', ], 'reorder' => [ 'parentless' => 'Bez źródła', 'success' => 'Zmieniono kolejność zdolności.', 'title' => 'Zmień kolejność zdolności', ], 'show' => [ 'tabs' => [ 'reorder' => 'Zmień kolejność', ], ], ]; ================================================ FILE: lang/pl/account/email.php ================================================ [ 'update' => 'Zmień email', ], 'fields' => [ 'email' => 'Nowy adres email', ], 'helpers' => [ 'email' => 'Sprawdź, czy jest wpisany poprawnie.', ], 'subtitle' => 'Zmień adres email przypisany do konta.', 'title' => 'Zmiana adresu email', ]; ================================================ FILE: lang/pl/account/password.php ================================================ [ 'update' => 'Zmień hasło', ], 'fields' => [ 'password' => 'Nowe hasło', ], 'helpers' => [ 'password' => 'Użyj managera by wygenerować silne hasło.', 'password_confirmation' => 'Tylko się nie pomyl!', ], 'subtitle' => 'Zmienia hasło konta. Spowoduje wylogowanie na wszystkich innych urządzeniach.', 'title' => 'Zmiana hasła', ]; ================================================ FILE: lang/pl/account/social.php ================================================ 'Zalogowano przez :provider', 'subtitle' => 'Zmienia logowanie przez :provider na logowanie za pośrednictwem Kanki, przy użyciu adresu email i hasła', 'title' => 'Zmiana sposobu logowania', ]; ================================================ FILE: lang/pl/assistance.php ================================================ [ 'campaign' => 'Kampania', ], 'opening' => 'Zespół Kanki przygotował tę stronę w celu rozwiązywania problemów.', 'placeholders' => [ 'campaign' => 'Wybierz kampanię której jesteś administratorem', ], 'select' => 'Wybierz kampanię której jesteś administratorem z listy poniżej by wytworzyć specjalną jednorazową przepustkę, która pozwoli członkowi zespołu Kanki tymczasowo dołączyć do niej jako administrator.', 'success' => [ 'opening' => 'Wytworzono przepustkę. Zespół Kanki został powiadomiony i wkrótce dołączy do kampanii by ci pomóc. Zwykle kontaktujemy się za pośrednictwem :discord jeżeli musimy coś wspólnie omówić.', 'secret' => 'Przepustki może użyć wyłącznie zweryfikowany członek zespołu Kanki. Nie przyda się nikomu poza tym, więc nie musisz robić z niej tajemnicy.', 'token' => 'Twoja przepustka dla wspierającego:', ], 'title' => 'Rozwiązywanie problemów', ]; ================================================ FILE: lang/pl/attribute_templates.php ================================================ [], 'create' => [ 'title' => 'Nowy szablon cech', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'auto_apply' => 'Stosuj automatycznie', 'is_enabled' => 'Aktywny', ], 'hints' => [ 'automatic' => 'Cechy przypisane automatycznie według szablonu :link.', 'automatic_apply' => '{1} Zastosowano automatycznie :count cechę z :link | [2,4] Zastosowano automatycznie :count cechy z :link | [5,] Zastosowano automatycznie :count cech z :link', 'entity_type' => 'Po ustawieniu, ten szablon cech będzie automatycznie przypisywany do nowych elementów wybranego typu.', 'is_disabled' => 'Ten szablon nie jest aktywny', 'is_enabled' => 'Aktywuj szablon by używać go w kampanii', 'parent_attribute_template' => 'Ten szablon może pochodzić od innego szablonu cech. Kiedy przypisujesz szablon do jakieś elementu, wszystkie jego szablony źródłowe zostają również przypisane.', ], 'index' => [], 'lists' => [ 'empty' => 'Stwórz szablony, pozwalące nadać ten sam zestaw cech wielu elementom.', ], 'placeholders' => [ 'name' => 'Nazwa szablonu cech', ], 'show' => [], ]; ================================================ FILE: lang/pl/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Błąd', 'rendering' => 'Podczas aplikowania wtyczki wystąpił błąd. Skontaktuj się z twórcą wtyczki.', ], ], 'helpers' => [], 'list' => [ 'sheets' => 'Karty postaci', ], 'pitch' => 'Wyszukaj i dodaj do :boosted-campaign karty postaci z :marketplace', ]; ================================================ FILE: lang/pl/auth.php ================================================ [ 'permanent' => 'Twoje konto zostało trwale zablokowane.', 'temporary' => '{1} Twoje konto zostało zablokowane na :days dzień|[2,*] Twoje konto zostało zablokowane na :days dni.', ], 'confirm' => [ 'confirm' => 'Potwierdź', 'error' => 'Niewłaściwe hasło, spróbuj jeszcze raz', 'helper' => 'Przed przejściem dalej potwierdź swoje hasło', 'title' => 'Potwierdzenie hasła', ], 'continue' => [ 'facebook' => 'Kontynuuj z Facebook', 'google' => 'Kontynuuj z Google', 'x' => 'Kontynuuj z X', ], 'failed' => 'Błędny login lub hasło.', 'helpers' => [ 'password' => 'Pokaż/ukryj hasło', ], 'login' => [ 'fields' => [ '2fa' => 'Hasło jednorazowe', 'email' => 'Email', 'password' => 'Hasło', ], 'no-account' => 'Nie masz konta?', 'or' => 'LUB', 'password_forgotten' => 'Nie pamiętasz hasła?', 'sign-up' => 'Zarejestruj się', 'submit' => 'Zaloguj', 'title' => 'Logowanie', ], 'register' => [ 'already' => 'Masz już konto? :login', 'errors' => [ 'email_already_taken' => 'Istnieje już konto związane z tym adresem email.', 'general_error' => 'Podczas rejestracji wystąpił błąd. Spróbuj jeszcze raz.', ], 'fields' => [ 'email' => 'Email', 'name' => 'Nazwa użytkownika', 'password' => 'Hasło', ], 'log-in' => 'Zaloguj się', 'submit' => 'Zarejestruj', 'title' => 'Rejestracja', 'tos' => 'Rejestrując konto zgadzasz się na nasze :terms i :privacy', ], 'reset' => [ 'fields' => [ 'email' => 'Adres email', 'password' => 'Hasło', 'password_confirmation' => 'Potwierdź hasło', ], 'send' => 'Prześlij link to resetowania hasła', 'submit' => 'Resetuj hasło', 'title' => 'Resetowanie hasła', ], 'tfa' => [ 'helper' => 'Włączono autoryzację dwuetapową. Wpisz hasło jednorazowe (OTP) ze swojej aplikacji autoryzującej', 'title' => 'Autoryzacja dwuetapowa', ], 'throttle' => 'Za dużo nieudanych prób logowania. Proszę spróbować za :seconds sekund.', 'x-twitter' => 'X, dawniej pod nazwą Twitter', ]; ================================================ FILE: lang/pl/banners.php ================================================ 'Użyj kodu promocyjnego :code by uzyskać 20% zniżki na pierwszą roczną subskrypcję!', ]; ================================================ FILE: lang/pl/billing/information.php ================================================ [ 'update' => 'Zmiana danych', ], 'helper' => 'Do wszystkich rachunków można dodać adres służbowy, numer VAT i tak dalej.', 'title' => 'Zmiana danych rachunku', ]; ================================================ FILE: lang/pl/billing/invoices.php ================================================ [ 'download' => 'Pobierz PDF', ], 'description' => 'Wyświetla rachunki z ostatnich 24 miesięcy', 'empty' => 'Nie znaleziono rachunków', 'fields' => [ 'amount' => 'Kwota', 'date' => 'Data', 'invoice' => 'Rachunek', 'status' => 'Status', ], 'paypal' => 'Pamiętaj, że w tym miejscu wyświetlane są wyłącznie wpłaty dokonane z pomocą Stripe, nie PayPal.', 'status' => [ 'paid' => 'Opłacony', 'pending' => 'Oczekujący', ], 'title' => 'Historia opłat', ]; ================================================ FILE: lang/pl/billing/menu.php ================================================ 'Historia opłat', 'overview' => 'Informacje', 'payment-method' => 'Metoda płatności', ]; ================================================ FILE: lang/pl/billing/payment_methods.php ================================================ 'Metoda płatności', 'types' => [ 'card' => 'Karta', ], ]; ================================================ FILE: lang/pl/bookmarks.php ================================================ [ 'customise' => 'Dostosuj menu boczne', ], 'create' => [ 'title' => 'Nowy skrót', ], 'destroy' => [], 'edit' => [ 'title' => 'Skrót :name', ], 'fields' => [ 'active' => 'Aktywne', 'dashboard' => 'Pulpit', 'default_dashboard' => 'Pulpit domyślny', 'filters' => 'Filtry', 'menu' => 'Menu', 'position' => 'Kolejność', 'random_type' => 'Losowy typ elementu', 'selector' => 'Konfiguracja skrótu', 'target' => 'Cel', ], 'helpers' => [ 'active' => 'Nieaktywne skróty nie pojawią się w menu bocznym', 'css' => 'Określa klasę CSS, która zostanie dodana do skrótu umieszczonego w menu bocznym.', 'dashboard' => 'Skrót prowadzący do jednego z własnych pulpitów kampanii. Ta opcja dostępna jest tylko w :boosted kampanii.', 'default_dashboard' => 'Odnośnik prowadzi do pulpitu domyślnego. Pulpity własne należy dopiero wybrać.', 'entity' => 'Stwórz skrót prowadzący wprost do jakiegoś elementu. Pole :tab pozwala decydować, która zakładka się wyświetli. Pole :menu pozwala określić, która podstrona zostanie otwarta.', 'position' => 'To pole pozwala ustalać kolejność (rosnącą) wyświetlania skrótów.', 'random' => 'Użyj tego pola by stworzyć skrót do losowego elementu. Możesz ustawić filtry, by skrót prowadził do losowego elementu danego typu.', 'selector' => 'Ustal dokąd skrót przeniesie użytkownika, który na niego kliknie', 'type' => 'Stwórz skrót prowadzący do listy elementów. By filtrować rezultaty, skopuj część adresu filtrowanej listy elementów po znaku :? w pole :filter.', ], 'index' => [], 'lists' => [ 'empty' => 'Zapisz skróty do najczęściej używanych elementów albo filtrów, ułatwiające dostęp.', ], 'placeholders' => [ 'filters' => 'location_id=15&type=city', 'menu' => 'Podstrona menu (użyj ostatniego tekstu adresu)', 'tab' => 'opis, relacje, notki', ], 'random_no_entity' => 'Nie znaleziono losowego elementu.', 'random_types' => [ 'any' => 'Dowolny element', ], 'reorder' => [ 'success' => 'Zmieniono kolejność skrótów.', 'title' => 'Zmiana kolejności skrótów', ], 'show' => [], 'targets' => [ 'dashboard' => 'Jeden z pulpitów kampanii', 'entity' => 'Pojedynczy element', 'random' => 'Losowy element', 'select' => 'Wybierz opcję', 'type' => 'Lista elementów należących do określonego typu/modułu', ], 'visibilities' => [ 'is_active' => 'Pokaż skróty w menu bocznym', ], ]; ================================================ FILE: lang/pl/bragi/backstory.php ================================================ 'Napisz krótką historię postaci na podstawie podanych informacji. Dopasuj styl i szczegóły do wskazanego systemu gry oraz gatunków. Możesz uwzględnić w opisie wygląd postaci, jej pochodzenie, przekonania, relacje z innymi, cele, wady oraz szczególne doświadczenia - o ile pasuje to do opisywanej postaci.', 'setup' => [ 'gender' => 'Płeć: :gender', 'genres' => 'Gatunki: :genres', 'name' => 'Imię postaci. :name', 'prompt' => 'Prompt: ":prompt"', 'pronouns' => 'Zaimki: :pronouns', 'systems' => 'System: :system', ], 'system' => 'Jesteś zawodowym prowadzącym gry fabularne oraz twórcą postaci. Tworzysz pasujące do świata, oddziałujące na emocje historie postaci na potrzeby gier fabularnych. Zwykle tworzysz 2-4 akapity liczące do 400 słów i opisujące wygląd, historię, przekonania, motywacje oraz przywary postaci.', ]; ================================================ FILE: lang/pl/bragi.php ================================================ [ 'generate' => 'Generuj', 'insert' => 'Użyj', ], 'errors' => [ 'invalid-sub' => 'Ten element dostępny jest w subskrypcji Wivern i Elemental.', 'out-of-tokens' => 'Nie masz już tokenów! Nowe otrzymasz automatycznie w dniu :date.', ], 'here' => 'tutaj', 'intro' => 'Hej! Jestem :name, SI która pomoże ci generować historie postaci tworzonych w Kance. Więcej dowiesz się :here.', 'kankappy' => 'Są tajemnymi uczniami Kankappy.', 'loading' => 'Poczekaj chwileczkę, myślę intensywnie i chwilę mi to zajmie!', 'placeholders' => [ 'prompt' => 'Wpisz prompt który zmienię w historię postaci.', ], 'token-limit' => 'Twoja liczba tokenów to :amount. Za każdym razem kiedy generujesz historię, zużywasz jeden z nich, więc działaj z rozwagą.', ]; ================================================ FILE: lang/pl/calendars/.gitkeep ================================================ ================================================ FILE: lang/pl/calendars/weather.php ================================================ [], 'create' => [ 'helper' => 'Dodaj informację o pogodzie, która pojawi się w kalendarzu.', 'success' => 'Dodano pogodę.', 'title' => 'Nowy efekt pogody', ], 'destroy' => [ 'success' => 'Usunięto pogodę.', ], 'edit' => [ 'success' => 'Zmieniono pogodę.', 'title' => 'Zmiana pogody', ], 'fields' => [ 'effect' => 'Efekt', 'name' => 'Nazwa', 'precipitation' => 'Opady', 'temperature' => 'Temperatura', 'weather' => 'Pogoda', 'wind' => 'Wiatr', ], 'options' => [ 'weather' => [ 'bolt' => 'Burza z piorunami', 'cloud' => 'Pochmurno', 'cloud-rain' => 'Deszczowo', 'cloud-showers-heavy' => 'Ulewa', 'cloud-sun' => 'Słonecznie i pochmurno', 'cloud-sun-rain' => 'Słonecznie, pochmurno i deszczowo', 'meteor' => 'Meteor', 'smog' => 'Smog', 'snowflake' => 'Śnieżnie', 'sun' => 'Słonecznie', 'wind' => 'Wietrznie', ], ], 'placeholders' => [ 'effect' => 'Efekt magiczny lub naturalny', 'name' => 'Opcjonalna nazwa pogody', 'precipitation' => 'Intensywność opadów', 'temperature' => 'Najwyższa i najniższa', 'wind' => 'Prędkość wiatru', ], ]; ================================================ FILE: lang/pl/calendars.php ================================================ [ 'add_epoch' => 'Dodaj epokę', 'add_intercalary' => 'Dodaj dni dodatkowe', 'add_month' => 'Dodaj miesiąc', 'add_moon' => 'Dodaj księżyc', 'add_reminder' => 'Dodaj epizod', 'add_season' => 'Dodaj porę roku', 'add_weather' => 'Dodaj pogodę', 'add_week' => 'Dodaj tydzień specjalny', 'add_weekday' => 'Dodaj dzień tygodnia', 'add_year' => 'Dodaj nazwę roku', 'set_today' => 'Ustaw jako aktualną datę', 'today' => 'Dziś', 'update_weather' => 'Aktualizuj pogodę', ], 'checkboxes' => [ 'is_recurring' => 'Coroczne', ], 'colours' => [], 'create' => [ 'title' => 'Nowy kalendarz', ], 'destroy' => [], 'edit' => [ 'today' => 'Zmieniono datę kalendarza.', ], 'event' => [ 'create' => [ 'success' => 'Utworzono epizod.', 'title' => 'Nowy epizod', ], 'destroy' => 'Usunięto epizod z \':name\'.', 'edit' => [ 'success' => 'Zmieniono epizod.', 'title' => 'Edycja epizodu elementu :name', ], 'errors' => [ 'invalid_entity' => 'Wybrano niewłaściwy element.', ], 'helpers' => [ 'other_calendar' => 'Edytujesz epizod zapisany w kalendarzu :calendar.', ], 'success' => 'Epizod \':event\' zaznaczono w kalendarzu :calendar.', ], 'events' => [ 'bulks' => [ 'delete' => '{1} Usunięto :count epizod.|[2,4] Usunięto :count epizody.|[5,*] Usunięto :count episodów.', 'patch' => '{1} Zmieniono :count epizod.|[2,4] Zmieniono :count epizody.|[5,*] Zmieniono :count epizodów.', ], 'end' => '(koniec)', 'filters' => [ 'show_after' => 'Pokaż aktualną datę i dalej', 'show_all' => 'Pokaż wszystko', 'show_before' => 'Pokaż daty przed aktualną', ], 'start' => '(początek)', ], 'fields' => [ 'comment' => 'Opis', 'current_day' => 'Obecny dzień', 'current_month' => 'Obecny miesiąc', 'current_year' => 'Obecny rok', 'date' => 'Obecna data', 'day' => 'Dzień', 'default_layout' => 'Domyślny układ', 'format' => 'Zapis', 'is_incrementing' => 'Upływ czasu', 'is_recurring' => 'Cykliczne', 'leap_year' => 'Lata przestępne', 'leap_year_amount' => 'Dodaj dni', 'leap_year_month' => 'Miesiąca', 'leap_year_offset' => 'Każdego', 'leap_year_start' => 'Rok przestępny', 'length' => 'Czas trwania', 'length_days' => ':count dzień|:count dni', 'month' => 'Miesiąc', 'months' => 'Miesiące', 'moons' => 'Księżyce', 'parameters' => 'Paramtery', 'recurring_until' => 'Powtarzaj do roku', 'reset' => 'Odnawianie tygodni', 'seasons' => 'Pory roku', 'show_birthdays' => 'Pokaż urodziny', 'skip_year_zero' => 'Pomiń rok zerowy', 'start_offset' => 'Przesunięcie rozpoczęcia', 'suffix' => 'Oznaczenie', 'week_names' => 'Tygodnie specjalne', 'weekdays' => 'Dni tygodnia', 'year' => 'Rok', ], 'helpers' => [ 'default_layout' => 'Wybierz domyślny układ, w jakim wyświetlany będzie kalendarz', 'format' => 'Specjalny model zapisu dat tego kalendarza.', 'month_type' => 'Miesiące dodatkowe nie mają dni tygodnia, ale wpływają na pory roku czy fazy księżyca.', 'moon_offset' => 'Domyślnie pierwsza pełnia ma miejsce pierwszego dnia roku 0. Zmieniając wartość przesunięcia modyfikujesz moment pełni. Przesunięcie może mieć wartość ujemną (maksymalnie długości pierwszego miesiąca) albo dodatnią (maksymalnie długości pierwszego miesiąca).', 'start_offset' => 'Domyślnie kalendarz zaczyna się pierwszego dnia roku 0. Liczba w tym polu zmienia położenie pierwszego dnia kalendarza.', ], 'hints' => [ 'event_length' => 'Czas trwania epizodu, w dniach. Wyświetlany będzie tylko przez pierwsze dwa lata.', 'is_incrementing' => 'Kalendarze, którym zaznaczono tę opcję, automatycznie przesuwają w przód datę każdego dnia o 00:00 UTC.', 'leap_year' => 'Ten kalendarz posiada lata przestępne.', 'months' => 'Kalendarz powinien mieć co najmniej 2 miesiące.', 'moons' => 'Dodanie księżyca spowoduje, że w kalendarzu wyświetlana będzie każda pełnia i nów. Jeżeli cykl księżycowy jest dłuższy niż 10 dni, pojawią się też informacje o pierwszej i trzeciej kwadrze.', 'parent_calendar' => 'W kalendarzu pojawiają się wszystkie epizody oraz efekty pogody z wybranego kalendarza źródłowego.', 'reset' => 'Każdy miesiąc lub rok zaczyna się zawsze od pierwszego dnia tygodnia.', 'seasons' => 'By dodać porę roku wystarczy określić, kiedy się zaczyna. Kanka obliczy resztę.', 'show_birthdays' => 'Co roku wyświetla w kalendarzu urodziny postaci o ustawionej dacie urodzin, aż do chwili ich śmierci.', 'skip_year_zero' => 'Domyślnie pierwszy rok kalendarza nosi numer zero. Zaznacz, by go pominąć.', 'weekdays' => 'Określ nazwy dni tygodnia. Tydzień musi mieć przynajmniej 2 dni.', 'weeks' => 'Nadaj nazwy szczególnie ważnym tygodniom tego kalendarza.', 'years' => 'Niektóre lata są tak ważne, że posiadają własne nazwy.', ], 'index' => [], 'layouts' => [ 'month' => 'Miesiąc', 'monthly' => 'Miesięczny', 'year' => 'Rok', 'yearly' => 'Roczny', ], 'lists' => [ 'empty' => 'Stwórz kalendarz z datami świąt i ważnych wydarzeń w świecie gry.', ], 'modals' => [ 'switcher' => [ 'title' => 'Przełącznik lat', ], ], 'month_types' => [ 'intercalary' => 'Dodatkowy', 'standard' => 'Zwykły', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Pełnia', 'fullmoon_name' => 'Pełnia księżyca :moon', 'month' => 'Co miesiąc', 'newmoon' => 'Nów', 'newmoon_name' => 'Nów księżyca :moon', 'none' => 'Brak', 'unnamed_moon' => 'Księżyc :number', 'year' => 'Co rok', ], ], 'resets' => [ '' => 'Nie', 'month' => 'Miesiące', 'year' => 'Lata', ], ], 'panels' => [ 'intercalary' => 'Dni dodatkowe', 'leap_year' => 'Rok przestępny', 'months' => 'Miesiące', 'weeks' => 'Tygodnie', 'years' => 'Lata specjalne', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Długość w dniach', 'month' => 'Po którym miesiącu', 'name' => 'Nazwa okresu dodatkowego', ], 'month' => [ 'alias' => 'Skrót nazwy', 'length'=> 'Dni', 'name' => 'Nazwa miesiąca', 'type' => 'Rodzaj', ], 'moon' => [ 'fullmoon' => 'Pełnia co ile dni?', 'name' => 'Nazwa księżyca', 'offset' => 'Opóźnienie pierwszej pełni', ], 'seasons' => [ 'day' => 'Dzień rozpoczęcia', 'month' => 'Miesiąc rozpoczęcia', 'name' => 'Nazwa pory roku', ], 'weeks' => [ 'name' => 'Nazwa tygodnia', 'number' => 'Numer', ], 'year' => [ 'name' => 'Nazwa roku', 'number' => 'Data', ], ], 'placeholders' => [ 'colour' => 'Kolor', 'comment' => 'Urodziny, święto, przesilenie', 'date' => 'Obecna data', 'leap_year_amount' => 'Liczba dodatkowych dni roku przestępnego', 'leap_year_month' => 'Miesiąc, do którego są dodane', 'leap_year_offset' => 'Co ile lat rok jest przestępny', 'leap_year_start' => 'Który rok jest przestępny jako pierwszy', 'length' => 'Długość epizodu w dniach', 'months' => 'Liczba miesięcy w roku', 'recurring_until' => 'Ostatni rok cyklu (zostaw puste, jeżeli cykl ma trwać bez końca)', 'seasons' => 'Liczba pór roku', 'suffix' => 'Skrót nazwy obecnej epoki (AD, p.n.e.)', 'type' => 'Rodzaj kalendarza', 'weekdays' => 'Liczba dni w tygodniu', ], 'show' => [ 'missing_details' => 'Nie można wyświetlić kalendarza. Do poprawnego wyświetlania niezbędne są przynajmniej 2 miesiące posiadające po 2 dni tygodnia.', 'moon_1first_quarter' => 'Pierwsza kwadra :moon', 'moon_full' => ':moon - pełnia', 'moon_last_quarter' => ':moon - ostatnia kwadra', 'moon_new' => ':moon - nów', 'tabs' => [ 'events' => 'Epizody', 'weather' => 'Pogoda', ], ], 'sorters' => [ 'after' => 'Dziś i wcześniej', 'before'=> 'Dziś i później', ], 'validators' => [ 'format' => 'Taki zapis jest niemożliwy.', 'moon_offset' => 'Przesunięcie pierwszej pełni nie może być większe, niż długość pierwszego miesiąca w kalendarzu.', ], 'warnings' => [ 'event_length' => 'Epizody trwające wiele lat są widoczne tylko przez pierwsze dwa lata. Więcej na ten temat - patrz : documentation.', ], ]; ================================================ FILE: lang/pl/callouts.php ================================================ [ 'subscription' => 'Dowiedz się więcej o subskrypcji', ], 'booster' => [ 'actions' => [ 'boost' => 'Doładuj :campaign', 'superboost' => 'Turbodoładuj :campaign', ], 'learn-more' => 'Czym są doładowania?', 'limitation' => 'Ta opcja dostępna jest tylko w doładowanych kampaniach', 'limitations' => [ 'boosted' => 'Dostęp do tej opcji wymaga doładowania kampanii.', 'superboosted' => 'Dostęp do tej opcji wymaga turbodoładowania kampanii.', ], 'multiple' => 'Te opcje dostępne są tylko w doładowanych kampaniach', 'pitches' => [ 'element-class' => 'Nadawaj elementom własne klasy CSS po :boosted-campaign.', 'icon' => 'W :boosted-campaigh używać możesz milionów ikon z FontAwesome.', ], 'titles' => [ 'boosted' => 'Po doładowaniu', 'superboosted' => 'Po turbodoładowaniu', ], ], 'premium' => [ 'learn-more' => 'Czym jest kampania premium?', 'limitation' => 'Opcja dostępna wyłącznie w kampaniach premium.', 'multiple' => 'By używać tych funkcji należy odblokować poziom premium kampanii :campaign.', 'title' => 'Opcja premium', 'unlock' => 'Odblokuj opcje premium dla :campaign', ], 'subscribe' => [ 'pitch-image' => 'Subskrybuj, by zwiększyć rozmiar dołączanych plików do :max MB.', 'share-booster' => 'Doładowanie zwiększa maksymalny rozmiar dołączanych plików dla wszystkich uczestników kampanii.', 'share-premium' => 'Zwiększa wielkość plików zamieszczanych przez wszystkich członków kampanii premium', ], ]; ================================================ FILE: lang/pl/campaigns/.gitkeep ================================================ ================================================ FILE: lang/pl/campaigns/achievements.php ================================================ 'Gratulacje!', 'connections' => '{0} Nie ma żadnych powiązań|{1} Stworzono jedno powiązanie|[2,4] Stworzono :amount powiązania|[5,] Dodano :amount powiązań', 'created' => '{0} Nie stworzono elementów :plural|{1} Stworzono jednen element :singular|[2,*] Stworzono :amount elementów :plural', 'dead' => '{0} Brak tajemniczych zgonów|{1} Jeden tajemniczy zgon|[2,4] :amount tajemnicze zgony|[5,] :amount tajemniczych zgonów', 'goal' => 'Cel :number', 'goal_reached' => 'Kampania uzyskała następujące osiągnięcie:', 'level' => 'poziom :number', 'markers' => '{0} Mapa nie ma znaczników|{1} Stworzono jeden znacznik|[2,4] Stworzono :amount znaczniki|[5,] Dodano :amount znaczników', 'painter' => '{0} Nie stworzono motywów|{1} Stworzono jeden motyw|[2,4] Stworzono :amount motywy|[5,] Dodano :amount motywów', 'pitch' => 'Osiągnięcia to fajny sposób celebracji postępów w budowie twojego świata. Pomagają śledzić progres, angażować graczy i chwalić się dokonaniami w kampaniach premium.', 'plugins' => '{0} Nie zainstalowano dodatków|{1} Zainstalowano jeden dodatek|[2,4] Zainstalowano :amount dodatki|[5,] Zainstalowano :amount dodatków', 'remaining' => [ 'generic' => 'Do następnego poziomu.', ], 'spotlight' => [ 'active' => [ 'cta' => 'Zobacz wyróżnione', ], 'private' => [ 'cta' => 'Sprawdź ustawienia publiczne', 'helper' => 'By kampania mogła zostać wyróżnona, musi być publiczna.', ], 'public' => [ 'cta' => 'Dowiedz się, jak działa wyróżnienie', 'helper' => 'Wybrane kampanie są prezentowane w Galerii oraz na blogu.', ], ], 'spotlighted' => '{0} Jeszcze nie wyróżnione|[1,*] Wyróżnone', 'tagged' => '{0} Nie dodano etykiet|{1} Dodano jedną etykietę|[2,4] Dodano :amount etykiety|[5,] Dodano :amount etykiet', 'titles' => [ 'calendars' => 'Czas to pieniądz', 'characters' => 'Zwać się będziesz', 'connections' => 'M jak miłość', 'creatures' => 'Zwierzyniec', 'dead' => 'Skrwawione ostrze', 'events' => 'Kopalnia wiedzy', 'families' => 'Planowanie rodziny', 'locations' => 'Niech się mury pną do góry', 'markers' => 'Palcem po mapie', 'organisations' => 'Załóż firmę', 'plugins' => 'Na dodatek', 'quests' => 'Szczwany plan', 'spotlighted' => 'W świetle reflektorów', 'tags' => 'Wszystko pod kontrolą', 'themes' => 'Pomaluj mój świat', ], 'tutorial' => 'Osiągnięcia rejestrują znaczące działania podejmowanie w kampanii, na przykład tworzenie elementów albo używanie ważnych funkcji. Mają wyłącznie funkcję informacyjną i aktualizują się na bieżąco.', ]; ================================================ FILE: lang/pl/campaigns/applications.php ================================================ [ 'accept' => 'Akceptuj', 'reject' => 'Odrzuć', ], 'apply' => [ 'apply' => 'Zgłoszenie', 'help' => 'Tak kampania jest otwarta dla nowych graczy. Możesz się do niej zgłosić wypełniając formularz. Kiedy administratorzy kampanii rozpatrzą zgłoszenie, otrzymasz powiadomienie', 'remove_text' => 'twoje zgłoszenie', 'success' => [ 'apply' => 'Zapisano zgłoszenie. Możesz je zmienić albo usunąć w dowolnej chwili. Otrzymasz powiadomienie, gdy administratorzy kampanii rozpatrzą prośbę.', 'remove'=> 'Usunięto twoje zgłoszenie', 'update'=> 'Zmieniono zgłoszenie. Możesz je zmienić albo usunąć w dowolnej chwili. Otrzymasz powiadomienie, gdy administratorzy kampanii rozpatrzą prośbę.', ], 'title' => 'Dołącz do :name', ], 'errors' => [], 'fields' => [ 'application' => 'Zgłoszenie', 'reason' => 'Powód przyjęcia/odrzucenia', ], 'helpers' => [ 'modal' => 'Do kampanii publicznej, którą otwarto na zgłoszenia, mogą się zgłaszać nowi uczestnicy.', 'no_applications_title' => 'Brak zgłoszeń', 'reason' => 'Jeśli podasz, kandydat otrzyma tę informację.', 'role' => 'Jeśli przyjmiesz kandydata, otrzyma tę rolę.', ], 'open' => [ 'closed' => 'Kampania jest zamknięta', 'open' => 'Kampania jest otwarta', 'title' => 'Kampania otwarta', ], 'placeholders' => [ 'note' => 'Napisz zgłoszenie, by dołączyć do kampanii', 'reason' => 'Powód', ], 'public' => [ 'private' => 'Kampania jest prywatna', 'public' => 'Kampania jest publiczna', 'title' => 'Kampania publiczna', ], 'statuses' => [], 'title' => 'Zgłoszenia', 'toggle' => [ 'closed' => 'Zamknięta na zgłoszenia', 'label' => 'Status', 'open' => 'Otwarta na zgłoszenia', 'success' => 'Zmieniono status kampanii.', 'title' => 'Status zgłoszenia', ], 'tutorial' => 'Zgłoszenia pozwalają innym wyrazić chęć dołączenia do kampanii. W tym celu wypełniają krótki formularz, a administrator może każde złożenie przejżeć, a potem zaakceptować lub odrzucić. Akceptacja oznacza dołączenie kogoś do kampanii w roli przyznanej na poprzednim etapie.', 'update' => [ 'approve' => 'Wybierz rolę, którą otrzymają uczestnicy dodawani do kampanii.', 'approved' => 'Zatwierdzono zgłoszenie.', 'reject' => 'Jeżeli chcesz, możesz wytłumaczyć dlaczego zgłoszenie zostało odrzucone.', 'rejected' => 'Odrzucono zgłoszenie.', ], ]; ================================================ FILE: lang/pl/campaigns/builder.php ================================================ 'To narzędzie pozwala stworzyć motyw wizualny kampanii. Poniżej znajdziesz informacje, jak dany wybór wpłynie na różne elementy kampanii. Gdy wybierzesz kolor, tekst zostanie automatycznie przełączony na barwę kontrastową. Więcej informacji o tworzeniu motywów znajdziesz w :docs.', 'pitch' => 'Hej, powstało narzędzie to tworzenia motywów i zmiany kolorów interfejsu kampanii ;)', 'pitch-go' => 'Przejdź do projektanta kampanii', 'reset' => 'Przywrócono styl domyślny.', 'success' => 'Zapisano stworzony styl.', 'title' => 'Projektant motywów', ]; ================================================ FILE: lang/pl/campaigns/dashboard-header.php ================================================ [ 'success' => 'Zaktualizowano nagłówek kampanii.', 'title' => 'Aktualizacja nagłówka kampanii', ], ]; ================================================ FILE: lang/pl/campaigns/default-images.php ================================================ [ 'add' => 'Dodaj domyślną ikonę', ], 'call-to-action' => 'Użyj własnej ilustracji domyślnej dla wszystkich postaci, miejsc i innych elementów kampanii. Pojawią się one na różnych listach.', 'create' => [ 'error' => 'Błąd zapisu ilustracji domyślnej. Czy :type posiada już ilustrację?', 'helper' => 'Pozwala zamieścić obraz, używany jako domyślna miniatura dla elementów określonego typu.', 'success' => 'Zapisano domyślną ilustrację dla elementu :type.', 'title' => 'Nowa ilustracja domyślna elementu', ], 'destroy' => [ 'success' => 'Usunięto domyślną ilustrację dla elementu :type.', ], 'empty' => 'Żaden moduł nie ma obecnie domyślnej miniatury.', 'helper' => 'Używane dla wszystkich elementów tego moduły bez własnej ilustracji.', 'index' => [], 'reset' => [ 'helper' => 'Czy na pewno usunąć domyślne ikony ze wszystkich modułów kampanii?', 'success' => 'Usunięto domyślne ikony ze wszystkich modułów.', 'title' => 'Przywracanie ikon domyślnych', 'warning' => 'To działanie jest ostateczne i nie można go cofnąć.', ], 'title' => 'Domyślne ikony', 'tutorial' => 'Dodaje domyślne ikony elementom nie posiadającym własnych ilustracji. Pojawiają się natychmiast w całej kampanii i zachowują spójność wizualną.', ]; ================================================ FILE: lang/pl/campaigns/defaults.php ================================================ [ 'character_personality_visibility' => 'Domyślna widoczność osobowości postaci', 'connections' => 'Widok powiązań', 'connections_mode' => 'Styl mapy powiązań', 'descendants' => 'Domyślne filtrowanie list', 'entity_privacy' => 'Widoczność nowych elementów', 'gallery_visibility' => 'Widoczność domyślnych grafik w galerii', 'post_collapsed' => 'Domyślne wyświetlanie komentarzy', 'private_mention_visibility' => 'Wzmiankowanie elementów tajnych', 'related_visibility' => 'Widoczność treści zależnych', ], 'helpers' => [ 'character_visibility' => 'Określa widoczność cech osobowości nowo stworzonych postaci.', 'connections' => 'Określa, czy powiązania elementu wyświetlane są domyślnie w formie mapy, czy listy.', 'connections_mode' => 'Określa domyślny styl mapy powiązań (w kampaniach premium).', 'descendants' => 'Określa, czy na listach zagnieżdżonych (na przykład postaci w danym miejscu) wyświetlane są tylko elementy pochodne bezpośrednio, czy wszystkie pochodne.', 'display' => 'Określa domyślne wyświetlanie stron elementów.', 'entity' => 'Reguluje domyślą widoczność przypisywaną przez Kankę nowym elementom.', 'entity_privacy' => 'Okresla widoczność nowo stworzonych postaci, miejsc i tak dalej.', 'gallery_visibility' => 'Domyślna widoczność grafik dodawnych do galerii.', 'post_collapsed' => 'Określa, czy komentarze na stronie elementu są domyślnie rozwinięte, czy zwinięte.', 'privacy' => 'Określa domyślną widoczność tworzonych elenentów. Będzie stosowana podczas dodawania nowych elementów i można ją zmienić ręcznie.', 'private_mention_visibility' => 'Kontroluje sposób wyświetlania nazw tajnych elementów wzmiankowanych w widocznych tekstach.', 'related_visibility' => 'Kontroluje widoczość komentarzy, cech i powiązań dodawanych do elementów.', ], 'sections' => [ 'display' => 'Układ treści', 'entity' => 'Ustawienia elementów', 'media' => 'Wyświetlanie grafik', 'mention' => 'Wyświetlanie wzmianek', ], 'tutorial' => 'Ustawienia domyślne pomagają w tworzeniu treści. Możesz tu wybrać widoczność elementów, komentarzy, grafik i tak dalej: ustawienia będą stosowane automatycznie podczas tworzenia nowej zawartości. Dzięki temu oszczędzisz czas i łatwiej zapanujesz na organizacją kampanii.', 'update' => [ 'success' => 'Zmieniono domyślne ustawienia kampanii.', ], 'values' => [ 'collapsed' => [ 'collapsed' => 'Zwinięte', 'default' => 'Domyślna', 'expanded' => 'Rozwinięte', ], 'connections' => [ 'explorer' => 'Mapa powiązań (premium)', 'list' => 'Lista', ], 'descendants' => [ 'all' => 'Wyświetla wszystkie pochodne', 'direct' => 'Wyświetla bezpośrednio pochodne', ], 'mentions' => [ 'private' => 'Wyświetla wzmiankowaną nazwę', 'visible' => 'Ukrywa wzmiankowaną nazwę', ], ], ]; ================================================ FILE: lang/pl/campaigns/delete.php ================================================ 'kopię zapasową', 'confirm' => 'Jeśli na pewno chcesz usunąć :campaign, wpisz :code w polu poniżej.', 'confirm-button' => 'Trwale usuń :name', 'helper' => 'Usunięcie kampanii jest trwałe i nie można go cofnąć. Wszystkie związane z nią dane, w tym obrazy i pliki, zostaną wymazane z naszych serwerów. Przed kontynuacją zalecamy wykonać :backup.', 'issue' => 'Przed usunięciem kampanii należy rozwiązać poniższe kwestie.', 'members' => 'Usuń z kampanii wszystkich innych użytkowników.', 'success' => 'Trwale usunięto :name.', 'title' => 'Usuwanie', ]; ================================================ FILE: lang/pl/campaigns/export.php ================================================ [ 'download' => 'Pobierz', 'export' => 'Eksportuj dane kampanii', ], 'confirm' => [ 'notification' => 'Uczestnicy o roli :admin zostaną powiadomieni gdy eksport będzie gotowy do pobrania.', 'title' => 'Potwierdź eksport', 'type' => 'Typ eksportu', 'warning' => 'Zaraz wyeksportujesz dane kampanii. To może potrwać dłuższą chwilę, zależnie od rozmiaru kampanii. Podczas generowania pliku możesz normalnie używać Kanki.', ], 'errors' => [ 'limit' => 'Dzisiaj już eksportowano kampanię. Spróbuj ponownie jutro.', 'premium' => 'Eksort markdown jest dostępny wyłącznie w kampaniach premium.', ], 'expired' => 'Odnośnik nieaktualny', 'helpers' => [ 'json' => 'Do archiwizacji i przywracania - można użyć w celu zaimportowania kampanii', 'markdown' => 'Do czytania i rozpowszechniania - format zrozumiały dla ludzi', 'premium' => 'Dostępne tylko w kampaniach premium.', ], 'progress' => 'Postęp', 'size' => 'Rozmiar', 'status' => [ 'failed' => 'Niepowodzenie', 'finished' => 'Zakończono', 'running' => 'W toku', 'scheduled' => 'Zaplanowano', ], 'success' => 'Przygotowanie do eksportu kampanii. Otrzymasz powiadomienie, gdy pliki będą gotowe do pobrania.', 'title' => 'Eksport kampanii', 'type' => 'Typ', 'types' => [ 'json' => 'JSON', 'md' => 'Markdown', ], ]; ================================================ FILE: lang/pl/campaigns/gallery.php ================================================ [ 'close' => 'Zamknij', 'file-link' => 'Link do pliku', 'focus_point' => 'Ustaw punkt centralny', 'image-link' => 'Link do obrazu', 'reset_focus' => 'Usuń punkt centralny', 'save' => 'Zapisz', 'upgrade' => 'Powiększ', ], 'breadcrumb' => 'Galeria', 'bulk' => [ 'destroy' => [ 'confirm' => 'Czy na pewno chcesz trwale usunąć wybrane elementy? Nie będzie można ich odzyskać.', 'success' => '{0}Nie usunięto plików.|{1}Usunięto jeden plik.|{2,4}Usunięto :count pliki.|{5,*}Usunięto :count plików.', ], ], 'cta' => 'Zarządzaj obrazami w kampanii i używaj ich ponownie.', 'destroy' => [ 'folder' => 'Usunięto katalog :name.', 'success' => 'Usunięto obraz :name', ], 'errors' => [ 'max' => 'Możesz wybrać do :count plików na raz.', 'permissions' => 'Role w kampanii nie mają uprawnienia :permission więc nie mogą dodawać ilustracji do galerii.', 'storage' => 'Brak miejsca, by załadować wybrane obrazki. Dostępne miejsce: :avaliable.', ], 'fields' => [ 'created_by' => 'Dodane przez', 'details' => 'Szczegóły', 'ext' => 'Typ', 'file_type' => 'Rodzaj pliku', 'folder' => 'Katalog', 'image_mentioned_in' => '{0} Ten obraz nie jest wzmiankowany przez żaden element kampanii.|{1} Wzmiankowany przez jeden element/wpis.|[2,*] Wzmiankowany przez :count elementy/wpisy.', 'image_used_in' => '{1}Użyto jako obrazu jednego elementu.|[2,*]Użyto jako obrazu :count elementów.', 'link' => 'Odnośnik', 'name' => 'Nazwa', 'size' => 'Rozmiar', 'unused' => 'Nieużywany', 'used_in' => 'Używany w', ], 'focus' => [ 'locked' => 'Punkt centralny można ustawić tylko w kampanii premium', 'removed' => 'Usunięto punkt centralny.', 'updated' => 'Zmieniono punkt centralny.', ], 'new_folder' => [ 'title' => 'Nowy katalog', ], 'no_folder' => 'Brak katalogu', 'pitch' => 'Zamieszczaj ilustracje kampanii bezpośrednio z poziomu edytora tekstu', 'placeholders' => [ 'search' => 'Wyszukaj obraz po nazwie', ], 'storage' => [ 'of' => 'z', 'title' => 'Miejsce', ], 'title' => 'Galeria kampanii :campaign', 'update' => [ 'folder' => 'Zmieniono katalog.', 'success' => 'Zmodyfikowano obraz.', ], 'uploader' => [ 'add' => 'Dodaj', 'new_folder' => 'Nowy katalog', 'or' => 'lub', 'select_file' => 'Wybierz plik', 'well' => 'Przeciągnij, by dodać', ], ]; ================================================ FILE: lang/pl/campaigns/import.php ================================================ [ 'import' => 'Załaduj wyeksportowane', ], 'fields' => [ 'updated' => 'Ostatnio zmienione', ], 'form' => 'Załaduj z', 'progress' => [ 'uploading' => 'Ładowanie', ], 'status' => [ 'failed' => 'Niepowodzenie', 'finished' => 'Zakończono', 'queued' => 'W kolejce', 'running' => 'W toku', ], 'title' => 'Import', ]; ================================================ FILE: lang/pl/campaigns/invites.php ================================================ [ 'helper' => 'Tworzy odnośnik z zaproszeniem, który możesz wysłać graczom by dołączyli do kampanii.', ], ]; ================================================ FILE: lang/pl/campaigns/limits.php ================================================ 'Osiągnięto limit', ]; ================================================ FILE: lang/pl/campaigns/logs.php ================================================ [ 'list' => 'Przechowujemy rejestr wszystkich większych zmian w kampanii przez :amounts dni. Nie dokumentują każdej drobnej zmiany wartości, ale ogólne przekształcenia stanu kampanii.', 'nothing' => 'Brak rejestru do wyświetlenia. Pamiętaj że przechowywany jest tylko przez :amount dni.', 'title' => 'Brak rejestru', ], 'pitch' => 'Miej oko na ogólne zmiany w kampanii premium przez :amount dni.', 'premium' => [ 'helper' => 'Rejestr starszy niż :amount dni można wyświetlić tylko w kampanii premium.', ], 'title' => 'Dziennik audytu', ]; ================================================ FILE: lang/pl/campaigns/members.php ================================================ [ 'limited' => ':amount z :total uczestników.', 'title' => 'Dostępni uczestnicy', 'unlimited' => ':amount z nieograniczonej liczby uczestników.', ], 'roles' => [ 'admin' => 'Nie możesz stąd nadać nikomu roli :admin. Służy do tego menu roli :admin.', 'helper' => 'Dodaj lub usuń role uczestnika :user.', 'success' => 'Zaktualizowano role uczestnika :user.', 'title' => 'Edycja ról', ], ]; ================================================ FILE: lang/pl/campaigns/modules.php ================================================ [ 'create' => 'Stwórz moduł', 'customise' => 'Modyfikuj', ], 'create' => [ 'helper' => 'Stwórz nowy moduł dla elementów, które nie pasują do żadnego innego.', 'success' => 'Stworzono nowy moduł.', 'title' => 'Nowy moduł', ], 'delete' => [ 'confirm' => 'Wpisz :code jeżeli na pewno chcesz usunąć moduł własny :name.', 'confirm-button' => '{0} Trwale usunęto :name|{1} Trwale usunięto :name i :count element|[2,4] Trwale usunięto :name i :count elementy|[5,*] Trwale usunięto :name i :count elementów', 'entities' => '{1} Usunie trwale :count element.|[2,4] usunie trwale :count elementy.|[5,*] usunie trwale :count elementów.', 'helper' => 'Czy na pewno usunąć moduł własny :name? Usunięte zostaną również wszystkie związane z nim elementy, zakładki i widżety.', 'success' => 'Usunięto moduł :name.', 'title' => 'Usunięcie modułu', ], 'errors' => [ 'disabled' => 'Moduł :name jest wyłączony. :fix', 'empty-custom' => 'Dodaje moduł własny, pozwalający organizować dane nie posiadające modułu domyślnego.', 'limit' => 'Ponieważ wciąż pracujemy nad tą funkcją, kampania może na razie posiadać :max modułów własnych.', 'limit-title' => 'Osagnięto limit własnych modułów', 'subscription-limit' => 'Kampania osiągnęła limit własnych modudłów. By go zwiększyć, osoba która odblokowała funkcje premium musi podnieść poziom subskrybcji.', ], 'fields' => [ 'icon' => 'Ikona modułu', 'image' => 'Ikona domyślna', 'plural' => 'Nazwa modułu w liczbie mnogiej', 'singular' => 'Nazwa modułu w liczbie pojedycznej', 'status' => 'Status modułu', 'update_name' => 'Zmiana nazwy modułu', ], 'helpers' => [ 'custom' => 'To jest moduł własny.', 'icon' => 'Ikona :fontawesome, na przykład :example.', 'plural' => 'Nazwa elementów nowego modułu w liczbie mnogiej. Na przykład: eliksiry.', 'roles' => 'Wybierz role, które będą widziały nowy moduł. Można je potem zmienić w menu uprawień ról.', 'singular' => 'Nazwa elementów nowego modułu w liczbie pojedynczej. Na przykład: eliksir.', 'status' => 'Wyłączone moduły nie są wyświetlane w menu, ale żadne dane nie zostają usunięte.', 'tutorial' => 'Moduły pozwalają zarządzać widocznością różnych kategorii elementów kampanii. Włącz te, których używasz, i wyłącz pozostałe. Wyłączenie modułu nigdy nie powoduje utraty danych - ukrywa go tylko w menu i opcjach nawigacji.', ], 'pitch' => 'Zmień nazwę i ikonę tego modułu dla całej kampanii.', 'pitch-custom' => 'Twórz własne moduły dla niecodziennych elementów.', 'pitch-title' => 'Odblokuj moduły własne', 'rename' => [ 'helper' => 'Zmień używaną w tej kampanii nazwę i ikonę modułu. Pozostaw puste, by używać opcji domyślnej.', 'success' => 'Zmodyfikowano moduł.', 'title' => 'Modyfikuj moduł :module', ], 'reset' => [ 'default' => 'Przywraca stan wyjściowy modułów domyślnych, ale nie własnych.', 'success' => 'Przywrócono domyślne moduły kampanii', 'title' => 'Przywracanie domyślnych nazw i ikon', 'warning' => 'Czy na pewno przywrócić domyślne nazwy i ikony modułów kampanii?', ], 'sections' => [ 'custom' => 'Moduły własne', 'default' => 'Moduły domyślne', 'early-access' => 'Wczesny dostęp', 'features' => 'Opcje elementów', ], 'states' => [ 'disable' => 'Nieaktywny', 'disabled' => 'Moduł jest nieaktywny', 'enable' => 'Aktywny', 'enabled' => 'Moduł jest aktywny', ], 'status' => [ 'enabled' => 'Włączono moduł', ], ]; ================================================ FILE: lang/pl/campaigns/overview.php ================================================ [ 'title' => 'Obserwujący', ], 'member' => [ 'title' => 'Uczestnictwo', ], 'premium' => [ 'enable' => 'Aktywuj opcje premium', ], 'status' => [ 'title' => 'Widoczność', ], ]; ================================================ FILE: lang/pl/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'Wyłącz dodatki', 'enable' => 'Aktywuj dodatki', 'update' => 'Aktualizuj dodatki', ], 'changelog' => 'Zmiany', 'disable' => 'Wyłącz dodatek', 'enable' => 'Aktywuj dodatek', 'find-plugins' => 'Znajdź dodatek', 'import' => 'Importuj', 'update' => 'Aktualizuj dodatek', 'update-to' => 'Zaktualizowano do wersji :version', 'update_available' => 'Dostępna aktualizacja!', ], 'bulks' => [ 'delete' => '{1} Usunięto :count dodatek.|[2,4] Usunięto :count dodatki.|[5,*] Usunięto :count dodatków.', 'disable' => '{1} Wyłączono :count dodatek.|[2,4] Wyłączono :count dodatki.|[5,*] Wyłączono :count dodatków.', 'enable' => '{1} Aktywowano :count dodatek.|[2,4] Aktywowano :count dodatki.|[5,*] Aktywowano :count dodatków.', 'update' => '{1} Zaktualizowano :count dodatek.|[2,4] Zaktualizowano :count dodatki.|[5,*] Zaktualizowano :count dodatków.', ], 'destroy' => [ 'success' => 'Usunięto dodatek :plugin', ], 'disabled' => [ 'success' => 'Deaktywowano dodatek :plugin', ], 'empty_list' => 'Ta kampania nie ma na razie żadnych dodatków. Odwiedź targowisko, zainstaluj dodatki i wróć, by je aktywować.', 'enabled' => [ 'success' => 'Aktywowano dodatek :plugin', ], 'errors' => [ 'invalid_plugin' => 'Niewłaściwy dodatek.', ], 'fields' => [ 'name' => 'Nazwa dodatku', 'obsolete' => 'Dodatek oznaczono jako przestarzały ponieważ przestał działać w sposób zamierzony przez twórców.', 'status' => 'Status', 'type' => 'Rodzaj dodatku', ], 'import' => [ 'button' => 'Importuj', 'created' => 'Stworzono następujące elementy:', 'fields' => [ 'only_new' => 'Tylko nowe elementy', 'private' => 'Elementy tajne', ], 'helper' => 'Zaimportujesz zaraz :elementów z dodatku :plugin. Jeżeli był importowany wcześniej, utracisz wszystkie zmiany wprowadzone w tych elementach.', 'no_new_entities' => 'Brak nowych elementów do zaimportowania.', 'option_only_import' => 'Importuj tylko nowe elementy i pomijaj już zaimportowanie.', 'option_private' => 'Importowane elementy są tajne.', 'success' => '{1} Zaimportowano :count element z dodatku :plugin.|[2,3,4] Zaimportowano :count elementy z dodatku :plugin.|[5,*] Zaimportowano :count elementów z dodatku :plugin.', 'title' => 'Import :plugin', 'updated' => 'Zmieniono następujące elementy:', ], 'info' => [ 'description' => 'Wyświetla ostatnie aktualizacje dodatku :plugin.', 'helper' => 'Wydano nowszą wersję tego dodatku - możesz go zaktualizować.', 'installed' => 'Zainstalowany', 'title' => 'Aktualizacje dodatku :plugin', 'updates' => 'Aktualizacje', 'versions' => 'Wersje', ], 'pitch' => 'Instaluj i zarządzaj dodatkami z :marketplace.', 'status' => [ 'always' => 'Ten rodzaj dodatku jest zawsze aktywny, póki go nie usunąć.', 'disabled' => 'Niektywny', 'enabled' => 'Aktywny', ], 'templates' => [ 'name' => ':name autorstwa :user', ], 'title' => 'Dodatki Kampanii :name', 'types' => [ 'attribute' => 'Szablon cech', 'pack' => 'Dodatkowa zawartość', 'theme' => 'Motyw', ], 'update' => [ 'success' => 'Zaktualizowano dodatek :plugin', ], ]; ================================================ FILE: lang/pl/campaigns/public.php ================================================ [ 'new' => 'Upublicznia kampanię wśród członków społeczności albo ogranicza dostęp wyłącznie do zaproszonych osób.', 'permissions' => 'Upublicznienie kampanii nie oznacza ujawnienia całej zawartości. Możesz określić, co widzą goście używajac uprawnień roli :public.', ], 'title' => 'Zmiana widoczności kampanii', 'update' => [ 'private' => 'Kampania jest od teraz prywatna i widzą ją tylko uczestnicy.', 'public' => 'Kampania jest od teraz publiczna, choć jej pojawienie się na stronie :public-campaigns może troszkę potrwać.', 'unlisted' => 'Kampanię usunięto z katalogu. Może się do niej dostać każda osoba posiadająca link, ale nie wyświetla się na stronie :public-campaigns.', ], ]; ================================================ FILE: lang/pl/campaigns/recovery.php ================================================ [ 'recover' => 'Odzyskaj', 'recover_selected' => 'Odzyskaj wybrane', ], 'error' => 'Podczas odzyskiwania elementów wystąpił błąd.', 'fields' => [ 'deleted' => 'Usunięto', 'deleted_at' => 'Usunięte :date przez :user', ], 'name_link' => 'Udało się odzyskać :name', 'order' => [ 'newest' => 'Sortowanie według: Newest', 'newest_first' => 'Najpierw najnowsze', 'oldest' => 'Sortowanie według :Oldest', 'oldest_first' => 'Najpierw najstarsze', 'type' => 'Sortowanie według :Type', 'type_order' => 'Rodzaj', ], 'posts' => [], 'premium' => 'Odzyskiwanie elementów możliwe jest w kampanii premium.', 'success_v2'=> '{1} odzyskano :count element .|[2,4] odzyskano :count elementy.|[5,*] odzyskano :count elementów.', 'title' => 'Odzyskiwanie elementów kampanii :campaign', 'toggle' => [], 'tutorial' => 'Przeglądaj i odzyskuj niedawno usunięte elementy kampanii. Elementy, komentarze i inne dane można odzyskać przez :amount dni - potem zostają trwale usunięte. Odzyskanie przywraca usuniętą zawartość w całości.', ]; ================================================ FILE: lang/pl/campaigns/roles.php ================================================ [ 'status' => 'Status :status', ], 'create' => [ 'helper' => 'Tworzy nową rolę w kampanii.', ], 'overview' => [ 'limited' => 'Stworzono :amount z :total ról.', 'title' => 'Dostępne role', 'unlimited' => 'Stworzono :amount z nieograniczonej liczby ról.', ], 'permissions' => [ 'campaign-features' => 'Składniki kampanii', 'content-modules' => 'Moduły zawartości', 'toggle' => [ 'action' => 'Przełącz wszystkie', 'tooltip' => 'Przełącz upoważnienie :action dla wszystkich modułów.', ], ], 'public' => [ 'helpers' => [ 'click' => 'Wybierz dowolny moduł by przełączyć publiczną widoczność wszystkich należących do niego elementów.', 'intro' => 'Kontroluje widoczność składników kampanii przez osby które w niej nie uczestniczą.', 'main' => 'Wybierz które moduły będą widoczne dla wszystkich przeglądajacych kampanię, w tym osób niezalogowanych. Kategoria obejmuje zarówno publiczność z zewnątrz, jak zalogowanych użytkowników Kanki którzy nie biorą udziału w kampanii.', 'preview' => 'Widok publiczności', ], ], 'show' => [ 'title' => 'Uprawnienia :role - :campaign', ], 'toggle' => [ 'disabled' => 'Dla uczestników w roli :role działanie :action na :entities jest obecnie niedostępne.', 'enabled' => 'Dla uczestników w roli :role działanie :action na :entities jest obecnie możliwe.', ], 'warnings' => [ 'adding-to-admin' => 'Uczestnicy posiadający rolę :role mają dostęp do wszystkich elementów kampanii i nie mogą zostać usunięci przez inne osoby w tej roli. Gdy minie :amount minut, mogą pozbyć się roli wyłącznie osobiście.', ], ]; ================================================ FILE: lang/pl/campaigns/sidebar.php ================================================ [ 'reset' => 'Przywróć domyślne', ], 'call-to-action' => 'Zmieniaj kolejność, ikony i nazwy elementów w menu kampanii.', 'helpers' => [ 'bookmarks' => 'Nie uwzględniono tu zakładek ponieważ każda posiada własną :position określającą w którym miejscu menu się pojawia.', 'image' => 'Dodaj ilustrację symbolizującą kampanię. Będzie wyświetlana w menu bocznym oraz menu przełączania kampanii. Możesz je potem zmienić w dowolnej chwili, edytując kampanię.', 'reordering'=> 'Zmień kolejność elementów menu przeciągając ikony po lewej.', ], 'image-success' => 'Zapisani nowy obraz kampanii. Można go zmienić w każdej chwili edytując kampanię.', 'reset' => [ 'success' => 'Przywrócono domyślną postać menu bocznego.', 'title' => 'Przywracanie postaci domyślnej', 'warning' => 'Czy na pewno chcesz przywrócić domyślą postać menu bocznego kampanii?', ], 'success' => 'Zapisano ustawienia menu.', 'title' => 'Ustawienia menu bocznego kampanii :campaign', 'tooltips' => [ 'image' => 'Zmień obraz w tle.', ], ]; ================================================ FILE: lang/pl/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Kalendarzy', 'title' => 'Strażnik Czasu', ], 'murderer' => [ 'goal' => 'Martwych postaci', 'title' => 'Morderca', ], ], 'fields' => [ 'created' => 'Stworzono dnia', 'creator' => 'Stworzono przez', 'from-elements' => 'Pozostałe', 'from-entities' => 'Elementy', 'from-posts' => 'Komentarze', 'general' => 'Ogólne', 'words' => 'Liczba słów', ], 'targets' => [], 'title2' => 'Statystyki', 'titles' => [ 'calendars' => 'Strażnik Czasu poziom :level', 'characters'=> 'Dawca Imion poziom :level', 'dead' => 'Szafarz śmierci poziom :level', 'families' => 'Planowanie rodziny poziom :level', 'locations' => 'Budowniczy poziom :level', 'quests' => 'Intrygant poziom :level', 'races' => 'Hodowca poziom :level', ], 'tutorial' => 'Statystyki kampanii pokazują liczbę elementów i ostatnią aktywność. Dane aktualizowane są co :amount godzin. Możesz dzięki temu obserwować rozwój kampanii i częstotliwość jej wykorzystania.', ]; ================================================ FILE: lang/pl/campaigns/styles.php ================================================ [ 'current' => 'Obecny motyw: :theme', 'disable' => 'Wyłącz', 'enable' => 'Uruchom', 'new' => 'Nowy styl', ], 'bulks' => [ 'delete' => '{1} Usunięto :count styl.|[2,4] Usunięto :count style.|[5,*] Usunięto :count stylów.', 'disable' => '{1} Wyłączono :count styl.|[2,4] Wyłączono :count style.|[5,*] Wyłączono :count stylów.', 'enable' => '{1} Uruchomiono :count styl.|[2,4] Uruchomiono :count style.|[5,*] Uruchomiono :count stylów.', ], 'create' => [ 'success' => 'Stworzono nowy styl', 'title' => 'Nowy styl', ], 'delete' => [ 'success' => 'Usunięto styl :name', ], 'errors' => [ 'max_content' => 'Regułą CSS nie może być dłuższa niż :amount znaków.', 'max_reached' => 'Osiągniętą maksymalną liczne (:max) stylów.', ], 'fields' => [ 'content' => 'Reguły CSS', 'is_enabled' => 'Aktywna', 'length' => 'Długość', 'modified' => 'Zmieniona', 'name' => 'Nazwa', 'order' => 'Kolejność', ], 'helpers' => [ 'here' => 'z naszego bloga', 'is_enabled' => 'Użyj tego motywu na każdej stronie', 'main' => 'Możesz tworzyć własne style CSS w doładowanych kampaniach. Będą ładowane po załadowaniu stylów pobranych z targowiska. Więcej o tworzeniu stylów dowiesz się :here.', 'tutorial' => 'Kontroluje estetykę kampanii. Pozwala wybrać kolory, preferencje układu treści i inne elementy wizualne. Modyfikacje dotyczą tylko tej kampanii i można je w każdej chwili zmienić.', ], 'pitch' => 'Twórz własne style CSS by nadać kampanii indywidualny charakter.', 'placeholders' => [ 'name' => 'Nazwa stylu', ], 'reorder' => [ 'save' => 'Zapisz kolejność', 'success' => '{1} Zmieniono kolejność :count stylu.|[2,*] Zmieniono kolejność :count stylów.', 'title' => 'Zmiana kolejności stylów', ], 'theme' => [ 'none' => 'Użyj preferencji użytkownika', 'override' => 'Motyw nadrzędny', 'success' => 'Zmieniono motyw kampanii.', 'title' => 'Zmień motyw kampanii', ], 'title' => 'Motywy kampanii', 'toggle' => [ 'disable' => 'Skutecznie zastosowano styl.', 'enable' => 'Skuteczne usunięto styl.', ], 'update' => [ 'success' => 'Zmieniono styl :name.', 'title' => 'Zmiana stylu', ], ]; ================================================ FILE: lang/pl/campaigns/vanity.php ================================================ 'Nazwa :vanity jest dostępna!', 'forever' => 'Nie można zmienić po ustawieniu. :docs', 'helper-v2' => 'Nadaj kampanii zapadający w pamięć adres internetowy. Zamiast domyślnego :default może na przykład wyświetlać się jako :example.', 'rule' => 'W polu :field należy umieścić przynajmniej jedną literę alfabetu.', 'rule2' => 'W polu :field nie można umieścić znaku /.', 'set' => 'Specjalny URL kampanii ustawiono trwale jako :vanity.', ]; ================================================ FILE: lang/pl/campaigns/visibilities.php ================================================ [ 'public' => 'Umieszczona w publicznym katalogu kampanii', 'unlisted' => 'Nieobecna w publicznym katalogu kampanii', ], 'helpers' => [ 'premium' => 'By użyć tego ustawienia należy odblokować funkcjonalności premium.', 'private' => 'Dostęp tylko dla zalogowanych członków.', 'public' => 'Może przeglądać każdy posiadacz linku.', ], 'titles' => [ 'private' => 'Prywatna', 'public' => 'Publiczna', 'unlisted' => 'Publiczna (poza katalogiem)', ], ]; ================================================ FILE: lang/pl/campaigns/webhooks.php ================================================ [ 'action' => 'Zmień status', 'add' => 'Stwórz webhook', 'bulks' => [ 'delete_success' => '{1} Usunięto :count webhook.|[2,4] Usunięto :count webhooki.|[5,*] Usunięto :count webhooków.', 'disable' => 'Wyłącz', 'disable_success' => '{1} Wyłączono :count webhook.|[2,4] Wyłączono :count webhooki.|[5,*] Wyłączono :count webhooków', 'enable' => 'Aktywuj', 'enable_success' => '{1} Aktywowano :count webhook.|[2,4] Aktywowano :count webhooki.|[5,*] Aktywowano :count webhooków', ], 'test' => 'Testuj webhook', 'update' => 'Edytuj webhook', ], 'create' => [ 'success' => 'Stworzono webhook', 'title' => 'Tworzenie webhooka', ], 'destroy' => [ 'success' => 'Usunięto webhook', ], 'edit' => [ 'success' => 'Zmieniono webhook', 'title' => 'Edycja webhooka', ], 'error' => [ 'pitch' => 'Ulepsz do poziomu premium by używać webhooków.', ], 'fields' => [ 'enabled' => 'Aktywny', 'event' => 'Sytuacja', 'events' => [ 'deleted' => 'Usunięcie elementu', 'edited' => 'Edycja elementu', 'new' => 'Nowy element', ], 'message' => 'Wiadomość', 'private_entities' => [ 'helper' => 'Nie używaj webhooka podczas edycji elementów prywatnych.', 'skip' => 'Omijaj prywatne', ], 'type' => 'Rodzaj', 'types' => [ 'custom' => 'Wiadomość', 'payload' => 'Payload', ], 'url' => 'Adres url', ], 'helper' => [ 'active' => 'Jeśli webhook jest aktywny', 'message' => 'Dodaj własną wiadomość z możliwością mapowania', 'status' => 'Aktywuje i wyłącza webhook', 'tutorial' => 'Dzięki webhookom możesz wysyłać aktualizacje kampanii w czasie rzeczywistym do narzędzi zewnętrznych, zawsze kiedy element jest tworzony, zmieniany albo usuwany. Możesz dodać wiele webhooków i testować je za pomocą tej strony.', ], 'placeholders' => [ 'message' => '{who} wprowadził/a zmiany w {name}, znajdziesz je tu: {url}', 'url' => 'Docelowy adres url webhooka', ], 'test' => [ 'success' => 'Wysłano zapytanie testowe', ], 'title' => 'Webhooki', 'toggle' => [ 'disable' => 'Skutecznie uruchomiono webhook.', 'enable' => 'Skutecznie wyłączono webhook.', ], ]; ================================================ FILE: lang/pl/campaigns.php ================================================ [], 'create' => [ 'success' => 'Kampania utworzona.', 'title' => 'Nowa kampania', ], 'destroy' => [], 'edit' => [ 'success' => 'Zmieniono kampanię.', ], 'entity_note_visibility' => [], 'entity_personality_visibilities' => [ 'private' => 'Osobowość nowych postaci jest domyślnie tajna.', ], 'entity_visibilities' => [ 'private' => 'Nowe elementy są tajne', ], 'errors' => [ 'access' => 'Nie masz dostępu do tej kampanii.', 'premium' => 'Ta opcja dostępna jest tylko w kampanii premium.', 'unknown_id' => 'Nieznana kampania.', ], 'export' => [], 'fields' => [ 'boosted' => 'Doładowanie przez', 'entity_count' => 'Liczba elementów', 'entry' => 'Opis kampanii', 'followers' => 'Obserwujący', 'genre' => 'Gatunek', 'header_image' => 'Ilustracja okładkowa', 'image' => 'Obraz', 'locale' => 'Język kampanii', 'name' => 'Nazwa', 'open' => 'Otwarta na zgłoszenia', 'premium' => 'Premium odblokowana przez :name', 'public' => 'Widoczność kampanii', 'public_campaign_filters' => 'Filtry kampanii publicznych', 'superboosted' => 'Turbodoładowana przez', 'system' => 'System', 'theme' => 'Motyw', 'vanity' => 'Specjalny URL', ], 'following' => 'Obserwowanie', 'helpers' => [ 'boosted' => 'Odblokowano nowe opcje ponieważ kampania jest doładowana. Więcej informacji znajdziesz na stronie :settings.', 'css' => 'Twórz własne style CSS do używania w kampanii. Uwaga - nadużywanie tej opcji może poskutkować usunięciem stworzonych stylów. Powtarzające się albo poważne wykroczenia mogą spowodować usunięcie kampanii.', 'dashboard' => 'Dostosuj sposób wyświetlania widżetów na pulpicie kampanii wypełniając poniższe pola', 'excerpt' => 'Podsumowanie kampanii będzie wyświetlane na pulpicie, więc poświęć mu kilka zdań. Najlepiej, gdy jest krótkie i dobitne.', 'header_image' => 'Obraz wyświetlany w tle nagłówka pulpitu kampanii', 'hide_history' => 'Zaznacz, by ukryć historię edycji elementów kampanii przed nieposiadającymi statusu administratora.', 'hide_members' => 'Zaznacz, by ukryć listę uczestników kampanii przed nieposiadającymi statusu administratora.', 'locale' => 'Język, w którym piszesz kampanię. Służy do tworzenia zawartości oraz filtrowania kampanii publicznych.', 'name' => 'Twoja kampania lub świat mogą się nazywać jakkolwiek, o ile nazwa ma przynamniej 4 litery lub cyfry.', 'no_entry' => 'Ta kampania nie ma chyba żadnego opisu! Pora to naprawić.', 'premium' => 'Część opcji jest dostępna, ponieważ odblokowano funkcje premium. Więcej informacji na stronie :settings.', 'public_campaign_filters' => 'Pomóż innym graczom znaleźć twoją kampanię wśród innych dostępnych publicznie, podając następujące informacje.', 'public_no_visibility' => 'Uwaga! Ta kampania jest publiczna, ale nie rola "publiczność" na razie niczego nie widzi. :fix', 'system' => 'Jeżeli kampania jest dostępna publicznie, system podany jest na stronie :link.', 'systems' => 'By nie zarzucać wszystkich użytkowników mnóstwem opcji, Kanka udostępnia niektóre możliwości tylko dla konkretnych systemów RPG (np. blok statystyk potworów do D&D 5 ed.). Jeżeli dodasz tu wspierany w ten sposób system, uzyskasz dostęp do takich treści.', 'theme' => 'Ustaw inny motyw tej kampanii, niż zaznaczony w ogólnych preferencjach użytkownika.', 'view_public' => 'By zobaczyć kampanię tak, jak obserwujący otwórz :link w trybie incognito.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Skopiuj odnośnik do schowka', 'link' => 'Nowy odnośnik', ], 'create' => [ 'buttons' => [ 'create' => 'Stwórz zaproszenie', ], 'success_link' => 'Stworzono odnośnik :link', 'title' => 'Zaproś kogoś do udziału w kampanii', ], 'destroy' => [ 'success' => 'Usunięto zaproszenie.', ], 'error' => [ 'inactive_token' => 'Ta przepustka jest już wykorzystana albo kampania została usunięta.', 'invalid_token' => 'Przepustka jest nieważna.', 'join' => 'Zaloguj się lub zarejestruj by dołączyć do kampanii :campaign.', ], 'fields' => [ 'created' => 'Wysłano', 'role' => 'Rola', 'token' => 'Kod', 'type' => 'Rodzaj', 'usage' => 'Maksymalna liczba użyć', ], 'helpers' => [ 'role' => 'Użytkownik musi dołączyć, zanim otrzyma uprawnienia administratora.', 'usage' => 'Po tylu użyciach link z zaproszeniem przestanie być aktywny.', ], 'unlimited_validity' => 'Nieograniczona', 'usages' => [ 'five' => '5 użyć', 'no_limit' => 'Bez limitu', 'once' => '1 użycie', 'ten' => '10 użyć', ], ], 'leave' => [ 'action' => 'Opuść kampanię', 'confirm' => 'Czy na pewno chcesz opuścić kampanię :name? Utracisz do niej dostęp do czasu, gdy administrator kampanii zaprosi cię ponownie.', 'confirm-button' => 'Tak, opuść kampanię', 'error' => 'Nie możesz opuścić kampanii.', 'fix' => 'Przejdź do uczestników kampanii', 'no-admin-left' => 'Nie możesz opuścić kampanii, ponieważ pozostanie wówczas bez administratorów. Nadaj najpierw innemu uczestnikowi rolę administratora', 'success' => 'Opuszczasz kampanię.', 'title' => 'Opuszczanie kampanii', ], 'members' => [ 'actions' => [ 'remove' => 'Usuń z kampanii', 'switch' => 'Przełącz', 'switch-back' => 'Powrót do profilu', 'switch-entity' => 'Zobacz jako', ], 'fields' => [ 'banned' => 'Użytkownik zablokowany', 'joined' => 'Dołączył(a)', 'last_login' => 'Ostatnie logowanie', 'name' => 'Użytkownik', 'role' => 'Rola', 'roles' => 'Role', ], 'helpers' => [ 'switch' => 'Przełącz uczestnika', ], 'impersonating' => [ 'message' => 'Oglądasz kampanię z perspektywy innego uczestnika. Niektóre funkcje mogą nie działać, ale reszta wygląda dokładnie tak, jak widzi ją ta osoba. By wrócić do własnego profilu użyj opcji Powrót do profilu, znajdującej się w miejscu opcji Wyloguj.', 'title' => 'Zalogowano jako :name', ], 'invite' => [ 'description' => 'Możesz włączać znajomych do udziału w kampanii przekazując im odnośnik z zaproszeniem. Po zaakceptowaniu, zostaną oni dodani do kampanii w przydzielonej roli. Możesz też zapraszać graczy mailem.', 'more' => 'Możesz dodawać nowe role tutaj: :link.', 'title' => 'Zaproszenia', ], 'removal' => 'Usuwasz ":member" z kampanii.', 'roles' => [ 'member' => 'Uczestnik', 'owner' => 'Administrator', 'player' => 'Gracz', 'public' => 'Publiczność', 'viewer' => 'Obserwator', ], 'switch_back_success' => 'Powrócono do podstawowego profilu.', ], 'mentions' => [], 'modules' => [], 'open_campaign' => [], 'options' => [], 'overview' => [ 'entity-count' => '{0} Brak elementów|{1} :amount element|[2,3,4] :amount elementy|[5,] :amount elementów', 'follower-count' => '{0} Brak śledzących|{1} :amount śledzący|[2,] :amount śledzących', ], 'panels' => [ 'dashboard' => 'Pulpit', 'privacy' => 'Domyślne ustawienia prywatności', 'setup' => 'Konfiguracja', 'sharing' => 'Udostępnianie', 'systems' => 'System', 'ui' => 'Wygląd', ], 'placeholders' => [ 'locale' => 'Język kampanii', 'name' => 'Tytuł tej kampanii', 'system' => 'D&D, Pathfinder, Fate, DSA', ], 'privacy' => [ 'hidden' => 'Ukryta', 'private' => 'Tajna', 'visible' => 'Widoczna', ], 'public' => [ 'helpers' => [ 'introduction' => 'Kampanie są domyślnie prywatne, ale można je upublicznić. Wówczas każdy zyska do nich dostęp za pomocą strony :public-campaigns, o ile w kampanii są jakieś elementy widoczne dla posiadaczy roli :public-role. A więc: wszyscy widzą publiczne kampanie, ale by mogli przeglądać ich elementy trzeba odpowiednio ustawić uprawnienia dla roli :public-role.', ], ], 'roles' => [ 'actions' => [ 'add' => 'Dodaj rolę', 'duplicate' => 'Powiel rolę', 'permissions' => 'Zarządzaj uprawnieniami', 'rename' => 'Zmień nazwę', 'save' => 'Zapisz rolę', ], 'admin_role' => 'administrator', 'bulks' => [ 'delete' => '{1} Usunięto :count rolę.|[2,3,4] Usunięto :count role.|[5,*] Usunięto :count ról.', 'edit' => '{1} Zmieniono :count rolę.|[2,3,4] Zmieniono :count role.|[5,*] Zmieniono :count ról.', ], 'create' => [ 'success' => 'Stworzono rolę.', 'title' => 'Stwórz nową rolę dla :name', ], 'destroy' => [ 'success' => 'Usunięto rolę.', ], 'edit' => [ 'success' => 'Zaktualizowano rolę.', 'title' => 'Edycja roli :name', ], 'fields' => [ 'copy_permissions' => 'Kopiuj uprawnienia', 'name' => 'Nazwa', 'permissions' => 'Uprawnienia', 'type' => 'Rodzaj', 'users' => 'Posiadacze', ], 'helper' => [ '1' => 'Kampania może posiadać dowodnie dużo ról. "Administrator" posiada automatycznie dostęp do wszystkich elementów kampanii, ale inne role mogą być ograniczone tylko do części elementów (postaci, miejsc, itd.).', '2' => 'Uprawnienia rozmaitych elementów można dodatkowo modyfikować w zakładce "Uprawnienia". Pojawi się ona, kiedy w kampanii przybędzie ról lub członków.', '3' => 'Ustawieniami można zarządzać globalnie, zapewniając rolom uprawienia dostępu do całych kategorii elementów kampanii i ukrywając część z nich za pomocą opcji "Tajne", albo lokalnie, włączając ręcznie widoczność konkretnych elementów.', '4' => 'Liczba ról w kampaniach doładowanych nie jest ograniczona.', 'permissions_helper' => 'Powiela wszystkie uprawnienia roli dotyczące modułów i elementów kampanii.', ], 'hints' => [ 'campaign_not_public' => 'Ustawiono uprawnienia roli Publiczność, ale kampania jest prywatna. Możesz to zmienić z pomocą zakładki Udostępnij w menu edycji kampanii.', 'empty_role' => 'Tej roli nie posiada żaden z uczestników kampanii.', 'role_admin' => 'Rola :admin zapewnia posiadaczom automatyczny dostęp do wszystkiego w kampanii.', 'role_permissions' => 'Zezwól roli :name na następujące działania na elementach', ], 'members' => 'Uczestnicy', 'modals' => [ 'details' => [ 'campaign' => 'Uprawnienia w kampanii umożliwiają, co następuje.', 'entities' => 'Oto krótkie zestawienie uprawnień, które mogą otrzymać użytkownicy w danej roli.', 'more' => 'Dalsze instrukcje znajdziesz w filmie instruktażowym na YouTube', 'title' => 'Szczegóły uprawnień', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Tworzenie', 'dashboard' => 'Pulpit', 'delete' => 'Usuwanie', 'edit' => 'Edytowanie', 'gallery' => [ 'browse' => 'Przeglądanie', 'manage' => 'Pełna kontrola', 'upload' => 'Dodawanie', ], 'manage' => 'Zarządzaj', 'members' => 'Uczestnicy', 'permission'=> 'Uprawnienia', 'read' => 'Oglądanie', 'toggle' => 'Zmień dla wszystkich', ], 'helpers' => [ 'add' => 'Pozwala tworzyć elementy danego rodzaju. Automatycznie umożliwia również oglądanie i edycję stworzonych elementów, nawet jeżeli rola nie posiada do tego uprawnień.', 'dashboard' => 'Pozwala edytować pulpity i ich widżety.', 'delete' => 'Pozwala usuwać elementy danego rodzaju.', 'edit' => 'Pozwala modyfikować elementy danego rodzaju.', 'gallery' => [ 'browse' => 'Pozwala przeglądać galerię i dodawać znajdujące się w niej orazy do elementów.', 'manage' => 'Pozwala wykonywać wszystkie działania w galerii, w tym edytować i usuwać obrazy.', 'upload' => 'Pozwala dodawać obrazy do galerii. Jeżeli uczestnik nie posiada też uprawnień do przeglądania, będzie widzieć tylko obrazy dodane samodzielnie.', ], 'manage' => 'Pozwala edytować kampanię jakby uczestnik był administratorem, ale nie daje uprawnień by ją usunąć.', 'members' => 'Pozwala zapraszać nowych uczestników kampanii.', 'not_public'=> 'Kampania nie jest publiczna, więc stworzone dla niej role publiczne są ignorowane. Kampanię możesz upublicznić zmieniając ustawienia.', 'permission'=> 'Pozwala zarządzać uprawnieniami tych elementów danego typu, które uczestnik może też edytować.', 'read' => 'Pozwala widzieć wszystkie elementy danego typu, które nie są tajne.', ], ], 'placeholders' => [ 'name' => 'Nazwa roli', ], 'title' => 'Role w kampanii :name', 'types' => [ 'owner' => 'Administrator', 'public' => 'Publiczność', 'standard' => 'Stadardowy', ], 'users' => [ 'actions' => [ 'add' => 'Dodaj uczestnika', 'remove' => ':user z roli :role', 'remove_user' => 'Uusń rolę użytkownika', ], 'create' => [ 'success' => 'Uczestnikowi przypisano rolę.', 'title' => 'Przypisz uczestnikowi rolę :name', ], 'destroy' => [ 'success' => 'Uczestnikowi odebrano rolę.', ], 'errors' => [ 'cant_kick_admins' => 'By uniknąć nadużyć, nie można usunąć roli :admin posiadanej przez innego użytkownika. W wypadku nadużyć prosimy o kontakt przez :discord albo :email.', 'needs_more_roles' => 'Zanim zrezygnujesz z roli :admin musisz nadać sobie jakąś inną rolę w kampanii.', ], 'fields' => [ 'name' => 'Nazwa', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Aktywny', ], 'boosted' => 'Opcja we wczesnym dostępie, na razie dostępna wyłącznie w :boosted.', 'deprecated' => [ 'help' => 'Ten moduł jest przestarzały - to znaczy, że ani go nie rozwijamy, ani nie sprawdzamy pod kątem błędów po aktualizacjach. Możesz go używać, ale ze świadomością, że ostatecznie zostanie z Kanki usunięty', 'title' => 'Przestarzałe', ], 'disabled' => 'Moduł :module został wyłączony.', 'enabled' => 'Moduł :module został aktywowany.', 'errors' => [ 'module-disabled' => 'Ten moduł jest obecnie wyłączony w ustawieniach kampanii. :fix', ], 'helpers' => [ 'abilities' => 'Twórz zdolności specjalne, na przykład czary, moce czy techniki, i przypisuj je innym elementom.', 'assets' => 'Zamieszczanie plików i nadawanie aliasów konkretnym elementom.', 'bookmarks' => 'Tworzy w menu bocznym zakładki elementów i filtrowanych list', 'calendars' => 'Wyposaż swój świat w systemy liczenia czasu.', 'characters' => 'Mieszkańcy tego świata.', 'conversations' => 'Rozmowy które odbywają fikcyjne postaci albo prawdziwi uczestnicy kampanii. Ten moduł bywa niedoceniany.', 'creatures' => 'Moduł istot pozwala tworzyć zwierzęta, potwory i rozmaite inne stworzenia.', 'dice_rolls' => 'Jeżeli używasz Kanki do prowadzenia kampanii, tu możesz zarządzać wykonywaniem rzutów kośćmi. Ten moduł bywa niedoceniany.', 'entity_attributes' => 'Wyświetla cechy elementów kampanii, na przykład Punkty Życia albo Szybkość.', 'events' => 'Święta, festyny, katastrofy, urodziny i wojny.', 'families' => 'Klany lub rodziny, ich członkowie i wzajemne relacje.', 'inventories' => 'Zarządzaj wyposażeniem elementów.', 'items' => 'Uzbrojenie, pojazdy, artefakty, eliksiry.', 'journals' => 'Rozmaite spostrzeżenia spisane przez postaci oraz notatki MG.', 'locations' => 'Planety, wymiary, kontynenty, państwa, miasta, rzeki, świątynie, gospody.', 'maps' => 'Dodaj do kampanii mapę i oznacz położenie innych elementów z pomocą warstw i znaczników.', 'notes' => 'Tajemnice, religie, historia, magia, rasy.', 'organisations' => 'Kulty, oddziały wojskowe, frakcje polityczne, gildie.', 'quests' => 'Zadania, które realizuje drużyna, z opisem zaangażowanych miejsc i postaci.', 'races' => 'Jeżeli świecie żyje wiele ras, możesz opisać je tutaj.', 'tags' => 'Każdy element można oznaczyć z pomocą rozmaitych etykiet ułatwiających wyszukiwanie. Nawet etykiety mogą mieć własne etykiety.', 'timelines' => 'Dzieje świata wedle rozmaitych historii.', 'whiteboards' => 'Rysuj i pisz po tablicach, by stworzyć wizualizację świata oraz rozgrywanych w nim przygód.', ], ], 'sharing' => [ 'filters' => 'Kampanie publiczne widoczne są na stronie :public-campaigns. Wypełniając poniższe pola pomagasz znaleźć tam twoją kampanię!', 'language' => 'Język, w którym napisano treść kampanii.', 'system' => 'Jeżeli gracie w grę RPG, tu wpisz system którego używacie.', ], 'show' => [ 'actions' => [ 'edit' => 'Edytuj kampanię', ], 'tabs' => [ 'achievements' => 'Osiągnięcia', 'customisation' => 'Dostosowanie', 'danger' => 'Zagrożenia', 'data' => 'Dane', 'default-images' => 'Domyślne ikony', 'defaults' => 'Domyślne', 'deletion' => 'Usunięcie', 'export' => 'Eksport', 'import' => 'Import', 'logs' => 'Rejestr', 'management' => 'Zarządzanie', 'members' => 'Uczestnicy', 'plugins' => 'Dodatki', 'recovery' => 'Odzyskiwanie', 'roles' => 'Role', 'sidebar' => 'Menu boczne', 'stats' => 'Statystyki', 'styles' => 'Motywy', 'webhooks' => 'Elementy webhook', ], 'title' => 'Kampania :name', ], 'status' => [ 'free' => 'Opcje premium nieaktywne.', 'legacy' => [ 'title' => 'Funkcja doładowania (pozostałość starszej wersji)', ], 'premium' => 'Opcje premium odblokowane przez :name', 'title' => 'Opcje premium', ], 'superboosted' => [], 'themes' => [ 'none' => 'Brak (powrót do ustawień użytkownika)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Widoczna tylko dla adminów', 'visible' => 'Widoczna dla uczestników', ], 'fields' => [ 'entity_history' => 'Historia edycji elementu', 'member_list' => 'Lista uczestników kampanii', ], 'helpers' => [ 'entity-history' => 'Kontroluje, kto może zobaczyć ostatnie zmiany w konkretnych elementach kampanii.', 'member-list' => 'Kontroluje kto może zobaczyć uczestników kampanii.', 'theme' => 'Wyświetla kampanię używając motywu użytkownika, albo wymusza wyświetlenie w jednym z poniższych motywów.', ], 'members' => [ 'hidden' => 'Widoczne tylko dla adminów kampanii', 'visible' => 'Widoczne dla wszystkich uczestników', ], ], 'visibilities' => [ 'private' => 'Prywatna', 'public' => 'Publiczna', 'unlisted' => 'Publiczna (niewidoczna)', ], 'warning' => [], ]; ================================================ FILE: lang/pl/characters.php ================================================ [ 'add_appearance' => 'Dodaj cechę wyglądu', 'add_personality' => 'Dodaj cechę osobowości', ], 'conversations' => [], 'create' => [ 'title' => 'Nowa postać', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'families' => [ 'helper' => 'Pozwala zmienić kolejność i określić, które rodziny :name będą widoczne lub ukryte dla nie-administratorów.', 'reorder' => [ 'success' => 'Zmieniono rodziny postaci.', ], 'title2' => 'Zarządzaj rodzinami', ], 'fields' => [ 'age' => 'Wiek', 'is_appearance_pinned' => 'Przypnij wygląd', 'is_dead' => 'Nie żyje', 'is_personality_pinned' => 'Przypnij osobowość', 'is_personality_visible' => 'Osobowość jawna', 'life' => 'Życie', 'physical' => 'Powierzchowność', 'pronouns' => 'Rodzaj gramatyczny', 'sex' => 'Płeć', 'title' => 'Tytuł', 'traits' => 'Opis', ], 'helpers' => [ 'age' => 'Możesz też połączyć ten element z kalendarzem kampanii, by automatycznie obliczyć wiek. :more', ], 'hints' => [ 'is_appearance_pinned' => 'Zaznacz, by cechy wyglądu postaci wyświetlane były w widoku podstawowym.', 'is_dead' => 'Ta postać jest martwa', 'is_personality_visible' => 'Odznacz by ukryć cały opis osobowości przed użytkownikami niebędących administratorami.', 'personality_not_visible' => 'Opis osobowości widoczny wyłącznie dla administratorów.', 'personality_visible' => 'Opis osobowości widoczny dla wszystkich.', ], 'index' => [], 'items' => [], 'journals' => [], 'labels' => [ 'appearance' => [ 'entry' => 'Opis cechy wyglądu', 'name' => 'Nazwa cechy wyglądu', ], 'personality' => [ 'entry' => 'Opis cechy osobowości', 'name' => 'Nazwa cechy osobowości', ], ], 'lists' => [ 'empty' => 'Stwórz pierwszego bohatera, łotra albo szarego mieszkańca powstającego świata.', ], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => 'Postać dodana do organizacji', 'title' => 'Nowa organizacja dla :name', ], 'destroy' => [ 'success' => 'Postać usunięta z organizacji', ], 'edit' => [ 'success' => 'Zaktualizowano organizacje postaci.', 'title' => 'Aktualizuj organizacje dla :name', ], 'fields' => [ 'role' => 'Rola', ], ], 'placeholders' => [ 'age' => 'Wiek', 'appearance_entry' => 'Opis', 'appearance_name' => 'Włosy, oczy, kolor skóry, wzrost', 'name' => 'Imię postaci', 'personality_entry' => 'Szczegóły', 'personality_name' => 'Pragnienia, manieryzmy, obawy, więzi', 'physical' => 'Fizyczne', 'pronouns' => 'On/Jego, Ona/Jej, Ono/Jego', 'sex' => 'Płeć', 'title' => 'Tytuł', 'traits' => 'Opis', 'type' => 'Bohater Niezależny, Postać Gracza, bóstwo', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Zadania, które postać zleciła.', 'quest_member' => 'Zadania, w których postać się pojawia.', ], ], 'races' => [ 'helper' => 'Pozwala zmienić kolejność i określić, które rasy :name będą widoczne lub ukryte dla nie-administratorów.', 'reorder' => [ 'success' => 'Zmieniono rasy postaci.', ], 'title2' => 'Zarządzaj rasami', ], 'sections' => [ 'appearance' => 'Wygląd', 'personality' => 'Osobowość', ], 'show' => [], 'warnings' => [ 'personality_hidden' => 'Nie masz uprawnień do edycji osobowości tej postaci.', ], ]; ================================================ FILE: lang/pl/colours.php ================================================ 'Błękitny', 'black' => 'Czarny', 'blue' => 'Niebieski', 'brown' => 'Brązowy', 'green' => 'Zielony', 'grey' => 'Szary', 'light-blue' => 'Niebieski', 'maroon' => 'Fuksja', 'navy' => 'Granatowy', 'none' => 'Brak', 'orange' => 'Pomarańczowy', 'pink' => 'Różowy', 'purple' => 'Fioletowy', 'red' => 'Czerwony', 'teal' => 'Morski', 'white' => 'Biały', 'yellow' => 'Żółty', ]; ================================================ FILE: lang/pl/concept.php ================================================ 'doładowana kampania', 'premium-campaign' => 'kampania premium', 'premium-campaign-count' => '{0} Brak kampanii premium |{1} 1 kampania premium |[2,3,4] :count kampanie premium|[5,*] :count kampanii premium', 'premium-campaigns' => 'kampanie premium', 'premium-feature' => 'Opcja premium', 'superboosted-campaign' => 'turbodoładowana kampania', ]; ================================================ FILE: lang/pl/confirm/editing.php ================================================ 'Wycofaj', 'description' => 'Najwyraźniej ktoś właśnie edytuje tę stronę? Chcesz się wycofać, czy zignorować to ostrzeżenie, ryzykując utratę danych?', 'ignore' => 'Edytuj mimo to', 'members' => 'Członkowie edytujący tę stronę:', 'title' => 'Uwaga', 'user' => ':user od :since', ]; ================================================ FILE: lang/pl/confirm.php ================================================ [ 'bulk' => 'Czy na pewno chcesz usunąć wybrane elementy?', 'helper' => 'Czy na pewno chcesz usunąć :name?', 'recoverable' => 'W :premium-campaign tę akcję można cofnąć przez :day dni.', 'title' => 'Usuwanie elementu', ], ]; ================================================ FILE: lang/pl/connections/web.php ================================================ [ 'back' => 'Powrót do powiązań', 'download' => 'Pobierz PNG', 'view' => 'Widok sieci', ], 'cta' => [ 'text' => 'To jest podgląd pełnej sieci powiązań kampanii. W kampaniach darmowych wyświetlane jest do :amount węzłów. Kampanie premium pozwalają zobaczyć i nawigować wśród wszystkich powiązań. Podnieś poziom kampanii i zobacz całą strukturę swojego świata na raz.', 'title' => 'Podgląd ograniczony do :amount powiązań', ], 'title' => 'Sieć powiązań', ]; ================================================ FILE: lang/pl/conversations.php ================================================ [ 'title' => 'Nowa konwersacja', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_closed' => 'Zamknięta', 'messages' => 'Wiadomości', 'participants' => 'Uczestnicy', ], 'hints' => [ 'empty' => 'Nikt nie uczestniczy w tej konwersacji.', 'participants' => 'Dodawaj uczestników konwersacji naciskając ikonę :icon w prawym górnym rogu.', ], 'index' => [], 'lists' => [ 'empty' => 'Spisuj rozmowy, listy i wymianę poglodów między postaciami i całymi frakcjami.', ], 'messages' => [ 'destroy' => [ 'success' => 'Usunięto wiadomość.', ], 'is_updated' => 'Zaktualizowano', 'load_previous' => 'Załaduj starsze wiadomości', 'placeholders' => [ 'message' => 'Twoja wiadomość', ], ], 'participants' => [ 'create' => [ 'success' => 'Uczestnik :entity wypowiedział się w konwersacji.', ], 'destroy' => [ 'success' => 'Usunięto uczestnika :entity z konwersacji.', ], 'helper' => 'Dodaje i usuwa uczestników :name.', 'modal' => 'Uczestnicy', 'title' => 'Uczestnicy :name', ], 'placeholders' => [ 'name' => 'Nazwa konwersacji', 'type' => 'W grze, przygotowanie, omawianie intrygi', ], 'show' => [ 'is_closed' => 'Konwersacja jest zamknięta.', ], 'tabs' => [ 'participants' => 'Uczestnicy', ], 'targets' => [ 'characters' => 'Postaci', 'members' => 'Gracze', ], ]; ================================================ FILE: lang/pl/cookieconsent.php ================================================ 'Zezwól', 'dismiss' => 'Odrzuć', 'header' => 'Zgoda na cookies', 'link' => 'Szczegóły', 'message' => 'Kanka wykorzystuje cookies by zagwarantować jak najlepsze doświadczanie użytkownika.', 'policy' => 'Polityka cookies', 'reject' => 'Nie zezwalaj', ]; ================================================ FILE: lang/pl/creatures.php ================================================ [ 'title' => 'Nowa istota', ], 'creatures' => [], 'fields' => [ 'is_extinct' => 'Wymarła', ], 'helpers' => [], 'hints' => [ 'is_dead' => 'Ta istota nie żyje.', 'is_extinct' => 'Ten rodzaj istot wymarł.', ], 'lists' => [ 'empty' => 'Dodawaj zwierzęta, potwory i istoty mityczne, z którymi drużyna się zmierzy albo zaprzyjaźni.', ], 'placeholders' => [ 'type' => 'Roślinożerna, wodna, mityczna', ], 'show' => [], ]; ================================================ FILE: lang/pl/crud.php ================================================ [ 'actions' => 'Akcje', 'apply' => 'Zastosuj', 'back' => 'Cofnij', 'change' => 'Zmień', 'close' => 'Zamknij', 'confirm' => 'Potwierdź', 'copy' => 'Kopiuj', 'copy_mention' => 'Kopiuj wzmiankę [ ]', 'copy_to_campaign' => 'Kopiuj do kampanii', 'disable' => 'Wyłącz', 'enable' => 'Włącz', 'explore_view' => 'Widok hierarchii', 'export' => 'Eksport (PDF)', 'find_out_more' => 'Więcej', 'go_to' => 'Idź do :name', 'help' => 'Pomoc', 'json-export' => 'Eksport (JSON)', 'markdown-export' => 'Eksport (zaznaczone)', 'move' => 'Zmień lub przenieś', 'new' => 'Nowe', 'new_child' => 'Nowy element pochodny', 'new_post' => 'Nowy komentarz', 'next' => 'Następne', 'open' => 'Otwórz', 'print' => 'Drukuj', 'reorder' => 'Kolejność', 'reset' => 'Reset', 'save-changes' => 'Zapisz zmiany', 'transform' => 'Przekształć', ], 'add' => 'Dodaj', 'alerts' => [ 'copy_attribute' => 'Wzmianka cechy została skopiowana do schowka.', 'copy_invite' => 'Skopiowano do schowka odnośnik zaproszenia.', 'copy_mention' => 'Zaawansowana wzmianka elementu została skopiowana do schowka.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Edytuj zaznaczone', 'permissions' => 'Zmiana uprawnień', ], 'age' => [ 'helper' => 'Możesz dodać liczbę poprzedzoną znakiem + lub -, by zmienić wiek o tyle lat.', ], 'buttons' => [ 'label' => 'Dla wybranych', ], 'edit' => [ 'locations' => 'Działania miejsca', 'tagging' => 'Działania etykiety', 'tags' => [ 'add' => 'Dodaj', 'remove' => 'Usuń', ], 'title' => 'Edycja wielu elementów', ], 'errors' => [ 'admin' => 'Tylko administratorzy mogą zmieniać tajny status elementu.', 'general' => 'Podczas wykonywania akcji nastąpił błąd. Jeżeli będzie się powtarzał, skontaktuj się z nami. Komunikat błędu: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Zastąp', ], 'helpers' => [ 'override' => 'Jeżeli pole jest zaznaczone, wybrane uprawnienia zastąpią dotychczasowe uprawnienia elementów. Jeżeli nie jest zaznaczone, wybrane uprawnienia zostaną dodane do istniejących.', ], 'title' => 'Zmień uprawnienia dla wielu elementów.', ], ], 'bulk_templates' => [ 'bulk_title' => 'Zastosuj szablon do wielu elementów', ], 'cancel' => 'Anuluj', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Kopiuj elementy do innej kampanii', 'panel' => 'Kopiuj', 'title' => 'Kopiuj :name do innej kampanii', ], 'create' => 'Stwórz', 'datagrid' => [ 'empty' => 'Na razie nic tu nie ma.', ], 'delete_modal' => [ 'callout' => 'Psst!', 'confirm' => 'Potwierdź usunięcie', 'permanent' => 'Tego działania nie można cofnąć.', 'recoverable' => 'Usunięte elementy można odzyskiwać przez :day dni w :boosted-campaign.', 'title' => 'Potwierdzanie usunięcia', ], 'destroy_many' => [], 'dynamic' => [ 'permission' => 'Nie masz uprawnień by tworzyć elementy modułu :module.', 'unknown' => 'Element niewłaściwy dla modułu :module.', ], 'edit' => 'Edytuj', 'errors' => [ 'boosted_campaigns' => 'By korzystać z tej funkcji, kampania musi być :boosted.', 'unavailable_feature' => 'Funkcja niedostępna', ], 'events' => [], 'fields' => [ 'archived' => 'Zarchiwizowane', 'calendar_date' => 'Data', 'child' => 'Pochodzenie', 'closed' => 'Zamknięta', 'colour' => 'Kolor', 'copy_abilities' => 'Kopiuj zdolności', 'copy_inventory' => 'Kopiuj wyposażenie', 'copy_links' => 'Kopiuj odnośniki elementu', 'copy_permissions' => 'Kopiuj uprawnienia (zastąpią obecnie obowiązujące uprawnienia)', 'copy_posts' => 'Kopiuj komentarze (oraz ich uprawnienia)', 'copy_reminders' => 'Kopiuj epizody', 'creator' => 'Tworzenie', 'date_range' => 'Zakres dat', 'excerpt' => 'Fragment', 'has_entity_files' => 'Ma dołączone pliki', 'has_entry' => 'Ma opis', 'has_image' => 'Ma obraz', 'has_posts' => 'Posiada komentarze', 'header_image' => 'Nagłówek', 'image' => 'Obraz', 'is_closed' => 'Ta rozmowa zostanie zamknięta i nie będzie można dodawać do niej nowych wiadomości.', 'is_private' => 'Tajne', 'is_private_v3' => 'Wyświetlaj tylko użytkownikom w roli :admin-role. To ustawienie zastępuje wszystkie inne.', 'is_star' => 'Przypięte', 'locations' => ':first w :second', 'name' => 'Nazwa', 'names' => 'Nazwy', 'parent' => 'Źródło', 'position' => 'Kolejność', 'replace_mentions' => 'Zamień wzmianki o cechach tego elementu wzmiankami nowego elementu', 'template' => 'Szablon', 'tooltip' => 'Dymek', 'type' => 'Rodzaj', 'visibility' => 'Widoczność', 'word-count' => 'Liczba słów: :number', ], 'files' => [ 'errors' => [ 'max' => 'Osiągnięto maksymalną liczbę (:max) plików dla tego elementu.', 'max_size' => 'Kampania osiągnęła maksymalną liczbę plików.', 'no_files' => 'Brak plików.', ], 'hints' => [ 'limit' => 'Do każdego elementu można dodać maksymalnie :max plików.', 'limitations' => 'Dopuszczalne formaty: :formats. Maksymalny rozmiar: :size.', ], ], 'filter' => 'Filtruj', 'filters' => [ 'all' => 'Pokaż wszystkie elementy pochodne', 'clear' => 'Usuń filtry', 'copy_helper' => 'Użyj skopiowanych do schowka filtrów by stworzyć filtry na pulpicie albo skróty.', 'copy_to_clipboard' => 'Kopiuj filtry do schowka', 'direct' => 'Pokaż elementy bezpośrednio pochodne', 'filtered' => 'Wyświetlono :count z :total elementów.', 'lists' => [ 'desktop' => [ 'all' => 'Pokaż wszystkie pochodne (:count)', 'filtered' => 'Pokaż bezpośrednio pochodne (:count)', ], 'paginated' => 'Przełącza między elementami pochodnymi bezpośrednio oraz wszystkimi pochodnymi. Dla usprawnienia wyniki dzielą się na strony.', ], 'mobile' => [ 'clear' => 'Wyczyść', 'copy' => 'Schowek', ], 'options' => [ 'any' => 'Każdy', 'children' => 'Z pochodnymi', 'exclude' => 'Nie zawiera', 'hide' => 'Ukryj', 'include' => 'Zawiera', 'none' => 'Brak', 'show' => 'Pokaż', ], 'show' => 'Pokaż filtry', 'sorting' => [ 'asc' => ':field rosnąco', 'desc' => ':field malejąco', 'helper' => 'Określa kolejność wyświetlania rezultatów.', ], 'title' => 'Filtry', ], 'fix-this-issue' => 'Napraw ten problem', 'forms' => [ 'actions' => [ 'calendar' => 'Dodaj datę kalendarzową', ], 'copy_options' => 'Opcje kopiowania', ], 'helpers' => [ 'copy_options' => 'Skopiuj następujące elementy elementu źródłowego do nowego elementu.', 'linking' => 'Łącza do innych elementów', 'parent' => 'Wybierz źródło od którego pochodzić będzie ten element.', ], 'hidden' => 'Ukryte', 'hints' => [ 'calendar_date' => 'Data kalendarzowa umożliwia łatwiejsze filtrowanie i pozwala umieścić w kalendarzu epizod.', 'image_dimension' => 'Sugerowany rozmiar: :dimension pikseli.', 'image_limitations' => 'Dozwolone formaty: :formats. Maksymalny rozmiar pliku :size.', 'image_recommendation' => 'Sugerowane wymiary: :width na :height pikseli.', 'is_star' => 'Elementy przypięte pojawiają się w menu elementu.', 'tooltip' => 'Zastąp dymek z poradą generowaną automatycznie następującą zawartością.', ], 'history' => [ 'created_clean' => 'Stworzone przez :name :date', 'created_date_clean' => 'Stworzone :date', 'unknown' => 'Nieznane', 'updated_clean' => 'Ostatnio zmienione przez :name :date', 'updated_date_clean' => 'Ostatnio zmienione :date', 'view' => 'Zobacz dziennik elementu', ], 'image' => [ 'error' => 'Nie udało nam się pozyskać wskazanego obrazu. Być może strona uniemożliwia pobieranie (na przykład Squarespace albo DeviantArt), albo odnośnik nie jest już aktywny. Upewnij się też, że obrazek nie jest większy niż :size.', ], 'is_private' => 'Ten element jest tajny, a zatem widoczny tylko dla uczestników posiadających rolę administratora.', 'keyboard-shortcut' => 'Skrót klawiaturowy :code', 'navigation' => [ 'cancel' => 'anuluj', 'or_cancel' => 'lub :cancel', 'skip_to_content' => 'Pomiń nawigację', ], 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Zezwól', 'deny' => 'Zabroń', 'ignore' => 'Pomiń', 'remove' => 'Wyczyść', ], 'bulk_entity' => [ 'allow' => 'Zezwól', 'deny' => 'Zabroń', 'inherit' => 'Kopiuj', ], 'delete' => 'Usuwanie', 'edit' => 'Edycja', 'private' => 'Uczyń tajnym', 'toggle' => 'Przełącz', 'view' => 'Wyświetlanie', ], 'fields' => [ 'member' => 'Uczestnik', 'role' => 'Rola', ], 'helpers' => [ 'setup' => 'Przy pomocy tej funkcji możesz dokładnie określić, jak role i uczestnicy kampanii mogą działać na ten element. :allow pozwala użytkownikowi albo roli wykonać dane działanie. :deny im to uniemożliwi. :inherit zastosuje uprawnienia roli głównej albo użytkownika. Użytkownik posiadający ustawienie :allow może działać na element, nawet jeżeli jego roli przypisano :deny.', ], 'success' => 'Zapisano uprawenienia.', 'title' => 'Uprawienia', 'too_many_members' => 'Kampania ma zbyt wielu uczestników (>10) by ich tu wyświetlić. Używaj przycisku Uprawienia w opisie elementu, by szczegółowo zarządzać uprawieniami.', ], 'placeholders' => [ 'calendar' => 'Wybierz kalendarz', 'entry' => 'Wpisz @ i trzy pierwsze litery nazwy by wzmiankować inny element kampanii.', 'fallback' => 'Wybierz :module', 'gallery_image' => 'Wybierz obraz z galerii kampanii', 'image_url' => 'Możesz również podać URL obrazu', 'journal' => 'Wybierz dziennik', 'location' => 'Wybierz miejsce', 'multiple' => 'Wybierze jeden lub wiele', 'name' => 'Nazwa elementu', 'organisation' => 'Wybierz organizację', 'parent' => 'Wybierz źródło', 'tag' => 'Wybierz etykietę', 'template' => 'Wybierz szablon', 'timeline' => 'Wybierz historię', 'type' => 'Rodzaj elementu', 'user' => 'Wybierz użytkownika', ], 'relations' => [], 'remove' => 'Usuń', 'reorder' => [ 'empty' => 'Brak elementów do zmiany kolejności.', ], 'save' => 'Zapisz', 'save_and_close' => 'Zapisz i zamknij', 'save_and_copy' => 'Zapisz i skopiuj', 'save_and_new' => 'Zapisz i nowe', 'save_and_update' => 'Zapisz i edytuj', 'save_and_view' => 'Zapisz i pokaż', 'search' => 'Szukaj', 'select' => 'Wybierz', 'tabs' => [ 'abilities' => 'Zdolności', 'inventory' => 'Wyposażenie', 'mentions' => 'Wzmianki', 'overview' => 'Ogólne', 'permissions' => 'Uprawnienia', 'premium' => 'Premium', 'profile' => 'Profil', 'reminders' => 'Epizody', ], 'timestamps' => [ 'edited' => 'Edytowano :ago', ], 'titles' => [ 'editing' => 'Edycja :name', 'new' => 'Nowy :module', ], 'tooltips' => [], 'update' => 'Aktualizacja', 'users' => [ 'unknown' => 'Nieznany', ], 'view' => 'Zobacz', 'visibilities' => [ 'admin' => 'Administrator', 'admin-self' => 'Ja i administrator', 'all' => 'Wszyscy', 'members' => 'Uczestnicy', 'self' => 'Ja', ], ]; ================================================ FILE: lang/pl/dashboard.php ================================================ [ 'customise' => 'Dostosuj pulpit', 'follow' => 'Śledź', 'join' => 'Dołącz', 'unfollow' => 'Przestań śledzić', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Edytuj', 'new' => 'Nowy pulpit', ], 'create' => [ 'helper' => 'Tworzy nowy pulpit dla :name, i pozwala określić które role mogą go zobaczyć albo dla których jest pulpitem domyślnym', 'success' => 'Stworzono w kampanii nowy pulpit :name.', 'title' => 'Nowy pulpit kampanii', ], 'custom' => [ 'text' => 'Edytujesz obecnie pulpit :name kampanii.', ], 'default' => [ 'text' => 'Edytujesz podstawowy pulpit kampanii.', 'title' => 'Pulpit podstawowy', ], 'delete' => [ 'success' => 'Usunięto pulpit :name', ], 'fields' => [ 'copy_widgets' => 'Kopiuj widżety', 'name' => 'Nazwa pulpitu', 'visibility' => 'Widoczność', ], 'helpers' => [ 'copy_widgets' => 'Skopiuj widżety z pulpitu :name na ten nowy pulpit.', ], 'pitch' => 'Konfiguruj wiele pulpitów, z uprawnieniami dla różnych ról kampanii.', 'placeholders' => [ 'name' => 'Nazwa pulpitu', ], 'update' => [ 'success' => 'Zaktualizowano pulpit :name.', 'title' => 'Aktualizuj pulpit :name', ], 'visibility' => [ 'default' => 'Podstawowy', 'none' => 'Brak', 'visible' => 'Widoczny', ], ], 'helpers' => [ 'follow' => 'Śledzenie kampanii sprawi, że pojawi się w menu przełączania kampanii (lewy górny róg), pod twoimi własnymi kampaniami.', 'join' => 'Kampania jest otwarta na nowych członków. Kliknij, by do niej dołączyć.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Dodaj widżet', 'back_to_dashboard' => 'Powrót do pulpitu', 'edit' => 'Edytuj widżet', 'new' => 'Nowy widżet :type', ], 'reorder' => [ 'helper' => 'Przeciągnij, by mnie przesunąć', 'success' => 'Zmieniono kolejność widżetów', ], 'title' => 'Konfiguracja pulpitu kampanii', 'tutorial' => [ 'blog' => 'ten wpis', 'text' => 'Potrzebujesz pomocy w przygotowaniu pulpitu kampanii? Sprawdź :blog, znajdziesz w nim porady i inspiracje.', ], ], 'title' => 'Pulpit', 'widgets' => [ 'advanced_options_boosted' => 'Więcej opcji, na przykład wyświetlanie przypięć, zapewnia :boosted_campaing.', 'calendar' => [ 'actions' => [ 'next' => 'Zmień datę na kolejny dzień', 'previous' => 'Zmień datę na poprzedni dzień', ], 'previous_events' => 'Poprzedni', 'upcoming_events' => 'Nadchodzące', ], 'campaign' => [ 'helper' => 'Ten widżet wyświetla nagłówek kampanii. Jest zawsze widoczny na podstawowym pulpicie.', ], 'create' => [ 'helper' => 'Wybór rodzaju widżetu do umieszczenia na pulpicie :name.', 'helper-default' => 'Wybór rodzaju widżetu do umieszczenia na pulpicie domyślnym.', 'success' => 'Dodano widżet do pulpitu.', 'title' => 'Nowy widżet', ], 'delete' => [ 'success' => 'Usunięto widżet z pulpitu.', ], 'fields' => [ 'class' => 'Klasa CSS', 'dashboard' => 'Pulpit', 'name' => 'Własna nazwa widżetu', 'optional-entity' => 'Odnośnik do elementu', 'order' => 'Kolejność', 'size' => 'Rozmiar', 'width' => 'Szerokość', ], 'helpers' => [ 'class' => 'Określ własną klasę css dodaną do widżetu', 'filters' => 'Kliknij by poznać dostępne opcje filtrowania.', ], 'orders' => [ 'name_asc' => 'Nazwa rosnąco', 'name_desc' => 'Nazwa malejąco', 'oldest' => 'Zmienione najdawniej', 'recent' => 'Ostatnie zmiany', ], 'preview' => [ 'displays' => [ 'expand' => 'Wpis do rozwinięcia', 'full' => 'Cały wpis', ], 'fields' => [ 'display' => 'Wyświetlanie', ], ], 'random' => [ 'helpers' => [ 'name' => 'Możesz wskazać nazwę losowego elementu przy pomocy {name}.', ], 'type' => [ 'all' => 'Wszystkie', ], ], 'recent' => [ 'advanced_filter' => 'Filtry zaawansowane', 'advanced_filters' => [ 'mentionless' => 'Niewzmiankujące (elementy, które nie wzmiankują żadnych innych elementów)', 'unmentioned' => 'Niewzmiankowane (elementy, których nie wzmiankuje żadnej inny element)', ], 'all-entities' => 'Wszystkie elementy', 'entity-header' => 'Używaj nagłówka elementu jako obrazu widżetu', 'filters' => 'Filtry', 'help' => 'Pokazuj tylko ostatni zmodyfikowany element, ale publikuj cały skrót', 'helpers' => [ 'entity-header' => 'Jeżeli element ma obraz w nagłówku (w doładowanej kampanii), widżet będzie wyświetlał nagłówek zamiast obrazu samego elementu.', 'show_attributes' => 'Wyświetla cechy elementu pod jego opisem.', 'show_members' => 'Jeżeli element jest rodziną albo organizacją, wyświetla jej członków pod opisem.', 'show_relations' => 'Wyświetla przypięte relacje pod opisem elementu', ], 'show_attributes' => 'Pokaż cechy', 'show_members' => 'Pokaż członków', 'show_relations' => 'Pokaż przypięte relacje', 'singular' => 'Pojedynczy', 'tags' => 'Filtruj listę niedawno zmienianych elementów według konkretnych etykiet.', 'title' => 'Ostatnie zmiany', ], 'tabs' => [ 'advanced' => 'Zaawanowane', 'setup' => 'Ustawienia', ], 'unmentioned' => [ 'title' => 'Elementy bez wzmianki', ], 'update' => [ 'success' => 'Zmodyfikowano widżet.', ], 'widths' => [ '0' => 'Automatyczna', '12'=> 'Pełny (100%)', '3' => 'Malutki (25%)', '4' => 'Mały (33%)', '6' => 'Połowa (50%)', '8' => 'Szeroki (66%)', '9' => 'Duży (75%)', ], ], ]; ================================================ FILE: lang/pl/dashboards/onboarding.php ================================================ [ 'continue' => 'Zacznij budowę', 'skip' => 'Na razie pomiń', ], 'families' => [ 'varren' => [ 'title' => 'Ród Varren', ], ], 'fields' => [ 'name' => 'Nazwa świata', ], 'intro' => 'Świat jest gotowy. Uczyń go swoim, a potem rozbuduj.', 'placeholders' => [ 'name' => 'Możesz zmienić w każdej chwili', ], 'quests' => [ 'crown' => [ 'title' => 'Poszukiwanie strzaskanej korony', ], ], 'roles' => [ 'co-writer' => 'Współautor', 'contributor' => 'Źródło pomysłów', ], 'selection' => [ 'campaign' => 'Kampania do gry fabularnej', 'campaign-description' => 'Zarządzaj kampanią, postaciami i sesjami swojej gry fabularnej.', 'helper' => '(Twoje wybory wpłyną na zawartość demonstacyjną i układ pulpitu)', 'intro' => 'Co tworzysz?', 'story' => 'Świat będący tłem opowieści', 'story-description' => 'Opisz dokładnie postacie, miejsca i chronologię wydarzeń.', 'title' => 'Rodzaj świata', 'worldbuilding' => 'Projekt światotwórczy', 'worldbuilding-description' => 'Stwórz i zarządzaj historią, miejscami i osobami zaludniającymi twój świat.', ], 'splash' => 'Witaj, :name, 👋, twój świat jest gotowy.', 'success' => 'Witaj! Dostosowaliśmy świat, by ułatwić ci rozpoczęcie pracy.', 'title' => 'Zmieniaj świat wedle uznania', 'ttrpg' => [ 'tags' => [ 'npcs' => 'BN', ], ], 'widgets' => [ 'active-quests' => 'Aktywne zadania', ], ]; ================================================ FILE: lang/pl/dashboards/setup.php ================================================ [ 'add' => 'Dodaj widżet', ], 'sections' => [ 'switch' => 'Przełącz na...', ], 'tooltips' => [ 'add' => 'Użyj by dodać nowy widżet na pulpicie.', 'switch' => 'Użyj by przełączyć na inny pulpit.', ], ]; ================================================ FILE: lang/pl/dashboards/widgets/calendar.php ================================================ 'Wyświetla nadchodzące epizody.', 'name' => 'Kalendarz', ]; ================================================ FILE: lang/pl/dashboards/widgets/campaign.php ================================================ 'Wyświetla nagłówek kampanii.', 'name' => 'Kampania', ]; ================================================ FILE: lang/pl/dashboards/widgets/header.php ================================================ 'Wyświetlna nagłówek tekstowy.', 'name' => 'Nagłowek', ]; ================================================ FILE: lang/pl/dashboards/widgets/help.php ================================================ 'Wyświetla na pulpicie odnośniki do pomocy i zasobów społeczności.', 'name' => 'Pomoc i społeczność', 'title' => 'Pomoc i społeczność', ]; ================================================ FILE: lang/pl/dashboards/widgets/onboarding.php ================================================ 'Wyświetla listę zadań od których zaczyna się budowa świata.', 'name' => 'Zaczynamy', 'tasks' => [ 'campaign' => [ 'name' => 'Twój pierwszy świat jest gotowy.', ], 'character' => [ 'helper' => 'Dodaje osobę zamieszkującą świat.', 'name' => 'Stwórz pierwszą postać.', ], 'invite' => [ 'helper' => 'Dodaje uczestników kampanii i przypisuje im rolę.', 'name' => 'Zaproś przyjaciela albo współautora.', ], 'location' => [ 'helper' => 'Uzupełnia świat o znajdujące się w nim miejsce.', 'name' => 'Stwórz pierwsze miejsce.', ], 'rename' => [ 'helper' => 'Nadaje kampanii porządny tytuł.', 'name' => 'Zmień nazwę świata.', ], 'widgets' => [ 'helper' => 'Dodaje albo zamienia kolejnością widżety.', 'name' => 'Dostosuj pulpit.', ], ], ]; ================================================ FILE: lang/pl/dashboards/widgets/preview.php ================================================ 'Wyświetla konkretny element.', 'name' => 'Wybierz element', ]; ================================================ FILE: lang/pl/dashboards/widgets/random.php ================================================ 'Wyświetla losowy element kampanii.', 'name' => 'Losowy element', ]; ================================================ FILE: lang/pl/dashboards/widgets/recent.php ================================================ 'Wyświetla listę ostatnio zmienionych elementów.', 'name' => 'Zmienione elementy', ]; ================================================ FILE: lang/pl/dashboards/widgets/welcome.php ================================================ 'Wyświetla na pulpicie komunikat powitalny zawierający rady i sugestie.', 'endings' => [], 'focus' => [ 'text' => 'Witaj, to właśnie ja!', 'title' => 'Cześć', ], 'intros' => [ '1' => 'Witaj u progu licznych nowych światów, :user! Przygotowaliśmy twoją pierwszą kampanię: znajdziesz w niej dwie przykładowe :characters i :locations. Wyświetlają się również tutaj, na pulpicie kampanii.', '2' => 'By zacząć tworzyć, kliknij na wielki przycisk :new-entity (albo naciśnij :letter na klawiaturze) i wybierz :character, by powołać do życia pierwszą postać. To nic trudnego! Wszystkie postacie, miejsce i inne :entities znajdziesz w menu po lewej stronie ekranu.', '3' => 'Oto pięć rad, jak używać Kanki', ], 'name' => 'Witaj', 'title' => 'Witaj w :kanka! 🎉', 'tricks' => [ '1' => 'Tworząc opis, nie wpisuj ręcznie nazw innych elementów kampanii. Zamiast tego wpisz :code i trzy pierwsze litery nazwy by :mention dany element. Wszystkie wzmianki automatycznie się zaktualizują, jeżeli zmienisz nazwę elementu.', '2' => 'By zmienić nazwę kampanii, motyw albo ilustrację okładkową wybierz :world z menu bocznego, a potem opcję :edit.', '3' => 'Tajemnice rozmaitych elementów lepiej zamieszczać jako :posts niż w głównym opisie.', '4' => 'By zaprosić przyjaciół do udziału w kampanii należy wybrać pozycję :world a potem :members. Znajdziesz tam opcję tworzenia zaproszeń.', '5' => 'Możesz usunąć wiadomość powitalną i wyświetlać na tym ekranie (to znaczy - pulpicie) inne informacje. Po prostu przewiń w dół i naciśnij :button.', 'mention' => 'wzmiankować', ], ]; ================================================ FILE: lang/pl/datagrids.php ================================================ [ 'back_to' => 'Wróć do :name', ], 'modes' => [ 'flatten' => 'Przełącz na zbiór', 'grid' => 'Przełącz na kafelki', 'nested' => 'Przełącz na hierarchię', 'table' => 'Przełącz na tabelę', ], 'tooltips' => [ 'nested' => 'Element ma pochodne. Kliknij na obrazek, by je wyświetlić.', ], ]; ================================================ FILE: lang/pl/datetime.php ================================================ 'dzień', 'days' => 'dni', 'elapsed_ago' => ':duration temu', 'hour' => 'godzina', 'hours' => 'godziny', 'just_now' => 'w tej chwili', 'minute' => 'minuta', 'minutes' => 'minuty', 'month' => 'miesiąc', 'months' => 'miesiące', 'second' => 'sekunda', 'seconds' => 'sekundy', 'week' => 'tydzień', 'weeks' => 'tygodnie', 'year' => 'rok', 'years' => 'lata', ]; ================================================ FILE: lang/pl/default.php ================================================ 'Strona Tytułowa', ]; ================================================ FILE: lang/pl/dice_roll_results.php ================================================ [ 'title' => 'Wyniki rzutów kośćmi', ], ]; ================================================ FILE: lang/pl/dice_rolls.php ================================================ [ 'title' => 'Nowy rzut kośćmi', ], 'destroy' => [ 'dice_roll' => 'Usunięto rzut kośćmi.', ], 'edit' => [], 'fields' => [ 'created_at' => 'Rzucono w', 'parameters' => 'Parametry', 'results' => 'Wyniki', 'rolls' => 'Rzuty', ], 'hints' => [ 'parameters' => 'Jak opisywać rzut kośćmi?', ], 'index' => [ 'actions' => [ 'results' => 'Wyniki', ], ], 'lists' => [ 'empty' => 'Twórz i zapisuj rzuty kośćmi, by stworzyć w Kance bazę ich rezultatów.', ], 'placeholders' => [ 'name' => 'Nazwa rzutu kośćmi', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Rzuć', ], 'error' => 'Rzut nieudany. Nie można przetworzyć parametrów.', 'fields' => [ 'creator' => 'Rzucający', 'date' => 'Data', 'result' => 'Wynik', ], 'hint' => 'Wszystkie rzuty wykonane dla tego szablonu rzutów kośćmi.', 'success' => 'Kości zostały rzucone.', ], 'show' => [ 'tabs' => [ 'results' => 'Wyniki', ], ], ]; ================================================ FILE: lang/pl/emails/activity/email.php ================================================ 'Zmieniono adres email przypisany do twojego konta Kanka na :email.', 'title' => 'Zmiana adresu email', ]; ================================================ FILE: lang/pl/emails/activity/password.php ================================================ 'Twoje hasło dostępu do Kanki zostało zmienione.', 'help' => 'Jeśli nie ty je zmieniłeś, skontaktuj się z nami: :email.', 'title' => 'Zmieniono hasło', ]; ================================================ FILE: lang/pl/emails/purge/first.php ================================================ 'Jeżeli korzystasz z konta regularnie, nie martw się - usuwamy wyłącznie konta i kampanie, które były od dłuższego czasu nieaktywne.', 'help' => 'Potrzebujesz pomocy? Dołącz do serwera :discord alb napisz na adres :email.', 'intro_account' => 'Piszemy by poinformować, że twoje konto zostanie usunięte za :amount dni, ponieważ twoje ostatnie logowanie odbyło się :duration miesięcy temu.', 'intro_campaigns' => 'Piszemy by poinformować, że twoje konto oraz związane z nim kampanie zostaną usunięte za :amount dni, ponieważ twoje ostatnie logowanie odbyło się :duration miesięcy temu.', 'keep' => 'Jeżeli chcesz zachować konto, zaloguj się na nie w ciągu najbliższych :amount dni.', 'title' => 'Twoje konto w serwisie Kanka zostanie usunięte za :amount dni', 'warning' => [ 'account' => 'Wówczas wszystkie dane związane z twoim kontem korzystającym z adresu :email zostaną trwale usunięte.', 'campaigns' => 'Wówczas wszystkie dane związane z twoim kontem korzystającym z adresu :email oraz poniższe kampanie zostaną trwale usunięte.', ], ]; ================================================ FILE: lang/pl/emails/purge/second.php ================================================ 'To ostateczne powiadomienie, że konto w serwisie Kanka używające adresu :email zostanie usunięte w ciągu :amount dni, ponieważ nie logowano się od :duration miesięcy.', 'title' => 'Ostatnie ostrzeżenie: twoje konto w serwisie Kanka zostanie usunięte za :amout dni.', ]; ================================================ FILE: lang/pl/emails/subscriptions/expiring.php ================================================ 'zaktualizuj dane karty.', 'primary' => 'To automatyczne przypomnienie o zbliżającym się wygaśnięciu ważności karty :brand **** :last.', 'title' => 'Wygaśnięcie ważności karty', 'valid' => 'Jeżeli chcesz zachować subskrypcję, :action.', ]; ================================================ FILE: lang/pl/emails/subscriptions/upcoming.php ================================================ 'Jeżeli nie chcesz odnawiać subskrypcji, zaloguj się do swojego konta Kanki i :link.', 'closing' => 'Z pozdrowieniami,', 'dear' => 'Drog_ :name', 'link' => 'ją anuluj', 'notice' => 'Ważna informacja o odnowieniu subskrybcji:', 'primary' => 'To automatyczne przypomnienie, że w dniu :date automatycznie obciążymy twoją kartę :brand **** :last płatnością za subskrypcję Kanki.', 'title' => 'Doroczna płatność subskrybcji Kanki', 'valid' => 'Upewnij się, że karta będzie ważna w dniu płatności.', ]; ================================================ FILE: lang/pl/emails/subscriptions/validation.php ================================================ 'Weryfikacja adresu subskrypcji', ]; ================================================ FILE: lang/pl/emails/validation.php ================================================ 'Nie udało się zweryfikować adresu, spróbuj ponownie.', 'modal' => 'By rozpocząć subscribe musisz zweryfikować adres e-mail. Zanim przejdziesz dalej sprawdź pocztę i użyj wysłanego przez nas linka.', 'success' => 'Udało się zweryfikować adres', ]; ================================================ FILE: lang/pl/emails/welcome/2024.php ================================================ 'Miłego światotwórstwa, dziękujemy, że towarzyszysz nam w tej podróży,', 'header' => 'Witaj na najlepszej platformie budowania kampanii, :name!', 'lead_1' => 'Kankę stworzyli pasjonaci gier fabularnych by ułatwić wspólne budowanie światów i uniknąć niedoskonałości innych narzędzi.', 'lead_2' => 'Tak powstała Kanka. Mamy nadzieję, że pomoże ci w organizacji kampanii i powołaniu świata do życia.', 'ps' => 'PS: Jeżeli chcesz się z nami skontaktować, znajdź nas na :discord albo napisz na :emal.', 'what_1' => 'By ułatwić ci start, stworzyliśmy dla ciebie pierwszą kampanię z dwiema przykładowymi postaciami i dwoma miejscami. Możesz też :start, jeżeli wolisz.', 'what_2' => 'Przydadzą ci się też pewnie dodatkowe zasoby:', 'what_3' => 'Nasz :kb zawiera odpowiedzi na podstawowe pytania o Kankę i twoje konto.', 'what_4' => 'Ten :doc opisuje wszystko bardziej szczegółowo.', 'what_5' => 'I wreszcie, :campaigns pozwolą ci sprawdzić, jak inni używają Kanki.', 'what_new' => 'zacząć od zera', 'what_now' => 'Co teraz?', 'why' => 'Otrzymujesz ten email z powodu rejestracji na naszej stronie.', ]; ================================================ FILE: lang/pl/emails/welcome.php ================================================ [ 'basics' => [ 'text_1' => 'Otrzymując narzędzie tak obszerne, jak Kanka, czasem trudno zrozumieć, jak zacząć i co robić. Odpowiedzi na najbardziej podstawowe pytania znajdziesz w naszym :kb, a bardziej rozbudowaną pomoc w :doc.', 'title' => 'Podstawy', ], 'chat' => [ 'text_1' => 'Lubimy słuchać użytkowników. Najłatwiej spotkać nas na :discord: tam spotkasz licznych aktywnych członków społeczności, osoby z zespołu oraz twórców Kanki - wszyscy chętnie odpowiedzą na twoje pytania. Możesz też do nas napisać na :email.', 'title' => 'Chcesz pogadać?', ], 'intro' => [ 'header' => 'Witaj pośród światotwórców, :name!', 'link' => 'Ruszaj do swojego śwata!', 'text_1' => 'Witaj pośród światotwórców, :name! Wspólnota krąży w naszych żyłach i cieszymy cię, widząc cię pośród nas. Kanka to dzieło pasjonatów gier fabularnych, którym zamarzyła się prosta i wspólnotowa platforma tworzenia światów, posiadająca przy tym wszystkie ważne funkcje.', 'text_2' => 'Przygotowaliśmy dla ciebie pierwszą kampanię, zawierającą dwie przykładowe postacie i dwa miejsca - żeby łatwiej ci było zacząć.', ], 'preview' => 'Zostań członkiem wspólnoty światotwórców, :name!', ], 'header' => ':name, witaj na platformie Kanka!', 'header_sub' => 'Gratulujemy, oto za tobą pierwszy krok ku projektowaniu własnych światów za pomocą serwisu :kanka.', 'pricing' => 'cena', 'section_1' => 'Co teraz?', 'section_11' => 'Twórz swój świat,', 'section_2' => 'Najważniejszą pomocą posłuży ci :discord, gdzie znajdziesz wielu zaangażowanych użytkowników, zespół wprowadzający oraz twórcę Kanki - wszyscy chętnie odpowiedzą na pytania.', 'section_4' => 'Na naszym :youtube znajdziesz filmy przedstawiające podstawy. Nie odejmują jeszcze wszystkich tematów, ale regularnie dodajemy nowe!', 'section_4_v2' => 'Nasza :knowledge-base zawiera odpowiedzi na najpopularniejsze pytania. Dokładniejsze i bardziej kompletne objaśnienia prezentuje natomiast :documentation!', 'section_6' => 'kontakt', 'section_7' => 'Jeżeli nie ma tam odpowiedzi na twoje pytania, albo chcesz po prostu pogadać, szukaj nasz w serwisie :facebook albo napisz na nasz :email. Jesteśmy wprawdzie maleńkim zespołem dwóch kumpli, ale staramy się odpowiadać na każdy otrzymany email, więc nie wahaj się pisać!', 'section_8' => 'Jeszcze jedno', 'section_9_v2' => 'Wszystkie funkcje Kanki są i zawsze będą darmowe - stawiamy to za punkt honoru. Ale jeżeli chcesz wesprzeć projekt, możesz go zasubskrybować. Otrzymasz dostęp do kilku dodatkowych funkcji oraz naszą dozgonną wdzięczność. Więcej dowiesz się ze strony :pricing.', 'social_account' => 'Jeżeli wciąż nie możesz zalogować się na konto, przypominamy dyskretnie, że logujesz się za pomocą :provider. Możesz to zmienić w ustawieniach konta.', 'title' => 'Jak zacząć używać Kanki', ]; ================================================ FILE: lang/pl/entities/.gitkeep ================================================ ================================================ FILE: lang/pl/entities/abilities.php ================================================ [ 'add' => 'Dodaj zdolności', 'reset' => 'Odśwież użycia zdolności', 'sync' => 'Dodaj rasowe', ], 'charges' => [ 'left' => 'Pozostało :amount', ], 'create' => [ 'helper' => 'Dodaje jedną lub więcej zdolności do :name.', 'success' => 'Zdolność :ability dodano do :entity', 'success_multiple' => 'Zdolności :ability dodano do :entity', 'title' => 'Dodaj zdolności dla :name', ], 'fields' => [ 'note' => 'Opis', 'position' => 'Kolejność', ], 'groups' => [ 'unorganised' => 'Nieprzypisane', ], 'helpers' => [ 'note' => 'W tym polu możesz oznaczać za pomocą zaawansowanych wzmianek elementy (np. :code) oraz cechy elementów (np. :attr).', 'recharge' => 'Odśwież wszystkie wykorzystane użycia zdolności.', 'sync' => 'Importuje zdolności związane z rasą postaci.', ], 'import' => [ 'errors' => [ 'no_race' => 'Ta postać nie ma rasy.', 'not_character' => 'Ten element nie jest postacią.', ], 'helper' => 'Dołącz zdolności następujących ras do których :name należy:', 'no_abilities' => 'Obecnie nie ma możliwych do zaimportowania zdolności ras do których należy :name.', 'race_abilities' => '{1} :name (:count ability)|[2,*] :name (:count abilities)', 'success' => 'Importowano {1} :count zdolność.|Importowano [2,*] :count zdolności.', ], 'recharge' => [ 'success' => 'Odświeżono wszystkie użycia.', ], 'reorder' => [ 'parentless' => 'Bez źródła', 'success' => 'Zmieniono kolejność zdolności.', ], 'show' => [ 'helper' => 'Dodaj zdolności, które posiada ten element. Możesz w każdej chwili zmienić ich widoczność albo je usunąć. Zdolności wywodzące się z tego samego źródła wyświetlane są jako kafelki.', 'reorder' => 'Zmień kolejność', 'title' => 'Zdolności elementu :name', ], 'types' => [ 'unorganised' => 'Zdolności podzielone są ze względu na źródło - pozostałe wymieniono niżej.', ], 'update' => [ 'success' => 'Zmieniono zdolność :ability elementu.', 'title' => 'Zdolność elementu :name', ], ]; ================================================ FILE: lang/pl/entities/actions.php ================================================ [ 'success' => 'Zarchiwizowano :name.', 'title' => 'Archiwizuj', ], 'convert' => 'Zmień moduł', 'copy-campaign' => 'Kopiuj do kampanii', 'json-export' => 'Eksport JSON', 'markdown-export' => 'Eksport Markdown', 'templates' => [], 'tooltips' => [ 'edit' => 'Pozwala modyfikować element', ], 'transfer' => 'Przenieś do kampanii', 'unarchive' => [ 'success' => 'Przywrócono :name z archiwum.', 'title' => 'Przywróć', ], ]; ================================================ FILE: lang/pl/entities/aliases.php ================================================ [ 'add' => 'Dodaj alias', ], 'create' => [ 'helper' => 'Tworzy alias dla :name, dzięki czemu będzie widoczny podczas globalnego wyszukiwania oraz poprzez wzmianki :code.', 'success' => 'Dodano alias :name do elementu :entity.', 'title' => 'Dodaj alias do :name', ], 'destroy' => [ 'success' => 'Usunięto alias :name.', ], 'fields' => [ 'name' => 'Nazwa', ], 'helpers' => [ 'primary' => 'Wyposażenie elementu w jeden lub więcej aliasów pozwoli wyszukiwać go za pomocą globalnego wyszukiwania (górny pasek) oraz przez wzmianki.', ], 'limit' => 'Kampania osiągnęła limit dostępnych aliasów. By go zwiększyć, odblokuj funkcje premium.', 'pitch' => 'Możliwość nadawania elementom aliasów pozwala łatwo je wyszukiwać i tworzyć wzmianki.', 'placeholders' => [ 'name' => 'Nowy alias', ], 'unboosted' => [], 'update' => [ 'success' => 'Zmieniono alias :name elementu :entity.', 'title' => 'Zmień alias :name', ], ]; ================================================ FILE: lang/pl/entities/assets.php ================================================ [ 'alias' => 'Alias', 'file' => 'Plik', 'link' => 'Odnośnik', ], 'copy_alias' => [ 'success' => 'Wzmiankowanie aliasu skopiowane do schowka.', ], 'show' => [ 'title' => 'Zasoby elementu :name', ], ]; ================================================ FILE: lang/pl/entities/attributes.php ================================================ [ 'load' => 'Wczytaj', 'manage' => 'Zarządzaj', 'more' => 'Więcej opcji', 'remove_all' => 'Usuń wszystko', 'save_and_edit' => 'Zastosuj i edytuj', 'save_and_story'=> 'Zastosuj i zobacz', 'show_hidden' => 'Pokaż ukryte cechy', 'toggle_privacy'=> 'Prywatne/Publiczne', ], 'errors' => [ 'api' => 'Niewłaściwe dane', 'loop' => 'W obliczeniu tej cechy występuje nie kończąca się pętla!', 'no_attribute_selected' => 'Wybierz najpierw jedną lub więcej cech.', 'too_many_v2' => 'Maksymalna liczba pól (:count/max). Skasuj jakieś cechy przed dodaniem nowych.', ], 'fields' => [ 'community_templates' => 'Szablony społeczności', 'is_private' => 'Szablony Tajne', 'is_star' => 'Przypięte', 'preferences' => 'Ustawienia', 'value' => 'Wartość', ], 'filters' => [ 'name' => 'Nazwa cechy', 'value' => 'Wartość cechy', ], 'helpers' => [ 'delete_all' => 'Czy na pewno chcesz usunąć cechy tego elementu?', 'is_private' => 'Tylko członkowie posiadający rolę :admin-role będą widzieć cechy elementu.', 'setup' => 'Element może posiadać cechy, na przykład Punkty Wytrzymałości albo Inteligencję. Cechę możesz ustalić i dodać ręcznie klikając na :manage albo zastosować szablon.', ], 'hints' => [], 'index' => [ 'success' => 'Zaktualizowano cechy :entity', 'title' => 'Cechy :name', ], 'labels' => [ 'checkbox' => 'Nazwa pola wyboru', 'name' => 'Nazwa cechy', 'section' => 'Nazwa sekcji', 'value' => 'Wartość cechy', ], 'live' => [ 'success' => 'Zmieniono cechę :attribute.', 'title' => 'Zmiana cechy :attribute.', ], 'placeholders' => [ 'attribute' => 'Liczba zwycięstw, Skala Wyzwania, Inicjatywa, Populacja', 'block' => 'Nazwa bloku', 'checkbox' => 'Nazwa pola wyboru', 'icon' => [ 'class' => 'Klasa FontAwesome lub RPG Awesome: fas fa-users', 'name' => 'Nazwa ikony', ], 'number' => 'Rodzaj liczby', 'random' => [ 'name' => 'Nazwa cechy', 'value' => '1-100 lub lista wartości rozdzielonych przecinkiem', ], 'section' => 'Nazwa sekcji', 'value' => 'Wartość cechy', ], 'ranges' => [ 'text' => 'Dostępne opcje: :options', ], 'sections' => [ 'unorganised' => 'Nieprzypisane', ], 'show' => [ 'hidden' => 'Ukryte cechy', 'title' => 'Cechy elementu :name', ], 'template' => [ 'load' => [ 'success' => 'Wczytano szablon', 'title' => 'Wczytaj szablon', ], 'pitch' => 'Załaduj cechy z szablonu albo dodatków zainstalowanych za pomocą :plugin.', 'success' => 'Zastosowano szablon cech :name dla :entity', 'title' => 'Zastosuj szablon cech dla :name', ], 'title' => 'Cechy', 'toasts' => [ 'bulk_deleted' => 'Usunięto cechy', 'bulk_privacy' => 'Zmieniono ustawienia prywatności', 'lock' => 'Zablokowano', 'pin' => 'Przypięto', 'unlock' => 'Odblokowano', 'unpin' => 'Odpięto', ], 'tutorials' => [], 'types' => [ 'attribute' => 'Cecha', 'block' => 'Blok', 'checkbox' => 'Pole wyboru', 'icon' => 'Ikona', 'number' => 'Liczba', 'random' => 'Losowy', 'section' => 'Sekcja', 'text' => 'Kilka wierszy', ], 'update' => [ 'success' => 'Zaktualizowano cechy elementu :entity.', ], 'visibility' => [ 'entry' => 'Cecha wyświetlana na stronie głównej elementu.', 'private' => 'Cecha widoczna wyłącznie dla posiadaczy roli "administrator".', 'public' => 'Cecha widoczna dla wszystkich.', 'tab' => 'Cecha wyświetlana wyłącznie w zakładce Cechy.', ], ]; ================================================ FILE: lang/pl/entities/children.php ================================================ 'Pochodne', ]; ================================================ FILE: lang/pl/entities/events.php ================================================ [ 'helper' => 'Tworzy epizod łączący :name z kalendarzem.', ], 'fields' => [ 'type' => 'Rodzaj epizodu', ], 'helpers' => [ 'characters' => 'Ustawienie rodzaju jako daty narodzin albo śmieci tej postaci automatycznie wyliczy jej wiek. :more.', 'founding' => 'Ustawienie typu :type automatycznie przeliczy wiek elementu od chwili powstania.', ], 'show' => [ 'actions' => [ 'add' => 'Dodaj epizod', ], 'title' => 'Epizody :name', ], 'types' => [ 'birth' => 'Narodziny', 'birthday' => 'Urodziny', 'death' => 'Śmierć', 'founded' => 'Powstanie', 'primary' => 'Główna', ], 'years-ago' => '{1} :count rok temu|[2,3,4] :count lata temu|[5,*] :count lat temu', ]; ================================================ FILE: lang/pl/entities/files.php ================================================ [ 'max' => [ 'helper' => 'Nie możesz dołączać nowych plików, póki któregoś nie usuniesz.', 'limit' => 'Element osiągnął limit dołączonych plików.', ], 'upgrade' => [ 'limit' => 'Osiągnięto limit :limit plików dla tego elementu.', 'premium' => 'Ulepsz kampanię do poziomu premium by dodawać nieograniczoną liczbę plików i zyskać większą swobodę twórczą.', 'upgrade' => 'Ulepsz kampanię do poziomu premium by zwiększyć limit plików do :limit i zyskać większą swobodę twórczą.', ], ], 'create' => [ 'helper' => 'Dodaje plik do :name. Zostanie doliczony do limitu pojemności galerii.', 'success_plural' => '{1} Dodano plik :name.|[2,4] Dodano :count pliki.|[5,*] Dodano :count plików.', 'title' => 'Nowy plik elementu :entity', ], 'destroy' => [ 'success' => 'Usunięto plik :file.', ], 'fields' => [ 'file' => 'Plik', 'files' => 'Pliki', 'name' => 'Nazwa pliku', ], 'max' => [ 'title' => 'Osiągnięto limit', ], 'update' => [ 'success' => 'Zaktualizowano plik :file.', 'title' => 'Zamieszanie plików elementu', ], ]; ================================================ FILE: lang/pl/entities/image.php ================================================ [ 'change_focus' => 'Zmień punkt centralny', 'change_visibility' => 'Zmień widoczność', 'replace_image' => 'Zamień obraz', 'save-replace' => 'Zamień obraz', 'save_focus' => 'Zapisz punkt centralny', 'view' => 'Zobacz obraz', ], 'call-to-action' => 'Kliknij na obraz elementu by ustawić centralny punkt wyświetlania, zamiast korzystać z wybranego automatycznie.', 'focus' => [ 'breadcrumb' => 'Punkt centralny', 'helper' => 'Kliknij na obraz by wskazać punkt centralny. Kliknij na punkt centralny, by go usunąć.', 'panel_title' => 'Punkt centralny', 'success' => 'Zmieniono punkt centralny.', 'title' => 'Punkt centralny obrazu elementu :name', 'unboosted' => 'Punkt centalny można ustawiać tylko w :boosted-campaigns', 'warning' => 'Wszystkie elementy używające tego samego obrazu z :gallery stosują wspólny punkt centralny.', ], 'gallery_permissions' => [ 'admin' => 'Ten obraz widzą tylko uczestnicy kampanii posiadający rolę :admin.', 'adminself' => 'Ten obraz widzi tylko :creator i uczestnicy kampanii posiadający rolę :admin.', 'member' => 'Ten obraz widzą tylko uczestniczy kampanii.', 'self' => 'Ten galerii widzisz wyłącznie ty.', ], 'replace' => [ 'breadcrumb' => 'Zmiana obrazu', 'panel_title' => 'Zmiana obrazu elementu', 'success' => 'Zmieniono obraz.', 'title' => 'Zmiana obrazu elementu :name', ], 'visibility' => [ 'helper' => 'Zmień widoczność obrazu w galerii, by określić kto go może wyświetlić.', 'updated' => 'Zmieniono widoczność obrazu.', ], ]; ================================================ FILE: lang/pl/entities/inventories.php ================================================ [ 'copy_from_entity' => 'Kopiuj z innego elementu', 'copy_inventory' => 'Kopiuj wyposażenie', 'generate' => 'Generuj', 'multiple' => 'Dodaj przedmioty', ], 'copy' => [ 'helper' => 'Kopiuje całe wyposażenie elementu do :name.', ], 'create' => [ 'helper' => 'Dodaje przedmiot do wyposażenia :name. Można też użyć przedmiotu już istniejącego w kampanii.', 'success' => 'Dodano :item do elementu :entity.', 'success_bulk' => '{0} Nie dodano przedmiotów do wyposażenia :entity.|{1} Dodano :count przedmiot do wyposażenia :entity.|[2,4] Dodano :count przedmioty do wyposażenia :entity.|5,] Dodano :count przedmiotów do wyposażenia :entity.', 'title' => 'Dodaj przedmiot dla :name', ], 'default_position' => 'Nieprzypisane', 'destroy' => [ 'success' => 'Usunięto przedmiot :item elementu :entity.', 'success_position' => 'Usunięto przedmioty umieszczone w :position elementu :entity.', ], 'fields' => [ 'amount' => 'Ilość', 'copy_entity_entry_v2' => 'Użyj opisu elementu', 'description' => 'Opis', 'is_equipped' => 'W użyciu', 'item_amount' => 'Liczba przedmiotów', 'match_all' => 'Użyj wszystkich etykiet', 'name' => 'Nazwa', 'position' => 'Umiejscowienie', 'qty' => 'Ilość', 'replace' => 'Zastąp wyposażenie', ], 'generate' => [ 'helper' => 'Generuje wyposażenie dla :name w oparciu o przedmioty już używane w kampanii.', 'title' => 'Generowanie wyposażenia', ], 'helpers' => [ 'amount' => 'Liczba przedmiotów', 'copy_entity_entry_v2' => 'Wyświetla główny opis elementu zamiast lokalnego.', 'description' => 'Dodaj lokalny opis przedmiotu', 'is_equipped' => 'Oznacza przedmioty, których element używa.', 'name' => 'Nazwij przedmiot. To konieczne, jeśli nie wybrano istniejącego obiektu.', 'replace' => 'Zastępuje obecne wyposażenie wygenerowanym', ], 'placeholders' => [ 'amount' => 'Dowolna ilość', 'description' => 'Używany, uszkodzony, przystosowany', 'name' => 'Wymagana jeżeli nie wybrano przedmiotu z listy', 'position' => 'Pod ręką, w plecaku, w skrzyni, w banku', ], 'show' => [ 'helper' => 'Elementom można przypisywać przedmioty, tworząc ich wyposażenie.', 'title' => 'Wyposażenie elementu :name', 'unsorted' => 'Nieposortowanie', ], 'togglers' => [ 'hide' => [ 'price' => 'Ukryj cenę', 'quantity' => 'Ukryj ilość', 'size' => 'Ukryj rozmiar', 'weight' => 'Ukryj wagę', ], 'show' => [ 'price' => 'Pokaż cenę', 'quantity' => 'Pokaż ilość', 'size' => 'Pokaż rozmiar', 'weight' => 'Pokaż wagę', ], ], 'tooltips' => [ 'equipped' => 'Przedmiot jest w użyciu.', ], 'tutorials' => [ 'all' => 'Śledzi, co :name posiada, przechowuje albo sprzedaje, dodając to do jego wyposażenia.', ], 'update' => [ 'success' => 'Zaktualizowano przedmiot :item elementu :entity', 'title' => 'Zaktualizowano przedmiot u :name', ], ]; ================================================ FILE: lang/pl/entities/links.php ================================================ [ 'add' => 'Dodaj odnośnik', ], 'call-to-action' => 'Dodaj odnośnik do zasobów zewnętrznych, na przykład DnDBeyond. Zostanie wyświetlony bezpośrednio w opisie elementu.', 'create' => [ 'helper' => 'Dodaje do :name odnośnik prowadzący na zewnątrz, na przykład do DnDBeyond.', 'success' => 'Dodano odnośnik :name do elementu :entity.', 'title' => 'Dodaj odnośnik do :name', ], 'destroy' => [ 'success' => 'Usunięto odnośnik :name z elementu :entity.', ], 'fields' => [ 'icon' => 'Ikona', 'name' => 'Nazwa', 'position' => 'Kolejność', 'url' => 'URL', ], 'go' => [ 'actions' => [ 'confirm' => 'Tak, na pewno', 'trust' => 'Nie pytaj ponownie', ], 'description' => 'Ten odnośnik prowadzi do :link. Czy na pewno chcesz tam trafić?', 'title' => 'Opuszczasz Kankę', ], 'helpers' => [ 'icon' => 'Możesz dostosować ikonę wyświetlaną przy odnośniku. Użyj dowolnej ikony z :fontawesome albo zostaw to pole puste, by wyświetlać ikonę domyślną.', 'parent' => 'Wyświetla skrót po tym elemencie w menu bocznym, a nie w sekcji "Skróty".', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'W doładowanych kampaniach można dodawać elementom odnośniki do stron zewnętrznych.', 'title' => 'Odnośniki elementu :name', ], 'unboosted' => [], 'update' => [ 'success' => 'Zaktualizowano odnośnik :name dla elementu :entity.', 'title' => 'Aktualizacja odnośnika elementu :name', ], ]; ================================================ FILE: lang/pl/entities/logs.php ================================================ [ 'create' => 'Stworzono', 'create_post' => 'Stworzono komentarz ":post"', 'delete' => 'Usunięto', 'delete_post' => 'Usunięto komentarz', 'reorder_post' => 'Zmieniono kolejność komentarzy', 'restore' => 'Przywrócono', 'reveal' => 'Pokaż szczegóły', 'update' => 'Zmieniono', 'update_post' => 'Zmieniono komentarz "post"', 'view' => 'Zobacz zmiany', ], 'call-to-action' => 'Pełen dziennik zmian z ostatnich :amount dni dostępny jest w kampaniach turbodoładowanych.', 'fields' => [ 'action' => 'Działanie', 'date' => 'Data', ], 'filters' => [ 'keywords' => 'Słowa kluczowe', ], 'impersonated' => 'Na konto zalogowano :name', 'none' => 'Brak', 'show' => [ 'title' => 'Rejestr Elementu :name', ], 'tooltips' => [ 'post' => 'Przejdź do komentarza', ], ]; ================================================ FILE: lang/pl/entities/map-points.php ================================================ 'Ten element oznaczono na następujących mapach.', 'title' => ':name na mapach', ]; ================================================ FILE: lang/pl/entities/mentions.php ================================================ [ 'element' => 'Nazwa elementu', 'type' => 'Rodzaj', ], 'helper' => 'Poniżej znajduje się lista elementów, w których opisie znajduje się wzmianka o tym elemencie.', 'mentioned_in_v2' => 'Ten element wzmiankowano w opisie :count elementów, notek albo kampanii. :more.', 'see_more' => 'Szczegóły', 'show' => [ 'title' => 'Wzmianki o elemencie :name', ], 'title' => 'Wzmianki o elemencie', ]; ================================================ FILE: lang/pl/entities/move.php ================================================ [ 'copy' => 'Kopiuj', 'transfer' => 'Przenieś', ], 'errors' => [ 'permission' => 'Nie możesz tworzyć elementów tego typu w kampanii docelowej.', 'permission_update' => 'Nie masz uprawień, by przenieś ten element.', 'same_campaign' => 'Musisz wybrać kampanię, do której element ma być przeniesiony.', 'unknown_campaign' => 'Nieznana kampania.', ], 'fields' => [ 'campaign' => 'Kampania docelowa', 'copy' => 'Skopiuj', 'select_one' => 'Wybierz kampanię', ], 'helpers' => [ 'copy' => 'Stwórz kopię elementu ze wskazanej kampanii.', ], 'panel' => [ 'description' => 'Wybierz kampanię do której element ma zostać przeniesiony albo skopiowany.', 'description_bulk_copy' => 'Wybierz kampanię, do której chcesz skopiować wybrane elementy.', 'title' => 'Przenieś lub skopiuj element do innej kampanii.', ], 'success' => 'Przeniesiono element :name.', 'success_copy' => 'Skopiowano element :name.', 'title' => 'Przenoszenie elementu :name', 'warnings' => [ 'custom' => 'Ten element nie jest częścią modułu domyślnego, ale własnego, stworzonego w tej kampanii. W kampanii docelowej stanie się elementem Notatek.', ], ]; ================================================ FILE: lang/pl/entities/notes.php ================================================ [ 'add' => 'Nowy komentarz', 'add_role' => 'Dodaj rolę', 'add_user' => 'Dodaj użytkownika', ], 'collapsed' => [ 'closed' => 'Komentarz zwinięty, wyświetlany jest nagłówek', 'open' => 'Komentarz rozwinięty', ], 'copy_mention' => [ 'copy' => 'Kopiuj wzmiankę zaawansowaną', 'copy_with_name' => 'Kopiuj wzmiankę zaawansowaną z tytułem wpisu', 'success' => 'Wzmianka zaawansowana skopiowana do schowka', ], 'create' => [ 'success' => 'Dodano komentarz :name do elementu :entity.', ], 'destroy' => [ 'success' => 'Usunięto komentarz :name do elementu :entity.', ], 'edit' => [ 'success' => 'Zmieniono komentarz :name do elementu :entity.', ], 'fields' => [ 'creator' => 'Twórca', 'display' => 'Wyświetlanie', 'name' => 'Nazwa', 'position' => 'Ustawienie', ], 'footer' => [ 'created' => 'Stworzony przez :user dnia :date', 'updated' => 'Zmieniony przez :user dnia :date', ], 'hint' => 'Komentarze to informacje, które nie mieszczą się w zwykłych polach opisu elementu albo które powinny pozostać tajne.', 'hints' => [ 'reorder' => 'Możesz zmieniać kolejność komentarzy po kliknięciu na ikonę :icon obok pozycji "historia" w menu elementu', ], 'index' => [], 'move' => [ 'copy' => 'Skopiuj do wskazanego elementu', 'copy_success' => 'Skopiowano komentarz :name do elementu :entity.', 'copy_title' => 'Zachowaj kopię', 'description' => 'Wybierz element do którego chcesz przenieść albo skopiować ten komentarz.', 'entity' => 'Wskazany element', 'move_success' => 'Przeniesiono komentarz :name do elementu :entity.', ], 'placeholders' => [ 'name' => 'Nazwa uwagi, spostrzeżenia lub komentarza', ], 'show' => [ 'advanced' => 'Uprawnienia zaawansowane', 'title' => 'Komentarz do elementu :entity', ], 'states' => [ 'collapsed' => 'Zwinięte', 'expanded' => 'Rozwinięte', ], 'warning' => [], ]; ================================================ FILE: lang/pl/entities/permissions.php ================================================ [ 'text' => 'Ten element jest tajny. Wprawdzie można ustawiać dla niego indywidualne uprawnienia, ale póki nie zostanie ujawniony, będą ignorowane, a element będzie widoczny tylko dla administratorów kampanii.', 'warning' => 'Uwaga', ], 'quick' => [ 'empty-permissions' => 'Ten element mogą zobaczyć tylko administratorzy kampanii, i nikt inny.', 'manage' => 'Zarządzaj uprawnieniami', 'screen-reader' => 'Otwórz ustawienia prywatności', 'success' => [ 'private' => 'Element :entity jest ukryty.', 'public' => 'Element :entity jest widoczny.', ], 'title' => 'Uprawnienia', 'viewable-by' => 'Widoczny dla', ], 'toggle' => [ 'label' => 'Tajność elementu', 'private' => [ 'description' => 'Widoczny tylko dla posiadaczy roli :admin.', 'title' => 'Tajny', ], 'public' => [ 'description' => 'Widoczny dla poniższych ról i uczestników', 'title' => 'Jawny', ], ], ]; ================================================ FILE: lang/pl/entities/pins.php ================================================ 'Odnośniki', 'title' => 'Przypięte', ]; ================================================ FILE: lang/pl/entities/profile.php ================================================ [ 'edit_profile' => 'Edytuj profil', ], 'history' => 'Historia', 'show' => [ 'tab_name' => 'Profil', 'title' => 'Profil elementu :name', ], ]; ================================================ FILE: lang/pl/entities/quests.php ================================================ 'Ten element jest częścią następujących zadań.', 'title' => 'Zadania elementu :name', ]; ================================================ FILE: lang/pl/entities/relations.php ================================================ [ 'mode-map' => 'Wizualizacja relacji', 'mode-table' => 'Tabela relacji i powiązań', ], 'bulk' => [ 'delete' => '{1} Usunięto :count relację.|[2,3,4] Usunięto :count relacje.|[5,*] Usunięto :count relacji.', 'fields' => [ 'delete_mirrored' => 'Usuń obustronnie', 'unmirror' => 'Rozwiąż obustronność', 'update_mirrored' => 'Aktualizuj obustronnie', ], 'helpers' => [ 'delete_mirrored' => 'Usuwa relacje obu stron', 'unmirror' => 'Rozwiązuje obustronność relacji', 'update_mirrored' => 'Aktualizuje relacje obu stron.', ], 'success' => [ 'editing' => '{1} Zmienono :count relację.|[2,3,4] Zmienono :count relacje.|[5,*] Zmienono :count relacji.', 'editing_partial' => '{1} Zmienono :count/:total relację.|[2,3,4] Zmienono :count/:total relacje.|[5,*] Zmienono :count/:total relacji.', ], ], 'call-to-action' => 'Zobacz rozkład rozmaitych relacji, łączących elementy kampanii.', 'connections' => [ 'map_point' => 'Punkt na mapie', 'mention' => 'Wzmianka', 'quest_element' => 'Część zadania', 'timeline_element' => 'Część historii', ], 'create' => [ 'helper' => 'Łączy :name z jednym lub kilkoma innymi elementami', 'new_title' => 'Nowa relacja', 'success_bulk' => '{1} Dodano :count relacji do :entity.|[2,4] Dodano :count relacje do :entity.|[5,*] Dodano :count relacji do :entity.', ], 'delete_mirrored' => [ 'helper' => 'Te elementy łączy relacja obustronna. Wybór tej opcji usunie obydwie strony relacji.', 'option' => 'Usuń relację obustronną.', ], 'destroy' => [ 'mirrored' => 'Usunie również drugą stronę relacji. Tej akcji nie można cofnąć.', 'success' => 'Usunięto relację :target elementu :entity.', ], 'fields' => [ 'attitude' => 'Nastawienie', 'is_pinned' => 'Przypięta', 'owner' => 'Źródło', 'target' => 'Obiekt', 'targets' => 'Elementy obiektu', 'two_way' => 'Stwórz relację obustronną', 'unmirror' => 'Zmień w relację jednostronną', ], 'filters' => [ 'connection' => 'Rodzaj relacji', 'name' => 'Cel relacji', ], 'helper' => 'Ustalaj relacje między elementami, określając ich rodzaj i widoczność. Relacje można przypinać do opisu elementów.', 'helpers' => [ 'description' => 'Opisuje charakter relacji między dwoma elementami.', 'no_relations' => 'Element nie jest obecnie związany z żadnym innym elementem tej kampanii.', ], 'hints' => [ 'attitude' => 'Pole opcjonalne, pozwalająca określić kolejność wyświetlania relacji, w porządku malejącym.', 'two_way' => 'Jeżeli wybierzesz relację obustronną, taka sama relacja zostanie stworzona dla obiektu. Jeżeli potem zmodyfikujesz relację dla jednej strony, druga nie zostanie zaktualizowana.', ], 'index' => [ 'title' => 'Relacje', ], 'options' => [ 'mentions' => 'Relacje + związki + wzmianki', 'only_relations' => 'Tylko relacje bezpośrednie', 'related' => 'Relacje + związki', 'relations' => 'Relacje', 'show' => 'Pokaż', ], 'panels' => [ 'related' => 'Związki', ], 'placeholders' => [ 'attitude' => '-100 do 100, gdzie 100 to bardzo pozytywny stosunek', ], 'show' => [ 'title' => 'Relacje elementu :name', ], 'types' => [ 'family_member' => 'Członek rodziny', 'organisation_member' => 'Członek organizacji', ], 'update' => [ 'success' => 'Zaktualizowano relację :target z elementem :entity.', 'title' => 'Zaktualizuj relacje dla :name', ], ]; ================================================ FILE: lang/pl/entities/reminders.php ================================================ [ 'add' => 'Połącz z kalendarzem', 'remove' => 'Usuń epizod', ], 'helpers' => [ 'pitch' => 'Pozwala zignorować prawdziwy kalendarz i połączyć element z fikcyjnym kalendarzem twojego świata, dla zwiększenia immersji.', ], ]; ================================================ FILE: lang/pl/entities/story.php ================================================ [ 'collapse_all' => 'Zwiń wszystkie', 'expand_all' => 'Rozwiń wszystkie', 'load_more' => 'Załaduj więcej', 'login_for_more' => 'Zaloguj się, by zobaczyć kolejne wpisy', ], 'reorder' => [ 'helper' => 'Przeciągaj komentarze by zmienić ich kolejność na stronie z opisem elementu', 'icon_tooltip' => 'Zmień kolejność', 'panel_title' => 'Zmiana kolejności', 'save' => 'Zapisz nową kolejność', 'success' => 'Zmieniono kolejność.', ], 'update' => [ 'title' => 'Aktualizacja elementu :entity', ], 'warning' => [], ]; ================================================ FILE: lang/pl/entities/tags.php ================================================ [ 'helper' => 'Dodaje albo usuwa etykiety :name.', 'title' => 'Etykietowanie', ], ]; ================================================ FILE: lang/pl/entities/timelines.php ================================================ 'Poniżej wyświetlono historie tego elementu.', 'show' => [ 'title' => 'Historie elementu :name', ], ]; ================================================ FILE: lang/pl/entities/tooltips.php ================================================ 'Ubsługiwany HTML: tekst (:text), struktura (:layout), listy/tabele oraz grafiki.', 'helper' => 'Zastępuje domyślny, generowany automatycznie tekst, wyświetlany gdy kursor wskazuje ten element.', 'label' => 'Zajawka', 'placeholder' => 'Opisz krótko i zwięźle element.', 'premium' => 'Odblokuj możliwość tworzenia własnych zajawek w :boosted-campaign.', ]; ================================================ FILE: lang/pl/entities/transform.php ================================================ [ 'convert' => 'Zmień moduł', ], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Nieznany lub niewłaściwy rodzaj elementu.', ], 'success' => '{1} zmieniono rodzaj :count elementu.|[2,*] zmieniono rodzaj :count elementów.', ], 'confirm' => [ 'checkbox' => 'Rozumiem, że po przekształceniu :entity w element innego moduły stracę następujące dane:', 'label' => 'Potwierdzenie utraty danych', ], 'documentation' => 'Dokumentacja: zmiana modułu elementu', 'fields' => [ 'current' => 'Obecny moduł', 'select_one' => 'Wybierz', 'target' => 'Nowy typ elementu', ], 'panel' => [ 'bulk_description' => 'Zmień rodzaj wielu elementów na raz. Pamiętaj, możesz utracić część danych ze względu na różnice pól opisu różnych rodzajów elementów.', 'bulk_title' => 'Przekształcanie wielu elementów', 'title' => 'Przekształć typ elementu', 'warning' => 'Możesz utracić niektóre dane, jeżeli nowy moduł używa innych pól opisu.', ], 'success' => 'Przekształcono element :name.', 'title' => 'Przekształcanie elementu :name', ]; ================================================ FILE: lang/pl/entities.php ================================================ 'Zdolności', 'ability' => 'Zdolność', 'attribute_template' => 'Szablon Cech', 'attribute_templates' => 'Szablony Cech', 'bookmark' => 'Zakładka', 'bookmarks' => 'Zakładki', 'calendar' => 'Kalendarz', 'calendars' => 'Kalendarze', 'campaign' => 'Kampania', 'campaigns' => 'Kampanie', 'character' => 'Postać', 'characters' => 'Postaci', 'conversation' => 'Rozmowa', 'conversations' => 'Rozmowy', 'creator' => [ 'actions' => [ 'create' => 'Stwórz :type', 'full' => 'Przejdź do wersji pełnej', 'more' => 'Szczegóły', ], 'back' => 'Powrót do menu wyboru', 'bulk_names' => 'Jedna nazwa na wiersz', 'duplicate' => 'Istnieje inny element tego typu o tej samej nazwie.', 'helper_v2' => 'Szybko stwórz szkic nowego elementu nie przerywając obecnej pracy.', 'missing_v2' => 'W tym panelu widoczne są tylko włączone moduły, w których masz prawo tworzyć elementy. :learn-more.', 'modes' => [ 'bulk' => 'Stwórz wiele', 'default' => 'Stwórz szybko', ], 'name' => [ 'new' => 'Nowa nazwa', 'remove' => 'Usuń', ], 'success_multiple' => '{1} Stworzono nowy element :link.|[2,*] Stworzono nowe elementy :link.', 'success_multiple_posts' => '{1} Dodano komentarz: :link.|[2,*] Dodano komentarze: :link.', 'title' => 'Nowy element', 'titles' => [ 'everything' => 'Wszystko', 'quick-access' => 'Szybki dostęp', ], 'tooltip' => 'Stwórz nowy element bez opuszczania obecnej strony', 'tooltips' => [ 'create' => 'Stwórz element i wróć do ekranu wyboru elementów.', 'create_more' => 'Stwórz element i zacznij tworzyć kolejny element tego typu', 'edit' => 'Stwórz element i rozpocznij jego edycję', ], ], 'creature' => 'Istota', 'creatures' => 'Istoty', 'dice_roll' => 'Rzut kośćmi', 'dice_rolls' => 'Rzuty kośćmi', 'event' => 'Wydarzenie', 'events' => 'Wydarzenia', 'families' => 'Rodziny', 'family' => 'Rodzina', 'inventories' => 'Wyposażenie', 'item' => 'Przedmiot', 'items' => 'Przedmioty', 'journal' => 'Dziennik', 'journals' => 'Dzienniki', 'location' => 'Miejsce', 'locations' => 'Miejsca', 'map' => 'Mapa', 'maps' => 'Mapy', 'new' => [], 'note' => 'Notatka', 'notes' => 'Notatki', 'organisation' => 'Organizacja', 'organisations' => 'Organizacje', 'quest' => 'Zadanie', 'quest_element' => 'Element zadania', 'quests' => 'Zadania', 'race' => 'Rasa', 'races' => 'Rasy', 'relation' => 'Relacja', 'relations' => 'Relacje', 'reminders' => 'Epizody', 'tag' => 'Etykieta', 'tags' => 'Etykiety', 'templates' => 'Szablony', 'timeline' => 'Historia', 'timeline_element' => 'Element historii', 'timelines' => 'Historie', 'whiteboard' => 'Tablica', 'whiteboards' => 'Tablice', ]; ================================================ FILE: lang/pl/errors.php ================================================ [ 'body' => 'Najwyraźniej nie masz uprawnień do przeglądania tej strony!', 'title' => 'Brak dostępu', ], '403-form' => [ 'help' => 'Powodem może być zakończenie twojej sesji. Przed zapisaniem spróbuj zalogować się ponownie w nowym oknie.', ], '404' => [ 'body' => 'Niestety nie możemy znaleźć strony której szukasz.', 'title' => 'Strony nie znaleziono', ], '500' => [ 'body' => [ '1' => 'Ups, chyba coś poszło nie tak.', '2' => 'Otrzymaliśmy raport o napotkanym błędzie, ale czasem warto przekazać nam dokładniejszą informację, jakie działanie go spowodowało.', ], 'title' => 'Błąd', ], '503' => [ 'body' => [ '1' => 'Trwa konserwacja Kanki, co zwykle oznacza, że instalujemy aktualizację!', '2' => 'Przepraszamy za niedogodność. Za kilka minut wszystko wróci do normy.', ], 'json' => 'Właśnie trwa konserwacja Kanki, spróbuj za kilka minut.', 'title' => 'Konserwacja', ], '503-form' => [], 'back-to-campaigns' => 'Wróć do jednej ze swoich kampanii.', 'footer' => 'Jeżeli potrzebujesz pomocy, skontaktuj się z nami przez hello@kanka.io albo na serwerze :discord', 'log-in' => 'Zalogowanie może pozwolić ci uzyskać dostęp.', 'post_layout' => 'Nieprawidłowy układ komentarza', 'private-campaign' => [ 'auth' => [ 'helper' => 'Nie masz dostępu do tej kampanii.', ], 'guest' => [ 'helper' => 'Kampania do której próbujesz się dostać jest prywatna i wymaga zalogowania.', 'login' => 'Dostęp do zawartości możesz uzyskać po zalogowaniu.', ], 'title' => 'Kampania prywatna', ], ]; ================================================ FILE: lang/pl/events.php ================================================ [ 'title' => 'Nowe wydarzenie', ], 'destroy' => [], 'edit' => [], 'events' => [ 'helper' => 'Ty wyświetlone są wydarzenia pochodzące od tego elementu.', ], 'fields' => [ 'date' => 'Data', ], 'helpers' => [ 'date' => 'W tym polu można umieścić wszystko - nie jest związane z kalendarzami kampanii. By je tam umieścić kalendarzu, dodaj ręcznie epizod za pomocą kalendarza albo zakładki "Epizody" elementu.', ], 'index' => [], 'lists' => [ 'empty' => 'Opisz ważne wydarzenia z historii świata: bitwy, koronacje czy odkrycia.', ], 'placeholders' => [ 'date' => 'Data tego wydarzenia', 'type' => 'Uroczystość, festiwal, katastrofa, bitwa, narodziny', ], 'show' => [], 'tabs' => [ 'calendars' => 'Wpisy w kalendarzu', ], ]; ================================================ FILE: lang/pl/export.php ================================================ 'Zawartość', 'hidden_campaign' => 'Ukryta kampania', 'index' => 'Indeks elementów', ]; ================================================ FILE: lang/pl/families/trees.php ================================================ [ 'clear' => 'Usuń wszystko', 'first' => 'Dodaj założyciela', 'founder' => 'Dodaj założyciela', 'rename-relation' => 'Zmień nazwę relacji', 'reset' => 'Odrzuć zmiany', 'save' => 'Zapisz', ], 'modal' => [ 'first-title' => 'Wybierz element', 'helper' => 'Zamień element na inny element kampanii', 'relation' => 'Relacja', 'title' => 'Zamień element', ], 'modals' => [ 'clear' => [ 'confirm' => 'Czy na pewno chcesz usunąć wszystkie dane i rozpocząć tworzenie rodowodu od nowa?', ], 'entity' => [ 'add' => [ 'founder' => 'Założyciel', 'member' => 'Członek', 'success' => 'Dodano element.', 'title' => 'Dodaj element', ], 'child' => [ 'success' => 'Dziecko dodane.', 'title' => 'Dodaj dziecko', ], 'edit' => [ 'helper' => 'Zaznacz, jeżeli relacja jest nieznana. Postać będzie można dodać później.', 'success' => 'Zmieniono element.', 'title' => 'Zmień element', ], 'founder' => [ 'title' => 'Dodawanie założyciela', ], 'remove' => [ 'confirm' => 'Czy na pewno chcesz usunąć ten element z rodowodu?', 'success' => 'Usunięto element.', ], ], 'relations' => [ 'add' => [ 'success' => 'Dodano relację.', 'title' => 'Dodaj relację', ], 'edit' => [ 'success' => 'Zmieniono relację.', 'title' => 'Zmień relację', ], 'unknown' => 'Nieznana', ], 'reset' => [ 'confirm' => 'Czy na pewno chcesz odrzucić wszystkie zmiany w rodowodzie?', ], ], 'pitch' => 'Stwórz drzewo genealogiczne rodzin w kampanii.', 'success' => [ 'cleared' => 'Usunięto rodowód.', 'reseted' => 'Zresetowano rodowód.', 'saved' => 'Zapisano rodowód.', ], 'title' => 'Rodowód', 'unknown' => 'nieokreślona', ]; ================================================ FILE: lang/pl/families.php ================================================ [ 'title' => 'Nowa rodzina', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Ta rodzina wymarła.', 'members' => 'Lista członków rodziny. Aby dodać postać do rodziny, wybierz ją z listy w pozycji "Rodzina" podczas edycji tej postaci.', ], 'index' => [], 'lists' => [ 'empty' => 'Układaj rodowody, klany i koligacje łączące stworzone postacie.', ], 'members' => [ 'create' => [ 'helper' => 'Dodaj jednego lub więcej członków do :name', 'success' => '{0} Nie dodano członków.|{1} Dodano 1 członka.|[2,*] Dodano :count członków.', 'title' => 'Nowi członkowie', ], ], 'placeholders' => [ 'name' => 'Nazwisko rodowe', 'type' => 'Królewska, szlachecka, wymarła', ], 'show' => [ 'tabs' => [ 'tree' => 'Drzewo genealogiczne', ], ], ]; ================================================ FILE: lang/pl/faq.php ================================================ [ 'account_settings' => 'Ustawienia konta', 'answer' => 'Aby usunąć konto, przejdź na stronę :account i przewiń na dół, do sekcji usuwania. W ten sposób usuniesz zarówno konto, jak wszystkie kampanie których jesteś jedynym uczestnikiem.', 'question' => 'Jak mogę usunąć konto?', ], 'app_backup' => [ 'answer' => 'Tworzymy dwie kopie zapasowe dziennie, żeby nie doszło do utraty danych. Na tym serwerze są nasze własne kampanie, nie zamierzamy ryzykować!', 'question' => 'Jak często Kanka archiwizuje dane?', ], 'attribute-templates' => [ 'answer' => <<<'TEXT' Najlepiej wytłumaczyć działanie szablonów cech na przykładzie. Dajmy na to, w twoim świecie jest wiele Miejsc, i dla większości z nich chcesz zanotować konkretne Cechy - na przykład "Populację", "Klimat" i "Poziom przestępczości". Możesz dodawać te pozycje ręcznie do każdego miejsca, ale o męczące i czasem zdarzy ci się zapomnieć o "Poziomie przestępczości". I tu przydają się szablony cech. Możesz stworzyć szablon zawierający dowolne cechy (Populację, Klimat, Poziom przestępczości i tak dalej) i dodawać go potem do kolejnych miejsc. Dzięki temu zadane cechy pojawią się w odpowiedniej zakładce. A tobie zostanie tylko zmienić ich wartości, zamiast dodawać je ręcznie za każdym razem. TEXT , 'question' => 'Co to są te "Szablony cech"?', ], 'backup' => [ 'answer' => 'Raz dziennie możesz wyeksportować dane ze swojej kampanii jako plik ZIP. W tym celu kliknij w menu po lewej stronie na pozycję "Kampania", a potem wybierz opcję "Eksportuj". Stworzony w ten sposób plik możesz pobrać przez 30 minut. Takich danych nie można załadować z powrotem do Kanki, więc pobieraj je dla własnego spokoju ducha albo jeżeli planujesz zrezygnować z używania naszego narzędzia.', 'question' => 'Jak mogę stworzyć kopię zapasową albo wyeksportować kampanię?', ], 'bugs' => [ 'answer' => 'Po prostu dołącz do naszego serwera :discord i zgłoś błąd na kanale #error-and-bugs', 'question' => 'Jak zgłaszać błędy?', ], 'campaign-sync' => [ 'answer' => 'Kanka na to nie pozwala. Jeżeli prowadzisz wiele drużyn w tym samym świecie możesz jednak dodać wszystkich graczy do tej samej kampanii, a potem dzielić im dostęp za pomocą systemu misji, etykiet i uprawnień.', 'question' => 'Czy mogę synchronizować elementy pomiędzy kilkoma kampaniami?', ], 'custom' => [ 'answer' => 'Kanka posiada zdefiniowany zestaw elementów, które wchodzą ze sobą w konkretne interakcje. Dopuszczenie rodzajów tworzonych przez użytkowników wymagałoby napisania aplikacji od nowa i stałoby w sprzeczności z naszym głównym celem - chcemy pomagać ludziom w tworzeniu światów, a nie zmuszać ich do główkowania, jak je katalogować. Możesz z łatwością odróżniać od siebie własne kategorie elementów dzięki systemowi etykiet.', 'question' => 'Czy mogę tworzyć własne rodzaje elementów?', ], 'delete-campaign' => [ 'answer' => 'Wejdź na pulpit kampanii i kliknij "Kampania" w menu po lewej stronie. Jeżeli jesteś jedynym użytkownikiem kampanii, pojawi się guzik "Usuń". Usunięcie kampanii to akcja nieodwracalna, która wymaże z naszych serwerów wszystko, łącznie z obrazami.', 'question' => 'Jak usunąć kampanię?', ], 'discord' => [ 'answer' => 'By połączyć Kankę z profilem na :discord należy najpierw kliknąć na awatar w prawym górnym rogu aplikacji, a następnie wybrać opcję Profil. Tam przejdź na podstronę :apps i wybierz Połącz.', 'question' => 'Jak połączyć konto Kanki z moim profilem Discord?', ], 'early-access' => [ 'answer' => 'Wczesny dostęp to sposób, w jaki nagradzamy naszych wspaniałych subskrybentów. Otrzymują oni możliwość korzystania z najnowszych modułów na 30 dni przed pozostałymi użytkownikami.', 'question' => 'Czym jest "wczesny dostęp"?', ], 'entity-notes' => [ 'answer' => 'Każdy element ma zakładkę "Komentarze", gdzie można zapisywać niewielkie fragmenty tekstu, widoczne wyłącznie dla ciebie (przydaje się przy kilku prowadzących), tylko dla administratorów, albo dla wszystkich. Możesz też pozwolić graczom dodawać i edytować takie notki, nie zapewniając im przy tym możliwości edycji całych elementów.', 'question' => 'Jak ukrywać w Kance tylko część informacji?', ], 'fields' => [ 'answer' => 'Odpowiedź', 'category' => 'Kategoria', 'locale' => 'Język', 'order' => 'Kolejność', 'question' => 'Pytanie', ], 'free' => [ 'answer' => <<<'TEXT' Tak! Uważamy szczerze, że nasza sytuacja finansowa nie powinna wpływać na radość z gry w RPG i wymyślania światów, więc Kanka zawsze będzie za darmo. Ale jeżeli chcesz przyczynić się rozwoju aplikacji, wesprzeć nas i głosować na funkcjonalności, które najbardziej ci się przydadzą, możesz wybrać płatną subskrypcję. Oprócz głosowania nad kierunkiem, w którym rozwijać się będzie Kanka, wspierając nas otrzymujesz też :boosters, możesz zamieszczać zamieszczać więcej plików, twoje imię trafia do galerii sław, dostajesz ładniejsze domyślne ikony i nie tylko! TEXT , 'question' => 'Czy aplikacja zawsze będzie darmowa?', ], 'gods-and-religions' => [ 'answer' => 'Polecamy tworzyć bogów jako Postaci, a religie jako Organizacje. Aby szybciej odszukać bóstwa na liście, możesz dać im odpowiednie etykiety.', 'question' => 'Jak tworzyć bogów i religie?', ], 'help' => [ 'answer' => 'Po pierwsze, bardzo dziękujemy za chęć pomocy! Zawsze szukamy ludzi gotowych pomóc przy tłumaczeniu, testowaniu funkcjonalności i wprowadzaniu nowych użytkowników. Zachęcamy też do promowania Kanki w miejscach, o których nawet nie pomyśleliśmy. Najlepiej, jeżeli dołączysz do serwera :discord, gdzie działa kanał dla osób które nam pomagają.', 'question' => 'Jak mogę pomóc?', ], 'map' => [ 'answer' => 'Moduł Map pozwala dodawać obrazy w formacie PNG, JPG i SGV. Mapy mogą mieć wiele warstw i można nanosić na nie oznaczenia o różnych kształtach i rozmiarach, wiążąc je z innymi elementami kampanii.', 'question' => 'Czy do Kanki mogę dodawać mapy?', ], 'mobile' => [ 'answer' => 'Obecnie Kanka nie posiada dedykowanej aplikacji mobilnej, ale większość opcji działa bez problemu na urządzeniach mobilnych. Mamy nadzieję, że subskrypcje pozwolą nam kiedyś zlecić komuś wykonanie takiej aplikacji, ale to raczej nie zdarzy się w bliskiej przyszłości.', 'question' => 'Czy istnieje wersja mobilna? Czy jest w planach?', ], 'monsters' => [ 'answer' => 'Polecamy używać modułu Rasy by dodawać ludy, gatunki, potwory i wszelkie żywe istoty nie będące postaciami.', 'question' => 'Jak tworzyć potwory?', ], 'multiworld' => [ 'answer' => 'Możesz brać udział w dowolnej liczbie kampanii, w tym w kampaniach własnego autorstwa. By zmienić kampanię albo dodać nową otwórz pulpit - klikając nazwę kampanii w lewym górnym rogu otworzysz menu zarządzania kampaniami.', 'question' => 'Czy mogę mieć więcej niż jedną kampanię?', ], 'nested' => [ 'answer' => 'Jeżeli wolisz używać domyślnie widoku hierarchii elementów, zamiast ich list, przejdź do Profilu i wybierz opcję Układ. Tam możesz zaznaczyć Widok hierarchii. Opcja działa wyłącznie dla twojego konta, a nie wszystkich uczestników kampanii.', 'question' => 'Czy mogę ustawić wyświetlanie hierarchii jako domyślne?', ], 'permissions' => [ 'answer' => 'Oczywiście, właśnie po to stworzyliśmy Kankę! Kiedy zaprosisz graczy do kampanii, możesz nadawać im role i uprawnienia. Stworzyliśmy bardzo elastyczny system (który pozwala zarządzać uprawnieniami globalnie i lokalnie), który łatwo dostosować do dowolnej sytuacji i potrzeb.', 'question' => 'Czy mogę jakoś ograniczyć liczbę informacji, do których mają dostęp moi gracze?', ], 'plans' => [ 'answer' => <<<'TEXT' Naszym planem długoterminowym jest stworzyć wszechstronną aplikację do budowania światów i zarządzana kampaniami, która jest systemowo niezależna i którą zarządza społeczność dzięki "Szablonom Społeczności". Chcemy też integrować naszą Kankę z innymi narzędziami, na przykład aplikacjami do gry zdalnej. Sami używamy Kanki, więc nie zamierzamy jej porzucać ani zaprzestawać rozwoju. Ale, tak na wszelki wypadek, cały projekt ma charakter open source i może być rozwijany gdyby coś się z nami stało. TEXT , 'question' => 'Jakie macie plany na dalszą przyszłość?', ], 'public-campaigns' => [ 'answer' => 'Możesz przeglądać stronę :public-campaigns by zobaczyć kampanie stworzone przez innych użytkowaników.', 'question' => 'Jak inni używają Kanki?', ], 'renaming-modules' => [ 'answer' => 'Podstawowa Kanka nie pozwala zmieniać nazw modułów, przez wzgląd na poprawność stylistyczną zwłaszcza w językach posiadających rodzaje gramatyczne. W kampaniach doładowanych można jednak modyfikować nazwy wyświetlane w menu po lewej stronie używając skryptów CSS.', 'question' => 'Czy mogę zmieniać nazwy modułów? Na przykład Rodziny na Klany, albo Organizacje na Frakcje?', ], 'sections' => [ 'community' => 'Społeczność', 'general' => 'Ogólne', 'other' => 'Inne', 'permissions' => 'Uprawnienia', 'pricing' => 'Opłaty', 'worldbuilding' => 'Tworzenie światów', ], 'show' => [ 'return' => 'Powrót do FAQ', 'timestamp' => 'Ostatnia aktualizacja: :date', 'title' => 'FAQ :name', ], 'unboost' => [ 'answer' => 'Usunięcie doładowania nie usuwa żadnych danych, które dodano podczas doładowania, po prostu je ukrywa. Jeżeli ponownie doładujesz kampanię, wszystkie opcje staną się znów dostępne w postaci, jaką miały przed usunięciem doładowania.', 'question' => 'Co dzieje się z kampanią, która przestaje być doładowana?', ], 'user-switch' => [ 'answer' => 'Uprawnienia, zwłaszcza w większych kampaniach, mogą być dość skomplikowane. Jako administrator możesz zawsze wejść na stronę użytkownika kampanii i nacisnąć opcję "Przełącz", która znajduje się obok nazwisk uczestników nie posiadających statusu admina. Logujesz się wówczas jako ten użytkownik i widzisz kampanię tak, jak on. W ten sposób najłatwiej sprawdzić, czy uprawnienia działają poprawnie.', 'question' => 'No to mam już uprawnienia kampanii, jak je teraz przetestować?', ], 'visibility' => [ 'answer' => 'Tylko osoby, które otrzymały od ciebie zaproszenie do kampanii widzą twoje dzieło. Wszystkie dane są prywatne i masz nad nimi pełną kontrolę. Jeżeli chcesz, możesz nadać kampanii status publiczny, który pozwala ją przeglądać niezarejestrowanym użytkownikom.', 'question' => 'Czy inni widzą mój świat?', ], ]; ================================================ FILE: lang/pl/fields.php ================================================ [ 'placeholder' => 'Wybierz obraz z galerii kampanii', ], 'gallery-header' => [ 'description' => 'Jeżeli element nie ma obrazu w nagłówku, wyświetlaj obraz z galerii kampanii.', ], 'gallery-image' => [ 'description' => 'Jeżeli element nie ma własnego obrazu, wyświetlaj obraz z galerii kampanii.', ], 'header-image' => [ 'boosted-description' => 'Wyświetlaj obraz w tle nagłówka elementu w :boosted-campaign', 'description' => 'Wyświetlaj obraz w tle nagłowka elementu. Dla osiągnięcia najlepszego efektu użyj bardzo dużego obrazu.', 'title' => 'Nagłówek', ], 'tooltip' => [], ]; ================================================ FILE: lang/pl/filters.php ================================================ [ 'bookmark' => 'Zakładka', ], 'alerts' => [ 'copy' => 'Filtry skopiowano do schowka', ], 'bookmark' => [ 'helper' => 'Tworzy zakładkę dla widoku używającego obecnych filtrów.', 'name' => ':module (z filtrem)', 'premium' => 'Dodanie kolejnych zakładek wymaga kampanii premium.', 'success' => 'Stworzono zakładkę', ], 'helpers' => [ 'guest' => 'Zaloguj się by zobaczyć wyniki filtrowania.', 'icon' => 'Dodaje zakładce specjalną ikonę :fontawesome, na przykład :example.', 'icon-premium' => 'Dodaje zakładce specjalną ikonę :fontawesome, na przykład :example w kampanii :premium.', ], ]; ================================================ FILE: lang/pl/footer.php ================================================ 'O nas', 'blog' => 'Blog', 'boosters' => 'Doładowania', 'community' => 'Społeczność', 'company' => 'Firma', 'contact' => 'Kontakt', 'copyright' => 'Copyright :copy :year Owlchester SNC', 'documentation' => 'Dokumentacja', 'features' => 'Funkcjonalności', 'kb' => 'Baza wiedzy', 'language-switcher' => [ 'other' => 'Inne języki', 'title' => 'Wybierz język', ], 'made' => 'Stworzono z ❤️ w Genewie, Szwajcaria', 'newsletter' => 'Newsletter', 'platform' => 'Platforma', 'plugins' => 'Biblioteka dodatków', 'premium' => 'Kampanie premium', 'press-kit' => 'Dla prasy', 'pricing' => 'Cena subskrybcji', 'privacy' => 'Prywatność', 'public-campaigns' => 'Kampanie publiczne', 'resources' => 'Zasoby', 'roadmap' => 'W przygotowaniu', 'security' => 'Bezpieczeństwo', 'server-time' => 'To czas naszego serwera (:server).', 'showcase' => 'Galeria', 'status' => 'Status usługi', 'terms' => 'Warunki', 'thanks' => 'Ogromne wyrazy wdzięczności dla wszystkich subskrybujących.', 'translator_call' => 'Kanka jest tłumaczona na inne języki dzięki zaangażowaniu naszej społeczności. Jeżeli chcesz pomóc w przekładzie na swój język, daj nam znać na naszym serwerze :discord.', 'whats-new' => 'Co nowego', ]; ================================================ FILE: lang/pl/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Głosowania społeczności', ]; ================================================ FILE: lang/pl/front/hall-of-fame.php ================================================ 'Galeria Sław', ]; ================================================ FILE: lang/pl/front/kb.php ================================================ [], 'show' => [], 'title' => 'Baza wiedzy', ]; ================================================ FILE: lang/pl/front/newsletter.php ================================================ [ 'learn_more' => 'Dowiedz się więcej', 'subscribe' => 'Subskrybuj', ], 'fields' => [ 'firstname' => 'Imię', 'lastname' => 'Nazwisko', 'notifications' => 'Powiadomienia', ], 'groups' => [ 'all' => 'Chcę otrzymywać co jakiś czas powiadomienia o nowych funkcjach, głosowaniach społeczności, wydarzeniach itd.', 'newsletter' => 'Newsletter', ], 'headline' => 'Zasubskrybuj jeden albo wszystkie newslettery, by pozostać na bieżąco.', 'title' => 'Powiadomienia E-mail', ]; ================================================ FILE: lang/pl/front.php ================================================ [], 'actions' => [], 'campaigns' => [ 'public' => [ 'filters' => [ 'is-premium' => 'To kampania premium!', ], ], ], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => 'Jasne!', 'link' => 'Więcej', 'message' => 'By zapewnić jak najlepsze doświadczenie, ta strona używa cookies', ], 'faq' => [], 'featured_campaigns' => [], 'features' => [ 'api' => [ 'link' => 'Dokumentacja API', ], 'patreon' => [ 'api_calls' => 'Więcej wywołań API (90 na minutę)', 'boosts' => 'Doładowania kampanii', 'default_image' => 'Własne domyślne ikony elementów kampanii', 'discord' => 'Prywatny kanał na Discordzie', 'free' => 'Darmowa', 'hall_of_fame' => 'Miejsce w :link', 'impact' => 'Wpływ na przyszłe funkcje', 'monthly_vote' => 'Udział w głosowaniu społeczności nad nowymi funkcjami', 'pagination' => 'Większa liczba elementów na stronie', 'upload_limit' => 'Wielkość plików', 'upload_limit_map' => 'Wielkość mapy', ], ], 'first_block' => [], 'footer' => [], 'goodbye' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Prowadzisz gry RPG, wymyślasz światy i historie? Dzięki naszemu menedżerowi kampanii i narzędziom światotwórczym z łatwością zorganizujesz, zaplanujesz i przeprowadzisz grę. Słuchamy naszej społeczności i, co najlepsze, jesteśmy całkiem za darmo!', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Pulpit', 'login' => 'Logowanie', 'register' => 'Rejestracja', 'register_free' => 'Darmowa rejestracja', ], 'meta' => [ 'description' => 'Kanka to elastyczny cyfrowy menedżer kampanii RPG i narzędzie do projektowania światów', 'title' => 'Kanka - menedżer kampanii RPG i narzędzie tworzenia światów dostępne online', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Za darmo', 'month' => 'miesięcznie', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Światotwórstwo, gry fabularne, zarządzanie kampaniami RPG', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/pl/gallery.php ================================================ [ 'gallery' => 'Z galerii', 'url' => 'Pobierz obraz z odnośnika', ], 'browse' => [ 'layouts' => [ 'large' => 'Duże miniatury', 'small' => 'Małe miniatury', ], 'search' => [ 'placeholder' => 'Wyszukaj obraz w galerii', ], 'title' => 'Galeria', 'unauthorized' => 'Żadna z twoich ról nie posiada uprawień do "przeglądania galerii".', ], 'cta' => [ 'action' => 'Odblokuj większy magazyn', 'helper' => 'Uzyskaj do :size GB magazynu w :premium-campaign.', 'title' => 'Magazy jest pełny', ], 'delete' => [ 'success' => '[0] Usunięto 0 elementów|[1] Usunięto jeden element|{2,4} Usunięto :count elementy|{5,*} Usunięto :count elementów', ], 'download' => [ 'errors' => [ 'copy_failed' => 'Nasz serwer nie mógł pobrać obrazu', 'gallery_full_free' => 'Galeria jest pełna. Odblokuj wersję premium by zwiększyć ilość miejsca.', 'gallery_full_premium' => 'Galeria jest pełna. Najpierw usuń nieużywane pliki.', 'invalid_format' => 'Niewłaściwy rodzaj pliku.', 'too_big' => 'Plik jest zbyt duży.', 'unauthorized' => 'Żadna z twoich ról nie posiada uprawień do "przesyłania obrazów".', ], ], 'file' => [ 'saved' => 'Zapisano', ], 'filters' => [ 'only_unused' => 'Pokaż tylko nieużywane pliki', 'sort' => 'Sortuj według', ], 'move' => [ 'success' => '[0] Przeniesiono 0 elementów|[1] Przeniesiono jeden element|{2,4} Przeniesiono :count elementy|{5,*} Przeniesiono :count elementów', ], 'update' => [ 'home' => 'Folder domowy', 'success' => '[0] Zmieniono 0 elementów|[1] Zmieniono jeden element|{2,4} Zmieniono :count elementy|{5,*} Zmieniono :count elementów', ], ]; ================================================ FILE: lang/pl/general.php ================================================ 'Odznacz wszystkie', 'documentation' => 'Więcej o tej funkcji dowiesz się z dokumentacji', 'done' => 'Gotowe', 'learn-more' => 'Więcej', 'no' => 'Nie', 'required' => 'Wymagane', 'select_all' => 'Wybierz wszystkie', 'success' => [ 'created' => 'Stworzono :name.', 'deleted' => 'Usunięto :name.', 'deleted-cancel' => 'Anulowano usunięcie :name.', 'updated' => 'Zmieniono :name.', ], 'tutorial' => 'Zobacz tutorial', 'yes' => 'Tak', ]; ================================================ FILE: lang/pl/genres.php ================================================ 'Historia alternatywna', 'cyberpunk' => 'Cyberpunk', 'fantasy' => 'Fantasy', 'historical' => 'Historia', 'many_worlds' => 'Wieloświat', 'modern' => 'Współczesność', 'occult' => 'Okultyzm', 'post_apocalyptic' => 'Postapokalipsa', 'pulp' => 'Pulp', 'science_fantasy' => 'Science fantasy', 'science_fiction' => 'Science fiction', 'space_opera' => 'Space opera', 'steampunk' => 'Steampunk', 'superhero' => 'Superbohaterowie', 'urban_fantasy' => 'Urban fantasy', 'western' => 'Western', ]; ================================================ FILE: lang/pl/header.php ================================================ [ 'title' => 'Co nowego', ], 'notifications' => [ 'dismiss' => 'Odrzuć', 'no-unread' => 'Brak nieprzeczytanych wiadomości', 'read_all' => 'Przeczytaj wszystko', ], 'qq' => [ 'tooltip' => 'Dodaj element albo komentarz', ], 'toggle_navigation' => 'Przełącz nawigację', 'user' => [ 'settings' => 'Ustawienia', 'sign-out' => 'Wyloguj', 'upgrade' => 'Zwiększanie', 'your-profile' => 'Twój profil', ], ]; ================================================ FILE: lang/pl/helpers.php ================================================ [], 'api-filters' => [ 'description' => 'W węźle końcowym API :name dostępne są następujące filtry', 'title' => 'Filtry API', ], 'attributes' => [ 'link' => 'Opcje cech', ], 'calendar-widget' => [ 'info' => 'Czemu wciąż to widzę?', 'title' => 'Widżet kalendarza', ], 'dice' => [], 'entity_templates' => [], 'filters' => [ 'title' => 'Jak używać filtrów', ], 'link' => [ 'description' => 'Możesz z łatwością tworzyć odnośniki do innych elementów kampanii przy pomocy następującego zapisu.', ], 'map' => [], 'pins' => [], 'public' => 'Zobacz tutorial na Youtube dotyczący kampanii publicznych.', 'troubleshooting' => [ 'description' => 'Skierował cię na tę stronę członek zespołu Kanki. Wybierz kampanię z rozwijanego menu by stworzyć zgłoszenie, dzięki któremu ktoś od nas będzie mógł czasowo dołączyć do kampanii jako administrator.', 'errors' => [ 'token_exists' => 'Dla :campaign istnieje już zgłoszenie', ], 'save_btn' => 'Stwórz zgłoszenie', 'select_campaign' => 'Wybierz kampanię', 'subtitle' => 'Przybądźcie z odsieczą!', 'success' => 'Skopuj następujące zgłoszenie i prześlij je do kogoś z zespołu Kanki', 'title' => 'Rozwiązywanie problemów', ], 'widget-filters' => [ 'description' => 'Możesz filtrować elementy wyświetlane przez zmodyfikowany widżet tworząc listę pól dla elementów albo wartości. Na przykład możesz użyć :example by filtrować martwe postaci na liście BNów.', 'link' => 'filtry widżetów', 'title' => 'Filtry widżetów pulpitu', ], ]; ================================================ FILE: lang/pl/history.php ================================================ [ 'show-old' => 'Zmiany', ], 'cta' => 'Wyświetla listę ostatnich zmian w kampanii.', 'empty' => 'Brak', 'fields' => [ 'action' => 'Działanie', 'details' => 'Szczegóły', 'when' => 'Kiedy', 'who' => 'Kto', ], 'filters' => [ 'all-actions' => 'Wszystkie działania', 'all-users' => 'Wszyscy uczestnicy', 'no-results' => 'Brak rezultatów. Użyj innego filtra albo wprowadź jakieś zmiany w elementach kampanii.', ], 'helpers' => [ 'base' => 'Lista zawiera zmiany elementów kampanii przeprowadzone w ciągu :amount miesięcy. Najnowsze wyświetlane są jako pierwsze.', 'changes' => 'Poniższe pola miały poprzednio następujące wartości.', ], 'log' => [ 'create' => ':user stworzył :entity', 'create_post' => ':user napisał komentarz ":post" dotyczący :entity', 'delete' => ':user usunął :entity', 'delete_post' => ':user usunął komentarz dotyczący :entity', 'reorder_post' => ':user zmienił kolejność komentarzy dotyczących :entity', 'restore' => ':user przywrócił :entity', 'update' => ':user zmienił :entity', 'update_post' => ':user zmienił komentarz ":post" dotyczący :entity', 'update_tree' => ':user zmienił genealogię elementu :entity', ], 'title' => 'Historia', 'unknown' => [ 'entity' => 'nieznany element', ], ]; ================================================ FILE: lang/pl/items.php ================================================ [ 'title' => 'Nowy przedmiot', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_equipped' => 'Na wyposażeniu', 'price' => 'Cena', 'size' => 'Rozmiar', 'weight' => 'Waga', ], 'helpers' => [], 'hints' => [], 'index' => [], 'inventories' => [], 'lists' => [ 'empty' => 'Dodawaj występującą w świecie broń, artefakty oraz przedmioty o szczególnym znaczeniu.', ], 'placeholders' => [ 'price' => 'Cena przedmiotu', 'size' => 'Wielkość, ciężar, wymiary', 'type' => 'Broń, eliksir, artefakt', 'weight'=> 'Waga jednego przedmiotu', ], 'show' => [ 'tabs' => [ 'inventories' => 'W posiadaniu', ], ], ]; ================================================ FILE: lang/pl/journals.php ================================================ [ 'title' => 'Nowy dziennik', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Autor', 'date' => 'Data', ], 'helpers' => [], 'index' => [], 'journals' => [], 'lists' => [ 'empty' => 'Twórz dzienniki opisujące dotychczasowe przygody, przemyślenia postaci albo podsumowania sesji.', ], 'placeholders' => [ 'author' => 'Osoba, która napisała dziennik', 'date' => 'Data utworzenia dziennika (w prawdziwym świecie)', 'type' => 'Sesja, jednostrzał, szkic', ], 'show' => [], ]; ================================================ FILE: lang/pl/languages.php ================================================ [ 'ca' => 'Kataloński', 'cs' => 'Czeski', 'de' => 'Niemiecki', 'el' => 'Grecki', 'en' => 'Angielski', 'en-US' => 'Amerykański angielski', 'es' => 'Hiszpański', 'fr' => 'Francuski', 'gl' => 'Galicyjski', 'he' => 'Hebrajski', 'hr' => 'Chorwacki', 'hu' => 'Węgierski', 'it' => 'Włoski', 'nb' => 'Norweski (Bokmål)', 'nl' => 'Holenderski', 'pl' => 'Polski', 'pt-BR' => 'Brazylijski portugalski', 'ru' => 'Rosyjski', 'sk' => 'Słowacki', 'tr' => 'Turecki', ], 'header'=> 'Języki', ]; ================================================ FILE: lang/pl/lists.php ================================================ [ 'learn' => 'Poznaj ten moduł', 'public'=> 'Sprawdź jak używają go inni', ], 'empty' => [ 'title' => 'Na razie brak :plural.', ], ]; ================================================ FILE: lang/pl/locations.php ================================================ [], 'create' => [ 'title' => 'Nowe miejsce', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [ 'is_destroyed' => 'Zniszczone', ], 'helpers' => [ 'characters' => 'Wyświetla postaci znajdujące się w tym miejscu i wszystkich miejscach pochodnych, albo wyłącznie tutaj.', ], 'hints' => [ 'is_destroyed' => 'To miejsce zostało zniszczone.', ], 'index' => [], 'items' => [], 'journals' => [], 'lists' => [ 'empty' => 'Dodaj pierwsze miasto, tawernę albo tajemnicze ruiny, by rozpocząć tworzenie świata', ], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'type' => 'Miasto, królestwo, ruiny', ], 'show' => [], ]; ================================================ FILE: lang/pl/maps/.gitkeep ================================================ ================================================ FILE: lang/pl/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Tryb edycji', 'exit-edit-mode' => 'Zakończ tryb edycji', 'finish-drawing' => 'Zakończ rysowanie wielokąta', ], 'notifications' => [ 'start-drawing' => 'Kliknij na mapę, by zacząć rysować wielokąt', ], 'toggle' => 'Otwórz/zamknij wszystkie grupy', ]; ================================================ FILE: lang/pl/maps/groups.php ================================================ [ 'add' => 'Dodaj nową kategorię', ], 'bulks' => [ 'delete' => '{1} Usunięto :count kategorię.|[2,3,4] Usunięto :count kategorie.|[5,*] Usunięto :count kategorii.', 'patch' => '{1} Zmieniono :count kategorię.|[2,3,4] Zmieniono :count kategorie.|[5,*] Zmieniono :count kategorii.', ], 'create' => [ 'helper' => 'Dodaje nową kategorię do :name. Potem można', 'success' => 'Stworzono kategorię :name.', 'title' => 'Nowa kategoria', ], 'delete' => [ 'success' => 'Usunięto kategorię :name.', ], 'edit' => [ 'success' => 'Zmieniono kategorię :name.', 'title' => 'Edycja kategorii :name', ], 'fields' => [ 'is_shown' => 'Pokaż kategorię znaczników', 'parent' => 'Grupa źródłowa', 'position' => 'Kolejność', ], 'helper' => [ 'amount_v3' => 'Znaczniki można włączyć w kategorie. Podczas eksploracji mapy można potem zaznaczyć kategorię znaczników, by wyświetlić albo ukryć wszystkie należące do niej elementy.', ], 'hints' => [ 'is_shown' => 'Zaznacz, by ta kategoria znaczników wyświetlała się na mapie domyślnie.', ], 'index' => [ 'title' => 'Kategorie mapy :name', ], 'pitch' => [ 'max' => [ 'helper' => 'Nie możesz tworzyć kolejnych kategorii dopóki którejś nie usuniesz.', 'limit' => 'Osiągnięto limit kategorii na tej mapie', ], 'upgrade' => [ 'limit' => 'Osiągnięto limit :limit kategorii na tej mapie', 'upgrade' => 'Ulepsz kampanię do poziomu premium by zwiększyć limit do :limit i uzyskać większą twórczą swobodę.', ], ], 'placeholders' => [ 'name' => 'Sklepy, skarby, BNi.', 'position' => 'Pole opcjonalne, pozwala ustalić kolejność w której pojawiają się kategorie.', 'position_list' => 'Po :name', ], 'reorder' => [ 'save' => 'Zapisz nową kolejność', 'success' => '{1} Przesunięto :count kategorię.|[2,3,4] Przesunięto :count kategorie.|[5,*] Przesunięto :count kategorii.', 'title' => 'Zmień kolejność kategorii', ], ]; ================================================ FILE: lang/pl/maps/layers.php ================================================ [ 'add' => 'Dodaj nową warstwę', ], 'base' => 'Warstwa podstawowa', 'bulks' => [ 'delete' => '{1} Usunięto :count warstwę.|[2,3,4] Usunięto :count warstwy.|[5,*] Usunięto :count warstw.', 'patch' => '{1} Zmieniono :count warstwę.|[2,3,4] Zmieniono :count warstwy.|[5,*] Zmieniono :count warstw.', ], 'create' => [ 'success' => 'Stworzono warstwę :name.', 'title' => 'Nowa warstwa', ], 'delete' => [ 'success' => 'Usunięto warstwę :name.', ], 'edit' => [ 'success' => 'Zmieniono warstwę :name.', 'title' => 'Edycja warstwy :name', ], 'fields' => [ 'position' => 'Kolejność', 'type' => 'Rodzaj warstwy', ], 'helper' => [ 'amount_v2' => 'Dodawaj do mapy warstwy by zmieniać ilustrację wyświetlaną pod znacznikami.', 'is_real' => 'Podczas używania OpenStreetMaps warstwy są niedostępne.', ], 'index' => [ 'title' => 'Warstwy mapy :name', ], 'pitch' => [ 'max' => [ 'helper' => 'Nie możesz dodawać kolejnych warstw dopóki którejś nie usuniesz.', 'limit' => 'Osiągnięto limit warstw mapy', ], 'upgrade' => [ 'limit' => 'Osiągnięto limit :limit warstw mapy', 'upgrade' => 'Ulepsz kampanię do poziomu premium by zwiększyć limit warstw do :limit i zyskać większą swobodę twórczą.', ], ], 'placeholders' => [ 'name' => 'Podziemia, poziom 2, wrak statku', 'position' => 'Pole opcjonalne, pozwala ustalić kolejność wyświetlania warstw.', 'position_list' => 'Po :name', ], 'reorder' => [ 'save' => 'Zapisz nową koleność', 'success' => '{1} Przesunięto :count warstwę.|[2,3,4] Przesunięto :count warstwy.|[5,*] Przesunięto :count warstw.', 'title' => 'Zmień kolejność warstw', ], 'short_types' => [ 'overlay' => 'Nakładka', 'overlay_shown' => 'Nakładka (pokazuj domyślnie)', 'standard' => 'Standardowa', ], 'types' => [ 'overlay' => 'Nakładka (wyświetlaj nad aktywną warstwą)', 'overlay_shown' => 'Nakładka wyświetlana domyślnie', 'standard' => 'Warstwa standardowa (do przełączania)', ], ]; ================================================ FILE: lang/pl/maps/markers.php ================================================ [ 'entry' => 'Dodaj opis do tego znacznika.', 'remove' => 'Usuń znacznik', 'reset-polygon' => 'Resetuj pozycje', 'save_and_explore' => 'Zapisz i eksploruj', 'start-drawing' => 'Zacznij rysować', 'update' => 'Edytuj znacznik', ], 'bulks' => [ 'delete' => '{1} Usunięto :count znacznik.|[2,3,4] Usunięto :count znaczniki.|[5,*] Usunięto :count znaczników.', 'patch' => '{1} Zmieniono :count znacznik.|[2,3,4] Zmieniono :count znaczniki.|[5,*] Zmieniono :count znaczników.', ], 'circle_sizes' => [ 'custom' => 'Własny', 'huge' => 'Ogromny', 'large' => 'Duży', 'small' => 'Mały', 'standard' => 'Domyślny', 'tiny' => 'Malutki', ], 'create' => [ 'success' => 'Stworzono znacznik :name.', 'title' => 'Nowy znacznik', ], 'delete' => [ 'success' => 'Usunięto znacznik :name.', ], 'details' => [ 'from-entity' => 'Z elementu', ], 'edit' => [ 'success' => 'Zmieniono znacznik :name.', 'title' => 'Edycja znacznika :name', ], 'fields' => [ 'bg_colour' => 'Kolor tła', 'circle_radius' => 'Promień okręgu', 'copy_elements' => 'Kopiuj elementy', 'custom_icon' => 'Własna ikona', 'custom_shape' => 'Własny kształt', 'font_colour' => 'Kolor ikony', 'group' => 'Kategoria znaczników', 'icon' => 'Ikona', 'is_draggable' => 'Można przesuwać', 'latitude' => 'Szerokość', 'longitude' => 'Długość', 'opacity' => 'Nieprzejrzystość', 'pin_size' => 'Wielkość znacznika', 'polygon_style' => [ 'stroke' => 'Kolor konturu', 'stroke-opacity' => 'Przejrzystość konturu', 'stroke-width' => 'Szerokość konturu', ], 'popupless' => 'Wyświetlanie dymków', 'size' => 'Rozmiar', ], 'helpers' => [ 'base' => 'Dodaj znacznik, klikając w dowolnym miejscu mapy.', 'copy_elements' => 'Kopiuj kategorie, warstwy i znaczniki.', 'copy_elements_to_campaign' => 'Kopiuje kategorie, warstwy i znaczniki na mapach. Znaczniki prowadzące do elementów zostaną zamienione na standardowe.', 'css' => 'Definiuje klasę CSS dodaną do znacznika.', 'custom_icon_v2' => 'Używaj ikon z :fontawesome, :rpgawesome, albo własnych plików SVG. Więcej instrukcji znajdziesz tutaj: :docs.', 'custom_radius' => 'Wybierz opcję z rozwijanej listy by określić wielkość.', 'draggable' => 'Pozwala przeciągać znacznik po mapie w trybie eksploracji.', 'is_popupless' => 'Wyłącza wyświetlanie dymków z opisem po najechaniu na element kursorem.', 'label' => 'Wyświetla na mapie test zawierający nazwę tego znacznika albo elementu, z którym jest związany.', 'polygon' => [ 'edit' => 'Modyfikuj wielokąt przeciągając ścianki i kąty.', ], ], 'hints' => [ 'entry' => 'Modyfikuj znacznik by stworzyć nowy opis elementu.', ], 'icons' => [ 'custom' => 'Własna', 'entity' => 'Element', 'exclamation' => 'Wykrzyknik', 'marker' => 'Znacznik', 'question' => 'Pytajnik', ], 'index' => [ 'title' => 'Znaczniki mapy :name', ], 'pitches' => [ 'poly' => 'Rysuj własne wielokąty, reprezentujące granice albo nieregularne obszary.', ], 'placeholders' => [ 'custom_icon' => 'Spróbuj :example1 albo :example2', 'custom_shape' => '100,100 200,240 340,110', 'name' => 'Wymagana, jeżeli nie powiązano z żadnym elementem', ], 'presets' => [ 'helper' => 'Kliknij na przygotowany wzór znacznika by go załadować, albo zaprojektuj nowy.', ], 'shapes' => [ '0' => 'Okrąg', '1' => 'Kwadrat', '2' => 'Trójkąt', '3' => 'Własny', ], 'sizes' => [ '0' => 'Malutki', '1' => 'Standardowy', '2' => 'Mały', '3' => 'Duży', '4' => 'Wielki', ], 'tabs' => [ 'circle' => 'Okrąg', 'label' => 'Podpis', 'marker' => 'Znacznik', 'polygon' => 'Wielokąt', 'preset' => 'Wzór', ], ]; ================================================ FILE: lang/pl/maps.php ================================================ [ 'back' => 'Powrót do :name', 'edit' => 'Edytuj mapę', 'explore' => 'Eksploruj', ], 'create' => [ 'title' => 'Nowa mapa', ], 'destroy' => [], 'edit' => [], 'errors' => [ 'chunking' => [ 'error' => 'Podczas przetwarzania mapy wystąpił błąd. Skontaktuj się z nami na :discord by uzyskać wsparcie.', 'running' => [ 'edit' => 'Podczas przetwarzania nie można edytować mapy', 'explore' => 'Podczas przetwarzania nie można wyświetlić mapy.', 'time' => 'Przetwarzanie mapy potrwa od kilku minut do kilku godzin, zależnie od jej wielkości.', ], ], 'dashboard' => [ 'missing' => 'Aby mapa mogła pojawić się na pulpicie, potrzebuje obrazu.', ], 'explore' => [ 'missing' => 'Dodaj do tej mapy obraz by móc ją eksplorować.', ], ], 'fields' => [ 'center_marker' => 'Znacznik', 'center_x' => 'Wyjściowa szerokość geograficzna', 'center_y' => 'Wyjściowa długość geograficzna', 'centering' => 'Wyśrodkowanie', 'distance_measure' => 'Miara odległości', 'distance_name' => 'Jednostki odległości', 'grid' => 'Siatka', 'has_clustering' => 'Grupuj znaczniki', 'initial_zoom' => 'Wyjściowe powiększenie', 'is_real' => 'Użyj OpenStreetMaps', 'max_zoom' => 'Maksymalne powiększenie', 'min_zoom' => 'Maksymalne oddalenie', 'tabs' => [ 'coordinates' => 'Współrzędne', 'marker' => 'Znacznik', ], ], 'helpers' => [ 'center' => 'Zmiana tych wartości wpłynie na obszar, na którym domyślnie skupia się mapa. Jeżeli zostawisz je puste, mapa skoncentruje się na środku.', 'centering' => 'Środkowanie na znaczniku ma pierwszeństwo wobec domyślnych współrzędnych', 'chunked_zoom' => 'Automatycznie grupuje znaczniki położone blisko siebie.', 'distance_measure' => 'Wyposażając mapę w miary odległości uruchomisz narzędzie odmierzania dystansu w trybie eksploracji.', 'distance_measure_2' => 'Wpisz wartość 0.0041, by 100 pikseli oznaczało 1 kilometr', 'grid' => 'Określ wielkość siatki wyświetlanej w trybie eksploracji', 'has_clustering' => 'Automatycznie grupuje znaczniki położone blisko siebie.', 'initial_zoom' => 'Powiększenie, w jakim wyświetla się mapa. Domyślna wartość to :default, najwyższa dopuszczalna wartość wynosi :max, a najniższa to :min.', 'is_real' => 'Zaznacz tej opcji by używać autentycznych map świata zamiast załączonego obrazu. Jej użycie wyłącza warstwy.', 'max_zoom' => 'Większość map można przybliżać. Domyślna wartość przybliżenia to :default, a najwyższa możliwa wynosi :max.', 'min_zoom' => 'Większość map można oddalać. Domyślna wartość oddalenia to :default, a najwyższa możliwa wynosi :min.', 'missing_image' => 'Zapisz obraz mapy zanim dodasz do niego znaczniki i warstwy.', ], 'index' => [], 'lists' => [ 'empty' => 'Dodaj mapę przedstawiającą twój świat i wskazująca położenie różnych miejsc.', ], 'maps' => [], 'panels' => [ 'groups' => 'Kategorie', 'layers' => 'Warstwy', 'legend' => 'Legenda', 'markers' => 'Znaczniki', 'settings' => 'Ustawienia', ], 'placeholders' => [ 'center_marker' => 'Zostaw pusty, by mapa wyświetlała się wyśrodkowana w centrum', 'center_x' => 'Pozostaw puste, by mapa wyświetlała się skupiona na środku', 'center_y' => 'Pozostaw puste, by mapa wyświetlała się skupiona na środku', 'distance_name' => 'Kilometry, mile, stopy, hamburgery', 'grid' => 'Odległość w pikselach między elementami siatki. Pozostaw puste, by ukryć siatkę.', 'name' => 'Nazwa mapy', 'type' => 'Loch, miasto, galaktyka', ], 'show' => [ 'tabs' => [ 'maps' => 'Mapy', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'Przetwarzamy mapę. To może potrwać od kilku minut do kilku godzin.', ], ], ]; ================================================ FILE: lang/pl/misc.php ================================================ [ 'member' => 'Zostań członkiem.', 'remove_v5' => 'Kankę tworzymy tylko we dwóch. Wesprzyj naszą misję i ciesz się wolnością od reklam - za cenę mniejszą niż dobra kawa.', ], ]; ================================================ FILE: lang/pl/notes.php ================================================ [ 'title' => 'Nowa notatka', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'Notatki pochodne', ], 'helpers' => [], 'hints' => [], 'index' => [], 'lists' => [ 'empty' => 'Gromadź pomysły, źródła, zaraz i informacje, które nie pasują nigdzie indziej.', ], 'placeholders' => [ 'note' => 'Wybierz notatkę źródłową', 'type' => 'Religia, rasa, system polityczny', ], 'show' => [], ]; ================================================ FILE: lang/pl/notifications.php ================================================ [ 'discord' => [ 'invalid' => 'Token Discord stracił ważność. Zsynchronizuj Discord i Kankę ponownie.', ], ], 'campaign' => [ 'application' => [ 'approved' => 'Zatwierdzono twoje zgłoszenie do kampanii :campaign.', 'approved_message' => 'Twoje zgłoszenie do kampanii :campaign zostało przyjęte. Dołączono wiadomość: :reason.', 'new' => 'Nowe zgłoszenie do udziału w kampanii :campaign.', 'rejected' => 'Odrzucono twoje zgłoszenie do kampanii :campaign. Oto powód: :reason', 'rejected_no_message' => 'Twoje zgłoszenie do kampanii :campaign zostało odrzucone.', ], 'asset_export' => 'Można pobrać wyeksportowane pliki kampanii. Odnośnik będzie dostępny przez :time minut.', 'boost' => [ 'add' => 'Kampania :campaign została doładowana przez :user.', 'remove' => ':user nie doładowuje już kampanii :campaign.', 'superboost' => 'Kampania :campaign została turbodoładowana przez :user.', ], 'created' => 'Stworzono :campaign.', 'deleted' => 'Usunięto kampanię :campaign', 'export' => 'Można pobrać wyeksportowaną kampanię. Odnośnik będzie dostępny przez :time minut.', 'export_error' => 'Podczas eksportowania plików kampanii wystąpił błąd. Jeżeli będzie się powtarzał, skontaktuj się z nami. To się zdarza w dużych kampaniach posiadających duże obrazy.', 'hidden' => 'Kampania :campaign została usunięta z listy kampanii publicznych.', 'import' => [ 'failed' => 'Import kampanii :campaign nieudany.', 'success' => 'Import kampanii :campaign zakończony.', ], 'join' => ':user dołącza do kampanii :campaign.', 'leave' => ':user opuszcza do kampanię :campaign.', 'new_owner' => 'Jesteś teraz administratorem :campaign.', 'plugin' => [ 'deleted' => 'Wtyczka :plugin została usunięta z targowiska, więc usunięto ją również z kampanii :campaign.', ], 'premium' => [ 'add' => 'Odblokowano opcje premium kampanii :campaign dzięki :user', 'remove' => ':user nie odblokowuje już wersji premium kampanii :campaign', ], 'removed-image' => 'Obraz lub nagłówek elementu :entity został usunięty ze względu na prawa autorskie.', 'role' => [ 'add' => 'Nadano ci rolę :role w kampanii :campaign.', 'remove' => 'Odebrano ci rolę :role w kampanii :campaign.', ], 'troubleshooting' => [ 'joined' => ':user z zespołu Kanki dołączył do kampanii :campaign', ], ], 'clear' => [ 'action' => 'Usuń wszystkie', 'success' => 'Usunięto powiadomienia', 'title' => 'Wyczyść powiadomienia', ], 'features' => [ 'approved' => 'Zaaprobowaliśmy twój pomysł na :feature.', 'finished' => 'Twój pomysł :feature stał się częścią Kanki!', 'rejected' => 'Odrzuciliśmy twój pomysł na :feature. Przyczyna: :reason.', ], 'header' => 'Masz :count powiadomień.', 'index' => [ 'title' => 'Powiadommienia', ], 'map' => [ 'chunked' => 'Zakończono przetwarzanie mapy :name i można już jej używać.', ], 'no_notifications' => 'Nie masz powiadomień', 'plugins' => [ 'comments' => [ 'new_comment' => ':user zamieścił nowy komentarz o dodatku :plugin.', 'new_reply' => ':user odpowiedział na twój komentarz o :plugin', ], ], 'subscriptions' => [ 'charge_fail' => 'Wystąpił problem w czasie przetwarzania płatności. Odczekaj chwilę i spróbuj jeszcze raz. Jeżeli nic się nie zmieni, skontaktuj się z nami.', 'deleted' => 'Po zbyt wielu nieudanych próbach obciążenia twojej karty skasowaliśmy twoją subskrypcję Kanki. Wejdź do ustawień subskrypcji i uaktualnij metodę płatności.', 'ended' => 'Twoja subskrypcja została zakończona. Usunięto doładowania kampanii i kanał na Discordzie. Do zobaczenia niedługo!', 'failed' => 'Nie można pobrać płatności. Uaktualnij ustawienia Metody Płatności.', 'started' => 'Subskrybujesz od teraz Kankę.', 'trial' => 'Zakończył się darmowy okres próbny Kanki. Mamy nadzieję, że ci się podobało i jeszcze do nas wrócisz!', ], 'unread' => 'Nowe powiadomienie', ]; ================================================ FILE: lang/pl/onboarding/attributes.php ================================================ 'Cechy pozwalają dodać do :name drobne informacje wielorazowego użytku, na przykład wiek, liczbę punktów wytrzymałości, rangę w organizacji i każdą inną konkretną właściwość, którą można potem sortować, cytować albo dodawać wielu elementom.', 'title' => 'Wprowadź konkretne dane elementu', ]; ================================================ FILE: lang/pl/onboarding/characters.php ================================================ 'To wystarczy na początek.', 'text' => 'Skup się na postawach: imieniu, krótkim opisie i jednej - dwóch cechach charakterystycznych. Potem możesz też dodać jej powiązania z innymi, cechy czy portret.', 'title' => 'Stwórz pierwszą postać', ]; ================================================ FILE: lang/pl/onboarding/locations.php ================================================ 'Póki co zachowaj prostotę.', 'text' => 'Zacznij skromnie. Nawij miejsce i napisz jednym zdanem, czym się wyróżnia. W miarę rozbudowy świata możesz dodać mapy, mieszkańców i mniejsze lokacje w obrębie tego miejsca.', 'title' => 'Stwórz pierwsze miejsce', ]; ================================================ FILE: lang/pl/onboarding/posts.php ================================================ 'Komentarze pozwalają oddzielić opis widoczny dla wszystkich od tajnych notatek, sekretów MG i informacji dodatkowych. Możesz ich tworzyć dowolnie dużo i okreśać, kto widzi który.', 'title' => 'Użyj komentarzy by dodać tajemnice i dodatkowe detale', ]; ================================================ FILE: lang/pl/onboarding/reminders.php ================================================ 'Epizod może byś ostatecznym terminem czegoś, datą w świecie gry, rocznicą albo dowolną inną informacją związaną z :name, o której chcesz pamiętać. Wyświetlają się w na tej stronie oraz w kalendarzu, pod przypisaną im datą.', 'title' => 'Tu możesz śledzić rozwój historii w czasie', ]; ================================================ FILE: lang/pl/onboarding/tags.php ================================================ 'BN', ]; ================================================ FILE: lang/pl/organisations.php ================================================ [ 'title' => 'Nowa organizacja', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_defunct' => 'Nie funkcjonuje', 'members' => 'Członkowie', ], 'helpers' => [], 'hints' => [ 'is_defunct' => 'Ta organizacja obecnie nie działa', ], 'index' => [], 'lists' => [ 'empty' => 'Twórz gildie, frakcje i tajne stowarzyszenia pływające na kształt świata.', ], 'members' => [ 'actions' => [ 'add_multiple' => 'Dodaj członków', ], 'create' => [ 'helper' => 'Dodaje jednego lub więcej członków do :name.', 'success_multiple' => '{1} Dodano :count członka do :name.|[2,*] Dodano :count członków do :name.', ], 'destroy' => [ 'success' => 'Usunięto członka organizacji.', ], 'edit' => [ 'helper' => 'Zmienia status członkostwa elementu :name.', 'title' => 'Edycja członka :name', ], 'fields' => [ 'parent' => 'Zwierzchnik', 'pinned' => 'Przypnij', 'role' => 'Rola', 'status' => 'Rodzaj członkostwa', ], 'helpers' => [ 'all_members' => 'Wszystkie postaci należące do tej organizacji i organizacji pochodnych.', 'members' => 'Wszystkie postaci należące do tej organizacji.', 'pinned' => 'Wybierz czy członkostwo ma być wyświetlane w sekcji "przypięte" wskazanych elementów.', ], 'pinned' => [ 'both' => 'Do obu', 'none' => 'Do żadnego', ], 'placeholders' => [ 'parent' => 'Zwierzchnik tego członka', 'role' => 'Przywódca, członek, Wielki Septon, mistrz szpiegów', ], 'status' => [ 'active' => 'Aktywna działalność', 'inactive' => 'Była działalność', 'unknown' => 'Nieznany', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'Kult, gang, podziemie niepodległościowe, fandom', ], 'show' => [], ]; ================================================ FILE: lang/pl/pagination.php ================================================ '« Poprzednia', 'next' => 'Następna »', ]; ================================================ FILE: lang/pl/partials.php ================================================ [ 'description' => 'Wystąpił jakiś problem z twoim działaniem.', 'title' => 'Ups!', ], ]; ================================================ FILE: lang/pl/passwords.php ================================================ 'Hasło zostało zresetowane!', 'sent' => 'Przypomnienie hasła zostało wysłane!', 'throttled' => 'Proszę zaczekać zanim spróbujesz ponownie.', 'token' => 'Token resetowania hasła jest nieprawidłowy.', 'user' => 'Nie znaleziono użytkownika z takim adresem e-mail.', ]; ================================================ FILE: lang/pl/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/pl/permissions.php ================================================ [ 'delete' => 'Uprawnienie do usunięcia tego elementu', 'edit' => 'Uprawnienie do edycji tego elementu', 'view' => 'Uprawnienie do wyświetlania tego elementu', ], 'members' => [ 'inherited' => ':member ma już to uprawnienie dzięki roli :role.', ], 'roles' => [ 'inherited' => 'Ta :rola ma już takie uprawnienia dla całego modułu :module.', ], ]; ================================================ FILE: lang/pl/pins.php ================================================ 'Więcej informacji o przypięciach znajdziesz w dokumentacji.', 'options' => [ 'no' => 'Odpięte', 'yes' => 'Przypięte do podstaw elementu', ], ]; ================================================ FILE: lang/pl/post_layouts.php ================================================ 'Organizacje tej postaci', 'connection_map' => 'Mapa powiązań', 'helper' => 'Ten komentarz wyświetla podstronę :subpage elementu.', 'location_characters' => 'Postaci w miejscu', 'location_events' => 'Wydarzenia w miejscu', 'location_quests' => 'Zadania w miejscu', 'pitch' => [ 'custom' => 'Wyświetla zawartość podstron elementu w komentarzu. Na przykład, pokazuje wyposażenie :entity.', 'title' => 'Zaawansowany układ komentarza', ], 'premium' => 'Niektóre opcje układu komentarza są niedostępne, ponieważ wymagają kampanii premium.', 'quest_elements' => 'Elementy zadania', ]; ================================================ FILE: lang/pl/posts.php ================================================ [ 'title' => 'Nowy komentarz', ], 'fields' => [ 'layout' => 'Układ komentarza', 'name' => 'Tytuł', ], 'helpers' => [ 'new' => 'Dodaj nowy komentarz do tego elementu.', 'visibility' => 'Zmienia widoczność komentarza :name.', ], 'move' => [ 'copy' => [ 'helper' => 'Zachowuje kopię komentarza w :name', ], 'helper' => 'Przenosi lub kopiuje komentarz :name do innego elementu', 'title' => 'Przenoszenie komentarza', ], 'permissions' => [ 'actions' => [ 'members' => 'Dodaj członków', 'roles' => 'Dodaj role', ], 'helpers' => [ 'members' => 'Zapewnia jednemu lub kilku członkom specjalnie uprawnienia w tym komentarzu.', 'roles' => 'Zapewnia jednej lub kilku rolom specjalnie uprawnienia w tym komentarzu.', ], ], 'placeholders' => [ 'name' => 'Tytuł komentarza', ], 'position' => [ 'dont_change' => 'Nie zmienaj', 'first' => 'Pierwszy', 'last' => 'Ostatni', ], 'remove' => [ 'title' => 'Usuwanie komentarza', ], 'visibility' => [ 'helper' => 'Zmienia widoczność komentarza :name', 'title' => 'Widoczność komentarza', ], ]; ================================================ FILE: lang/pl/presets.php ================================================ [ 'create' => 'Stwórz nowy wzór znacznika', ], 'create' => [ 'success' => 'Stworzono wzór :name', 'title' => 'Nowy wzór', ], 'destroy' => [ 'success' => 'Usunięto wzór :name.', ], 'edit' => [ 'success' => 'Zmieniono wzór :name.', 'title' => 'Edycja wzoru :name', ], 'fields' => [ 'name' => 'Nazwa wzoru', ], 'lists' => [ 'empty' => 'W kampanii nie ma na razie wzorów znaczników.', ], 'placeholders' => [ 'name' => 'Nazwa wzoru', ], ]; ================================================ FILE: lang/pl/profiles.php ================================================ [], 'avatar' => [ 'success' => 'Zmieniono awatar.', ], 'campaign_switcher_order_by' => [], 'edit' => [ 'success' => 'Zmieniono profil', ], 'editors' => [], 'fields' => [ 'avatar' => 'Awatar', 'bio' => 'Życiorys', 'email' => 'Email', 'hide_subscription' => 'Ukryj moje imię na stronie :hall_of_fame.', 'last_login_share' => 'Informuj innych członków kampanii o moim ostatnim logowaniu', 'link' => 'Media społecznościowe', 'login_sharing' => 'Informacja o logowaniu', 'name' => 'Nazwa', 'new_password' => 'Nowe hasło', 'new_password_confirmation' => 'Potwierdź nowe hasło', 'newsletter' => 'Chcę dostawać okazjonalne emaile', 'password' => 'Obecne hasło', 'profile-name' => 'Nazwa profilu', 'pronouns' => 'Zaimki', 'settings' => 'Ustawienia', 'subscription_hiding' => 'Tajna subskrybcja', 'theme' => 'Motyw', ], 'helpers' => [ 'link' => 'Zmienia odnośnik do profilu społecznościowego wyświetlany w twoim :profile i na :marketplace. Jeżeli wiersz jest pusty, użyta zostanie nazwa konta.', 'profile-name' => 'Zmienia nazwę wyświetlaną w twoim :profile i na :marketplace. Jeżeli wiersz jest pusty, użyta zostanie nazwa konta.', 'pronouns' => 'Zmienia zaimki wyświetlane w twoim :profile i na :marketplace. Jeżeli wiersz jest pusty, nie będziemy informować o twoich zaimkach.', ], 'link' => [ 'button' => 'Profil społecznościowy :name', ], 'newsletter' => [ 'helpers' => [ 'header' => 'Zasubskrybuj następujące powiadomienia e-mailem, by być na bieżąco z Kanką.', ], 'options' => [ 'monthly' => 'Newsletter Kanki', ], 'title' => 'Newslettery', 'updated' => 'Zmieniono ustawienia newslettera.', ], 'password' => [ 'success' => 'Zmieniono hasło', ], 'placeholders' => [ 'bio' => 'Kilka informacji osobistych do wyświetlenia w profilu publicznym.', 'email' => 'Twój adres email', 'name' => 'Nazwa, która będzie wyświetlana', 'new_password' => 'Twoje nowe hasło', 'new_password_confirmation' => 'Potwierdź nowe hasło', 'password' => 'Potwierdź zmianę wpisując hasło', ], 'sections' => [ 'dangerzone' => 'Uwaga!', 'delete' => [ 'confirm' => 'Usuń teraz moje konto', 'delete' => 'Usuń konto', 'goodbye' => 'Jeśli tak, przepisz :code w polu poniżej.', 'helper' => 'Usunięcie konta spowoduje również usunięcie wszystkich kampanii, których jesteś jednym członkiem. To działanie nieodwracalne, którego nie można cofnąć.', 'subscribed' => 'Anuluj :subscription zanim możliwe będzie usunięcie konta.', 'title' => 'Usuwanie konta', 'warning' => 'Po usunięciu konta wszystkie dane zostaną wykasowane. Czy na pewno chcesz to zrobić?', ], 'password' => [ 'title' => 'Zmiana hasła', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'Życiorys będzie widoczny w twoim :link.', 'profile' => 'profilu publicznym', ], 'success' => 'Zmieniono ustawienia.', ], 'theme' => [ 'success' => 'Zmieniono motyw.', 'themes' => [ 'dark' => 'Ciemny', 'default' => 'Domyślny', 'future' => 'Futurystyczny', 'midnight' => 'Granatowy', ], ], 'title' => 'Aktualizuj profil', 'workflows' => [ 'created' => 'Stworzony element', 'default' => 'Lista elementów', ], ]; ================================================ FILE: lang/pl/quests.php ================================================ [ 'title' => 'Nowe zadanie', ], 'destroy' => [], 'edit' => [], 'elements' => [ 'create' => [ 'success' => 'Nowy element zadania :name', 'title' => 'Nowy element zadania :name', ], 'destroy' => [ 'success' => 'Usunięto element zadania :entity.', ], 'edit' => [ 'success' => 'Zmieniono element zadania :entity.', 'title' => 'Zmień element zadania :name', ], 'fields' => [ 'entity_or_name' => 'Wybierz inny element kampanii albo nadaj nazwę temu elementowi.', ], ], 'fields' => [ 'copy_elements' => 'Kopiuj elementy związane z zadaniem', 'date' => 'Data', 'element_role' => 'Rola', 'instigator' => 'Zleceniodawna', 'is_completed' => 'Ukończona', 'location' => 'Miejsce rozpoczęcia', 'role' => 'Rola', ], 'helpers' => [ 'is_completed' => 'Wybierz, jeżeli zadanie można uznać za ukończone.', ], 'hints' => [], 'index' => [], 'lists' => [ 'empty' => 'Twórz zadania, by dokumentować cele drużyny, przebieg zdarzeń oraz motywacje postaci.', ], 'placeholders' => [ 'date' => 'Data zadania w prawdziwym świecie', 'entity' => 'Nazwa elementu z tego zadania', 'location' => 'Miejsce, w którym rozpoczyna się zadanie.', 'role' => 'Rola elementu w tym zadaniu', 'type' => 'Wątek osobisty, misja poboczna, zadanie główne', ], 'show' => [ 'actions' => [ 'add_element' => 'Dodaj element', ], 'tabs' => [ 'elements' => 'Elementy', ], ], ]; ================================================ FILE: lang/pl/races.php ================================================ [], 'create' => [ 'title' => 'Nowa rasa', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'members' => 'Członkowie', ], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Ta rasa wymarła.', ], 'index' => [], 'lists' => [ 'empty' => 'Zaprojektuj gatunki, kultury i odmiany istot zamieszkujących twój świat.', ], 'members' => [ 'create' => [ 'helper' => 'Dodaje jedną lub więcej postaci do :name', 'submit' => 'Dodaj członka', 'success' => '{0} Nie dodano członków.|{1} Dodano 1 członka.|[2,*] Dodano :count członków.', 'title' => 'Nowi członkowie', ], ], 'placeholders' => [ 'type' => 'Człowiek, sidhe, borg', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/pl/redirects.php ================================================ 'Twoja sesja wygasła. Spróbuj jeszcze raz.', 'unknown_entity' => 'Przepraszamy, nie wiemy co to jest \':entity\'.', ]; ================================================ FILE: lang/pl/referrals.php ================================================ [ 'copy' => 'Kopiuj', ], 'benefits' => 'Wspólne budowanie światów.', 'fields' => [ 'link' => 'Twój odnośnik ze skierowaniem:', ], 'stats' => [ 'badge' => 'Odznaka: Światotórca :level', 'empty' => 'Na razie brak. Wyślij link, by zacząć.', 'invited' => 'Zaproszono:', 'subscribers' => 'Skieowanych subskrybentów: :amount', 'users' => '[1] jeden użytkownik|{2,*} :użytnowników', ], 'title' => 'Skieruj znajomych do Kanki', 'toasts' => [ 'copied' => 'Skopiowano odnośnik do schowka', ], ]; ================================================ FILE: lang/pl/releases.php ================================================ [ 'event' => 'Wydarzenie', 'livestream' => 'Livestream', 'other' => 'Inne', 'release' => 'Aktualizacja', 'vote' => 'Głosowanie społeczności', ], 'index' => [ 'description' => 'Najnowsze aktualizacje kanka.io', 'title' => 'Aktualizacja', ], 'post' => [ 'footer' => 'Przez :name :date', ], 'show' => [ 'return' => 'Powrót do aktualizacji', 'title' => 'Aktualizacja :name', ], ]; ================================================ FILE: lang/pl/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'Das Schwarze Auge', '12'=> 'Świat Mroku', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/pl/search/fulltext.php ================================================ 'Przeszukuje elementy, notatki i cechy w poszukiwaniu słowa :term.', 'title' => 'Przeszukiwanie tekstu', ]; ================================================ FILE: lang/pl/search.php ================================================ 'Szukaj wszędzie', 'lookup' => [ 'empty' => 'Brak wyników', 'hint' => 'Wpisz przynajmniej 3 litery, by przeszukać całą kampanię.', 'keyboard' => 'Wciśnij :k by wyszukać, :esc by odrzucić', 'recents' => 'Ostatnie', 'results' => 'Wyniki', ], 'no_results' => 'Nie znaleziono.', 'placeholder' => 'SZUKAJ', 'preview' => [ 'links' => 'Odnośniki', 'no-connections' => 'Brak przypiętych relacji do wyświetlenia', ], 'title' => 'Szukaj', ]; ================================================ FILE: lang/pl/seo.php ================================================ 'Pulpit kampanii :campaign', 'entity-list' => 'Przeglądaj :module kampanii :campaign', ]; ================================================ FILE: lang/pl/settings/account.php ================================================ 'Kontroluj adres email, hasło, opcje bezpieczeństwa i inne ustawienia konta.', 'title' => 'Dane konta', ]; ================================================ FILE: lang/pl/settings/api.php ================================================ [ 'title' => 'Autoryzowane aplikacje', ], 'clients' => [ 'empty' => 'Nie stworzono żadnego klienta OAuth.', 'form' => [ 'name' => 'Nazwa klienta', 'name_helper' => 'Słowo, które twoi użytkownicy rozpoznają i mu zaufają.', 'name_placeholder' => 'Nazwa dla klienta', 'redirect' => 'Przekierowanie', 'redirect_helper' => 'Aplikacje z autoryzowanym wywołaniem zwrotnym.', 'redirect_placeholder' => 'http://my-super-app.com/callback', ], 'new' => 'Stwórz nowego klienta', 'title' => 'Klienty OAuth', 'update'=> 'Zmiana klienta', ], 'fields' => [ 'client' => 'ID klienta', 'client_name' => 'Nazwa klienta', 'scopes' => 'Zakresy', 'secret' => 'Tajne', 'token_name' => 'Nazwa klucza', ], 'new' => [ 'copy' => 'Klucz skopiowano do schowka', 'title' => 'Twój nowy osobisty klucz dostępu:', ], 'revoke' => 'Wycofaj', 'revoke-confirm' => 'Potwierdź wycofanie', 'tokens' => [ 'empty' => 'Nie stworzono żadnego klucza dostępu.', 'form' => [ 'name' => 'Nazwa klucza', 'name_placeholder' => 'Nazwa dla klucza', ], 'new' => 'Stwórz nowy klucz', 'title' => 'Osobiste klucze dostępu', ], ]; ================================================ FILE: lang/pl/settings/appearance.php ================================================ [ 'learn-more' => 'Więcej informacji o ustawieniach znajdziesz w dokumentacji.', 'save' => 'Zapisz ustawienia', ], 'campaign-switcher' => [ 'alphabetical' => 'Alfabetycznie (A-Z)', 'date_created' => 'Data utworzenia (najpierw najstarsze)', 'date_joined' => 'Data dołączenia (najpierw najstarsze)', 'r_alphabetical' => 'Alfabetycznie (Z-A)', 'r_date_created' => 'Data utworzenia (najpierw najnowsze)', 'r_date_joined' => 'Data dołączenia (najpierw najnowsze)', ], 'dismissible' => [ 'main' => 'Kontroluj wygląd Kanki. Ustawienia konkretnych kampanii mają pierwszeństwo przed ogólnymi.', ], 'editors' => [ 'default' => 'Domyślny (:name)', 'legacy' => 'Dawniejszy (:name)', ], 'explore' => [ 'grid' => 'Kafelki (domyślne)', 'table' => 'Tabela', ], 'fields' => [ 'campaign-order' => 'Kolejność wyświetlania kampanii', 'date-format' => 'Format daty', 'editor' => 'Edytor tekstu', 'entity-explore' => 'Listy elementów', 'mentions' => 'Wzmianki w czasie edycji', 'new-entity-workflow' => 'Praca nad nowym elementem', 'pagination' => 'Wpisy na stronę', 'theme' => 'Preferowany motyw', ], 'helpers' => [ 'advanced-mentions' => 'W czasie wpisywania tekstu wzmianki mogą być wyświetlane w postaci nazw elementów lub wzmianek zaawansowanych.', 'campaign-order' => 'Określ kolejność wyświetlania kampanii w menu ich wyboru.', 'date-format' => 'Określ format wyświetlania dat z rzeczywistego kalendarza (gdy to możliwe).', 'entity-explore' => 'Określ sposób wyświetlania elementów kampanii.', 'new-entity-workflow' => 'Wskaż, jaki interface ma się wyświetlić po stworzeniu nowego elementu.', 'overridable' => 'Indywidualne ustawienia kampanii mają pierwszeństwo.', 'pagination' => 'Określ ile elementów długich list ma się wyświetlać na jednej stronie.', 'theme' => 'Wybierz, jak Kanka wyglądać będzie u ciebie.', ], 'mentions' => [ 'advanced' => 'Wyświetlaj wzmianki zaawansowane :code', 'default' => 'Wzmianki jako nazwy elementów', ], 'nested' => [], 'success' => 'Zapisano opcje wyglądu.', 'values' => [ 'pagination' => ':amount wyników na stronie', 'pagination-sub' => ':amount (dostępne dla subskrybentów)', ], ]; ================================================ FILE: lang/pl/settings/boosters.php ================================================ [ 'boost_name' => 'Doładuj :name', ], 'available' => 'Dostępne doładowania :amount/:total', 'benefits' => [ 'boosted' => 'Użycie :one doładowania zapewnia dostęp do następujących funkcji: :marketplace, zmiana motywu, możliwość załączania większych plików, odzyskiwanie usuniętych elementów i :more.', 'more' => 'inne świetne opcje.', 'superboosted' => 'Turbodoładowanie kampanii z pomocą :amount doładowań odblokowuje wszystkie opcje kampanii doładowanej oraz galerię, pełny dziennik zmian każdego elementu i :more.', ], 'boost' => [ 'actions' => [ 'confirm' => 'Doładuj!', 'remove' => 'Wycofaj doładowanie :campaign', 'subscribe' => 'Zasubskrybuj Kankę', 'upgrade' => 'Zwiększ poziom subskrybcji', ], 'confirm' => 'Wspaniale, zamierzasz doładować :campaign. To działanie wykorzysta jedno (:cost) z doładowań, których możesz użyć.', 'duration' => 'Doładowania pozostają przypisane do kampanii póki ich nie wycofasz ręcznie albo nie zakończysz sybskrypcji.', 'errors' => [ 'boosted' => 'O nie! Najwyraźniej :campaign jest już doładowana!', 'out-of-boosters' => 'O nie! Brakuje ci doładowań. Masz ich :available, a potrzebujesz :cost. Możesz albo wycofać doładowanie innej kampanii albo :upgrade.', ], 'pitch' => 'Zasubskrybuj, aby otrzymać doładowania.', 'success' => 'Kampania :campaign jest od teraz doładowana. Ciesz się nowymi możliwościami!', 'title' => 'Doładuj :campaign', 'upgrade' => 'zwiększyć poziom subskrypcji', ], 'campaign' => [ 'boosted' => 'Doładowana przez :user od :time', 'premium' => 'Premium dzięki :user od :time', 'standard' => 'Standardowa', 'superboosted' => 'Turbodoładowana przez :user od :time', 'unboosted' => 'Niedoładowana', ], 'intro' => [ 'anyone' => 'Możesz doładowywać kampanie, które stworzył ktoś inny, o ile jesteś ich uczestnikiem albo możesz je zobaczyć. To znaczy, kampanie w które grasz albo które możesz przeglądać, gdyż są :public.', 'data' => 'Po usunięciu doładowań kampanii dostęp do dodatkowych opcji zostaje usunięty. Aby go odzyskać należy doładować kampanię ponownie.', 'first' => 'Przydzielając kampaniom doładowania zyskujesz dostęp do funkcji zaawansowanych. Liczba dostępnych doładowań zależy od :subsription - pozostaje do twojej dyspozycji, póki subskrybujesz Kankę. By doładować kampanię wystarczy przydzielić jej jedno doładowanie. Turbodoładowanie kampanii wymaga trzech doładowań.', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Możliwość odzyskania usuniętych elementów do :days wstecz', 'customisable' => 'Pełną kontrolę nad wyglądem kampanii', 'icons' => 'Dostęp do tysięcy ikon, które można umieszczać na mapie i w historii', 'title' => 'W doładowanych kampaniach otrzymujesz', 'upload' => 'Możliwość dodawania większych plików (dla wszystkich)', ], 'description' => 'Doładowanie kampanii zapewnia dostęp do świetnych możliwości, i to dla wszystkich uczestników. Mało ci? Spróbuj turbodoładowania.', 'more' => 'Pełną listę udogodnień znajdziesz na stronie :booster.', 'title' => 'Wznieś kampanię na nowym poziom, zapewniając wszystkim uczestnikom dostęp do licznych udogodnień', ], 'ready' => [ 'available' => 'Dostępne doładowania kampanii.', 'pricing' => 'Każdy poziom subskrypcji zawiera przynajmniej jedno doładowanie kampanii. Ceny zaczynają się od :amount miesięcznie.', 'pricing-amount' => ':currency:amount', 'title' => 'Doładuj kampanię', ], 'superboost'=> [ 'actions' => [ 'confirm' => 'Turbodoładuj!', 'instead' => 'Turbodoładuj za :count!', 'remove' => 'Wycofaj turbodoładnie :campaign', ], 'confirm' => 'Wspaniale, zamierzasz turbodoładować :campaign. To działanie wymaga wykorzystania trzech (:cost) spośród twoich dostępnych doładowań.', 'errors' => [ 'boosted' => 'O nie! Najwyraźniej :campaign jest już turbodoładowana!', ], 'success' => 'Kampania :campaign jest od teraz turbodoładowana. Ciesz się nowymi możliwościami!', 'title' => 'Turbodoładuj :campaign', 'upgrade' => 'Masz chrapkę na ostateczną wersję Kanki? Turbodoładuj :campaign za pomocą :cost dodatkowych doładowań.', ], 'title' => 'Doładowania kampanii', 'unboost' => [ 'confirm' => 'Tak, na pewno', 'status' => [ 'boosting' => 'doładowanie', 'superboosting' => 'turbodoładowanie', ], 'success' => 'Kampania :campaign nie jest już turbodoładowana, odzyskujesz swoje doładowania.', 'title' => 'Usuwanie doładowania', 'warning' => 'Czy na pewno chcesz zakończyć :action :campaign? Odzyskasz w ten sposób wydane doładowania, a elementy kampanii związane z doładowaniem zostaną ukryte aż do ponownego doładowania.', ], ]; ================================================ FILE: lang/pl/settings/premium.php ================================================ [ 'remove' => 'Usuń premium', 'unlock' => 'Aktywuj premium', ], 'create' => [ 'actions' => [ 'confirm' => 'Wersja premium!', ], 'confirm' => 'Hurra! Zaraz odblokujesz wersję premium kampanii :campaign. Użyjesz w tym celu jednego z dostępnych rozszerzeń.', 'duration' => 'Kampanie premium zachowują status, póki go nie usuniesz ręcznie albo do zakończenia subskrypcji.', 'success' => 'Kampania :campaign ma teraz wersję premium. Korzystaj z wielu świetnych opcji!', ], 'exceptions' => [ 'already' => 'W tej kampanii używasz już opcji premium.', 'out-of-stock' => 'Nie masz wolnych rozwinięć do wersji premium. Usuń status premium innej kampanii albo :upgrade.', ], 'pitch' => [ 'description' => 'Używaj wersji premium kampanii, by odblokować wspaniałe opcje dla wszystkich uczestników.', 'title' => 'W kampaniach premium otrzymujesz', ], 'ready' => [ 'available' => 'Dostępne kampanie premium.', 'pricing' => 'Każdy poziom subskrypcji oferuje co najmniej jedno rozwinięcie do wersji premium. Cena zaczyna się od :amount miesięcznie.', 'pricing-amount' => ':currency:amount', 'title' => 'Aktywuj premium', ], 'remove' => [ 'confirm' => 'Tak, na pewno', 'cooldown' => 'Funkcje premium kampanii :campaign można usunąć po :date.', 'success' => 'Z :campaign usunięto wersję premium. Możesz teraz odblokować ten status w innej kampanii.', 'title' => 'Usuwanie opcji premium', 'warning' => 'Czy na pewno usunąć wersję premium kampanii :campaign? Pozwoli ci to nadać status premium innej kampanii, a cała zawartość premium zostanie ukryta do ponownej aktywacji wersji premium.', ], ]; ================================================ FILE: lang/pl/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Wyłącz autoryzację dwueatpową', 'disable-confirm' => 'Kliknij ponownie by potwierdzić', 'finish' => 'Skończ konfigurację i się zaloguj', ], 'activation_helper' => 'Wykonuj poniższe polecenia by skonfigurować autoryzację dwuetapową.', 'disable' => [ 'helper' => 'Jeżeli chcesz wyłączyć autoryzację dwuetapową, kliknij poniżej. Pamiętaj przy tym, że konto będzie od tej pory dostępne dla każdej osoby znającej hasło.', 'title' => 'Wyłącz autoryzację dwuetapową', ], 'enable_instructions' => 'By zacząć aktywację, wygeneruj autoryzacyjny kod QR i zeskanuj go w Aplikacji Autoryzacyjnej Goole (:ios, :android) albo innym programie tego rodzaju', 'enabled' => 'Twoje konto zabezpieczone jest obecnie autoryzacją dwuetapową', 'error_enable' => 'Niewłaściwy kod, spróbuj ponownie', 'fields' => [ 'otp' => 'Wpisz Hasło Jednorazowe (OTP) z aplikacji autoryzacyjnej', 'qrcode' => 'Zeskanuj poniższy kod QR aplikacją autoryzacyjną, by uzyskać Hasło Jednorasowe (OTP)', ], 'generate_qr' => 'Wygeneruj kod QR', 'helper' => 'Autoryzacja dwuetapowa zwiększa bezpieczeństwo, wymaga bowiem dwóch metod weryfikacji tożsamości podczas każdego logowania', 'learn_more' => 'Więcej o autoryzacji dwuetapowej', 'social' => 'Autoryzacja dwuetapowa Kanki dostępna jest dla użytkowników logujących się za pomocą emaila i hasła. Zmień metodę logowania zanim uzyskasz dostęp do tej opcji.', 'success_disable' => 'Wyłączono autoryzację dwuetapową', 'success_enable' => 'Włączono autoryzację dwuetapową. Zaloguj się ponownie by dokończyć konfigurację.', 'success_key' => 'Wygenerowano kod QR. Zakończ konfigurację by aktywować autoryzację dwuetapową', 'title' => 'Autoryzacja dwuetapowa', ], 'actions' => [ 'social' => 'Przejdź na Logowanie Kanki', 'update_email' => 'Zmień email', 'update_password' => 'Zmień hasło', ], 'email' => 'Zmiana emaila', 'email_success' => 'Zmieniono email.', 'password' => 'Zmiana hasła', 'password_success' => 'Zmieniono hasło.', 'social' => [ 'error' => 'To konto używa już Logowania Kanki', 'helper' => 'Twoim kontem zarządza obecnie :provider. Możesz przejść na system logowania, którym zarządza Kanka, ustawiając hasło.', 'success' => 'Konto używa od teraz logowania Kanki.', 'title' => 'Kanka przez serwis społecznościowy', ], 'title' => 'Konto', ], 'api' => [ 'helper' => 'Witaj w Kanka API. Generuj Osobiste Żetony Dostępu, by używać wywołań API do gromadzenia informacji o kampaniach, w których uczestniczysz.', 'link' => 'Przeczytaj dokumentację API', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Połącz', 'remove' => 'Usuń', ], 'benefits' => 'Kanka posiada możliwość integracji z kilkoma narzędziami zewnętrznymi. Kolejne dostępne będą w przyszłości.', 'discord' => [ 'confirm' => 'Czy na pewno chcesz odłączyć Discord? Stracisz wszystkie zsynchronizowane z nim role.', 'errors' => [ 'add' => 'Podczas łączenia konta Kanki z Discordem nastąpił błąd. Spróbuj jeszcze raz.', ], 'success' => [ 'add' => 'Połączono z kontem Discord.', 'remove' => 'Odłączono konto Discord.', ], 'text' => 'Automatyczny dostęp do poziomu subskrypcji.', 'unlock' => 'Odblokuj role na Discordzie', ], 'title' => 'Integracja z aplikacjami', ], 'billing' => [ 'placeholder' => 'Jeżeli chcesz, by do rachunku dodano dodatkowe dane kontaktowe albo podatkowe (adres firmy, numer NIP i tak dalej), wpisz je poniżej. Pojawią się w rozliczeniu.', 'save' => 'Zapisz dane płatności', 'title' => 'Dane płatności', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'Kampania :name jest już doładowana.', 'exhausted_boosts' => 'Nie masz już doładowań. Musisz usunąć doładowanie z którejś kampanii, by je ponownie wykorzystać.', 'exhausted_superboosts' => 'Nie masz doładowań. Potrzebujesz 3, by turbodoładować kampanię.', ], ], 'countries' => [ 'austria' => 'Austria', 'belgium' => 'Belgia', 'france' => 'Francja', 'germany' => 'Niemcy', 'italy' => 'Włochy', 'netherlands' => 'Holandia', 'spain' => 'Hiszpania', ], 'invoices' => [], 'layout' => [ 'title' => 'Układ', ], 'marketplace' => [], 'menu' => [ 'account' => 'Konto', 'api' => 'API', 'appearance' => 'Wygląd', 'apps' => 'Aplikacje', 'boosters' => 'Doładowania', 'notifications' => 'Powiadomienia', 'other' => 'Inne', 'patreon' => 'Patreon', 'payment_options' => 'Opcje płatności', 'personal_settings' => 'Ustawienia osobiste', 'premium' => 'Kampanie premium', 'profile' => 'Profil', 'settings' => 'Ustawienia', 'subscription' => 'Subskrypcja', 'subscription_status' => 'Status subskrypcji', ], 'patreon' => [ 'deprecated' => 'Przestarzała funkcja. Jeżeli chcesz wspierać Kankę, rozważ subskrypcję. Integracja z Patreonem jest dostępna tylko dla osób, które połączyły swoje konta Patren z Kanką zanim wycofaliśmy się z tego serwisu.', 'pledge' => 'Deklaracja :name', 'remove' => [ 'button' => 'Odłącz konto Patreon', 'success' => 'Odłązcono twoje konto Patreon', 'text' => 'Dołączenie kontra Patreon spowoduje usunięcie z listy wspierających, utratę doładować kampanii i innych korzyści dostępnych dla wspierających. Treści związane z doładowaniem (na przykład nagłówki) nie zostają usunięte. Odnawiając subskrypcje odzyskasz dostęp do danych oraz możliwość ponownego doładowywania kampanii.', 'title' => 'Odłącz swoje konto Patreon od Kanki', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Aktualizuj profil', ], 'avatar' => 'Zdjęcie profilowe', 'success' => 'Zmieniono profil.', 'title' => 'Profil osobisty', ], 'referrals' => [ 'title' => 'Skierowania', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Usuń subskrypcję', 'subscribe' => 'Subskrybuj', 'update_currency' => 'Zapisz preferowaną walutę', ], 'billing' => [ 'helper' => 'Informacje o płatnościach bezpiecznie przetwarza i przechowuje :stripe. To metoda płatności stosowana we wszystkich subskrypcjach.', 'saved' => 'Zapisz metodę płatności', ], 'cancel' => [ 'grace' => [ 'text' => 'Twoja subskrypcja zostanie anulowania :date, kiedy to kampanie premium zmienią się w zwykłe i utracisz dostęp do funkcji dostępnych dla wspierających Kankę.', 'title' => 'Okres karencji', ], 'options' => [ 'competitor' => 'Wybieram produkt konkurencji', 'financial' => 'Moja sytuacja finansowa się zmieniła', 'missing_features' => 'Nie ma opcji, których potrzebuję', 'not_for' => 'Subskrybuję dla kogoś innego', 'not_playing' => 'Już nie gram albo kampania została zawieszona', 'not_using' => 'Nie używam ostatnio Kanki', 'other' => 'Inne', 'testing' => 'Tylko testuję Kankę', ], 'text' => 'Szkoda, że rezygnujesz! Po zaniechaniu subskrypcji konto pozostanie aktywne do końca okresu rozliczeniowego. Potem stracisz doładowania i inne korzyści wynikające ze wspierania Kanki. Wypełniając poniższy formularz nasz nam znać, co możemy poprawić i dlaczego rezygnujesz.', 'title' => 'Anulowane subskrybcji', ], 'cancelled' => 'Anulowano subskrypcję. Możesz ją odnowić, gdy tylko ta wygaśnie.', 'change' => [ 'text' => [ 'downgrade_monthly' => 'Zmniejszasz poziom subskrybcji do :tier, więc miesięczy rachunek wynosić będzie od tej pory :amount.', 'downgrade_yearly' => 'Zmniejszasz poziom subskrybcji do :tier, więc roczny rachunek wynosić będzie od tej pory :amount.', 'monthly' => 'Subskrybujesz na poziomie :tier, płacąc miesięcznie :amount.', 'upgrade_monthly' => 'Zwiększasz subskrypcję do wersji :tier za :upgrade, więc miesięczny rachunek wyniesie :amount', 'upgrade_paypal' => 'Zwiększasz subskrypcję do :tier za :upgrage do :date.', 'upgrade_yearly' => 'Zwiększasz subskrypcję do wersji :tier za :upgrade, więc roczny rachunek wyniesie :amount', 'yearly' => 'Subskrybujesz na poziomie :tier, płacąc rocznie :amount.', ], 'title' => 'Zmiana poziomu subskrypcji', ], 'coupon' => [ 'check' => 'Sprawdź kod promocyjny', 'invalid' => 'Niewłaściwy kod promocyjny.', 'label' => 'Kod promocyjny', 'percent_off' => 'Uzyskujesz zniżką na pierwszą roczną subskrypcję w wysokości :percent%!', ], 'currencies' => [ 'brl' => 'BRL', 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Zmień preferowaną walutę rozliczenia', ], 'errors' => [ 'callback' => 'Nasz dostawca płatności zgłosił błąd. Spróbuj ponownie i skontaktuj się z nami, jeżeli się powtórzy.', 'failed' => 'Mamy obecnie kłopoty techniczne z systemem opłat. Pomoc uzyskasz pod adresem o :email.', 'subscribed' => 'Nie można przetworzyć subskrypcji. Stripe sugeruje następującą radę.', ], 'fields' => [ 'active_since' => 'Aktywna od', 'active_until' => 'Aktywna do', 'billing' => 'Płatność', 'currency' => 'Waluta płatności', 'payment_method' => 'Metoda płatności', 'plan' => 'Obecny plan', 'reason' => 'Powód', 'reset' => 'Reset informacji o płatnościach', 'reset_billing' => 'Rozumiem, że zmiana waluty powoduje usunięcie historii płatności i konieczność ponownego wprowadzenia metody płatności.', ], 'helpers' => [ 'alternatives' => 'Opłać subskrypcję używając :method. Ten sposób płatności nie odnawia się automatycznie na koniec cyklu. :method jest dostępna tylko w Euro.', 'alternatives-2' => 'Opłać subskrypcję za pomocą :method. To płatność jednorazowa, która nie odnowi się automatycznie na koniec okresu rozliczeniowego.', 'alternatives_warning' => 'Jeżeli używasz tej metody, nie możesz zmienić poziomu subskrypcji. Zasubskrybuj ponownie, gdy ta subskrypcja wygaśnie.', 'alternatives_yearly' => 'Z powodu ograniczeń cyklu płatniczego, :method jest dostępna tylko dla subskrypcji rocznych.', 'currency_block' => 'Zmiana waluty jest niemożliwa podczas aktywnej subskrypcji Kanki. Możesz zmienić walutę po zakończeniu subskrypcji.', 'currency_reset' => 'Zmiana waluty usunie historię opłat i wymagać będzie ponownego wprowadzenia metody płatności.', 'paypal_v3' => 'Opłać bezpiecznie roczną subskrypcję przy pomocy PayPala.', 'stripe' => 'Informacje o płatnościach bezpiecznie przetwarza i przechowuje :stripe.', ], 'manage_subscription' => 'Zarządzaj subskrypcją', 'payment_method' => [ 'actions' => [ 'add' => 'Dodaj', 'add_new' => 'Dodaj metodę płatności', 'change' => 'Zmień metodę płatności', 'save' => 'Zapisz metodę płatności', 'show_alternatives' => 'Alternatywne sposoby płatności', ], 'add_one' => 'Nie masz zapisanych metod płatności.', 'alternatives' => 'Możesz subskrybować Kankę przy pomocy tych alternatyw. Twoje konto zostanie obciążone raz i subskrypcja nie odnowi się automatycznie.', 'card' => 'Karta', 'card_name' => 'Nazwisko na karcie', 'country' => 'Kraj pobytu', 'ending' => 'Ważność do', 'helper' => 'Karta zostanie użyta do wszystkich twoich subskrypcji', 'new_card' => 'Dodaj metodę płatności', 'saved' => ':brand o numerze kończącym się na :last4', ], 'periods' => [ 'monthly' => 'Miesięczne', 'yearly' => 'Roczne', ], 'placeholders' => [ 'downgrade_reason' => 'Jeśli chcesz, powiedz nam czemu zmniejszasz poziom subskrybcji', 'reason' => 'Jeżeli chcesz, powiedz nam dlaczego rezygnujesz ze wspierania Kanki. Czy brakuje ci jakichś funkcji, czy też zmieniła się twoja sytuacja finansowa?', ], 'plans' => [ 'cost_monthly' => ':currency :amount rozliczane miesięcznie', 'cost_yearly' => ':currency :amount rozliczane rocznie', ], 'sub_status' => 'Informacje o subskrypcji', 'subscription' => [ 'actions' => [ 'cancel' => 'Anuluj subskrybcję', 'downgrading' => 'Skontaktuj się z nami by zmniejszyć poziom subskrypcji', 'rollback' => 'Zmień na Kobolda', 'subscribe' => 'Zmień na poziom :tier miesięcznie', 'subscribe_annual' => 'Zmień na poziom :tier rocznie', ], ], 'success' => [ 'alternative' => 'Zarejestrowaliśmy płatność. Otrzymasz powiadomienie kiedy tylko zostanie przetworzona i aktywujemy subskrypcję.', 'callback' => 'Subskrypcja udana. Zaktualizujemy twoje konto gdy tylko obsługujący płatności powiadomi nas o zmianie (to może potrwać kilka minut).', 'currency' => 'Zmieniono walutę rozliczenia.', 'subscribed' => 'Subskrypcja udana. Nie zapomnij o newsletterze głosowań społeczności, by zawsze wiedzieć kiedy rozpoczyna się głosowanie. Możesz zmienić ustawienia newslettera na stronie profilu.', ], 'tiers' => 'Poziomy subskrypcji', 'trial_period' => 'Subskrypcje roczne mają 14-dniowy okres wypowiedzenia. Jeżeli chcesz anulować subskrypcję roczną i uzyskać zwrot pieniędzy, skontaktuj się z nami przez :email.', 'upgrade_downgrade' => [ 'button' => 'Informacje o zmianie subskrypcji', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Wszystkie korzyści subskrypcji pozostaną aktywne do końca okresu rozliczeniowego.', 'boosts' => 'To samo dotyczy doładowań kampanii. Po utracie doładowania, dodatkowe elementy kampanii nie zostają usunięte, jedynie ukryte.', 'kobold' => 'By anulować subskrypcję, zmień jej poziom na Kobold.', 'premium' => 'To samo dotyczy kampanii premium. Po usunięciu rozwinięcia, opcje premium nie zostają usunięte, ale ukryte do czasu ponownej aktywacji.', ], 'title' => 'Gdy anulujesz subskrypcję', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Twój poziom zostanie aktywny do końca okresu rozliczeniowego, po czym zostanie odpowiednio zmniejszony.', ], 'provide_reason' => 'Powiedz nam proszę, dlaczego zmniejszasz poziom subskrypcji - o ile to możliwe.', 'title' => 'Gdy obniżasz subskrypcję', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Pobierzemy pieniądze od razu i natychmiast uzyskasz dostęp do nowego poziomu.', 'prorate' => 'Zwiększając subskrypcję z poziomu Owlbear na poziom Elemental płacisz tylko różnicę w cenie.', ], 'title' => 'Gdy podnosisz subskrypcję', ], ], 'warnings' => [ 'incomplete' => 'Nie mogliśmy obciążyć karty. Zaktualizuj dane karty kredytowej - podejmiemy kolejną próbę w ciągu kilku dni. Jeżeli znów się nie uda, twoja subskrypcja zostanie anulowana.', 'patreon' => 'Twoje konto jest powiązane z Patreonem. Usuń swoje konto Patreon z Kanki zanim przejdziesz na bezpośrednią subskrypcję.', ], ], ]; ================================================ FILE: lang/pl/sidebar.php ================================================ [ 'count' => 'Członek :member', 'created_campaigns' => 'Twoje kampanie', 'follow_more' => 'Znajdź kampanie', 'followed_campaigns'=> 'Kampanie, które śledzisz', 'new_campaign' => 'Nowa kampania', 'public_campaigns' => 'Kampanie publiczne', 'reorder' => 'Zmień kolejność', 'updated' => 'Zmieniono', ], 'dashboard' => 'Pulpit', 'entity-creator' => 'Szybkie tworzenie', 'gallery' => 'Galeria', 'game' => 'Rozgrywka', 'other' => 'Pozostałe', 'recent' => 'Ostatnie zmiany', 'relations' => 'Relacje', 'settings' => 'Ustawienia', 'time' => 'Czas', 'world' => 'Świat', ]; ================================================ FILE: lang/pl/spotlights.php ================================================ [ 'actions' => [ 'retract' => 'Wycofanie zgłoszenia', ], 'description' => 'Zgłoszenie zostało wysłane i jest teraz oceniane. Poinformujemy cię, czy zostało przyjęte, czy odrzucone.', 'title' => 'Zgłoszenie wysłane', ], 'apply' => [ 'errors' => [ 'empty' => 'Pytanie :field wymaga dłuższej odpowiedzi.', ], ], 'approved' => [ 'description' => 'Gratulujemy! Zgłoszenie zostało przyjęte i umieściliśmy kampanię na stronie :spotlight.', 'title' => 'Zgłoszenie przyjęte', ], 'faq' => [ 'finisher' => 'Zgłoszenie nie gwarantuje wyróżenia. Czytamy je wszystkie, ale nie możemy przyjąć każdego.', 'how' => [ 'a' => [ 'end' => 'Nie liczy się liczba obserwujących, popularność ani poziom subskrybcji.', 'lead' => 'Wybieramy 1-3 kampanii miesięcznie.', 'req1' => 'Wyraźnej specyfiki i tematyki', 'req2' => 'Przemyślanego światotwórstwa', 'req3' => 'Ciekawychy historii i dobrej prezentacji', 'requirements' => 'Wybór jest redakcyjny, a nie konkursowy. Szukamy:', ], 'q' => 'Jak kampanie są wybierane?', ], 'reapply' => [ 'a' => 'Tak. Jeżeli kampania nie została wybrana, możesz ją zgłosić ponownie, zwłaszcza jeśli się rozwinęła.', 'q' => 'Czy mogę zgłosić się więcej niż raz?', ], 'selected' => [ 'a' => [ 'end' => 'Przed publikacją otrzymasz zawiadomienie.', 'lead' => 'Po wybraniu:', 'req1' => 'Kampania otrzyma osiągnięcie Wyróżnienie', 'req2' => 'Zostanie przedstawiona na :blog i stronie :showcase', 'req3' => 'Możemy delikatnie zredagować odpowiedzi dla poprawy czytelności', ], 'q' => 'Co, jeśli moja kampania zostanie wybrana?', ], 'what' => [ 'a' => 'Wyróżnienie pozwala pokazać najlepsze kampanie stworzone w Kance. Wybrane zgłoszenia są wyświetalne na stronie Wyróżnienia Kanki i na blogu, pod postacią krótkiego wywiadu.', 'q' => 'Czym jest wyróżnienie?', ], 'who' => [ 'a' => [ 'end' => 'Nie liczy się rozmiar ani system.', 'lead' => 'Można zgłosić każdą publiczną kampanię.', 'req1' => 'Być widoczna publicznie', 'req2' => 'Wykazywać aktywność (zawartość, historia lub gracze)', 'req3' => 'Prezentować świat, z którego inni mogą się czegoś nauczyć', 'requirements' => 'Kampania powinna:', ], 'q' => 'Kto może się zgłosić?', ], ], 'form' => [ 'actions' => [ 'apply' => 'Wyślij zgłoszenie', 'retract' => 'Wycofaj zgłoszenie', 'save' => 'Zapisz szkic', ], 'draft' => 'To szkic zgłoszenia. Możesz go zapisać i wrócić do niego później.', 'not-public' => 'Kampania nie jest widoczna dla publiczności, więc nie może zostać wyróżniona.', 'preset' => 'Powiedz nam coś o :campaign i wyjaśnij, czemu twoim zdaniem zasługuje na wyróżnienie. Możesz zapisać fomularz i wrócić do tego pytania później.', 'required' => 'Pole obowiązkowe', 'title' => 'Formularz zgłoszenia do wyróżnienia', ], 'overview' => [ 'cta' => 'Zgłoś :name do wyróżnienia', 'not-public' => 'Kampania :name nie jest widoczna dla publiczności', 'showcase' => 'Zobacz wyróżnione kampanie', ], 'placeholders' => [ 'inspiration' => 'Książki, gry, historia, muzyka, klimat', 'kanka' => 'Opowiedz, dlaczego Kanka okazała się dobrym narzędziem do budowy tego świata', 'proud' => 'Na przykład lore, gracze, długowieczność, status', 'stories' => 'Tragiczne, heroiczne, polityczne, rodzinne, zupełnie chaotyczne', 'time' => 'Od miesięcy, lat, dziesięcioleci, na przestrzeni wielu żyć?', 'world' => 'Główne motywy, emocje, konflikty (haczyk)', ], 'questions' => [ 'inspiration' => 'Co zainspirowało ten świat', 'kanka' => 'Czemu używasz Kanki w swojej kampanii?', 'proud' => 'Co budzi twoją największą dumę?', 'share' => 'Zezwalam zespołowi Kanki na wykorzystanie odpowiedzi w materiałach promocyjnych.', 'stories' => 'Jakie historie powstają podczas waszych sesji?', 'time' => 'Od jak dawna rozwijasz ten świat?', 'world' => 'Co jest istotą tego świata?', ], 'rejected' => [ 'description' => 'Twoje zgłoszenie zostało odrzucone. Spróbuj ponownie!', 'title' => 'Zgłoszenie odrzucone', ], 'retract' => [ 'success' => 'Twoje zgłoszenie zostało wycofane. Możesz je ponownie edytować.', ], 'rules' => 'Co miesiąc wybieramy 1-3 kampanii do :showcase Kanki. Wybór nie jest gwarantowany. Po wyróżnieniu, kampania otrzymuje specjalne osiągnięcie i publikujemy wywiad z jej twórcą.', 'started' => 'Na początek wybierz którąś ze swoich kampanii', 'title' => 'Zgłoś do wyróżnienia', ]; ================================================ FILE: lang/pl/starter.php ================================================ [ 'name' => ':user użytkownika', ], 'character1' => [], 'character2' => [], 'item1' => [], 'kingdom1' => [], 'kingdom2' => [], 'name' => ':name (przykład)', 'note1' => [], ]; ================================================ FILE: lang/pl/subscription.php ================================================ [ 'main' => 'Subskrybcja pozwala dołączać większe obrazki, usuwa reklamy, zapewnia :boosters i :more. Płatności realizuje :stripe, więc nie przechowujemy na naszych serwerach informacji o kartach kredytowych.', 'more' => 'inne świetne opcje', ], 'errors' => [ 'grace' => 'Obecna subskrypcja kończy się :date, potem zostanie odnowiona.', 'invalid_card_country' => [ 'brl' => 'Niestety, posiadacze brazylijskich kart kredytowych mogą płacić wyłącznie w BRL. Jeśli uważasz to za błąd, napisz do nas na adres :email.', ], 'invalid_currency' => 'Poprzednia subskrypcja w :old uniemożliwia nową w :new. Przełącz walutę na :old albo napisz do nas na adres: email, jeżeli chcesz ją zmienić.', ], ]; ================================================ FILE: lang/pl/subscriptions/cancellation.php ================================================ 'Szkoda, że się żegnamy! Twoja subskrybcja pozostanie aktywna do :date. Potem kampanie premium zmienią status na zwykłe i wyłączymy wszystkie zwiazane z nimi benefity.', 'loss' => [ 'ads' => [ 'title' => 'Wolność od reklam dla ciebie i twoich graczy', ], 'discord' => [ 'title' => 'Rola ":role" na Discordzie naszej społeczności', ], 'downgrade' => 'Możesz zmniejszyć poziom subskrybcji zamiast zupełnie z niej rezygnować i zachować związne z nią korzyści.', 'premium' => [ 'players' => '{1}:count gracz utraci dostęp do funkcji premium |[2,*]:count graczy utraci dostęp do funkcji premium', 'plugins' => '{1}Dostęp do :count zainstalowanego dodatku|[2,*]Dostęp do :count zainstalowanych dodatków', 'storage' => 'Twoje :current', 'title' => '{0}Status premium ":campaign"|{1}Status premium ":campaign" i :count innej kampanii|[2,*]Status premium ":campaign" i :count innych kampanii', ], 'roadmap' => 'Wciąż ulepszamy Kankę zgodnie z sugestiami użytkowników i naszą :roadmap, może funkcja której potrzebujesz jest tuż za rogiem?', 'title' => 'Zanim anulujesz, pomyśl co tracisz:', ], 'pause' => [ 'button' => 'Wstrzymaj subskrybcję na 1-3 miesięcy', 'helper' => 'Zachowujesz wszystko. Nic nie tracisz. Możesz podjać w każdej chwili.', ], ]; ================================================ FILE: lang/pl/subscriptions/cancelled.php ================================================ [ 'adfree' => 'Brak reklam', 'discord' => 'Role na :discord tylko dla subskrybentów', 'helper' => 'Twoja subskrypcja zachowuje ważność do :date. Do tego czasu wciąż odnosisz specjalne korzyści, takie jak:', 'limit' => 'Większe limity plików', 'more' => 'I nie tylko!', 'premium' => 'Kampanie premium i ich dodatkowe funkcje', 'title' => 'Korzyści są wciąż aktywne', ], 'change' => [ 'action' => 'Odnów subskrypcję', 'helper' => 'Chętnie powitamy cię znów! Możesz odnowić subskrypcję w każdej chwili i podjąć pracę jak gdyby nigdy nic.', 'title' => 'Chcesz zmienić zdanie?', ], 'contact' => [ 'feedback' => 'Jeżeli uważasz, że coś możemy robić lepiej, nie wahaj się nam powiedzieć:', 'helper' => 'Nawet bez subskrypcji jesteś częścią społeczności Kanki. Możesz dalej budować światy i wrócić do subskrybowania w dogodniejszym momencie.', 'send' => 'Prześlij nam swoją opinię', 'title' => 'Zostańmy w kontakcie', ], 'next' => [ 'data' => 'Dane będą bezpieczne! Niczego nie usuniemy, i w każdej chwili możesz zasubskrybować Kankę ponownie', 'discord' => 'Nie będziesz mieć już dostępu do opcji i kanałów dostępnych tylko dla subskrybentów', 'helper' => 'Po zakończeniu subskrypcji:', 'premium' => 'Każda kampania premium utraci ten status', 'title' => 'Co stanie się teraz?', ], 'seo_title' => 'Szkoda, że ochodzisz', 'subtitle' => 'Dziękujemy za dotychczasową subskrypcję, twoje wsparcie wiele dla nas znaczyło! Oto, co stanie się teraz:', 'title' => 'Szkoda, że nas opuszczasz, :name', ]; ================================================ FILE: lang/pl/subscriptions/confirm.php ================================================ [ 'pay' => 'Zapłać teraz :amount :currency', 'paypal' => 'Zapłać :amount :currency PayPalem', 'subscribe' => 'Subskrybujesz za :currency:amount', ], 'helpers' => [ 'auto-renew' => [ 'monthly' => 'Subskrybcja odnawia się automatycznie co miesiąc. Opłatę pobierzemy w dniu :date.', 'none' => 'PayPal umożliwia zapłatę jednorazową i nie odnawia się automatycznie. Twoja subskrybcja wygaśnie po :date i trzeba ją będzie odnowić.', 'yearly' => 'Subskrypcja odnawia się automatycznie co 12 miesięcy. Opłatę pobierzemy w dniu :date.', ], 'paypal' => 'Przeniesiesz się na stronę PayPal by dokończyć płatność.', 'refund' => 'Oferujemy 14-dniowy okres rezygnacji z każdej subskrypcji rocznej. Nie musisz się tłumaczyć, wystarczy napisać maila na adres :email, by wszcząć procedurę zwrotu kosztów.', 'tiny' => 'Dziękujemy za wsparcie malutkiej grupki zapalonych światotwórców.', ], 'title' => 'Subskrybcja :name', ]; ================================================ FILE: lang/pl/subscriptions/faq.php ================================================ [ 'answer' => 'Oczywiście! Stosowną opcję znajdziesz na stronie obecnej subskrypcji. Po anulowaniu zachowasz dostęp do wszystkich korzyści do rozpoczęcia nowego okresu rozliczeniowego. Pamiętaj, że subskrypcja przez PayPal ustaje automatycznie po opłaconym okresie, ponieważ firma nie pozwala na automatyczne odnowienie.', 'question' => 'Czy mogę anulować subskrypcję w każdej chwili?', ], 'cost' => [ 'answer' => 'Kanka posiada trzy poziomy subskrypcji, nazwane od potworów z D&D: Owlbear, Wyvern i Elemental. Cena zależy od wybranej waluty (USD, EUR albo BRL). Jeżeli wybierzesz plan roczny, uzyskasz dwa darmowe miesiące w stosunku do rozliczenia miesięcznego.', 'question' => 'Ile kosztuje subskrybcja?', ], 'data' => [ 'answer' => 'Nie martw się, nigdy nie usuwamy danych po zakończeniu subskrypcji. Kampanie premium wracają po prostu do formy podstawowej i dodatkowe opcje przestają być dostępne. Jeżeli wznowisz subskrypcję, natychmiast odzyskasz dostęp do funkcji premium w takiej postaci, w jakiej je zostawiłeś.', 'question' => 'Co stanie się z moimi danymi po anulowaniu subskrybcji?', ], 'discount' => [ 'answer' => 'Tak! Nagrodą za subskrypcję roczną są dwa darmowe miesiące w stosunku do opłaty miesięcznej. W ten sposób dziękujemy za dłuższe zobowiązanie wobec Kanki.', 'question' => 'Czy subskrypcja roczna zwiera zniżkę?', ], 'downgrade' => [ 'answer' => 'Możesz zmienić poziom subskrypcji w każdej chwili. Jeśli ją zwiększasz, zapłacisz w bieżącym okresie tylko różnicę między obecnym i przyszłym planem. Podczas redukcji nowy, niższy poziom zacznie obowiązywać od początku kolejnego okresu rozliczeniowego - do tej chwili nie stracisz obecnych korzyści.', 'question' => 'Jak zwiększyć/zmniejszyć poziom subskrybcji?', ], 'fail' => [ 'answer' => 'Jeżeli nie uda się pobrać płatności, zostaniesz powiadomiony mailem i podejmiemy jeszcze trzy próby obciążenia karty kredytowej. Jeżeli również zakończą się niepowodzeniem, subskrypcja zostanie zawieszona. Problem można zwykle łatwo rozwiązać, aktualizując informacje dotyczące :billing za pomocą poprawnych danych.', 'question' => 'Co, jeśli płatność się nie uda?', ], 'help' => [ 'answer' => 'Dzięki subskrypcji opłacasz nasz czas, serwery i możliwość utrzymywania Kanki bez konieczności wzrostu za wszelką cenę. Szybciej eliminujemy błędy, tworzymy funkcje w które naprawdę wierzymy i działamy w interesie społeczności, a nie inwestorów. Czyli: Kanka dzięki temu żyje i się rozwija.', 'question' => 'Jak moja subskrypcja wspiera Kankę?', ], 'methods' => [ 'answer' => 'Przyjmujemy karty kredytowe oraz płatność przez PayPal w dolarach, euro i realach brazylijskich. Traktujemy bezpieczeństwo danych z najwyższą powagą, więc wszystkie dane kart kredytowych przechowuje nasz zaufany partner obsługujący płatności, :stripe.', 'question' => 'Jakie przyjmujecie metody płatności?', ], 'refund' => [ 'answer' => 'Tak! W wypadku subskrypcji rocznej oferujemy 14-dniowy okres próbny, w czasie którego można uzyskać zwrot kosztów. Nie musisz podawać powodu. Wystarczy, że napiszesz maila na adres :email prosząc o zwrot pieniędzy, i wszystkim się zajmiemy.', 'question' => 'Czy zwracacie pieniądze?', ], 'renewal' => [ 'answer' => 'Tak, jeśli używasz do subskrypcji karty kredytowej, po zakończeniu każdego okresu rozliczeniowego automatycznie obciążymy ją tą samą kwotą. Inaczej jest w wypadku subskrypcji przez PayPal: nie odnawia się automatycznie i po każdym okresie należy ją odnowić ręcznie.', 'question' => 'Czy podczas odnawiania subskrypcji płatność będzie automatyczna?', ], 'security' => [ 'answer' => 'Bezpieczeństwo danych to nasz priorytet. Korzystamy z usług :stripe, firmy zapewniającej najwyższe standardy bezpieczeństwa płatności zgodne z PCI. Zarządza i przechowuje wszystkie dane użytkowników w sposób zgodny z wymaganiami RODO. Żadne dane nie trafiają na nasze serwery.', 'question' => 'Czy moje informacje finansowe są bezpieczne?', ], 'sharing' => [ 'answer' => 'Oczywiście! Opcje premium kampanii aktywowane dzięki subskrypcji są dostępne dla wszystkich uczestników - nawet jeżeli sami nie subskrybują Kanki. Dzięki temu możecie dzielić się kosztami tworzenia światów.', 'question' => 'Czy mogę dzielić z kimś konto/subskrypcję?', ], 'title' => 'Popularne pytania', 'trial' => [ 'answer' => 'Kanka nie posiada wprawie darmowej wersji próbnej, ale oferuje za darmo liczne narzędzia do tworzenia światów i zarządzania kampanią. Gdy przestaną ci wystarczać, subskrypcja pozwoli ci korzystać z opcji premium, w tym zwiększonego limitu plików, braku reklam i dodatkowych funkcji światotwórczych.', 'question' => 'Czy macie darmową wersję próbną?', ], 'update' => [ 'answer' => 'Aktualizacja danych do płatności jest prosta: wejdź na stronę :billing w ustawieniach konta. Tam możesz zmienić metodę płatności, zaktualizować dane karty albo zmienić adres rozliczenia.', 'question' => 'Jak zaktualizować dane do płatności?', ], 'why' => [ 'answer' => 'Kankę stworzył i rozwija mały, niezależny zespół. Dzięki subskrybcji możemy realizować długoterminowe cele, usprawniać Kankę i unikać ciemnych wzorców. Nie płacisz korporacji: utrzymujesz bezpośrednio ludzi, którzy tworzyli, postawili i rozwijają platformę.', 'question' => 'Dlaczego Kanka ma płatną subskrybcję?', ], ]; ================================================ FILE: lang/pl/subscriptions/finish.php ================================================ [ 'action' => 'Połącz ze swoim kontem Discord', 'enjoy' => 'Przejdź na Discord i ciesz się nowymi korzyściami', 'helper' => 'Jako subskrybent otrzymasz elitarne role oraz prywatne kanały w naszej społeczności Discord - ale wpierw musisz połączyć swoje konto Discord z Kanką.', 'title' => 'Uzyskaj korzyści na Discordzie', ], 'header' => 'Twoja subskrypcja jest aktywna, witaj w wewnętrznym kręgu światotwórców!', 'help' => [ 'contact-us' => 'skontaktuj się z nami', 'helper' => 'Jeśli masz pytania albo opinie, :contact-us albo zajrzyj do naszych :docs.', 'title' => 'Potrzebujesz pomocy?', ], 'next' => 'Twoje wsparcie pomaga nam rosnąć i ulepszać Kankę. Oto jak możesz skorzystać z korzyści związanych z subskrypcją:', 'premium' => [ 'action' => 'Odblokuj', 'helper' => 'Odblokuj funkcje premium, w tym własne moduły, dostęp do :plugins i nie tylko.', 'title' => 'Używaj funkcji premium kampanii', ], 'roadmap' => [ 'action' => 'Zobacz mapę drogową i sugerowane funkcje', 'helper' => 'Kankę tworzymy z myślą o was. Możesz zobaczyć dostępny publicznie plan rozwoju i głosować na funkcje, które chcesz wprowadzić.', 'title' => 'Pomóż kształtować przyszłość Kanki', ], 'title' => 'Dziękujemy za wspieranie Kanki!', ]; ================================================ FILE: lang/pl/subscriptions/free-trial.php ================================================ [ 'accept' => 'Rozpocznij okres próbny', 'magic' => 'Nie potrzeba karty kredytowej. Czysta magia.', ], 'final' => [ 'magic' => 'Żadnych zobowiązań, żadnych pułapek. Po prostu więcej magii w kampanii.', 'title' => 'Rozpocznij 15-dniowy okres próbny', ], 'header' => 'Widzimy twoje wysiłki w krainach Kanki. By je uhonorować, oferujemy ci :what. Nic nie zapłacisz: ani złotem, ani klejnotami, ani kartą kredytową.', 'included' => [ 'title' => 'Co otrzymujesz', 'upsell'=> [ 'action' => 'Zobacz poziomy subskrypcji', 'pitch' => 'To tylko poziom :tier. Chcesz zyskać jeszcze więcej?', ], ], 'pitch' => [ 'title' => 'Ciesz się darmowym 15-dniowym okresem próbnym na nasz koszt! Użyj wszystkich funkcji premium by zobaczyć, czego ci dotychczas brakowało.', ], 'started' => [ 'header' => 'Udało ci się rozpocząć darmowy okres próbny, witaj w wewnętrznym kręgu światotwórców.', 'title' => 'Witaj w okresie próbnym!', ], 'tease' => [ 'helper' => 'Chcesz zamieszczać jeszcze więcej plików i zyskać dalsze kampanie premium? Przejdź na stronę :subscription i zobacz, co na ciebie tam czeka.', 'title' => 'A może chcesz więcej', ], 'title' => 'Czeka na ciebie nowa przygoda!', 'what' => ':amount -dniową możliwość korzystania z poziomu :tier', 'why' => [ 'helper' => 'Nie szczędzisz wysiłków w budowie swojej kampanii. Doceniamy twoją lojalność. To drobny wyraz uznania ze strony twórców Kanki.', 'title' => 'Zasługujesz na nagrodę', ], ]; ================================================ FILE: lang/pl/subscriptions/paypal.php ================================================ [ 'contact' => 'Jeśli problem się powtarza, napisz do nas na :email.', 'failed' => 'PayPal nie wygenerował rachunku. Spróbuj ponownie.', 'incomplete' => 'Płatność PayPal jest niekompletna. Spróbuj ponownie.', 'rejected' => 'PayPal wygenerował niewłaściwy rachunek. Spróbuj ponownie.', ], ]; ================================================ FILE: lang/pl/subscriptions/promos.php ================================================ [ 'inactive' => 'Ta promocja jest już zakończona.', 'invalid' => 'Nieznana promocja.', 'only-new' => 'Promocja dostępna jest tylko dla nowych subskrybentów.', ], ]; ================================================ FILE: lang/pl/subscriptions/renew.php ================================================ [ 'renew' => 'Odnów subskrybcję', ], 'helper' => 'Możesz jednak wybrać odnowę subskrypcji, by nieprzerwanie korzystać ze wszystkich związanych z nią funkcji.', 'title' => 'Odnowienie subskrybcji', ]; ================================================ FILE: lang/pl/subscriptions.php ================================================ [ 'failed' => 'Stripe nie zdołało pobrać płatności. W związku z tym twoja subskrypcja została anulowana.', ], ]; ================================================ FILE: lang/pl/tags.php ================================================ [ 'actions' => [ 'add' => 'Dodaj nową etykietę', 'add_entity' => 'Dodaj do elementu', ], 'create' => [ 'attach_success' => '{1} Dodano etykietę :name :count elementowi.|[2,*] Dodano etykietę :name :count elementom.', 'attach_success_entity' => 'Pomyślnie zmieniono etykiety :name.', 'entity' => 'Dodaj etykiety do :name', 'helper' => 'Oznacza jeden lub więcej elementów etykietą :name', 'title' => 'Oznaczanie etykietą', ], ], 'create' => [ 'title' => 'Nowa etykieta', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Pochodne', 'is_auto_applied' => 'Dodawaj automatycznie', 'is_hidden' => 'Ukryj w nagłówkach i dymkach', ], 'helpers' => [ 'no_children' => 'Obecnie nie oznaczono tą etykietą żadnych elementów.', 'no_posts' => 'Obecnie nie oznaczono tą etykietą żadnych komentarzy.', ], 'hints' => [ 'children' => 'Na liście znajdują się wszystkie elementy posiadające tę etykietę i etykiety pochodne.', 'is_auto_applied' => 'Zaznacz by dodawać tę etykietę automatycznie do nowych elementów.', 'is_hidden' => 'Po zaznaczeniu, ta etykieta nie będzie wyświetlana w nagłówku i dymkach elementu', 'tag' => 'Na liście znajdują się wszystkie elementy posiadające tę etykietę.', ], 'index' => [], 'lists' => [ 'empty' => 'Stosuj etykiety, by łączyć i grupować różne elementy, ułatwiając filtrowanie i nawigację.', ], 'placeholders' => [ 'type' => 'Wiedza tajemna, wojna, historia, religia, weksylologia', ], 'show' => [ 'tabs' => [ 'children' => 'Pochodne', ], ], 'tags' => [], 'transfer' => [ 'entities' => [ 'helper' => 'Zmienia etykietę :name na inną u wszystkich elementów', 'title' => 'Zmiana etykiety elementów', ], 'fail' => 'Nie udało się zmienić elementom etykiety :tag na nową etykietę :newTag.', 'fail_post' => 'Nie udało się zmienić komentarzom etykiety :tag na nową etykietę :newTag.', 'posts' => [ 'helper' => 'Zmienia etykietę :name na inną u wszystkich komentarzy', 'title' => 'Zmiana etykiety komentarzy', ], 'success' => 'Zamieniono elementom etykietę :tag na nową etykietę :newTag.', 'success_post' => 'Zamieniono komentarzom etykietę :tag na nową etykietę :newTag.', 'transfer' => 'Zamień', ], ]; ================================================ FILE: lang/pl/teams.php ================================================ [ 'lead' => 'Solidne i przyjemne tworzenie światów.', 'translations' => 'Tłumaczenie', ], 'leads' => [ 'translators' => 'Kankę przełożyło na liczne języki to oto grono tłumaczy.', ], 'people'=> [ 'itzamna' => [ 'title' => 'Młodszy developer', ], 'jay' => [ 'title' => 'Założyciel i główny projektant', ], 'jon' => [ 'title' => 'Współzałożyciel i dyrektor finansowy', ], 'kaz' => [ 'title' => 'Pogromca bugów', ], 'laura' => [ 'title' => 'Media Społecznościowe', ], ], ]; ================================================ FILE: lang/pl/tiers.php ================================================ [ 'pay' => [ 'monthly' => 'Płatność miesięczna', 'save' => '2 miesiące za darmo', 'yearly' => 'Płatność roczna', ], 'subscribe' => [ 'choose' => 'Wybierz :tier', 'downgrade' => 'Zmniejsz do poziomu :tier', 'monthly' => ':tier miesięcznie', 'upgrade' => ':tier miesięcznie', 'yearly' => ':tier rocznie', ], ], 'current' => 'Obecna subskrypcja', 'features' => [ 'api_requests' => ':amount wywołań API / min', 'boosters' => 'Doładowania kampanii', 'discord' => 'Role na Discordzie', 'feature_influence' => 'Wpływ na nowe funkcje', 'file_size' => 'Dodawanie plików o rozmiarze :size', 'import' => 'Import kampanii', 'modules' => ':count własnych modułów', 'nice_image' => 'Domyślne ikony elementów', 'no_ads' => 'Brak reklam', 'pagination' => 'Do :amount elementów wyświetlanych na stronie', 'premium' => 'Każdy zawiera: :storage GB miejsca, :modules własne moduły', 'roadmap' => 'Głosuj na pomysły w przygotowaniu', 'storage' => ':count GB przestrzeni dyskowej', ], 'helpers' => [ 'premium' => 'To korzyści z odblokowania kampanii premium.', ], 'periods' => [ 'billed_monthly' => 'opłata miesięczna', 'billed_yearly' => 'opłata roczna', ], 'pricing' => ':currency :amount / miesiąc', 'ribbons' => [ 'best-value' => 'Najkorzystniejsza oferta', 'current' => 'Obecna subskrypcja', ], 'target' => [ 'elemental' => 'Dla koneserów światotwórstwa pracujących jednocześnie z wieloma rozległymi światami i wielkimi kampaniami.', 'owlbear' => 'Dla samotnych światotwórców, którzy chcą dodatkowych opcji w głównej kampanii.', 'wyvern' => 'Dla mistrzów gry prowadzących kilka kampanii oraz uczestników grupowych projektów narracyjnych.', ], 'tiny' => 'malutki zespół', 'toggle' => [], 'why' => 'Kankę tworzy :tiny zapalonych światotwórców. Subckrybcje pozwalają im utrzymać siebie, serwery i znaleźć czas konieczny do poprawiania jakości platformy. Nie stosują mrocznych wzorców projektowych, nie podlegają presji inwestorów i nie dążą do bezustannego wzrostu. Rozwój jest stały i zgodny z życzeniami społeczności.', ]; ================================================ FILE: lang/pl/timelines/.gitkeep ================================================ ================================================ FILE: lang/pl/timelines/elements.php ================================================ [ 'copy_with_name' => 'Kopiuj zaawansowaną wzmiankę z nazwą elementu.', 'success' => 'Skopiowano do schowka zaawansowaną wzmiankę z nazwą elementu.', ], 'create' => [ 'success' => 'Dodano element historii.', 'title' => 'Nowy element historii', ], 'delete' => [ 'success' => 'Usunięto element :name.', ], 'edit' => [ 'success' => 'Zaktualizowano element', 'title' => 'Edycja elementu historii', ], 'fields' => [ 'date' => 'Data', 'era' => 'Epoka', 'icon' => 'Ikona', 'use_entity_entry' => 'Wyświetlaj dołączony element. Po zaznaczeniu, ewentualny opis elementu będzie wyświetlany jako pierwszy.', 'use_event_date' => 'Użyj daty wydarzenia', ], 'helpers' => [ 'date' => 'Jeżeli ta część historii związana jest z istniejącym już wydarzeniem, używa daty tego wydarzenia.', 'entity_is_private' => 'Element powiązany z tym wpisem jest tajny.', 'icon' => 'Skopiuj kod HTML ikony z :fontawesome lub :rpgawesome.', 'is_collapsed' => 'Szczegóły elementu są domyślnie zwinięte.', ], 'placeholders' => [ 'date' => 'np. 42 marca albo 1332-1337', 'name' => 'Wymagana, jeżeli nie wskazano elementu', 'position' => 'Kolejność na liście elementów tej epoki. Zostaw puste, by dodać na końcu.', ], 'warning' => [], ]; ================================================ FILE: lang/pl/timelines/eras.php ================================================ [ 'add' => 'Dodaj nową epokę', ], 'bulks' => [ 'delete' => '{0} Usunięto :count epok.|{1} Usunięto :count epokę.|[2,3,4] Usunięto :count epoki.|[5,*] Usunięto :count epok.', ], 'create' => [ 'success' => 'Stworzono epokę \':name\'.', 'title' => 'Nowa epoka', ], 'delete' => [ 'success' => 'Usunięto epokę \':name\'.', ], 'edit' => [ 'success' => 'Zmieniono epokę \':name\'.', 'title' => 'Edycja epoki :name', ], 'fields' => [ 'abbreviation' => 'Skrót', 'end_year' => 'Rok zakończenia', 'is_collapsed' => 'Zwinięta', 'start_year' => 'Rok rozpoczęcia', ], 'helpers' => [ 'eras' => 'Przed dodaniem epok należy stworzyć historię.', 'is_collapsed' => 'Epoka domyślnie wyświetlana jest zwinięta (zminimalizowana).', 'primary' => 'Podziel historię na epoki. By historia działała poprawnie, musi posiadać przynajmniej jedną epokę.', ], 'index' => [ 'title' => 'Epoki historii :name', ], 'placeholders' => [ 'abbreviation' => 'AD, PNE, 3E', 'end_year' => 'Rok zakończenia epoki. Pozostaw puste, jeżeli trwa obecnie.', 'name' => 'Nowoczesność, epoka brązu, wojny galaktyczne', 'start_year' => 'Rok rozpoczęcia epoki. Pozostaw puste, jeżeli to pierwsza epoka historii.', ], 'reorder' => [], ]; ================================================ FILE: lang/pl/timelines.php ================================================ [ 'add_element' => 'Dodaj do epoki :era', 'back' => 'Powrót do :name', 'save_order' => 'Zapisz nową kolejność', ], 'create' => [ 'title' => 'Nowa historia', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_elements' => 'Kopiuj składowe', 'copy_eras' => 'Kopiuj epoki', 'eras' => 'Epoki', 'reverse_order' => 'Odwróć kolejność epok', ], 'helpers' => [ 'no_era_v2' => 'Ta historia nie ma obecnie żadnych epok. Stwórz jedną lub więcej epok, do których możesz potem dodawać elementy.', 'reverse_order' => 'Zaznacz by wyświetlać epoki w odwróconym porządku chronologicznym (od najdawniejszej)', ], 'index' => [], 'lists' => [ 'empty' => 'Przygotuj oś czasu, na której umieścisz najważniejsze wydarzenia kształujące obecną postać świata.', ], 'placeholders' => [ 'type' => 'Główna, kronika dziejów świata, historia królestwa', ], 'reorder' => [ 'empty' => 'Dodaj epoki i ich elementy, by móc zmienić ich kolejność.', 'success' => 'Zmieniono kolejność :name', 'title' => 'Zmień kolejność :name', ], 'show' => [ 'tabs' => [ 'reorder-elements' => 'Zmiana kolejności elementów', ], ], 'timelines' => [], ]; ================================================ FILE: lang/pl/users/profile.php ================================================ [ 'wordsmith' => 'Oto istny wieszcz! Zwycięzca konkursu światotwórczego', ], 'fields' => [ 'achievements' => 'Osiągnięcia', 'banned' => 'Użytkownik zablokowany', 'entities_created' => 'Stworzonych elementów :help :count', 'member_since' => 'Użytkuje Kankę od :date', 'public_campaigns' => 'Kampanie publiczne', 'subscriber_since' => 'Subskrybuje od :date', ], 'helpers' => [ 'entities_created' => 'Powyższe wartości są aktualizowane codziennie.', ], 'title' => 'Profil :name', ]; ================================================ FILE: lang/pl/validation-inline.php ================================================ 'Pole musi zostać zaakceptowane.', 'active_url' => 'Pole jest nieprawidłowym adresem URL.', 'after' => 'Pole musi być datą późniejszą od :date.', 'after_or_equal' => 'Pole musi być datą nie wcześniejszą niż :date.', 'alpha' => 'Pole może zawierać jedynie litery.', 'alpha_dash' => 'Pole może zawierać jedynie litery, cyfry i myślniki.', 'alpha_num' => 'Pole może zawierać jedynie litery i cyfry.', 'array' => 'Pole musi być tablicą.', 'before' => 'Pole musi być datą wcześniejszą od :date.', 'before_or_equal' => 'Pole musi być datą nie późniejszą niż :date.', 'between' => [ 'array' => 'Pole musi składać się z :min - :max elementów.', 'file' => 'Pole musi zawierać się w granicach :min - :max kilobajtów.', 'numeric' => 'Pole musi zawierać się w granicach :min - :max.', 'string' => 'Pole musi zawierać się w granicach :min - :max znaków.', ], 'boolean' => 'Pole musi mieć wartość logiczną prawda albo fałsz.', 'confirmed' => 'Potwierdzenie pola nie zgadza się.', 'date' => 'Pole nie jest prawidłową datą.', 'date_equals' => 'Pole musi być datą równą :date.', 'date_format' => 'Pole nie jest w formacie :format.', 'different' => 'Pole oraz :other muszą się różnić.', 'digits' => 'Pole musi składać się z :digits cyfr.', 'digits_between' => 'Pole musi mieć od :min do :max cyfr.', 'dimensions' => 'Pole ma niepoprawne wymiary.', 'distinct' => 'Pole ma zduplikowane wartości.', 'email' => 'Pole nie jest poprawnym adresem e-mail.', 'ends_with' => 'Pole musi kończyć się jedną z następujących wartości: :values.', 'exists' => 'Zaznaczona wartość jest nieprawidłowa.', 'file' => 'Pole musi być plikiem.', 'filled' => 'Pole nie może być puste.', 'gt' => [ 'array' => 'Pole musi mieć więcej niż :value elementów.', 'file' => 'Pole musi być większe niż :value kilobajtów.', 'numeric' => 'Pole musi być większe niż :value.', 'string' => 'Pole musi być dłuższe niż :value znaków.', ], 'gte' => [ 'array' => 'Pole musi mieć :value lub więcej elementów.', 'file' => 'Pole musi być większe lub równe :value kilobajtów.', 'numeric' => 'Pole musi być większe lub równe :value.', 'string' => 'Pole musi być dłuższe lub równe :value znaków.', ], 'image' => 'Pole musi być obrazkiem.', 'in' => 'Zaznaczony element jest nieprawidłowy.', 'in_array' => 'Pole nie znajduje się w :other.', 'integer' => 'Pole musi być liczbą całkowitą.', 'ip' => 'Pole musi być prawidłowym adresem IP.', 'ipv4' => 'Pole musi być prawidłowym adresem IPv4.', 'ipv6' => 'Pole musi być prawidłowym adresem IPv6.', 'json' => 'Pole musi być poprawnym ciągiem znaków JSON.', 'lt' => [ 'array' => 'Pole musi mieć mniej niż :value elementów.', 'file' => 'Pole musi być mniejsze niż :value kilobajtów.', 'numeric' => 'Pole musi być mniejsze niż :value.', 'string' => 'Pole musi być krótsze niż :value znaków.', ], 'lte' => [ 'array' => 'Pole musi mieć :value lub mniej elementów.', 'file' => 'Pole musi być mniejsze lub równe :value kilobajtów.', 'numeric' => 'Pole musi być mniejsze lub równe :value.', 'string' => 'Pole musi być krótsze lub równe :value znaków.', ], 'max' => [ 'array' => 'Pole nie może mieć więcej niż :max elementów.', 'file' => 'Pole nie może być większe niż :max kilobajtów.', 'numeric' => 'Pole nie może być większe niż :max.', 'string' => 'Pole nie może być dłuższe niż :max znaków.', ], 'mimes' => 'Pole musi być plikiem typu :values.', 'mimetypes' => 'Pole musi być plikiem typu :values.', 'min' => [ 'array' => 'Pole musi mieć przynajmniej :min elementów.', 'file' => 'Pole musi mieć przynajmniej :min kilobajtów.', 'numeric' => 'Pole musi być nie mniejsze od :min.', 'string' => 'Pole musi mieć przynajmniej :min znaków.', ], 'not_in' => 'Zaznaczona wartość jest nieprawidłowa.', 'not_regex' => 'Format pola jest nieprawidłowy.', 'numeric' => 'Pole musi być liczbą.', 'password' => 'Hasło jest nieprawidłowe.', 'present' => 'Pole musi być obecne.', 'regex' => 'Format pola jest nieprawidłowy.', 'required' => 'Pole jest wymagane.', 'required_if' => 'Pole jest wymagane gdy :other ma wartość :value.', 'required_unless' => 'Pole jest wymagane jeżeli :other nie znajduje się w :values.', 'required_with' => 'Pole jest wymagane gdy :values jest obecny.', 'required_with_all' => 'Pole jest wymagane gdy wszystkie :values są obecne.', 'required_without' => 'Pole jest wymagane gdy :values nie jest obecny.', 'required_without_all' => 'Pole jest wymagane gdy żadne z :values nie są obecne.', 'same' => 'Pole i :other muszą być takie same.', 'size' => [ 'array' => 'Pole musi zawierać :size elementów.', 'file' => 'Pole musi mieć :size kilobajtów.', 'numeric' => 'Pole musi mieć :size.', 'string' => 'Pole musi mieć :size znaków.', ], 'starts_with' => 'Pole musi zaczynać się jedną z następujących wartości: :values.', 'string' => 'Pole musi być ciągiem znaków.', 'timezone' => 'Pole musi być prawidłową strefą czasową.', 'unique' => 'Taka wartość już występuje.', 'uploaded' => 'Nie udało się wgrać pliku.', 'url' => 'Format pola jest nieprawidłowy.', 'uuid' => 'Pole musi być poprawnym identyfikatorem UUID.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], ]; ================================================ FILE: lang/pl/validation.php ================================================ 'Pole :attribute musi zostać zaakceptowane.', 'active_url' => 'Pole :attribute jest nieprawidłowym adresem URL.', 'after' => 'Pole :attribute musi być datą późniejszą od :date.', 'after_or_equal' => 'Pole :attribute musi być datą nie wcześniejszą niż :date.', 'alpha' => 'Pole :attribute może zawierać jedynie litery.', 'alpha_dash' => 'Pole :attribute może zawierać jedynie litery, cyfry i myślniki.', 'alpha_num' => 'Pole :attribute może zawierać jedynie litery i cyfry.', 'array' => 'Pole :attribute musi być tablicą.', 'before' => 'Pole :attribute musi być datą wcześniejszą od :date.', 'before_or_equal' => 'Pole :attribute musi być datą nie późniejszą niż :date.', 'between' => [ 'numeric' => 'Pole :attribute musi zawierać się w granicach :min - :max.', 'file' => 'Pole :attribute musi zawierać się w granicach :min - :max kilobajtów.', 'string' => 'Pole :attribute musi zawierać się w granicach :min - :max znaków.', 'array' => 'Pole :attribute musi składać się z :min - :max elementów.', ], 'boolean' => 'Pole :attribute musi mieć wartość logiczną prawda albo fałsz.', 'confirmed' => 'Potwierdzenie pola :attribute nie zgadza się.', 'date' => 'Pole :attribute nie jest prawidłową datą.', 'date_equals' => 'Pole :attribute musi być datą równą :date.', 'date_format' => 'Pole :attribute nie jest w formacie :format.', 'different' => 'Pole :attribute oraz :other muszą się różnić.', 'digits' => 'Pole :attribute musi składać się z :digits cyfr.', 'digits_between' => 'Pole :attribute musi mieć od :min do :max cyfr.', 'dimensions' => 'Pole :attribute ma niepoprawne wymiary.', 'distinct' => 'Pole :attribute ma zduplikowane wartości.', 'email' => 'Pole :attribute nie jest poprawnym adresem e-mail.', 'ends_with' => 'Pole :attribute musi kończyć się jedną z następujących wartości: :values.', 'exists' => 'Zaznaczone pole :attribute jest nieprawidłowe.', 'file' => 'Pole :attribute musi być plikiem.', 'filled' => 'Pole :attribute nie może być puste.', 'gt' => [ 'numeric' => 'Pole :attribute musi być większe niż :value.', 'file' => 'Pole :attribute musi być większe niż :value kilobajtów.', 'string' => 'Pole :attribute musi być dłuższe niż :value znaków.', 'array' => 'Pole :attribute musi mieć więcej niż :value elementów.', ], 'gte' => [ 'numeric' => 'Pole :attribute musi być większe lub równe :value.', 'file' => 'Pole :attribute musi być większe lub równe :value kilobajtów.', 'string' => 'Pole :attribute musi być dłuższe lub równe :value znaków.', 'array' => 'Pole :attribute musi mieć :value lub więcej elementów.', ], 'image' => 'Pole :attribute musi być obrazkiem.', 'in' => 'Zaznaczony element :attribute jest nieprawidłowy.', 'in_array' => 'Pole :attribute nie znajduje się w :other.', 'integer' => 'Pole :attribute musi być liczbą całkowitą.', 'ip' => 'Pole :attribute musi być prawidłowym adresem IP.', 'ipv4' => 'Pole :attribute musi być prawidłowym adresem IPv4.', 'ipv6' => 'Pole :attribute musi być prawidłowym adresem IPv6.', 'json' => 'Pole :attribute musi być poprawnym ciągiem znaków JSON.', 'lt' => [ 'numeric' => 'Pole :attribute musi być mniejsze niż :value.', 'file' => 'Pole :attribute musi być mniejsze niż :value kilobajtów.', 'string' => 'Pole :attribute musi być krótsze niż :value znaków.', 'array' => 'Pole :attribute musi mieć mniej niż :value elementów.', ], 'lte' => [ 'numeric' => 'Pole :attribute musi być mniejsze lub równe :value.', 'file' => 'Pole :attribute musi być mniejsze lub równe :value kilobajtów.', 'string' => 'Pole :attribute musi być krótsze lub równe :value znaków.', 'array' => 'Pole :attribute musi mieć :value lub mniej elementów.', ], 'max' => [ 'numeric' => 'Pole :attribute nie może być większe niż :max.', 'file' => 'Pole :attribute nie może być większe niż :max kilobajtów.', 'string' => 'Pole :attribute nie może być dłuższe niż :max znaków.', 'array' => 'Pole :attribute nie może mieć więcej niż :max elementów.', ], 'mimes' => 'Pole :attribute musi być plikiem typu :values.', 'mimetypes' => 'Pole :attribute musi być plikiem typu :values.', 'min' => [ 'numeric' => 'Pole :attribute musi być nie mniejsze od :min.', 'file' => 'Pole :attribute musi mieć przynajmniej :min kilobajtów.', 'string' => 'Pole :attribute musi mieć przynajmniej :min znaków.', 'array' => 'Pole :attribute musi mieć przynajmniej :min elementów.', ], 'not_in' => 'Zaznaczony :attribute jest nieprawidłowy.', 'not_regex' => 'Format pola :attribute jest nieprawidłowy.', 'numeric' => 'Pole :attribute musi być liczbą.', 'password' => 'Hasło jest nieprawidłowe.', 'present' => 'Pole :attribute musi być obecne.', 'regex' => 'Format pola :attribute jest nieprawidłowy.', 'required' => 'Pole :attribute jest wymagane.', 'required_if' => 'Pole :attribute jest wymagane gdy :other ma wartość :value.', 'required_unless' => 'Pole :attribute jest wymagane jeżeli :other nie znajduje się w :values.', 'required_with' => 'Pole :attribute jest wymagane gdy :values jest obecny.', 'required_with_all' => 'Pole :attribute jest wymagane gdy wszystkie :values są obecne.', 'required_without' => 'Pole :attribute jest wymagane gdy :values nie jest obecny.', 'required_without_all' => 'Pole :attribute jest wymagane gdy żadne z :values nie są obecne.', 'same' => 'Pole :attribute i :other muszą być takie same.', 'size' => [ 'numeric' => 'Pole :attribute musi mieć :size.', 'file' => 'Pole :attribute musi mieć :size kilobajtów.', 'string' => 'Pole :attribute musi mieć :size znaków.', 'array' => 'Pole :attribute musi zawierać :size elementów.', ], 'starts_with' => 'Pole :attribute musi zaczynać się jedną z następujących wartości: :values.', 'string' => 'Pole :attribute musi być ciągiem znaków.', 'timezone' => 'Pole :attribute musi być prawidłową strefą czasową.', 'unique' => 'Taki :attribute już występuje.', 'uploaded' => 'Nie udało się wgrać pliku :attribute.', 'url' => 'Format pola :attribute jest nieprawidłowy.', 'uuid' => 'Pole :attribute musi być poprawnym identyfikatorem UUID.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ ], ]; ================================================ FILE: lang/pl/visibilities.php ================================================ [ 'admin' => 'Tylko administratorzy widzą ten element.', 'admin-self' => 'Tylko ty i administratorzy widzą ten element.', 'all' => 'Wszyscy widzą ten element.', 'members' => 'Tylko uczestnicy kampanii widzą ten element.', 'self' => 'Tylko ty widzisz ten element.', ], 'title' => 'Zmiany widoczności', 'toast' => 'Skutecznie zmieniono widoczność', 'tooltip' => 'Opis różnych opcji widoczności elementów.', ]; ================================================ FILE: lang/pl/whiteboards/draw.php ================================================ [ 'add-circle' => 'Dodaj okrąg', 'add-entity' => 'Dodaj element', 'add-image' => 'Dodaj obraz', 'add-square' => 'Dodaj kwadrat', 'add-text' => 'Dodaj tekst', 'duplicate' => 'Skopiuj wybrane', 'end-drawing' => 'Zakończ rysowanie', 'lock' => 'Zablokuj', 'push-to-back' => 'Przesuń w tył', 'push-to-front' => 'Przesuń w przód', 'start-drawing' => 'Rozpocznij rysowanie', 'unlock' => 'Odblokuj', ], 'entity-search' => [ 'placeholder' => 'Wpisz nazwę albo alias elementu', 'title' => 'Wyszukiwanie elementów', ], 'errors' => [ 'websockets' => [ 'disconnected' => 'Utracono połączenie z websocket. Spróbuj ponownie.', 'error' => 'Wystąpił błąd połączenia z serwerem websocket.', 'unavailable' => 'Serwer websocket jest niedostępny. Spróbuj później.', ], ], 'fields' => [ 'color' => 'Kolor', ], 'pen' => [ 'large-stroke' => 'Gruba linia', 'thin-stroke' => 'Cienka linia', ], 'reset' => [ 'helper' => 'Czy na pewno chcesz wymazać tablicę? Tej akcji nie można cofnąć.', 'title' => 'Wymaż tablicę', ], 'roles' => [ 'edit' => 'Ten użytkownik może edytować tablicę', 'view' => 'Ten użytkownik może wyświetlić tablicę', ], 'toast' => [ 'copy' => [ 'success' => 'Skopiowano elementy do schowka.', ], 'paste' => [ 'error' => 'Coś poszło nie tak', ], ], ]; ================================================ FILE: lang/pl/whiteboards.php ================================================ [ 'draw' => 'Rysowanie', ], 'create' => [ 'title' => 'Nowa tablica', ], 'cta' => [ 'text' => 'By odblokować tablice, osoba która odblokowała funkcje premium musi mieć poziom :wywern albo :elemental.', 'title' => 'Tablica to specjalna funkcja premium', ], 'lists' => [ 'empty' => 'Używaj tablic do wizualnej organizacji pomysłów, relacji i struktury narracji.', ], 'placeholders' => [ 'type' => 'Pomysły, relacje, struktura narracji', ], 'update' => [], ]; ================================================ FILE: lang/pt-BR/abilities.php ================================================ [], 'children' => [ 'actions' => [ 'attach' => 'Anexar a entidades', ], 'create' => [ 'attach_success' => '{1} Anexada a habilidade :name a :count entidade.|[2,*] Anexada a habilidade :name a :count entidades.', 'helper' => 'Anexar :name a uma ou várias entidades.', 'title' => 'Anexar entidades', ], 'description' => 'Entidades que possuem a habilidade', 'title' => 'Entidades com a Habilidade :name', ], 'create' => [ 'title' => 'Nova Habilidade', ], 'destroy' => [], 'edit' => [], 'entities' => [], 'fields' => [ 'charges' => 'Cargas', ], 'helpers' => [], 'index' => [], 'lists' => [ 'empty' => 'Adicione poderes, magias ou talentos. Muitos criadores usam isso para modelar classes de D&D.', ], 'placeholders' => [ 'charges' => 'Quantidade de cargas. Atributos de referência com {Level} * {CHA}', 'name' => 'Bola de Fogo, Alerta, Ataque Astuto', 'type' => 'Magia, Talento, Ataque', ], 'reorder' => [ 'parentless' => 'Sem Habilidade Primária', 'success' => 'Habilidades reordenadas com sucesso.', 'title' => 'Reordenar as habilidades', ], 'show' => [ 'tabs' => [ 'reorder' => 'Reordenar Habilidades', ], ], ]; ================================================ FILE: lang/pt-BR/account/email.php ================================================ [ 'update' => 'Atualizar e-mail', ], 'fields' => [ 'email' => 'Novo endereço de e-mail', ], 'helpers' => [ 'email' => 'Certifique-se de que está escrito corretamente.', ], 'subtitle' => 'Altere o endereço de e-mail associado à sua conta.', 'title' => 'Atualize seu endereço de e-mail', ]; ================================================ FILE: lang/pt-BR/account/password.php ================================================ [ 'update' => 'Atualizar senha', ], 'fields' => [ 'password' => 'Nova senha', ], 'helpers' => [ 'password' => 'Use um gerenciador de senhas para gerar uma senha forte.', 'password_confirmation' => 'Não erre!', ], 'subtitle' => 'Altere a senha da sua conta. Isso desconectará você de todos os outros dispositivos.', 'title' => 'Atualize a senha da sua conta', ]; ================================================ FILE: lang/pt-BR/account/social.php ================================================ 'Login por :provider', 'subtitle' => 'Mude de um login gerenciado por :provider para um gerenciado por Kanka, onde você faz login com seu e-mail e senha.', 'title' => 'Mudar para um login Kanka', ]; ================================================ FILE: lang/pt-BR/articles.php ================================================ [ 'move' => 'Ir para a introdução', ], 'helpers' => [ 'permissions' => 'Essas permissões podem substituir a configuração :visibility do artigo.', ], 'tabs' => [ 'layout' => 'Layout', 'main' => 'Principal', 'permissions' => 'Permissões especiais', ], ]; ================================================ FILE: lang/pt-BR/assistance.php ================================================ [ 'campaign' => 'Campanha', ], 'opening' => 'Um membro da equipe de Kanka enviou você para esta página com o objetivo de ajudá-lo.', 'placeholders' => [ 'campaign' => 'Selecione uma campanha da qual você é administrador', ], 'select' => 'Selecione uma campanha da qual você é administrador no menu suspenso abaixo para gerar um token de uso único especial, que permitirá que um membro da equipe Kanka participe temporariamente da campanha como administrador.', 'success' => [ 'opening' => 'Seu token de assistência foi gerado com sucesso. A equipe Kanka foi notificada e entrará em contato com sua campanha em breve para ajudar você. Normalmente, entraremos em contato via :discord se precisarmos coordenar algo diretamente.', 'secret' => 'Somente um membro verificado da equipe Kanka pode usar esse token. Ele é inútil para os demais, então não há necessidade de tratá-lo como um segredo.', 'token' => 'Seu token de assistência:', ], 'title' => 'Assistência', ]; ================================================ FILE: lang/pt-BR/attribute_templates.php ================================================ [], 'bulk' => [ 'entity_type' => [ 'unset' => 'Não definido', ], ], 'create' => [ 'title' => 'Novo Modelo de Atributo', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'auto_apply' => 'Auto-aplicar', 'is_enabled' => 'Habilitado', ], 'hints' => [ 'automatic' => 'Os seguintes :count atributos foram automaticamente aplicado de :link', 'automatic_apply' => '{1} O seguinte :count atributo foi aplicado automaticamente de :link | [2,] Os seguintes :count atributos foram aplicados automaticamente de :link.', 'entity_type' => 'Aplique automaticamente os atributos deste modelo a novas entidades do tipo selecionado.', 'is_disabled' => 'Esse modelo está desabilitado.', 'is_enabled' => 'Habilite esse modelo para usar na campanha.', 'parent_attribute_template' => 'Este modelo de atributo pode ser secundário de outro modelo de atributo. Ao aplicar este modelo de atributo, ele e todos os seus modelos primários serão aplicados.', ], 'index' => [], 'lists' => [ 'empty' => 'Criar um conjunto de ferramentas para reutilizar propriedades comuns em várias entradas.', ], 'placeholders' => [ 'name' => 'Nome do Modelo de Atributo', ], 'show' => [], ]; ================================================ FILE: lang/pt-BR/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Erro', 'rendering' => 'Ocorreu um erro ao processar o plug-in do mercado. Entre em contato com o criador do plug-in.', ], ], 'helpers' => [], 'list' => [ 'sheets' => 'Planilhas de Personagem', ], 'pitch' => 'Encontre e adicione planilhas de personagens do :marketplace em uma :boosted-campaign.', ]; ================================================ FILE: lang/pt-BR/auth.php ================================================ [ 'permanent' => 'Você foi banido permanentemente.', 'temporary' => '{1} Você foi banido por :days dia.|[2,*] Você foi banido por :days dias.', ], 'confirm' => [ 'confirm' => 'Confirmar', 'error' => 'Senha inválida, por favor tente novamente.', 'helper' => 'Por favor confirme sua senha antes de continuar.', 'title' => 'Confirmação de senha', ], 'continue' => [ 'facebook' => 'Continuar com Facebook', 'google' => 'Continuar com Google', 'x' => 'Continuar com X', ], 'failed' => 'Essas credenciais não correspondem ao do nosso sistema.', 'helpers' => [ 'password' => 'Mostrar / Esconder senha', ], 'login' => [ 'fields' => [ '2fa' => 'Senha de uso único', 'email' => 'Email', 'password' => 'Senha', ], 'no-account' => 'Não tem uma conta?', 'or' => 'OU', 'password_forgotten' => 'Esqueceu sua senha?', 'sign-up' => 'Inscrever-se', 'submit' => 'Entrar', 'title' => 'Entrar', ], 'register' => [ 'already' => 'Já possui uma conta? :login', 'errors' => [ 'email_already_taken' => 'Já há uma conta registrada com esse email.', 'general_error' => 'Um erro ocorreu enquanto sua conta era registrada. Por favor tente novamente.', ], 'fields' => [ 'email' => 'Email', 'name' => 'Usuário', 'password' => 'Senha', ], 'log-in' => 'Conectar-se', 'submit' => 'Registrar', 'title' => 'Registrar', 'tos' => 'Ao registrar uma conta, você concorda com nossos :terms e :privacy.', ], 'reset' => [ 'fields' => [ 'email' => 'Endereço de E-mail', 'password' => 'Senha', 'password_confirmation' => 'Confirme sua senha', ], 'send' => 'Enviar Link de Redefinição de Senha', 'submit' => 'Redefinir senha', 'title' => 'Redefinir senha', ], 'tfa' => [ 'helper' => 'A autenticação de dois fatores está habilitada. Forneça a senha de uso único (OTP) fornecida pelo seu aplicativo autenticador.', 'title' => 'Autenticação de Dois-Fatores', ], 'throttle' => 'Muitas tentativas de login. Por favor tente novamente em :seconds segundos.', 'x-twitter' => 'X anteriormente conhecido como Twitter', ]; ================================================ FILE: lang/pt-BR/banners.php ================================================ 'Use o código promocional :code para obter 20% de desconto na sua primeira assinatura anual!', ]; ================================================ FILE: lang/pt-BR/billing/information.php ================================================ [ 'update' => 'Atualizar info', ], 'helper' => 'Seu endereço comercial, número de IVA etc. podem ser adicionados a todos os seus recibos.', 'title' => 'Atualizar informações de cobrança', ]; ================================================ FILE: lang/pt-BR/billing/invoices.php ================================================ [ 'download' => 'Download PDF', ], 'description' => 'Exibindo faturas dentro dos últimos 24 meses.', 'empty' => 'Nenhuma fatura encontrada', 'fields' => [ 'amount' => 'Valor', 'date' => 'Data', 'invoice' => 'Fatura', 'status' => 'Status', ], 'paypal' => 'Observe que apenas pagamentos feitos pelo Stripe, e não pelo PayPal, são visíveis aqui.', 'status' => [ 'paid' => 'Pago', 'pending' => 'Pendente', ], 'title' => 'Histórico de cobrança', ]; ================================================ FILE: lang/pt-BR/billing/menu.php ================================================ 'Histórico de cobrança', 'overview' => 'Visão geral', 'payment-method' => 'Método de pagamento', ]; ================================================ FILE: lang/pt-BR/billing/payment_methods.php ================================================ 'Método de pagamento', 'types' => [ 'card' => 'Cartão', ], ]; ================================================ FILE: lang/pt-BR/bookmarks.php ================================================ [ 'customise' => 'Personalizar barra lateral', ], 'create' => [ 'title' => 'Novo marcador', ], 'destroy' => [], 'edit' => [ 'title' => 'Marcador :name', ], 'fields' => [ 'active' => 'Ativo', 'dashboard' => 'Dashboard', 'default_dashboard' => 'Dashboard padrão', 'filters' => 'Filtros', 'menu' => 'Sub-menu', 'position' => 'Posição', 'random_type' => 'Tipo Aleatório de Entidade', 'selector' => 'Configuração do Marcador', 'target' => 'Alvo', ], 'helpers' => [ 'active' => 'Marcadores inativos não aparecerão na barra lateral.', 'css' => 'Adicione uma classe CSS que será adicionada ao link do marcador na barra lateral.', 'dashboard' => 'Faça com que o link rápido seja direcionado a um dos dashboards personalizados da campanha.', 'default_dashboard' => 'Em vez disso, crie um link para o dashboard padrão da campanha. Um dashboard personalizado ainda precisa ser selecionado.', 'entity' => 'Configure este marcador para ir diretamente para uma entidade. O campo :menu controla qual subpágina da entidade será aberta.', 'position' => 'Use este campo para controlar em qual ordem crescente os links aparecem no menu.', 'random' => 'Use este campo para ter um marcador indo para uma entidade aleatória. Você pode filtrar o link para que ele vá para um tipo específico de entidade.', 'selector' => 'Configure para onde este marcador vai quando um usuário clica nele na barra lateral.', 'type' => 'Configure este link rápido para ir diretamente para uma lista de entidades. Para filtrar os resultados, copie partes da url na lista de entidades filtradas após o sinal :? no campo de filtro :filter.', ], 'index' => [], 'lists' => [ 'empty' => 'Salve marcadores para suas entradas mais usadas ou listas filtradas para acesso mais rápido.', ], 'placeholders' => [ 'filters' => 'location_id=15&type=city', 'menu' => 'Subpágina do menu (use o último texto da url)', 'tab' => 'Registro, relações, notas', ], 'random_no_entity' => 'Nenhuma entidade aleatória encontrada.', 'random_types' => [ 'any' => 'Qualquer entidade', ], 'reorder' => [ 'success' => 'Marcadores reordenados.', 'title' => 'Reordenar marcadores', ], 'show' => [], 'targets' => [ 'dashboard' => 'Um dos dashboards da campanha', 'entity' => 'Uma única entidade', 'random' => 'Uma entidade aleatória', 'select' => 'Escolha uma opção', 'type' => 'Lista de entidades de um tipo/módulo de entidade específico', ], 'visibilities' => [ 'is_active' => 'Mostrar o marcador na barra lateral', ], ]; ================================================ FILE: lang/pt-BR/bragi/backstory.php ================================================ 'Escreva uma breve história do personagem inspirada nessas informações. Adapte o tom e os detalhes aos sistemas e gêneros de jogo selecionados. Você pode incluir elementos como a aparência, origem, crenças, relacionamentos, objetivos, falhas ou experiências marcantes do personagem — o que melhor se adequar ao personagem.', 'setup' => [ 'gender' => 'Gênero: :gender', 'genres' => 'Gêneros: :genres', 'name' => 'Nome do personagem: :name', 'prompt' => 'Prompt: ":prompt"', 'pronouns' => 'Pronomes: :pronouns', 'systems' => 'Sistema de jogo: :systems', ], 'system' => 'Você é um contador de histórias e escritor de personagens profissional de RPG. Você cria histórias imersivas e emocionalmente impactantes para RPGs de mesa. Seu estilo se adapta ao tom e ao cenário do jogo. Você normalmente escreve de 2 a 4 parágrafos e até 400 palavras, abordando aparência, história, crenças, motivações e falhas.', ]; ================================================ FILE: lang/pt-BR/bragi.php ================================================ [ 'generate' => 'Gerar', 'insert' => 'Usar', ], 'errors' => [ 'invalid-sub' => 'Para acessar esse recurso, você precisa ter uma assinatura Wyvern ou Elemental.', 'out-of-tokens' => 'Você está sem fichas! Você receberá automaticamente mais em :date.', ], 'here' => 'aqui', 'intro' => 'Oi! Sou :name, uma IA aqui para ajudá-lo a gerar histórias de fundo para seus personagens em Kanka. Você pode aprender mais sobre mim :here.', 'kankappy' => 'Eles são secretamente um discípulo de Kankappy.', 'loading' => 'Aguarde, estou pensando muito e pode levar até um minuto!', 'placeholders' => [ 'prompt' => 'Forneça um prompt que será transformado em uma história de fundo.', ], 'token-limit' => 'Atualmente você tem :amount tokens. Cada vez que eu gero uma história de fundo, isso usa um token, então use-os com sabedoria!', ]; ================================================ FILE: lang/pt-BR/calendars/weather.php ================================================ [], 'create' => [ 'helper' => 'Adicione informações meteorológicas que serão exibidas no calendário.', 'success' => 'Clima adicionado.', 'title' => 'Novo clima', ], 'destroy' => [ 'success' => 'Clima removido.', ], 'edit' => [ 'success' => 'Clima atualizado.', 'title' => 'Atualizar clima', ], 'fields' => [ 'effect' => 'Efeito', 'name' => 'Nome', 'precipitation' => 'Precipitação', 'temperature' => 'Temperatura', 'weather' => 'Clima', 'wind' => 'Vento', ], 'options' => [ 'weather' => [ 'bolt' => 'Trovão', 'cloud' => 'Nublado', 'cloud-rain' => 'Chuvoso', 'cloud-showers-heavy' => 'Chuva forte', 'cloud-sun' => 'Nublado e Ensolarado', 'cloud-sun-rain' => 'Nuvem, Sol e Chuva', 'meteor' => 'Meteoro', 'smog' => 'Nevoeiro', 'snowflake' => 'Neve', 'sun' => 'Ensolarado', 'wind' => 'Ventoso', ], ], 'placeholders' => [ 'effect' => 'Efeito mágico ou natural', 'name' => 'Texto opcional e personalizado do clima', 'precipitation' => 'Quantidade de água', 'temperature' => 'Máxima e mínima do dia', 'wind' => 'Velocidade do vento', ], ]; ================================================ FILE: lang/pt-BR/calendars.php ================================================ [ 'add_epoch' => 'Adicionar uma época', 'add_intercalary' => 'Adicionar dias intercalares', 'add_month' => 'Adicionar um mês', 'add_moon' => 'Adicionar uma lua', 'add_reminder' => 'Adicionar um lembrete', 'add_season' => 'Adicionar uma estação', 'add_weather' => 'Definir efeitos do clima', 'add_week' => 'Adicionar uma semana nomeada', 'add_weekday' => 'Adicionar um dia da semana', 'add_year' => 'Adicionar um ano nomeado', 'set_today' => 'Definir como dia atual', 'today' => 'Hoje', 'update_weather' => 'Atualizar o clima', ], 'checkboxes' => [ 'is_recurring' => 'Ocorre todos os anos', ], 'create' => [ 'title' => 'Novo Calendário', ], 'destroy' => [], 'edit' => [ 'today' => 'Data do calendário atualizada.', ], 'event' => [ 'create' => [ 'success' => 'Evento de calendário criado.', 'title' => 'Adicionar um Evento de Calendário para :name', ], 'destroy' => 'Lembrete removido do calendário \':name\'', 'edit' => [ 'success' => 'Lembrete atualizado.', 'title' => 'Atualizar Lembrete para :name', ], 'errors' => [ 'invalid_entity' => 'Seleção de entidade inválida.', ], 'helpers' => [ 'other_calendar' => 'Você está editando um lembrete que está no calendário :calendar.', ], 'success' => 'Lembrete \':event\' adicionado ao calendário.', ], 'events' => [ 'bulks' => [ 'delete' => '{1} :count lembrete removido.|[2,*] :count lembretes removidos.', 'patch' => '{1} :count lembrete atualizado.|[2,*] :count lembretes atualizados.', ], 'end' => '(fim)', 'filters' => [ 'show_after' => 'Mostrar hoje e depois', 'show_all' => 'Mostrar tudo', 'show_before' => 'Mostrar antes de hoje', ], 'start' => '(começo)', ], 'fields' => [ 'comment' => 'Comentário', 'current_day' => 'Dia Atual', 'current_month' => 'Mês Atual', 'current_year' => 'Ano Atual', 'date' => 'Data Atual', 'day' => 'Dia', 'default_layout' => 'Layout padrão', 'format' => 'Formato', 'is_incrementing' => 'Avançando data', 'is_recurring' => 'Recorrente', 'leap_year' => 'Anos bissextos', 'leap_year_amount' => 'Adicionar Dias', 'leap_year_month' => 'Mês', 'leap_year_offset' => 'Cada', 'leap_year_start' => 'Ano Bissexto', 'length' => 'Duração do Evento', 'length_days' => ':count day|: contar dias', 'month' => 'Mês', 'months' => 'Meses', 'moons' => 'Luas', 'parameters' => 'Parâmetros', 'recurring_until' => 'Recorrente Até o Ano', 'reset' => 'Reinicialização Semanal', 'seasons' => 'Estações', 'show_birthdays' => 'Mostrar Aniversários', 'skip_year_zero' => 'Ignorar o Ano Zero', 'start_offset' => 'Deslocamento Inicial', 'suffix' => 'Sufixo', 'week_names' => 'Nomes da Semana', 'weekdays' => 'Dias da Semana', 'year' => 'Ano', ], 'helpers' => [ 'default_layout' => 'Selecione qual layout o calendário deve usar por padrão quando visualizado.', 'format' => 'Adicione formatação de data personalizada para entidades do calendário.', 'month_type' => 'Os meses intercalares não usam os dias da semana, mas ainda influenciam as luas e as estações.', 'moon_offset' => 'Por padrão, a primeira lua cheia aparece no primeiro dia do ano 0. A alteração do deslocamento mudará quando a primeira lua cheia for exibida. Este valor pode ser negativo (até a duração do primeiro mês) ou positivo (até a duração do primeiro mês).', 'start_offset' => 'Por padrão, o calendário começa no primeiro dia da semana do ano 0. A alteração deste campo influencia onde o primeiro dia do calendário é colocado.', ], 'hints' => [ 'event_length' => 'Quanto tempo um evento deve durar. Um evento não pode durar mais de dois meses.', 'is_incrementing' => 'O avanço de calendário automaticamente terá seu incremento da data atual às 00:00 UTC.', 'leap_year' => 'Configure anos bissextos para o calendário.', 'months' => 'Seu calendário deve ter pelo menos 2 meses.', 'moons' => 'Adicionar luas fará com que elas apareçam no calendário em cada lua cheia e nova. Se o período da lua cheia for maior que 10 dias, as luas minguantes e crescentes também serão exibidas.', 'parent_calendar' => 'Relacionar o calendário a um calendário primário incluirá os lembretes e os efeitos do clima do calendário primário.', 'reset' => 'Sempre comece no início do mês ou ano no primeiro dia da semana.', 'seasons' => 'Crie estações para o seu calendário, informando quando cada uma delas começa. Kanka cuidará do resto.', 'show_birthdays' => 'Mostre os aniversários anuais dos personagens que têm um lembrete de aniversário neste calendário até a data de sua morte.', 'skip_year_zero' => 'Por padrão, o primeiro ano do calendário é o ano zero. Habilite esta opção para pular o ano zero.', 'weekdays' => 'Defina os nomes dos dias da semana. São necessários pelo menos 2 dias de semana.', 'weeks' => 'Defina alguns nomes para as semanas mais importantes do seu calendário.', 'years' => 'Alguns anos são tão importantes que têm um nome próprio.', ], 'index' => [], 'layouts' => [ 'month' => 'Mês', 'monthly' => 'Mensal por padrão', 'year' => 'Ano', 'yearly' => 'Anual por padrão', ], 'lists' => [ 'empty' => 'Crie um calendário para acompanhar datas, festivais ou eventos do jogo ao longo do tempo.', ], 'modals' => [ 'switcher' => [ 'title' => 'Seletor de Ano', ], ], 'month_types' => [ 'intercalary' => 'Intercalado', 'standard' => 'Padrão', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Lua cheia', 'fullmoon_name' => ':moon lua cheia', 'month' => 'Mensal', 'newmoon' => 'Lua nova', 'newmoon_name' => ':moon lua nova', 'none' => 'Nenhum', 'unnamed_moon' => 'Lua :number', 'year' => 'Anual', ], ], 'resets' => [ '' => 'Nenhum', 'month' => 'Mensal', 'year' => 'Anual', ], ], 'panels' => [ 'intercalary' => 'Dias Intercalares', 'leap_year' => 'Ano Bissexto', 'months' => 'Meses', 'weeks' => 'Semanas', 'years' => 'Anos Nomeados', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Duração em dias', 'month' => 'No final de qual mês', 'name' => 'Nome da intercalação', ], 'month' => [ 'alias' => 'Apelido ​​do Mês', 'length'=> 'Dias', 'name' => 'Nome do Mês', 'type' => 'Tipo', ], 'moon' => [ 'fullmoon' => 'Lua cheia a cada (dias)', 'name' => 'Nome da Lua', 'offset' => 'Primeiro deslocamento da lua cheia', ], 'seasons' => [ 'day' => 'Início do dia', 'month' => 'Início do mês', 'name' => 'Nome da Estação', ], 'weeks' => [ 'name' => 'Nome da Semana', 'number' => 'Número', ], 'year' => [ 'name' => 'Nome do Ano', 'number' => 'Ano', ], ], 'placeholders' => [ 'colour' => 'Cor', 'comment' => 'Aniversário, festival, solstício', 'date' => 'A data atual', 'leap_year_amount' => 'Número de dias adicionado em um ano bissexto', 'leap_year_month' => 'Mês em que os dias são adicionados', 'leap_year_offset' => 'A cada quantos anos é um ano bissexto', 'leap_year_start' => 'Primeiro ano que é um ano bissexto', 'length' => 'Duração do evento em dias', 'months' => 'Número de meses em um ano', 'recurring_until' => 'Ultimo ano recorrente (deixe vazio para ser recorrente sempre)', 'seasons' => 'Número de estações', 'suffix' => 'Sufixo atual da Era (AC, DC)', 'type' => 'Tipo do calendário', 'weekdays' => 'Número de dias em uma semana', ], 'show' => [ 'missing_details' => 'Este calendário não pôde ser exibido. Os calendários precisam de pelo menos 2 meses e 2 dias da semana para serem renderizados corretamente.', 'moon_1first_quarter' => 'Lua minguante de :moon', 'moon_full' => 'Lua cheia de :moon', 'moon_last_quarter' => 'Lua crescente de :moon', 'moon_new' => 'Lua nova de :moon', 'tabs' => [ 'events' => 'Lembretes', 'weather' => 'Clima', ], ], 'sorters' => [ 'after' => 'Hoje & depois', 'before'=> 'Hoje & antes', ], 'validators' => [ 'format' => 'O formato da data é inválido.', 'moon_offset' => 'O deslocamento da primeira lua cheia não pode ser maior que a duração do primeiro mês do calendário.', ], 'warnings' => [ 'event_length' => 'Os lembretes que abrangem vários anos só são visíveis nos primeiros dois anos. Saiba mais em nossa :documentation.', ], ]; ================================================ FILE: lang/pt-BR/callouts.php ================================================ [ 'subscription' => 'Saiba mais sobre assinaturas', 'upgrade' => 'Atualizar para premium', ], 'booster' => [ 'actions' => [ 'boost' => 'Impulsionar :campaign', 'superboost' => 'Superimpulsionar :campaign', ], 'learn-more' => 'O que são impulsionamentos?', 'limitation' => 'Para acessar esse recurso, a campanha precisa ser impulsionada.', 'limitations' => [ 'boosted' => 'Para acessar esse recurso, a campanha precisa ser impulsionada.', 'superboosted' => 'Para acessar esse recurso, a campanha precisa ser super-impulsionada.', ], 'multiple' => 'Para acessar esses recursos, a campanha precisa ser impulsionada.', 'pitches' => [ 'element-class' => 'Dê a este elemento uma classe CSS personalizada com uma :boosted-campaign.', 'icon' => 'Desbloqueie milhões de ícones personalizados do FontAwesome com uma :boosted-campaign.', ], 'titles' => [ 'boosted' => 'Recurso impulsionado', 'superboosted' => 'Recurso superimpulsionado', ], ], 'premium' => [ 'learn-more' => 'O que são campanhas premium?', 'limitation' => 'Para acessar esse recurso, os recursos premium precisam estar ativados.', 'multiple' => 'Para acessar esses recursos, os recursos premium precisam ser habilitados para :campaign.', 'title' => 'Recurso de campanha premium', 'unlock' => 'Desbloqueie recursos premium para :campaign', ], 'subscribe' => [ 'pitch-image' => 'Inscreva-se para desbloquear até :max MB tamanhos de upload de arquivo.', 'share-booster' => 'Impulsione :campaign para aumentar o tamanho do upload do arquivo para todos os membros da campanha.', 'share-premium' => 'Aumente o tamanho do upload do arquivo para todos os membros da campanha com uma campanha premium.', ], ]; ================================================ FILE: lang/pt-BR/campaigns/achievements.php ================================================ 'Parabéns!', 'connections' => '{0} Nenhuma conexão criada|{1} Uma conexão criada|[2,*] :amount de conexões criadas', 'created' => '{0} Nenhum :plural criado|{1} Um :singular criado|[2,*] :amount :plural criado', 'dead' => '{0} Nenhum mistério de assassinato|{1} Um mistério de assassinato|[2,*] :quantidade de mistérios de assassinato', 'goal' => 'Objetivo :number', 'goal_reached' => 'A campanha desbloqueou a seguinte conquista:', 'level' => 'nível :number', 'markers' => '{0} Nenhum marcador de mapa criado|{1} Um marcador de mapa criado|[2,*] :amount de marcadores de mapa criados', 'painter' => '{0} Nenhum tema criado|{1} Um tema criado|[2,*] :amount de temas criados', 'pitch' => 'Conquistas de campanha são uma maneira divertida de celebrar marcos na sua jornada de construção de mundo. Acompanhe o progresso, interaja com seus jogadores e exiba suas conquistas com uma campanha premium.', 'plugins' => '{0} Nenhum plugin instalado|{1} Um plugin instalado|[2,*] :amount de plugins instalados', 'remaining' => [ 'generic' => 'Mais e o próximo nível será desbloqueado.', ], 'spotlight' => [ 'active' => [ 'cta' => 'Veja os destaques', ], 'private' => [ 'cta' => 'Revisar configurações públicas', 'helper' => 'Torne sua campanha pública para ser elegível ao Destaque.', ], 'public' => [ 'cta' => 'Aprenda como funciona o destaque', 'helper' => 'As campanhas selecionadas são apresentadas no Kanka Showcase e no blog.', ], ], 'spotlighted' => '{0} Ainda não destacado|[1,*] Em destaque', 'tagged' => '{0} Nenhuma entidade com tag|{1} Uma entidade com tag|[2,*] :amount de entidades com tag', 'titles' => [ 'calendars' => 'Guardião do tempo', 'characters' => 'Nomeador', 'connections' => 'Cupido', 'creatures' => 'Criador', 'dead' => 'Assassino', 'events' => 'Mestre do conhecimento', 'families' => 'Planejador de família', 'locations' => 'Construtor', 'markers' => 'Cartógrafo', 'organisations' => 'Fusões e aquisições', 'plugins' => 'Conhecedor de plugins', 'quests' => 'Mente brilhante', 'spotlighted' => 'Em destaque', 'tags' => 'Sob controle', 'themes' => 'Pintor', ], 'tutorial' => 'As conquistas registram ações importantes nesta campanha, como criar entidades ou usar recursos essenciais. Elas são apenas informativas e são atualizadas automaticamente conforme você explora e constrói.', ]; ================================================ FILE: lang/pt-BR/campaigns/applications.php ================================================ [ 'accept' => 'Aceitar', 'reject' => 'Rejeitar', ], 'apply' => [ 'apply' => 'Aplicar', 'help' => 'Essa campanha é aberta a novos membros. Inscreva-se para participar preenchendo o formulário. Você será notificado quando os administradores da campanha revisarem sua solicitação.', 'remove_text' => 'sua submissão', 'success' => [ 'apply' => 'Sua solicitação foi salva. Você ainda pode alterá-la ou cancelá-la a qualquer momento. Você será notificado quando os administradores da campanha revisarem o pedido.', 'remove'=> 'Sua solicitação foi removida.', 'update'=> 'Sua solicitação foi atualizada. Você ainda pode alterá-la ou cancelá-la a qualquer momento. Você será notificado quando os administradores da campanha revisarem o pedido.', ], 'title' => 'Inscreva-se em :name', ], 'errors' => [], 'fields' => [ 'application' => 'Solicitação', 'reason' => 'Motivo de aprovação/rejeição', ], 'helpers' => [ 'modal' => 'Uma campanha aberta a solicitações e ao público pode fazer com que os usuários se inscrevam para participar da campanha.', 'no_applications_title' => 'Nenhuma solicitação encontrada', 'reason' => 'Se fornecido, o solicitante será notificado sobre esse motivo.', 'role' => 'Se aprovado, o cargo ào qual o solicitante será adicionado.', ], 'open' => [ 'closed' => 'Campanha está fechada', 'open' => 'Campanha está aberta', 'title' => 'Campanha aberta', ], 'placeholders' => [ 'note' => 'Escreva a sua solicitação para se inscrever na campanha.', 'reason' => 'Sua razão', ], 'public' => [ 'private' => 'Campanha está privada.', 'public' => 'Campanha está pública.', 'title' => 'Campanha pública', ], 'statuses' => [], 'toggle' => [ 'closed' => 'Fechada a inscrições', 'label' => 'Status', 'open' => 'Aberta a inscrições', 'success' => 'Status das inscrições da campanha atualizado.', 'title' => 'Status das inscrições', ], 'update' => [ 'approve' => 'Selecione o cargo do usuário que será adicionado em sua campanha.', 'approved' => 'Inscrição aprovada.', 'reject' => 'Escreva uma mensagem opcional para os usuários explicando por que você está rejeitando sua inscrição.', 'rejected' => 'Inscrição rejeitada.', ], ]; ================================================ FILE: lang/pt-BR/campaigns/builder.php ================================================ 'Construa visualmente um tema para a campanha com esta interface. Role para baixo para ver como as mudanças afetariam vários elementos da campanha. Quando uma cor é selecionada, uma cor "contraste" é automaticamente selecionada para colorir o texto. Saiba mais sobre temas em nossa :docs.', 'pitch' => 'Psst, temos um construtor de temas, se tudo o que você quer fazer é mudar algumas das cores da campanha 😉', 'pitch-go' => 'Leve-me ao construtor de temas', 'reset' => 'O estilo do construtor de temas foi reiniciado.', 'success' => 'O estilo do construtor de temas foi salvo.', 'title' => 'Construtor de temas', ]; ================================================ FILE: lang/pt-BR/campaigns/categories.php ================================================ [ 'permission-disabled' => 'Esta categoria está desativada.', ], 'helpers' => [ 'aliases' => 'Adicione pseudônimos e identidades secretas às entradas do mundo.', 'media' => 'Faça o upload de documentos multimídia (imagens, PDFs, áudio) e links externos para as inscrições.', ], 'tab' => 'Categorias', ]; ================================================ FILE: lang/pt-BR/campaigns/dashboard-header.php ================================================ [ 'success' => 'Cabeçalho do dashboard da campanha atualizado.', 'title' => 'Atualizar cabeçalho do dashboard da campanha', ], ]; ================================================ FILE: lang/pt-BR/campaigns/default-images.php ================================================ [ 'add' => 'Carregar uma nova imagem em miniatura', ], 'call-to-action' => 'Carregue uma imagem em miniatura personalizada para todos os personagens, locais ou outras entidades da campanha. Essas imagens são mostradas em várias listas.', 'create' => [ 'error' => 'Erro ao salvar as novas imagens em miniatura padrões de entidade. O :type já está definido?', 'helper' => 'Carregue uma imagem que será usada como miniatura padrão para entidades do módulo selecionado.', 'success' => 'Nova imagem em miniatura para :type criada.', 'title' => 'Nova imagem em miniatura padrão', ], 'destroy' => [ 'success' => 'Imagem em miniatura padrão para :type removida.', ], 'empty' => 'Nenhum módulo tem atualmente uma configuração de miniatura padrão.', 'helper' => 'Usado para todas as entidades deste módulo sem uma imagem.', 'index' => [], ]; ================================================ FILE: lang/pt-BR/campaigns/delete.php ================================================ 'backup', 'confirm' => 'Se você tem certeza de que deseja excluir permanentemente :campaign, escreva :code no campo abaixo.', 'confirm-button' => 'Excluir permanentemente :name', 'helper' => 'Excluir uma campanha é uma ação permanente e irreversível. Isso removerá todos os dados relacionados à campanha de nossos servidores, incluindo imagens e anexos. Recomendamos fazer um backup antes de continuar.', 'issue' => 'O seguinte problema precisa ser corrigido antes que a campanha possa ser excluída.', 'members' => 'Todos os outros membros precisam ser removidos da campanha.', 'success' => ':name foi excluído permanentemente.', 'title' => 'Exclusão', ]; ================================================ FILE: lang/pt-BR/campaigns/export.php ================================================ [ 'download' => 'Download', 'export' => 'Exportar a campanha', ], 'confirm' => [ 'notification' => 'Os membros do cargo :admin serão notificados quando a exportação estiver pronta para download.', 'title' => 'Confirmação de exportação', 'warning' => 'Você está prestes a exportar os dados da campanha. Este processo pode demorar muito dependendo do tamanho da campanha. Você pode continuar usando o Kanka enquanto nossos servidores geram a exportação.', ], 'errors' => [ 'limit' => 'A campanha já foi exportada uma vez hoje. Por favor, tente novamente amanhã.', ], 'expired' => 'Link expirado', 'helpers' => [], 'progress' => 'Progresso', 'size' => 'Tamanho', 'status' => [ 'failed' => 'Fracassado', 'finished' => 'Finalizado', 'running' => 'Executando', 'scheduled' => 'Agendado', ], 'success' => 'A exportação da campanha está sendo preparada. Você será notificado em Kanka assim que estiver pronto para download.', 'title' => 'Exportação da Campanha', ]; ================================================ FILE: lang/pt-BR/campaigns/gallery.php ================================================ [ 'close' => 'Fechar', 'file-link' => 'Link do arquivo', 'focus_point' => 'Configurar ponto de foco', 'image-link' => 'Link da imagem', 'reset_focus' => 'Redefinir ponto de foco', 'save' => 'Salvar', 'upgrade' => 'Atualizar espaço de armazenamento', ], 'breadcrumb' => 'Galeria', 'bulk' => [ 'destroy' => [ 'confirm' => 'Tem certeza de que deseja remover permanentemente os elementos selecionados? Essa ação não pode ser desfeita.', 'success' => '{0}Nenhum arquivo removido.|{1}Um arquivo removido.|{2,*} :count arquivos removidos.', ], ], 'cta' => 'Gerencie e reutilize imagens em toda a campanha.', 'destroy' => [ 'folder' => 'Pasta :name removida.', 'success' => 'Imagem :name removida.', ], 'errors' => [ 'max' => 'Por favor, selecione apenas até :count arquivos por vez.', 'permissions' => 'Seu cargo de campanha não possui a permissão :permission para carregar imagens para a galeria da campanha.', 'storage' => 'Não há espaço de armazenamento suficiente para carregar as imagens selecionadas. Espaço de armazenamento disponível: :available.', ], 'fields' => [ 'created_by' => 'Carregada por', 'details' => 'Detalhes', 'ext' => 'Sair', 'file_type' => 'Tipo do arquivo', 'folder' => 'Pasta', 'image_mentioned_in' => '{0} Esta imagem não é mencionada em nenhuma das entidades da campanha.|{1} Mencionada em uma introdução/post.|[2,*] mencionada em :count introduções/posts.', 'image_used_in' => '{0} Esta imagem não é usada em nenhuma das entidades da campanha.|{1} Usada como a imagem de uma entidade.|[2,*] Usada como a imagem de :count entidades.', 'link' => 'Link', 'name' => 'Nome', 'size' => 'Tamanho', 'unused' => 'Não usado em lugar nenhum', 'used_in' => 'Usado em', ], 'focus' => [ 'locked' => 'Uma campanha premium é necessária para definir o ponto focal de uma imagem.', 'removed' => 'Foco da imagem removido.', 'updated' => 'Foco da imagem atualizado.', ], 'new_folder' => [ 'title' => 'Nova pasta', ], 'no_folder' => 'Sem pasta', 'pitch' => 'Carregue imagens para a galeria da campanha diretamente do editor de texto.', 'placeholders' => [ 'search' => 'Procurar nome da imagem...', ], 'storage' => [ 'of' => 'de', 'title' => 'Armazenamento', ], 'title' => 'Galeria da Campanha :campaign', 'update' => [ 'folder' => 'Pasta modificada.', 'success' => 'Imagem modificada.', ], 'uploader' => [ 'add' => 'Adicionar nova', 'new_folder' => 'Nova Pasta', 'or' => 'ou', 'select_file' => 'Selecione um arquivo', 'well' => 'Solte o arquivo para fazer upload', ], ]; ================================================ FILE: lang/pt-BR/campaigns/import.php ================================================ [ 'import' => 'Carregar a exportação', ], 'fields' => [ 'updated' => 'Última atualização', ], 'form' => 'Carregar formulário', 'progress' => [ 'uploading' => 'Carregando', ], 'status' => [ 'failed' => 'Fracassado', 'finished' => 'Finalizado', 'queued' => 'Enfileirado', 'running' => 'Executando', ], 'title' => 'Importar', ]; ================================================ FILE: lang/pt-BR/campaigns/invites.php ================================================ [ 'helper' => 'Crie um link de convite para enviar aos seus jogadores, para que eles possam participar da campanha.', ], ]; ================================================ FILE: lang/pt-BR/campaigns/limits.php ================================================ 'Limite alcançado', ]; ================================================ FILE: lang/pt-BR/campaigns/logs.php ================================================ [ 'list' => 'Mantemos um log de todas as principais alterações feitas na campanha por até :amount dias. Esses logs não detalham alterações individuais nos valores, mas sim o estado geral da campanha.', 'nothing' => 'Não há logs para exibir. Lembre-se de que os logs são mantidos por até :amount dias.', 'title' => 'Nenhum log', ], 'pitch' => 'Acompanhe as alterações gerais feitas na campanha por até :amount dias com uma campanha premium.', 'premium' => [ 'helper' => 'Logs com mais de :amount dias só podem ser visualizados com uma campanha premium.', ], 'title' => 'Log de auditoria', ]; ================================================ FILE: lang/pt-BR/campaigns/members.php ================================================ [ 'limited' => ':amount de :total membros', 'title' => 'Membros disponíveis', 'unlimited' => ':amount de membros ilimitados', ], 'roles' => [ 'admin' => 'Não é possível adicionar usuários diretamente ao cargo :admin aqui. Isso é feito na interface do cargo :admin.', 'helper' => 'Adicione ou remova cargos do membro :user.', 'success' => 'Cargos atualizados com sucesso para :user.', 'title' => 'Editar cargos dos membros', ], ]; ================================================ FILE: lang/pt-BR/campaigns/modules.php ================================================ [ 'create' => 'Criar módulo', 'customise' => 'Personalizar', ], 'create' => [ 'helper' => 'Crie um novo módulo personalizado para armazenar entidades que não cabem nos outros módulos.', 'success' => 'Novo módulo criado.', 'title' => 'Novo módulo', ], 'delete' => [ 'confirm' => 'Escreva :code se tiver certeza de que deseja excluir permanentemente o módulo personalizado :name.', 'helper' => 'Tem certeza de que deseja remover o módulo personalizado :name? Isso também excluirá permanentemente todas as entidades, favoritos e widgets vinculados a este módulo.', 'success' => 'Módulo :name removido.', 'title' => 'Remoção de módulo', ], 'errors' => [ 'disabled' => 'O módulo :name está desabilitado. :fix', 'limit' => 'As campanhas estão atualmente limitadas apenas a :max módulos personalizados enquanto ajustamos esse novo recurso.', ], 'fields' => [ 'icon' => 'Ícone do módulo', 'plural' => 'Nome plural do módulo', 'singular' => 'Nome singular do módulo', ], 'helpers' => [ 'custom' => 'Esse é um módulo personalizado.', 'icon' => 'Dê a este módulo um ícone especial :fontawesome, por exemplo :example.', 'plural' => 'O nome plural das entidades do novo módulo. Por exemplo, poções', 'roles' => 'Selecione os cargos que devem ter permissão para visualizar entidades deste novo módulo. Isso pode ser alterado posteriormente nas permissões da função.', 'singular' => 'O nome singular de uma entidade do novo módulo. Por exemplo, poção', ], 'pitch' => 'Renomeie e altere o ícone associado a este módulo para toda a campanha.', 'pitch-custom' => 'Crie módulos personalizados para representar qualquer tipo de entidade no seu mundo. Sem limites, apenas criatividade.', 'rename' => [ 'helper' => 'Altere o nome e o ícone do módulo ao longo da campanha. Deixe em branco para usar o padrão de Kanka.', 'success' => 'Módulo personalizado.', 'title' => 'Personalizar o módulo :module', ], 'reset' => [ 'default' => 'Isso redefinirá apenas os módulos padrão, não os personalizados.', 'success' => 'Os módulos da campanha foram redefinidos.', 'title' => 'Redefinir nomes e ícones dos módulos personalizados', 'warning' => 'Tem certeza de que deseja redefinir os módulos de campanha para seus nomes e ícones originais?', ], 'sections' => [ 'custom' => 'Módulos personalizado', 'default' => 'Módulos padrão', 'features' => 'Recursos', ], 'states' => [ 'disable' => 'Desabilitar', 'enable' => 'Habilitar', ], ]; ================================================ FILE: lang/pt-BR/campaigns/overview.php ================================================ [ 'title' => 'Seguidores', ], 'member' => [ 'title' => 'Membros', ], 'premium' => [ 'enable' => 'Habilitar recursos premium', ], 'status' => [ 'title' => 'Visibilidade', ], ]; ================================================ FILE: lang/pt-BR/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'Desabilitar plugins', 'enable' => 'Habilitar plugins', 'update' => 'Atualizar plugins', ], 'changelog' => 'Histórico de alterações', 'disable' => 'Desativar plugin', 'enable' => 'Ativar plugin', 'find-plugins' => 'Encontrar plugins', 'import' => 'Importar', 'update' => 'Atualizar plug-in', 'update-to' => 'Atualizar para versão :version', 'update_available' => 'Atualização disponível!', ], 'bulks' => [ 'delete' => '{1} Removido :count plugin.|[2,*] Removidos :count plugins.', 'disable' => '{1} Desabilitado :count plugin.|[2,*] Desabilitados :count plugins.', 'enable' => '{1} Habilitado :count plugin.|[2,*] Habilitados :count plugins.', 'update' => '{1} Atualizado :count plugin.|[2,*] Atualizados :count plugins.', ], 'destroy' => [ 'success' => 'Plug-in :plugin removido.', ], 'disabled' => [ 'success' => 'Plug-in :plugin desativado.', ], 'empty_list' => 'A campanha não tem nenhum plugin no momento. Vá ao mercado para instalar alguns e volte para ativá-los.', 'enabled' => [ 'success' => 'Plugin :plugin ativado', ], 'errors' => [ 'invalid_plugin' => 'Plugin inválido', ], 'fields' => [ 'name' => 'Nome do plugin', 'obsolete' => 'Este plugin foi marcado como obsoleto pela equipe do Kanka, o que significa que não funciona mais como planejado originalmente por seu criador.', 'status' => 'Status', 'type' => 'Tipo de plugin', ], 'import' => [ 'button' => 'Importar', 'created' => 'Criada as seguintes entidades:', 'fields' => [ 'only_new' => 'Somente novas entidades', 'private' => 'Entidades privadas', ], 'helper' => 'Você está prestes a importar :count entidades do plugin :plugin. Se este plug-in foi importado anteriormente, as alterações feitas nas entidades importadas podem ser perdidas.', 'no_new_entities' => 'Não há novas entidades a serem importadas.', 'option_only_import' => 'Importe apenas novas entidades, ignorando entidades importadas anteriormente.', 'option_private' => 'Importe todas as entidades como privadas.', 'success' => '{1} Importada :count entidades do plugin :plugin.|[2,*] Importada :count enidades do plugin :plugin.', 'title' => 'Importar :plugin', 'updated' => 'Atualizadas as seguintes entidades:', ], 'info' => [ 'description' => 'Exibindo as últimas atualizações para o plugin :plugin.', 'helper' => 'Quando uma nova versão de um plugin é liberada, você pode atualizá-lo para a nova versão para sua campanha.', 'installed' => 'Instalado', 'title' => 'Atualizações do plug-in :plugin', 'updates' => 'Atualizações', 'versions' => 'Versões', ], 'pitch' => 'Instale e gerencie plugins do :marketplace.', 'status' => [ 'always' => 'Este tipo de plugin está sempre ativo, a menos que seja removido.', 'disabled' => 'Desativado', 'enabled' => 'Ativado', ], 'templates' => [ 'name' => ':name por :user', ], 'title' => 'Plugins - :name', 'types' => [ 'attribute' => 'Modelo de Atributo', 'pack' => 'Pacote de Conteúdo', 'theme' => 'Tema', ], 'update' => [ 'success' => 'Plugin :plugin atualizado.', ], ]; ================================================ FILE: lang/pt-BR/campaigns/public.php ================================================ [], 'title' => 'Alterar a visibilidade da campanha', 'update' => [ 'private' => 'A campanha agora é privada e visível apenas para os seus membros.', 'public' => 'A campanha agora é pública. Pode levar algum tempo para aparecer na página :public-campaigns.', ], ]; ================================================ FILE: lang/pt-BR/campaigns/recovery.php ================================================ [ 'recover' => 'Recuperar', 'recover_selected' => 'Recuperar selecionado', ], 'error' => 'Um erro ocorreu ao tentar recuperar as entidades', 'fields' => [ 'deleted' => 'Removido', 'deleted_at' => 'Removido :date por :user', ], 'name_link' => ':nome foi recuperado com sucesso', 'order' => [ 'newest' => 'Ordenar por: Mais recente', 'newest_first' => 'Mais recente primeiro', 'oldest' => 'Ordenar por: Mais antigo', 'oldest_first' => 'Mais antigo primeiro', 'type' => 'Ordenar por: Tipo', 'type_order' => 'Tipo', ], 'posts' => [], 'premium' => 'Recuperar elementos é um recurso premium da campanha.', 'success_v2'=> '{1} :count elemento foi recuperado.|[2,*] :count elementos foram recuperados.', 'title' => 'Recuperação de Entidades - :campaign', 'toggle' => [], ]; ================================================ FILE: lang/pt-BR/campaigns/roles.php ================================================ [ 'status' => 'Status :status', ], 'create' => [ 'helper' => 'Crie um novo cargo para a campanha.', ], 'overview' => [ 'limited' => ':amount de ::total cargos criados.', 'title' => 'Cargos disponíveis', 'unlimited' => ':amount de cargos ilimitados criados.', ], 'public' => [], 'show' => [ 'title' => ':role permissões - :campaign', ], 'toggle' => [ 'disabled' => 'Membros do cargo :role não podem mais :action :entities', 'enabled' => 'Membros da cargo :role agora podem :action :entities', ], 'warnings' => [ 'adding-to-admin' => 'Os membros do cargo :name têm acesso a tudo na campanha e não podem ser removidos por outros membros do cargo . Após :amount minutos, somente eles próprios podem se retirar do cargo.', ], ]; ================================================ FILE: lang/pt-BR/campaigns/sidebar.php ================================================ [ 'reset' => 'Redefinir ao padrão', ], 'call-to-action' => 'Personalize a ordem, os ícones e os nomes dos elementos na barra lateral da campanha.', 'helpers' => [ 'bookmarks' => 'Os favoritos não são listados aqui porque cada um tem sua própria configuração :position que determina onde ele aparece na barra lateral.', 'image' => 'Adicione uma imagem para representar a campanha. Esta imagem será usada na barra lateral e na interface do seletor de campanhas. Você pode alterá-la a qualquer momento editando a campanha.', 'reordering'=> 'Reordene a barra lateral arrastando e soltando os ícones do lado esquerdo.', ], 'image-success' => 'A nova imagem da campanha foi salva. Esta imagem pode ser alterada novamente editando a campanha.', 'reset' => [ 'success' => 'Redefinição da configuração da barra lateral da campanha.', 'title' => 'Redefinir a configuração da barra lateral', 'warning' => 'Tem certeza de que deseja redefinir a barra lateral da sua campanha para os valores padrão?', ], 'success' => 'Configuração da barra lateral da campanha salva.', 'title' => 'Configuração da barra lateral da campanha :campaign', 'tooltips' => [ 'image' => 'Alterar esta imagem de fundo', ], ]; ================================================ FILE: lang/pt-BR/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Calendários', 'title' => 'Guardião do Tempo', ], 'murderer' => [ 'goal' => 'Personagens mortos', 'title' => 'Assassino', ], ], 'fields' => [ 'created' => 'Criado em', 'creator' => 'Criado por', 'general' => 'Geral', ], 'targets' => [], 'title2' => 'Estatísticas', 'titles' => [ 'calendars' => 'Nível :level de Guardião do Tempo', 'characters'=> 'Nível :level de Nomeador', 'dead' => 'Nível :level de Assassino', 'families' => 'Nível :level de Planejador Familiar', 'locations' => 'Nível :level de Construtor', 'quests' => 'Nível :level de Conspirador', 'races' => 'Nível :level de Reprodutor', ], ]; ================================================ FILE: lang/pt-BR/campaigns/styles.php ================================================ [ 'current' => 'Tema atual: :theme', 'disable' => 'Desabilitar', 'enable' => 'Habilitar', 'new' => 'Novo estilo', ], 'bulks' => [ 'delete' => '{1} Removido :count estilo.|[2,*] Removidos :count estilos.', 'disable' => '{1} Desabilitado :count estilo.|[2,*] Desabilitados :count estilos.', 'enable' => '{1} Habilitado :count estilo.|[2,*] Habilitados :count estilos.', ], 'create' => [ 'success' => 'Novo estilo criado.', 'title' => 'Nova estilo', ], 'delete' => [ 'success' => 'Estilo :name removido.', ], 'errors' => [ 'max_content' => 'A regra CSS não pode ter mais de :amount caracteres.', 'max_reached' => 'Número máximo de estilos (:max) alcançado.', ], 'fields' => [ 'content' => 'Regra CSS', 'is_enabled' => 'Ativado', 'length' => 'Comprimento', 'modified' => 'Modificado', 'name' => 'Nome', 'order' => 'Ordem', ], 'helpers' => [ 'here' => 'no nosso blog', 'is_enabled' => 'Habilite este tema em todas as páginas.', 'main' => 'Você pode criar um estilo CSS personalizado para sua campanha impulsionada. Esses estilos são carregados após quaisquer temas do mercado que estiverem habilitados para a campanha. Você pode aprender mais sobre como estilizar sua campanha :here.', ], 'pitch' => 'Crie um estilo CSS personalizado para personalizar totalmente a aparência da campanha.', 'placeholders' => [ 'name' => 'Nome do estilo', ], 'reorder' => [ 'save' => 'Salvar nova ordem', 'success' => '{1} Reordenado :count estilo.|[2,*] Reordenados :count estilos', 'title' => 'Reordenar estilos', ], 'theme' => [ 'none' => 'Usar preferência do usuário', 'override' => 'Substituição de tema', 'success' => 'Tema da campanha atualizado.', 'title' => 'Atualizar tema da campanha.', ], 'title' => 'Temas da Campanha', 'toggle' => [ 'disable' => 'Estilo desabilitado com sucesso.', 'enable' => 'Estilo habilitado com sucesso.', ], 'update' => [ 'success' => 'Estilo :name atualizado.', 'title' => 'Atualizar estilo', ], ]; ================================================ FILE: lang/pt-BR/campaigns/vanity.php ================================================ 'O nome :vanity está disponível!', 'rule' => 'O campo :field precisa de pelo menos um caractere alfabético.', 'rule2' => 'O campo :field não permite o seguinte caractere: /.', 'set' => 'A URL personalizada da campanha está permanentemente definido como :vanity.', ]; ================================================ FILE: lang/pt-BR/campaigns/webhooks.php ================================================ [ 'action' => 'Alterar status', 'add' => 'Criar webhook', 'bulks' => [ 'delete_success' => '{1} :count webhook removido.|[2,*] :count webhook removidos.', 'disable' => 'Desativar', 'disable_success' => '{1} :count webhook desativado.|[2,*] :count webhook desativados.', 'enable' => 'Ativar', 'enable_success' => '{1} :count webhook ativado.|[2,*] :count webhook ativados.', ], 'test' => 'Testar webhook', 'update' => 'Atualizar webhook', ], 'create' => [ 'success' => 'Webhook criado com sucesso', 'title' => 'Adicionar novo webhook', ], 'destroy' => [ 'success' => 'Webhook removido com sucesso', ], 'edit' => [ 'success' => 'Webhook atualizado com sucesso', 'title' => 'Atualizar webhook', ], 'error' => [ 'pitch' => 'Desbloqueie recursos premium para acessar webhooks.', ], 'fields' => [ 'enabled' => 'Ativado', 'event' => 'Evento', 'events' => [ 'deleted' => 'Entidade removida', 'edited' => 'Entidade editada', 'new' => 'Nova entidade', ], 'message' => 'Mensagem', 'private_entities' => [ 'helper' => 'Não acione o webhook ao atualizar entidades privadas.', 'skip' => 'Pular entidades privadas', ], 'type' => 'Tipo', 'types' => [ 'custom' => 'Mensagem', 'payload' => 'Payload', ], 'url' => 'Url', ], 'helper' => [ 'active' => 'Se o webhook estiver ativo no momento', 'message' => 'Adicione uma mensagem personalizada com suporte para mapeamentos', 'status' => 'Alternar o status ativo do webhook', ], 'placeholders' => [ 'message' => '{who} fez alterações em {name}, confira em {url}', 'url' => 'Url do webhook de destino', ], 'test' => [ 'success' => 'Requisição de teste enviada', ], 'title' => 'Webhooks', 'toggle' => [ 'disable' => 'Webhook desabilitado com sucesso.', 'enable' => 'Webhook habilitado com sucesso.', ], ]; ================================================ FILE: lang/pt-BR/campaigns.php ================================================ [], 'create' => [ 'success' => 'Campanha criada.', 'title' => 'Nova Campanha', ], 'destroy' => [], 'edit' => [ 'success' => 'Campanha atualizada.', ], 'entity_note_visibility' => [], 'entity_personality_visibilities' => [ 'private' => 'Novos personagens tem sua personalidade privada por padrão.', ], 'entity_visibilities' => [ 'private' => 'Novas entidades são privadas.', ], 'errors' => [ 'access' => 'Você não tem acesso a esta campanha.', 'premium' => 'Esse recurso está disponível somente para campanhas premium.', 'unknown_id' => 'Campanha Desconhecida.', ], 'export' => [], 'fields' => [ 'boosted' => 'Impulsionada por', 'entity_count' => 'Número de Entidades', 'entry' => 'Descrição da campanha', 'followers' => 'Seguidores', 'genre' => 'Gênero(s)', 'header_image' => 'Background do dashboard da campanha', 'image' => 'Imagem da barra lateral', 'locale' => 'Local', 'name' => 'Nome', 'open' => 'Aberta a inscrições', 'premium' => 'Premium desbloqueado por :name', 'public' => 'Visibilidade da campanha', 'public_campaign_filters' => 'Filtros de Campanhas Públicas', 'superboosted' => 'Super-impulsionada por', 'system' => 'Sistema', 'theme' => 'Tema', 'vanity' => 'URL personalizada', ], 'following' => 'Seguindo', 'helpers' => [ 'boosted' => 'Alguns recursos requerem que a campanha esteja sendo impulsionada. Mais informações na página :settings.', 'css' => 'Escreva seu próprio CSS que será carregado nas páginas de sua campanha. Observe que qualquer abuso desse recurso pode levar à remoção do seu CSS personalizado. Ofensas repetidas ou graves podem levar à remoção de sua campanha.', 'dashboard' => 'Personalize a forma como o widget do dashboard da campanha é exibido preenchendo os campos a seguir.', 'excerpt' => 'O resumo da campanha será exibido no painel, então escreva algumas frases apresentando o seu mundo. Mantenha-o curto para obter os melhores resultados.', 'header_image' => 'Imagem exibida como plano de fundo no widget cabeçalho da campanha do dashboard.', 'hide_history' => 'Habilite esta opção para ocultar o histórico de entidades para membros não administradores da campanha.', 'hide_members' => 'Habilite esta opção para ocultar a lista de membros da campanha para membros não administradores.', 'locale' => 'O idioma em que sua campanha está escrita. É usado para gerar conteúdo e agrupar campanhas públicas.', 'name' => 'Sua campanha/mundo pode ter qualquer nome, desde que contenha pelo menos 4 letras ou números.', 'no_entry' => 'Parece que a campanha ainda não tem descrição! Vamos consertar isso.', 'premium' => 'Alguns recursos estão disponíveis porque os recursos premium desta campanha estão desbloqueados. Saiba mais na página :settings.', 'public_campaign_filters' => 'Ajude outras pessoas a encontrar a campanha entre outras campanhas públicas, fornecendo as seguintes informações.', 'public_no_visibility' => 'Atenção! Sua campanha é pública, mas a função pública da campanha não pode acessar nada. :fix.', 'system' => 'Se a sua campanha estiver publicamente visível, o sistema será mostrado na página :link.', 'systems' => 'Para evitar sobrecarregar os usuários com opções, alguns recursos do Kanka estão disponíveis apenas com sistemas de RPG específicos (ou seja, o bloco de estatísticas do monstro D&D 5e). Adicionar sistemas suportados aqui habilitará esses recursos.', 'theme' => 'Force o tema da campanha, substituindo a preferência do usuário.', 'view_public' => 'Para visualizar sua campanha como um visualizador público faria, abra :link em uma janela anônima.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Copiar link para sua área de transferência', 'link' => 'Convidar pessoas', ], 'create' => [ 'buttons' => [ 'create' => 'Gerar link', ], 'success_link' => 'Link criado: :link', 'title' => 'Convide amigos para :campaign', ], 'destroy' => [ 'success' => 'Convite removido.', ], 'error' => [ 'inactive_token' => 'Esse token já foi utilizado, ou a campanha não existe mais.', 'invalid_token' => 'Esse token não é mais válido.', 'join' => 'Faça login ou registre uma nova conta para participar da :campaign.', ], 'fields' => [ 'created' => 'Criado', 'role' => 'Cargo', 'token' => 'Token', 'type' => 'Tipo', 'usage' => 'Expira depois de', ], 'helpers' => [ 'role' => 'Os usuários precisam participar antes de serem promovidos à função de administrador.', 'usage' => 'Quantas vezes o link de convite pode ser usado antes de se tornar inativo.', ], 'unlimited_validity' => 'Ilimitado', 'usages' => [ 'five' => '5 usos', 'no_limit' => 'Nunca', 'once' => '1 uso', 'ten' => '10 usos', ], ], 'leave' => [ 'action' => 'Sair da campanha', 'confirm' => 'Você tem certeza que deseja sair da campanha :name? Você não poderá acessá-la novamente, a não ser que o dono da campanha te convide novamente.', 'confirm-button' => 'Sim, sair da campanha', 'error' => 'Não foi possível sair da campanha.', 'fix' => 'Acesse os membros da campanha', 'no-admin-left' => 'Não é possível sair da campanha porque isso a deixaria sem nenhum administrador. Adicione primeiro outro membro ao cargo de administrador.', 'success' => 'Você saiu da campanha.', 'title' => 'Saindo da campanha', ], 'members' => [ 'actions' => [ 'remove' => 'Remover da campanha', 'switch' => 'Visualizar campanha como usuário', 'switch-back' => 'Voltar para meu usuário', 'switch-entity' => 'Visualizar como', ], 'fields' => [ 'banned' => 'Usuário está banido', 'joined' => 'Juntou-se em', 'last_login' => 'Último login', 'name' => 'Usuário', 'role' => 'Cargo', 'roles' => 'Cargos', ], 'helpers' => [ 'switch' => 'Trocar para este usuário', ], 'impersonating' => [ 'message' => 'Você está vendo a campanha como outro usuário. Alguns recursos foram desabilitados, mas o resto age exatamente como o usuário veria. Para voltar ao seu usuário, use o botão Trocar de Volta localizado onde o botão Logout normalmente está localizado.', 'title' => 'Personificando :name', ], 'invite' => [ 'description' => 'Você pode convidar amigos para se juntar a sua campanha fornecendo o endereço de email deles. Assim que eles aceitarem o convite, serão adicionados como um "Espectador". Você também pode cancelar o convite a qualquer momento.', 'more' => 'Você pode adicionar novos cargos em :link', 'title' => 'Convidar', ], 'removal' => 'Você está removendo ":member" da campanha.', 'roles' => [ 'member' => 'Membro', 'owner' => 'Administrador', 'player' => 'Jogador', 'public' => 'Público', 'viewer' => 'Espectador', ], 'switch_back_success' => 'Você voltou para sua conta.', ], 'mentions' => [], 'modules' => [], 'open_campaign' => [], 'options' => [], 'overview' => [ 'entity-count' => '{0} Nenhuma entidade|{1} :amount entidade|[2,*] :amount entidades', 'follower-count' => '{0} Nenhum seguidor|{1} :amount seguidor|[2,*] :amount seguidores', ], 'panels' => [ 'dashboard' => 'Dashboard', 'privacy' => 'Configurações de privacidade padrão', 'setup' => 'Configuração', 'sharing' => 'Compartilhamento', 'systems' => 'Sistemas', 'ui' => 'Interface', ], 'placeholders' => [ 'locale' => 'Idioma', 'name' => 'O nome da sua campanha', 'system' => 'D&D, Pathfinder, Fate, DSA', ], 'privacy' => [ 'hidden' => 'Escondido', 'private' => 'Privado', 'visible' => 'Visível', ], 'public' => [ 'helpers' => [ 'introduction' => 'As campanhas são privadas por padrão e podem ser tornadas públicas. Isso permite que qualquer pessoa as acesse, e as torna disponíveis na página :public-campaigns se elas tiverem entidades visíveis para a função :public-role. Uma campanha pública é visível para todos, mas para que seu conteúdo seja visível, a função :public-role precisa de permissões adequadas.', ], ], 'roles' => [ 'actions' => [ 'add' => 'Criar cargo', 'duplicate' => 'Duplicar cargo', 'permissions' => 'Gerenciar permissões', 'rename' => 'Renomear função', 'save' => 'Salvar função', ], 'admin_role' => 'cargo de administrador', 'bulks' => [ 'delete' => '{1} Removido :count cargo.|[2,*] Removidos :count cargos.', 'edit' => '{1} Atualizado :count cargo.|[2,*] Atualizados :count cargos.', ], 'create' => [ 'success' => 'Cargo :name criado.', 'title' => 'Novo cargo', ], 'destroy' => [ 'success' => 'Cargo :name removido.', ], 'edit' => [ 'success' => 'Cargo :name atualizado.', 'title' => 'Editar cargo :name', ], 'fields' => [ 'copy_permissions' => 'Copiar permissões', 'name' => 'Nome', 'permissions' => 'Permissões', 'type' => 'Tipo', 'users' => 'Usuários', ], 'helper' => [ '1' => 'Uma campanha pode ter quantos cargos quiser. O cargo de "Administrador" tem automaticamente acesso a tudo de uma campanha, mas cada outro cargo pode ter permissões específicas em cada tipo de entidade (personagem, local, etc).', '2' => 'Entidades podem ter permissões mais refinadas visualizando a aba "Permissões" dessa entidade. Essa aba aparece uma vez que sua campanha tenha vários cargos ou membros.', '3' => 'Pode-se optar pelo sistema de "exclusão", onde o acesso para visualização de todas as entidades é dado aos cargos, e usar a caixa de seleção "Privado" nas entidades para escondê-las. Ou pode-se optar por não dar aos cargos muitas permissões, mas configurar cada entidade ser visível individualmente.', '4' => 'Campanhas impulsionadas podem ter uma quantidade ilimitada de cargos.', 'permissions_helper' => 'Duplique todas as permissões do cargo, tanto nos módulos quanto nas entidades.', ], 'hints' => [ 'campaign_not_public' => 'A função pública tem permissões, mas a campanha é privada. Você pode alterar essa configuração na guia Compartilhamento ao editar a campanha.', 'empty_role' => 'A função ainda não tem membros.', 'role_admin' => 'A função :name concede automaticamente acesso a tudo na campanha para seus membros.', 'role_permissions' => 'Habilitar o cargo \':name\' a fazer as seguintes ações em todas as entidades.', ], 'members' => 'Membros', 'modals' => [ 'details' => [ 'campaign' => 'As permissões da campanha permitem o seguinte:', 'entities' => 'Aqui está uma rápida recapitulação do que os membros dessa função obtêm quando uma permissão é definida.', 'more' => 'Para mais detalhes, veja nosso vídeo tutorial no Youtube', 'title' => 'Detalhes da permissão', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Criar', 'articles' => 'Artigos', 'dashboard' => 'Dashboard', 'delete' => 'Deletar', 'edit' => 'Editar', 'gallery' => [ 'browse' => 'Navegar', 'manage' => 'Controle total', 'upload' => 'Carregar', ], 'manage' => 'Gerenciar', 'members' => 'Membros', 'permission' => 'Permissões', 'read' => 'Ver', 'toggle' => 'Mudar para todos', ], 'helpers' => [ 'add' => 'Permitir a criação de entidades deste tipo. Eles terão permissão automática para visualizar e editar as entidades que criarem, se não tiverem permissão para visualizar ou editar.', 'articles' => 'Permite adicionar, editar e excluir artigos, mesmo que o membro não possa editar a entidade.', 'dashboard' => 'Permitir editar os dashboards e os widgets dos dashboards.', 'delete' => 'Permitir remover todas as entidades desse tipo.', 'edit' => 'Permitir editar todas as entidades desse tipo.', 'gallery' => [ 'browse' => 'Permitir visualizar a galeria e definir a imagem de uma entidade da galeria.', 'manage' => 'Permita tudo na galeria que um administrador pode, incluindo edição e exclusão de imagens.', 'upload' => 'Permite fazer upload de imagens para a galeria. Só verão as imagens que enviaram se não forem combinadas com a permissão de navegar.', ], 'manage' => 'Permitir a edição da campanha como um administrador de campanha faria, sem permitir que os membros excluam a campanha.', 'members' => 'Permitir convidar novos membros para a campanha.', 'not_public' => 'A campanha não é pública. Permissões para a função pública podem ser definidas, mas serão ignoradas. Vá e edite a campanha para torná-la pública.', 'permission' => 'Permitir a configuração de permissões em entidades desse tipo que eles podem editar.', 'read' => 'Permitir a visualização de todas as entidades deste tipo que não sejam privadas.', ], ], 'placeholders' => [ 'name' => 'Nome do cargo', ], 'title' => 'Cargos - :name', 'types' => [ 'owner' => 'Administrador', 'public' => 'Público', 'standard' => 'Padrão', ], 'users' => [ 'actions' => [ 'add' => 'Adicionar membro', 'remove' => ':user do cargo :role', 'remove_user' => 'Remover usuário do cargo', ], 'create' => [ 'success' => 'Usuário adicionado ao cargo :role', 'title' => 'Adicione um membro para o cargo :name', ], 'destroy' => [ 'success' => 'Usuário removido do cargo :role.', ], 'errors' => [ 'cant_kick_admins' => 'Para evitar abusos, não é possível remover outros membros do cargo de :admin da campanha. Em caso de problemas, entre em contato conosco no :discord ou no :email.', 'needs_more_roles' => 'Você precisa se adicionar a outro cargo na campanha antes de poder se remover do cargo :admin.', ], 'fields' => [ 'name' => 'Nome', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Habilitar', ], 'boosted' => 'Este recurso está em acesso antecipado e atualmente disponível apenas para :boosted.', 'deprecated' => [ 'help' => 'Este módulo está obsoleto, o que significa que não é mais mantido e que os bugs não são testados a cada nova atualização. Use este módulo com o conhecimento de que ele será removido do Kanka.', 'title' => 'Descontinuado', ], 'disabled' => 'O módulo :module está desabilitado.', 'enabled' => 'O módulo :module está habilitado.', 'errors' => [ 'module-disabled' => 'O módulo solicitado está atualmente desabilitado nas configurações da campanha. :fix.', ], 'helpers' => [ 'abilities' => 'Crie habilidades, sejam talentos, feitiços ou poderes que podem ser atribuídos a entidades.', 'assets' => 'Carregue arquivos, defina links e defina pseudônimos para entidades individuais.', 'bookmarks' => 'Crie marcadores para entidades ou listas filtradas que aparecem na barra lateral.', 'calendars' => 'Um lugar para definir todos os calendários do seu mundo.', 'characters' => 'O povo que habita seu mundo.', 'conversations' => 'Conversas fictícias entre personagens ou entre usuários da campanha. Este módulo está obsoleto.', 'creatures' => 'Construa as criaturas, animais e monstros do seu mundo com o módulo de criaturas.', 'dice_rolls' => 'Para aqueles que usam Kanka para campanhas de RPG, uma maneira de cuidar das rolagens de dados.', 'entity_attributes' => 'Acompanhe os atributos nas entidades da campanha, por exemplo, PVs ou DESLOCAMENTO.', 'events' => 'Feriados, festivais, desastres, aniversários, guerras.', 'families' => 'Clãs ou famílias, suas relações e seus membros.', 'inventories' => 'Gerenciar inventários em suas entidades.', 'items' => 'Armas, veículos, relíquias, poções.', 'journals' => 'Observações escritas por personagens, ou preparações de sessões para o mestre do jogo.', 'locations' => 'Planetas, planos, continentes, rios, estados, acampamentos, templos, tavernas.', 'maps' => 'Faça upload de mapas com camadas e marcadores apontando para outras entidades na campanha.', 'notes' => 'Conhecimento, natureza, história, magia, culturas.', 'organisations' => 'Cultos, religiões, facções, guildas.', 'quests' => 'Para manter controle de várias missões com personagens e locais.', 'races' => 'Se a sua campanha tiver mais de uma raça, isso tornará mais fácil manter o controle.', 'tags' => 'Cada entidade pode ter várias tags. As tags podem pertencer a outras tags e as entradas podem ser filtradas por tag.', 'timelines' => 'Represente a história do seu mundo com Linhas do Tempo.', 'whiteboards' => 'Desenhe e escreva em quadros brancos para planejar visualmente seu mundo e seus objetivos.', ], ], 'sharing' => [ 'filters' => 'As campanhas públicas são visíveis na página :public-campaigns. O preenchimento desses campos facilita a descoberta da campanha.', 'language' => 'O idioma no qual o conteúdo da campanha está escrito.', 'system' => 'Se estiver jogando um TTRPG, o sistema usado para jogar na campanha.', ], 'show' => [ 'actions' => [ 'edit' => 'Editar campanha', ], 'tabs' => [ 'achievements' => 'Conquistas', 'customisation' => 'Personalização', 'danger' => 'Perigo', 'data' => 'Dados', 'default-images' => 'Thumbnails padrão', 'defaults' => 'Padrões', 'deletion' => 'Eliminação', 'export' => 'Exportar', 'import' => 'Importar', 'logs' => 'Logs', 'management' => 'Gerenciamento', 'members' => 'Membros', 'plugins' => 'Plugins', 'recovery' => 'Restaurar', 'roles' => 'Cargos', 'sidebar' => 'Configurar barra lateral', 'stats' => 'Status', 'styles' => 'Temas', 'webhooks' => 'Webhooks', ], 'title' => 'Visão Geral - :name', ], 'status' => [ 'free' => 'Recursos premium desativados.', 'legacy' => [ 'title' => 'Recursos impulsionados (legado)', ], 'premium' => 'Recursos premium desbloqueados por :name.', 'title' => 'Recursos premium', ], 'superboosted' => [], 'themes' => [ 'none' => 'Nenhum (padrão para configurações do usuário)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Apenas visível aos administradores da campanha.', 'visible' => 'Visível aos membros', ], 'fields' => [ 'entity_history' => 'Registro de histórico da entidade', 'member_list' => 'Lista de membros da campanha', ], 'helpers' => [ 'entity-history' => 'Controle quem pode ver as alterações recentes feitas em entidades individuais da campanha.', 'member-list' => 'Controle quem pode ver quem está na campanha.', 'theme' => 'Exiba a campanha no tema do usuário ou force-a a renderizar em um dos seguintes temas.', ], 'members' => [ 'hidden' => 'Apenas visível aos administradores da campanha', 'visible' => 'Visível aos membros', ], ], 'visibilities' => [ 'private' => 'Campanha privada', 'public' => 'Campanha pública', 'unlisted' => 'Público (não listado)', ], 'warning' => [], ]; ================================================ FILE: lang/pt-BR/characters.php ================================================ [ 'add_appearance' => 'Adicionar uma aparência', 'add_personality' => 'Adicionar uma personalidade', ], 'conversations' => [], 'create' => [ 'title' => 'Novo Personagem', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'families' => [ 'helper' => 'Reordene e controle quais famílias de :name são visíveis ou ocultas para não administradores.', 'reorder' => [ 'success' => 'Famílias de personagens atualizadas com sucesso.', ], 'title2' => 'Gerenciar famílias', ], 'fields' => [ 'age' => 'Idade', 'is_appearance_pinned' => 'Aparência fixada', 'is_dead' => 'Morto', 'is_personality_pinned' => 'Personalidade fixada', 'is_personality_visible' => 'Personalidade visível', 'life' => 'Vida', 'physical' => 'Físico', 'pronouns' => 'Pronomes', 'sex' => 'Gênero', 'title' => 'Título', 'traits' => 'Características', ], 'helpers' => [ 'age' => 'Você pode vincular essa entidade a um calendário de sua campanha para calcular automaticamente sua idade. :more.', ], 'hints' => [ 'is_appearance_pinned' => 'Exiba os traços de aparência na página de visão geral.', 'is_dead' => 'Este personagem está morto.', 'is_personality_visible' => 'Os traços de personalidade são visíveis para todos, não apenas para os membros do cargo :admin.', 'personality_not_visible' => 'Traços de personalidade deste personagem estão atualmente visíveis apenas para usuários Admin.', 'personality_visible' => 'Traços de personalidade deste personagem estão visíveis para todos.', ], 'index' => [], 'items' => [], 'journals' => [], 'labels' => [ 'appearance' => [ 'entry' => 'Descrição da aparência', 'name' => 'Nome da aparência', ], 'personality' => [ 'entry' => 'Descrição do traço de personalidade', 'name' => 'Nome do traço de personalidade', ], ], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => ':character adicionado à :organisation.', 'title' => 'Filiação', ], 'destroy' => [ 'success' => 'Filiação removida.', ], 'edit' => [ 'success' => 'Filiação atualizada.', 'title' => 'Atualizar filiação de :name', ], 'fields' => [ 'role' => 'Função', ], ], 'placeholders' => [ 'age' => 'Idade', 'appearance_entry' => 'Descrição', 'appearance_name' => 'Cabelo, Olhos, Pele, Altura', 'name' => 'Nome do personagem', 'personality_entry' => 'Detalhes', 'personality_name' => 'Objetivos, Maneirismos, Medos, Vínculos', 'physical' => 'Físico', 'pronouns' => 'Ele/Seu, Ela/Sua, Eles/Seus', 'sex' => 'Gênero', 'title' => 'Título', 'traits' => 'Traços', 'type' => 'NPC, Personagem de Jogador, Divindade', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Missões das quais o personagem delegou.', 'quest_member' => 'Missões das quais o personagem é membro.', ], ], 'races' => [ 'helper' => 'Reordene e controle quais raças de :name são visíveis ou ocultas para não administradores.', 'reorder' => [ 'success' => 'Raças de personagens atualizadas com sucesso', ], 'title2' => 'Gerenciar raças', ], 'sections' => [ 'appearance' => 'Aparência', 'personality' => 'Personalidade', ], 'show' => [], 'warnings' => [ 'personality_hidden' => 'Você não tem permissão para editar traços de personalidade deste personagem.', ], ]; ================================================ FILE: lang/pt-BR/colours.php ================================================ 'Ciano', 'black' => 'Preto', 'blue' => 'Azul', 'brown' => 'Marrom', 'green' => 'Verde', 'grey' => 'Cinza', 'light-blue' => 'Azul-claro', 'maroon' => 'Bordô', 'navy' => 'Azul-marinho', 'none' => 'Nenhuma', 'orange' => 'Laranja', 'pink' => 'Rosa', 'purple' => 'Roxo', 'red' => 'Vermelho', 'teal' => 'Azul-esverdeado', 'white' => 'Branco', 'yellow' => 'Amarelo', ]; ================================================ FILE: lang/pt-BR/concept.php ================================================ 'campanha impulsionada', 'premium-campaign' => 'campanha premium', 'premium-campaign-count' => '{0} Nenhuma Campanha Premium |{1} 1 Campanha Premium |[2,*] :count Campanhas Premium', 'premium-campaigns' => 'campanhas premium', 'premium-feature' => 'Recurso Premium', 'superboosted-campaign' => 'campanha super impulsionada', ]; ================================================ FILE: lang/pt-BR/confirm/editing.php ================================================ 'Voltar', 'description' => 'Parece que outra pessoa está editando esta página! Quer voltar ou ignorar este aviso, correndo o risco de perder dados?', 'ignore' => 'Editar do mesmo jeito', 'members' => 'Membros editando essa página:', 'title' => 'Aviso', 'user' => ':user desde :since', ]; ================================================ FILE: lang/pt-BR/confirm.php ================================================ [ 'bulk' => 'Tem certeza de que deseja excluir os elementos selecionados?', 'helper' => 'Tem certeza de que deseja excluir :name?', 'recoverable' => 'Esta ação pode ser desfeita por até :day dias com uma :premium-campaign.', 'title' => 'Excluir elemento', ], ]; ================================================ FILE: lang/pt-BR/conversations.php ================================================ [ 'title' => 'Novo Diálogo', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_closed' => 'Fechado', 'messages' => 'Mensagens', 'participants' => 'Participantes', ], 'hints' => [ 'empty' => 'Não há participantes nesta conversa.', 'participants' => 'Por favor, adicione participantes ao seu diálogo pressionando o ícone :icon no canto superior direito.', ], 'index' => [], 'messages' => [ 'destroy' => [ 'success' => 'Mensagem removida', ], 'is_updated' => 'Atualizado', 'load_previous' => 'Carregar mensagens anteriores', 'placeholders' => [ 'message' => 'Sua mensagem', ], ], 'participants' => [ 'create' => [ 'success' => 'Participante :entity adicionado ao diálogo.', ], 'destroy' => [ 'success' => 'Participante :entity removido do diálogo.', ], 'helper' => 'Adicione e remova participantes de :name.', 'modal' => 'Participantes', 'title' => 'Participantes de :name', ], 'placeholders' => [ 'name' => 'Nome do diálogo', 'type' => 'No Jogo, Preparação, Plot', ], 'show' => [ 'is_closed' => 'Diálogo está fechado.', ], 'tabs' => [ 'participants' => 'Participantes', ], 'targets' => [ 'characters' => 'Personagens', 'members' => 'Membros', ], ]; ================================================ FILE: lang/pt-BR/cookieconsent.php ================================================ 'Permitir cookies', 'dismiss' => 'Liberar', 'header' => 'Consentimento de cookies', 'link' => 'Saiba mais', 'message' => 'Kanka usa cookies para garantir que você obtenha a melhor experiência em nosso site.', 'policy' => 'Política de cookies', 'reject' => 'Recusar', ]; ================================================ FILE: lang/pt-BR/creatures.php ================================================ [ 'title' => 'Nova Criatura', ], 'creatures' => [], 'fields' => [ 'is_dead' => 'Morta', 'is_extinct' => 'Extinta', ], 'helpers' => [], 'hints' => [ 'is_dead' => 'Essa criatura está morta.', 'is_extinct' => 'Essa criatura está extinta.', ], 'lists' => [ 'empty' => 'Adicione feras, monstros ou seres míticos que seus heróis possam enfrentar ou com os quais possam fazer amizade.', ], 'placeholders' => [ 'type' => 'Herbívoro, Aquático, Mítica', ], 'show' => [], ]; ================================================ FILE: lang/pt-BR/crud.php ================================================ [ 'actions' => 'Ações', 'apply' => 'Aplicar', 'back' => 'Voltar', 'change' => 'Alterar', 'close' => 'Fechar', 'confirm' => 'Confirmar', 'copy' => 'Copiar', 'copy_mention' => 'Copiar menção [ ]', 'copy_to_campaign' => 'Copiar para campanha', 'disable' => 'Desativar', 'enable' => 'Ativar', 'explore_view' => 'Visão Aninhada', 'export' => 'Exportar (PDF)', 'find_out_more' => 'Descubra mais', 'go_to' => 'Ir para :name', 'help' => 'Ajuda', 'json-export' => 'Exportar (JSON)', 'markdown-export' => 'Exportar (Markdown)', 'move' => 'Mover', 'new' => 'Novo', 'new_child' => 'Novo filho', 'new_post' => 'Novo post', 'next' => 'Próximo', 'open' => 'Abrir', 'print' => 'Imprimir', 'reorder' => 'Reordenar', 'reset' => 'Redefinir', 'transform' => 'Transformar', ], 'add' => 'Adicionar', 'alerts' => [ 'copy_attribute' => 'A menção da entidade foi copiada para a sua área de transferência.', 'copy_invite' => 'O link de convite da campanha foi copiado para a sua área de transferência.', 'copy_mention' => 'A menção avançada da entidade foi copiada para sua área de transferência.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Editar & tagging', 'permissions' => 'Alterar permissões', ], 'age' => [ 'helper' => 'Você pode usar + e - antes do número para atualizar a idade por esse valor.', ], 'buttons' => [ 'label' => 'Para selecionado(s)', ], 'edit' => [ 'locations' => 'Ação para locais', 'tagging' => 'Ação para tags', 'tags' => [ 'add' => 'Adicionar', 'remove' => 'Remover', ], 'title' => 'Editando múltiplas entidades', ], 'errors' => [ 'admin' => 'Apenas administradores de campanha podem mudar o status privado de entidades.', 'general' => 'Ocorreu um erro ao processar sua ação. Tente novamente e entre em contato conosco se o problema persistir. Mensagem de erro: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Sobrepor', ], 'helpers' => [ 'override' => 'Se selecionado, as permissões das entidades selecionadas serão substituídas por estas. Se desmarcado, as permissões selecionadas serão adicionadas às existentes.', ], 'title' => 'Alterar permissões para várias entidades', ], ], 'bulk_templates' => [ 'bulk_title' => 'Aplicar um modelo a múltiplas entidades', ], 'cancel' => 'Cancelar', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Copiar entidades para outra campanha', 'panel' => 'Copiar', 'title' => 'Copiar :name para outra campanha', ], 'create' => 'Criar', 'datagrid' => [ 'empty' => 'Nada para mostrar ainda', ], 'delete_modal' => [ 'callout' => 'Psst!', 'confirm' => 'Confirmar remoção', 'permanent' => 'Essa ação é permanente.', 'recoverable' => 'As entidades podem ser recuperadas por até :day dias com uma :boosted-campaign.', 'title' => 'Confirmação de remoção', ], 'destroy_many' => [], 'dynamic' => [ 'permission' => 'Você não tem as permissões corretas para criar uma entidade do módulo :module.', 'unknown' => 'Entidade inválida do módulo :module.', ], 'edit' => 'Editar', 'errors' => [ 'boosted_campaigns' => 'Esse recurso está somente disponível para :boosted.', 'unavailable_feature' => 'Recurso indisponível', ], 'events' => [], 'fields' => [ 'calendar_date' => 'Data do Calendário', 'child' => 'Filho', 'closed' => 'Fechado', 'colour' => 'Cor', 'copy_abilities' => 'Copiar Habilidades', 'copy_inventory' => 'Copiar Inventário', 'copy_links' => 'Copiar Links', 'copy_permissions' => 'Copiar Permissões (isso substituirá os valores definidos na guia de permissões)', 'copy_posts' => 'Copiar Posts (isso inclui as permissões dos posts)', 'copy_reminders' => 'Copiar Lembretes', 'creator' => 'Criador', 'date_range' => 'Intervalo de datas', 'excerpt' => 'Resumo', 'has_entity_files' => 'Possui arquivos de entidade', 'has_entry' => 'Possui introdução', 'has_image' => 'Possui uma imagem', 'has_posts' => 'Possui posts', 'header_image' => 'Imagem de Cabeçalho', 'image' => 'Imagem', 'is_closed' => 'Diálogo será fechado e não aceitará novas mensagens.', 'is_private' => 'Privado', 'is_private_v3' => 'Exiba-o apenas aos membros do cargo de :admin-role. Isso substitui qualquer outra permissão.', 'is_star' => 'Fixado', 'locations' => ':first em :second', 'name' => 'Nome', 'names' => 'Nomes', 'parent' => 'Primário', 'position' => 'Posição', 'replace_mentions' => 'Substitua as menções de atributo na introdução pelas da nova entidade', 'template' => 'Modelo', 'tooltip' => 'Dica de Contexto', 'type' => 'Tipo', 'visibility' => 'Visibilidade', 'word-count' => 'Contagem de palavras: :number', ], 'files' => [ 'errors' => [ 'max' => 'Você atingiu o número máximo (:max) de arquivos para esta entidade.', 'max_size' => 'A campanha atingiu a capacidade máxima de armazenamento de arquivos.', 'no_files' => 'Sem arquivos', ], 'hints' => [ 'limit' => 'Cada entidade pode ter no máximo :max arquivos carregados nela.', 'limitations' => 'Formatos suportados: :formats. Tamanho máximo do arquivo: :size', ], ], 'filter' => 'Filtro', 'filters' => [ 'all' => 'Filtrar para todos os descendentes', 'clear' => 'Limpar Filtros', 'copy_helper' => 'Use os filtros copiados em sua área de transferência como valores para filtros em widgets do dashboard e links rápidos.', 'copy_to_clipboard' => 'Copiar filtros para a área de transferência', 'direct' => 'Filtrar para descendentes diretos', 'filtered' => 'Exibindo um total de :count :entity', 'lists' => [ 'desktop' => [ 'all' => 'Mostrar todos descendentes (:count)', 'filtered' => 'Mostrar descendentes diretos (:count)', ], ], 'mobile' => [ 'clear' => 'Limpar', 'copy' => 'Área de transferência', ], 'options' => [ 'children' => 'Corresponde a este ou seus descendentes', 'exclude' => 'Não corresponde', 'hide' => 'Esconder', 'include' => 'Corresponde', 'none' => 'Vazio', 'show' => 'Mostrar', ], 'show' => 'Mostrar Filtros', 'sorting' => [ 'asc' => ':field Ascendente', 'desc' => ':field Descendente', 'helper' => 'Controle em qual ordem os resultados aparecem.', ], 'title' => 'Filtros', ], 'fix-this-issue' => 'Corrija este problema', 'forms' => [ 'actions' => [ 'calendar' => 'Adicionar uma data no calendário', ], 'copy_options' => 'Copiar Opções', ], 'helpers' => [ 'copy_options' => 'Copie os seguintes elementos relacionados da origem para a nova entidade.', 'linking' => 'Vinculando a outras entidades', 'parent' => 'Selecione um pai para o qual a entidade será filho', ], 'hidden' => 'Escondido', 'hints' => [ 'calendar_date' => 'Uma data de calendário permite fácil filtragem em listas e também mantém um lembrete no calendário selecionado.', 'image_dimension' => 'Dimensões recomendadas :dimension pixels.', 'image_limitations' => 'Formatos suportados: :formats. Tamanho máximo do arquivo: :size.', 'image_recommendation' => 'Dimensões recomendadas :width por :height px.', 'is_star' => 'Elementos fixados aparecerão no menu de visão geral da entidade.', 'tooltip' => 'Substitua a dica de contexto gerado automaticamente pelo conteúdo a seguir. Qualquer código HTML será removido, mas você ainda pode mencionar outras entidades usando menções avançadas.', ], 'history' => [ 'created_clean' => 'Criado pelo :name :date', 'created_date_clean' => 'Criado :date', 'unknown' => 'Desconhecido', 'updated_clean' => 'Última modificação feita por :name :date', 'updated_date_clean' => 'Última modificação :date', 'view' => 'Ver histórico da entidade', ], 'image' => [ 'error' => 'Nós não fomos capazes de conseguir a imagem requisitada. Pode ser que o site não autorize o download da imagem por nós (tipicamente para Squarespace e DeviantArt), ou o link não está mais válido. Por favor certifique-se que a imagem não é maior do que :size.', ], 'is_private' => 'Essa entidade é privada e está visível aos membros da campanha com o cargo de Admin.', 'keyboard-shortcut' => 'Atalho de teclado :code', 'navigation' => [ 'cancel' => 'cancelar', 'or_cancel' => 'ou :cancel', 'skip_to_content' => 'Pular navegação', ], 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Permitir', 'deny' => 'Negar', 'ignore' => 'Pular', 'remove' => 'Remover', ], 'bulk_entity' => [ 'allow' => 'Permitir', 'deny' => 'Negar', 'inherit' => 'Herdar', ], 'delete' => 'Remover', 'edit' => 'Editar', 'private' => 'Tornar privado', 'toggle' => 'Alternar', 'view' => 'Visualizar', ], 'fields' => [ 'member' => 'Membro', 'role' => 'Cargo', ], 'helpers' => [ 'setup' => 'Use esta interface para ajustar como cargos e usuários podem interagir com esta entidade. :allow permitirá que o usuário ou cargo execute esta ação. :deny irá negar a eles essa ação. :inherit usará o cargo do usuário ou a permissão do cargo principal. Um usuário definido como :allow é capaz de realizar a ação, mesmo se seu cargo for definido como :deny', ], 'success' => 'Permissões salvas.', 'title' => 'Permissões', 'too_many_members' => 'Esta campanha tem muitos membros (>:number) para exibir nesta interface. Use o botão Permissões na visualização da entidade para controlar as permissões em detalhes.', ], 'placeholders' => [ 'calendar' => 'Escolha um calendário', 'entry' => 'Use @ seguido de três letras para citar outras entidades da campanha.', 'fallback' => 'Escolha :module', 'gallery_image' => 'Escolha uma imagem da galeria da campanha', 'image_url' => 'Você também pode carregar a imagem de uma URL', 'journal' => 'Escolha um diário', 'location' => 'Escolha um local', 'multiple' => 'Escolha um ou vários', 'name' => 'Nome da entidade', 'organisation' => 'Escolha uma organização', 'parent' => 'Escolha um primário', 'tag' => 'Escolha uma tag', 'template' => 'Escolha um template', 'timeline' => 'Escolha uma linha do tempo', 'type' => 'Tipo da entidade', 'user' => 'Escolha um usuário', ], 'relations' => [], 'remove' => 'Remover', 'reorder' => [ 'empty' => 'Não há elementos para reordenar.', ], 'save' => 'Salvar', 'save_and_close' => 'Salvar e Fechar', 'save_and_copy' => 'Salvar e Copiar', 'save_and_new' => 'Salvar e Novo', 'save_and_update' => 'Salvar e Editar', 'save_and_view' => 'Salvar e Visualizar', 'search' => 'Buscar', 'select' => 'Selecionar', 'tabs' => [ 'abilities' => 'Habilidades', 'inventory' => 'Inventário', 'mentions' => 'Menções', 'overview' => 'Visão Geral', 'permissions' => 'Permissões', 'premium' => 'Premium', 'profile' => 'Perfil', 'reminders' => 'Lembretes', ], 'titles' => [ 'editing' => 'Editando :name', 'new' => 'Novo(a) :module', ], 'tooltips' => [], 'update' => 'Atualizar', 'users' => [ 'unknown' => 'Desconhecido', ], 'view' => 'Ver', 'visibilities' => [ 'admin' => 'Administradores', 'admin-self' => 'Apenas eu & Administradores', 'all' => 'Todos', 'members' => 'Membros da campanha', 'self' => 'Apenas eu', ], ]; ================================================ FILE: lang/pt-BR/dashboard.php ================================================ [ 'customise' => 'Personalizar dashboard', 'follow' => 'Seguir', 'join' => 'Entrar', 'unfollow' => 'Deixar de seguir', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Editar nome & permissões', 'new' => 'Novo Dashboard', ], 'create' => [ 'helper' => 'Crie um novo dashboard para :name e atribua quais cargos podem vê-lo ou tê-lo como dashboard padrão.', 'success' => 'Novo dashboard :name da campanha criado', 'title' => 'Novo Dashboard de Campanha', ], 'custom' => [ 'text' => 'Atualmente você está editando o dashboard :name da campanha.', ], 'default' => [ 'text' => 'Atualmente você está editando o dashboard padrão da campanha.', 'title' => 'Dashboard Padrão', ], 'delete' => [ 'success' => 'Dashboard :name removido.', ], 'fields' => [ 'copy_widgets' => 'Copiar widgets', 'name' => 'Nome do dashboard', 'visibility' => 'Visibilidade', ], 'helpers' => [ 'copy_widgets' => 'Duplica os widgets do dashboard :name neste novo.', ], 'pitch' => 'Crie vários dashboards com permissões personalizadas para cada cargo da campanha.', 'placeholders' => [ 'name' => 'Nome do dashboard', ], 'update' => [ 'success' => 'Dashboard :name da campanha atualizado.', 'title' => 'Atualizar dashboard :name da campanha', ], 'visibility' => [ 'default' => 'Padrão', 'none' => 'Nenhum', 'visible' => 'Visível', ], ], 'helpers' => [ 'follow' => 'Seguir uma campanha fará com que ela apareça no seletor de campanha (canto superior esquerdo) abaixo de suas campanhas.', 'join' => 'Essa campanha é aberta a novos membros. Clique para solicitar sua inscrição.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Adicionar um widget', 'back_to_dashboard' => 'Voltar para o dashboard', 'edit' => 'Editar um widget', 'new' => 'Novo widget de :type', ], 'reorder' => [ 'helper' => 'Arraste-me para me mover', 'success' => 'Widgets reordenados.', ], 'title' => 'Configurar Dashboard', 'tutorial' => [ 'blog' => 'nosso tutorial', 'text' => 'Precisa de ajuda para configurar o dashboard de sua campanha? Leia o :blog para alguma ajuda e inspiração.', ], ], 'title' => 'Dashboard', 'widgets' => [ 'advanced_options_boosted' => 'Habilite mais opções como mostrar fixados com uma :boosted_campaign.', 'calendar' => [ 'actions' => [ 'next' => 'Alterar data para o dia seguinte', 'previous' => 'Alterar data para o dia anterior', ], 'previous_events' => 'Anterior', 'upcoming_events' => 'Posterior', ], 'campaign' => [ 'helper' => 'Este widget exibe o cabeçalho da campanha. Este widget é sempre exibido no dashboard padrão.', ], 'create' => [ 'helper' => 'Selecione um tipo de widget para adicionar ao dashboard :name.', 'helper-default' => 'Selecione um tipo de widget para adicionar ao dashboard padrão.', 'success' => 'Widget adicionado ao dashboard.', 'title' => 'Novo widget', ], 'delete' => [ 'success' => 'Widget removido so dashboard.', ], 'fields' => [ 'class' => 'Classe CSS', 'dashboard' => 'Dashboard', 'name' => 'Nome personalizado do widget', 'optional-entity' => 'Link para entidade', 'order' => 'Ordenação', 'size' => 'Tamanho', 'width' => 'Largura', ], 'helpers' => [ 'class' => 'Defina uma classe CSS personalizada para adicionar ao widget.', 'filters' => 'Clique para aprender sobre as opções de filtro disponíveis.', ], 'orders' => [ 'name_asc' => 'Nome ascendente', 'name_desc' => 'Nome descendente', 'oldest' => 'Mais antigo modificado', 'recent' => 'Recentemente modificado', ], 'preview' => [ 'displays' => [ 'expand' => 'Introdução expansível', 'full' => 'Introdução completa', ], 'fields' => [ 'display' => 'Exibir', ], ], 'random' => [ 'helpers' => [ 'name' => 'Você pode fazer referência ao nome da entidade aleatória com {name}', ], 'type' => [ 'all' => 'Tudo', ], ], 'recent' => [ 'advanced_filter' => 'Filtro avançado', 'advanced_filters' => [ 'mentionless' => 'Sem Menções (entidades que não mencionam outras entidades)', 'unmentioned' => 'Não Mencionadas (entidades que não são mencionadas por outras entidades)', ], 'all-entities' => 'Todas entidades', 'entity-header' => 'Use o cabeçalho da entidade como imagem', 'filters' => 'Filtros', 'help' => 'Exiba somente a primeira entidade como uma prévia em vez de uma lista.', 'helpers' => [ 'entity-header' => 'Se sua entidade tiver um cabeçalho de entidade (recurso de campanha aprimorada), defina este widget para usar essa imagem ao invés da imagem da entidade.', 'show_attributes' => 'Exiba os atributos fixados da entidade abaixo da introdução.', 'show_members' => 'Se a entidade for uma família ou organização, exiba seus membros abaixo da introdução.', 'show_relations' => 'Mostrar as relações fixadas da entidade abaixo da introdução.', ], 'show_attributes' => 'Mostrar atributos fixados', 'show_members' => 'Mostrar membros', 'show_relations' => 'Mostrar relações fixadas', 'singular' => 'Pré-visualização', 'tags' => 'Filtrar a lista de entidades com tags especificadas.', 'title' => 'Lista de entidade', ], 'tabs' => [ 'advanced' => 'Avançado', 'setup' => 'Configurar', ], 'unmentioned' => [ 'title' => 'Entidades não mencionadas', ], 'update' => [ 'success' => 'Widget modificado.', ], 'widths' => [ '0' => 'Automático', '12'=> 'Completo (100%)', '3' => 'Minúsculo (25%)', '4' => 'Pequeno (33%)', '6' => 'Metade (50%)', '8' => 'Largo (66%)', '9' => 'Grande (75%)', ], ], ]; ================================================ FILE: lang/pt-BR/dashboards/premium.php ================================================ 'Crie resumos personalizados para sua campanha e páginas iniciais para seus jogadores. Fixe entidades, mapas, calendários, galerias e muito mais em qualquer layout que você escolher.', 'title' => 'Dashboards personalizados', ]; ================================================ FILE: lang/pt-BR/dashboards/widgets/welcome.php ================================================ [], 'focus' => [ 'text' => 'Aqui, sou eu!', 'title' => 'Hey', ], 'intros' => [ '1' => 'Diga olá para sua nova casa de construção de mundo, :user! Configuramos sua primeira campanha e incluímos dois exemplos de :characters e :locations. Estes também são visíveis aqui no dashboard da campanha.', '2' => 'Para começar, clique no grande botão :new-entity (ou pressione :letter sobre o seu teclado) e clique em :characters para criar seu primeiro personagem. Isto é muito fácil! Você pode encontrar todos os seus personagens, locais e outras :entities na barra lateral à esquerda da página.', '3' => 'Aqui estão nossas 5 principais dicas para usar o Kanka', ], 'title' => 'Bem-vindo ao :kanka! 🎉', 'tricks' => [ '1' => 'Ao escrever descrições, não reescreva os nomes dos elementos da campanha. Em vez disso, digite :code e três letras para :mencionar outras entidades da campanha. Essas menções serão atualizadas automaticamente quando você alterar seus nomes.', '2' => 'Para editar o nome, tema ou imagem da campanha, clique em :world na barra lateral, seguido do botão :edit.', '3' => 'Escreva informações secretas sobre entidades como :posts em vez de no campo de texto principal.', '4' => 'Convide seus amigos para a campanha clicando em :world e :members. A partir daí, você pode criar links de convite.', '5' => 'Você pode remover esta mensagem de boas-vindas e mostrar outras informações nesta página (chamada de dashboard). Role para baixo e clique no botão :button.', 'mention' => 'menção', ], ]; ================================================ FILE: lang/pt-BR/datagrids.php ================================================ [ 'back_to' => 'Voltar para :name', ], 'modes' => [ 'flatten' => 'Alternar para um layout achatado', 'grid' => 'Alternar para a visualização em grade', 'nested' => 'Alterar para um layout aninhado', 'table' => 'Alternar para a exibição de tabela', ], 'tooltips' => [ 'nested' => 'Esta entidade tem filhos. Clique na imagem para visualizá-los.', ], ]; ================================================ FILE: lang/pt-BR/datetime.php ================================================ 'dia', 'days' => 'dias', 'elapsed_ago' => ':duration atrás', 'hour' => 'hora', 'hours' => 'horas', 'just_now' => 'agora', 'minute' => 'minuto', 'minutes' => 'minutos', 'month' => 'mês', 'months' => 'meses', 'second' => 'segundo', 'seconds' => 'segundos', 'week' => 'semana', 'weeks' => 'semanas', 'year' => 'ano', 'years' => 'anos', ]; ================================================ FILE: lang/pt-BR/default.php ================================================ 'Título da Página', ]; ================================================ FILE: lang/pt-BR/dice_roll_results.php ================================================ [ 'title' => 'Resultados das Rolagens de Dado', ], ]; ================================================ FILE: lang/pt-BR/dice_rolls.php ================================================ [ 'title' => 'Nova Rolagem de Dados', ], 'destroy' => [ 'dice_roll' => 'Rolagem de dados removida', ], 'edit' => [], 'fields' => [ 'created_at' => 'Rolado em', 'parameters' => 'Parâmetros', 'results' => 'Resultados', 'rolls' => 'Rolagens', ], 'hints' => [ 'parameters' => 'Quais opções de dados estão disponíveis?', ], 'index' => [ 'actions' => [ 'results' => 'Resultados', ], ], 'placeholders' => [ 'name' => 'Nome da Rolagem de Dados', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Rolagem', ], 'error' => 'Rolagem de dados mal sucedida. Não é possível analisar os parâmetros.', 'fields' => [ 'creator' => 'Criador', 'date' => 'Data', 'result' => 'Resultado', ], 'hint' => 'Todas as rolagens feitas para este modelo de rolagem de dados.', 'success' => 'Dados rolados.', ], 'show' => [ 'tabs' => [ 'results' => 'Resultados', ], ], ]; ================================================ FILE: lang/pt-BR/emails/activity/email.php ================================================ 'O e-mail da sua conta Kanka foi alterado para :email.', 'title' => 'E-mail alterado', ]; ================================================ FILE: lang/pt-BR/emails/activity/password.php ================================================ 'A senha da sua conta Kanka foi alterada.', 'help' => 'Se não foi você, entre em contato conosco pelo e-mail :email.', 'title' => 'Senha alterada', ]; ================================================ FILE: lang/pt-BR/emails/purge/first.php ================================================ 'Se você usa sua conta regularmente, não se preocupe, estamos apenas excluindo contas e campanhas que não estão em uso ativo.', 'help' => 'Precisa de ajuda para usar o Kanka? Junte-se ao nosso :discord ou entre em contato conosco em :email', 'intro_account' => 'Entramos em contato para informar que sua conta será excluída em :amount dias, pois você não a usou nos últimos :duration meses.', 'intro_campaigns' => 'Entramos em contato para informar que sua conta e as seguintes campanhas serão excluídas em :amount dias, pois você não as utilizou nos últimos :duration meses.', 'keep' => 'Se desejar manter sua conta ativa, faça login nos próximos :amount dias.', 'title' => 'Sua conta Kanka será excluída em :amount dias', 'warning' => [ 'account' => 'Feito isso, todos os dados associados à sua conta, em :email, serão excluídos permanentemente.', 'campaigns' => 'Feito isso, todos os dados associados à sua conta, no email :email, bem como as seguintes campanhas serão excluídas permanentemente.', ], ]; ================================================ FILE: lang/pt-BR/emails/purge/second.php ================================================ 'Este é um lembrete final de que a conta Kanka no e-mail :email será excluída em :amount dias, pois você não a usou nos últimos :duration meses.', 'title' => 'Aviso final: sua conta Kanka será excluída em :amount dias', ]; ================================================ FILE: lang/pt-BR/emails/subscriptions/expiring.php ================================================ 'atualize os dados do seu cartão', 'primary' => 'Este é um aviso automático de que seu :brand **** :last está expirando em breve.', 'title' => 'Cartão expirando para sua assinatura', 'valid' => 'Se você deseja manter sua assinatura, por favor :action.', ]; ================================================ FILE: lang/pt-BR/emails/subscriptions/upcoming.php ================================================ 'Se você não deseja renovar sua assinatura, faça login em sua conta do Kanka e :link.', 'closing' => 'Sinceramente,', 'dear' => 'Caro :name', 'link' => 'cancele sua assinatura', 'notice' => 'Aviso importante sobre renovações:', 'primary' => 'Este é um lembrete automático de que vamos cobrar automaticamente o seu :brand **** :last em :date, por sua assinatura no Kanka.', 'title' => 'Pagamento anual da sua assinatura do Kanka', 'valid' => 'Certifique-se de que seu cartão de crédito esteja válido na data do pagamento.', ]; ================================================ FILE: lang/pt-BR/emails/subscriptions/validation.php ================================================ 'Validação de e-mail da conta Kanka.', ]; ================================================ FILE: lang/pt-BR/emails/validation.php ================================================ 'Falha na validação, tente novamente.', 'modal' => 'Um e-mail válido é necessário para assinaturas. Por favor, verifique sua caixa de entrada para obter um link para confirmar seu e-mail antes de continuar o processo de assinatura.', 'success' => 'E-mail validado com sucesso.', ]; ================================================ FILE: lang/pt-BR/emails/welcome/2024.php ================================================ 'Feliz construção de mundo e obrigado por participar dessa jornada conosco,', 'header' => 'Bem-vindo ao melhor lugar para criar sua campanha, :name!', 'lead_1' => 'Kanka é uma criação de jogadores apaixonados por RPG que queriam uma abordagem fácil e colaborativa para a construção de mundos, sem compromisso com os recursos.', 'lead_2' => 'Kanka é o que surgiu disso. Estamos aqui para ajudar você a organizar sua campanha e deixar que você se concentre em dar vida ao seu mundo.', 'ps' => 'PS: Se você quiser entrar em contato, pode nos encontrar no :discord ou em :email.', 'what_1' => 'Para ajudar você a começar, criamos sua primeira campanha e incluímos dois personagens e locais de exemplo. Você pode :start se preferir.', 'what_2' => 'Você também deve conferir estes recursos:', 'what_3' => 'Nosso :kb se você tiver dúvidas básicas sobre os recursos do Kanka e sua conta.', 'what_4' => 'O :doc aborda tudo com mais detalhes.', 'what_5' => 'E Finalmente :campaigns mostram o que outros fizeram com o Kanka.', 'what_new' => 'comece um novo', 'what_now' => 'E agora?', 'why' => 'Você recebeu este e-mail porque se inscreveu em nosso site.', ]; ================================================ FILE: lang/pt-BR/emails/welcome.php ================================================ [ 'basics' => [ 'text_1' => 'Com uma ferramenta tão extensa como o Kanka, pode ser difícil saber por onde começar ou o que fazer. Nosso :kb aborda as perguntas mais básicas que você possa ter e, para obter mais ajuda, acesse nossa :doc.', 'title' => 'O básico', ], 'chat' => [ 'text_1' => 'Adoramos ouvir nossos usuários. Somos mais ativos no :discord, onde você encontrará muitos de nossos usuários dedicados, uma equipe de integração, bem como os fundadores do Kanka, que podem responder a quaisquer perguntas que você possa ter. Você também pode nos enviar um e-mail para: e-mail.', 'title' => 'Quer conversar?', ], 'intro' => [ 'header' => 'Bem-vindo à melhor comunidade de construção de mundos, :name!', 'link' => 'Vá para o seu mundo!', 'text_1' => 'Diga olá para sua nova casa de construção de mundo, :name! A comunidade está em nosso DNA e estamos muito satisfeitos por você se juntar a nós. Kanka é uma criação de jogadores de RPG apaixonados que acreditam em uma maneira simplificada e comunitária de abordar a construção do mundo, sem comprometer os recursos.', 'text_2' => 'Configuramos sua primeira campanha e incluímos dois exemplos de personagens e locais para ajudá-lo a começar.', ], 'preview' => 'Faça parte da melhor comunidade de construção de mundos, :name!', ], 'header' => 'Bem-vindo ao Kanka :name!', 'header_sub' => 'Parabéns, você deu o primeiro passo para a criação do seu mundo no :kanka!', 'pricing' => 'preços', 'section_1' => 'Para onde agora?', 'section_11' => 'Crie seu mundo,', 'section_2' => 'O recurso mais importante é o :discord, onde você encontrará muitos dos nossos usuários, um time de plantão, assim como o fundador do Kanka, que poderá responder tantas dúvidas quanto você possa ter.', 'section_4' => 'Nosso :youtube tem vídeos cobrindo os conceitos mais básicos do Kanka. Nem todos os tópicos estão explicados ainda, mas adicionamos novos vídeos regularmente.', 'section_4_v2' => 'Nossa :knowledge-base cobre as perguntas mais básicas que você possa ter, e para uma ajuda mais completa, você pode acessar nossa :documentation!', 'section_6' => 'Entre em contato', 'section_7' => 'Se você não achou uma resposta para a sua pergunta, ou quer simplesmente falar conosco, você pode nos achar no :facebook, ou você pode nos enviar um email através do :email. Nos somos um time pequeno de dois amigos, mas tentamos sempre responder todos os emails que recebemos, então por favor, não hesite em nos contatar.', 'section_8' => 'Uma última coisa', 'section_9_v2' => 'Certificamo-nos de que todos os recursos básicos do Kanka são gratuitos e sempre o manteremos assim. No entanto, se você deseja nos apoiar neste projeto, você pode se tornar um assinante e obter acesso a recursos adicionais, bem como nossa eterna gratidão. Saiba mais na página :pricing.', 'social_account' => 'Se você estiver tendo problemas para fazer login em sua conta, um pequeno lembrete de que você está usando um login :provider. Você pode alterar isso nas configurações da sua conta.', 'title' => 'Bem-vindo ao Kanka', ]; ================================================ FILE: lang/pt-BR/entities/abilities.php ================================================ [ 'add' => 'Adicionar habilidades', 'reset' => 'Redefinir usos da habilidade', 'sync' => 'Adicionar das raças', ], 'charges' => [ 'left' => ':amount sobrando', ], 'create' => [ 'helper' => 'Anexar uma das diversas habilidades a :name.', 'success' => 'Habilidade :ability adicionada a :entity.', 'success_multiple' => 'Habilidades :abilities adicionadas a entidade.', 'title' => 'Adicionar habilidade a :name', ], 'fields' => [ 'note' => 'Nota', 'position' => 'Posição', ], 'groups' => [ 'unorganised' => 'Desorganizado', ], 'helpers' => [ 'note' => 'Você pode fazer referência a entidades usando as menções avançadas (ex :code) e atributos da entidade (ex :attr) nesse campo.', 'recharge' => 'Redefina todas as cargas de habilidades que foram usadas.', 'sync' => 'Importe habilidades definidas nas raças do personagem.', ], 'import' => [ 'errors' => [ 'no_race' => 'O personagem não possui raça.', 'not_character' => 'A entidade não é um personagem.', ], 'success' => '{1} :count habilidade importada.|[2,*] :count habilidades importadas.', ], 'recharge' => [ 'success' => 'Todas as cargas foram redefinidas.', ], 'reorder' => [ 'parentless' => 'Sem Pai', 'success' => 'Habilidades reordenadas com sucesso', ], 'show' => [ 'helper' => 'Adicione habilidades a esta entidade. Você sempre pode editar a visibilidade ou remover uma habilidade. Habilidades pertencentes à mesma habilidade primária serão exibidas como caixas de filtro.', 'reorder' => 'Reordenar Habilidades', 'title' => 'Habilidades de :name', ], 'types' => [ 'unorganised' => 'As habilidades são agrupadas pelo campo pai e retornam para cá.', ], 'update' => [ 'success' => 'Habilidade :ability da entidade atualizada.', 'title' => 'Habilidade de Entidade para :name', ], ]; ================================================ FILE: lang/pt-BR/entities/actions.php ================================================ [ 'set' => 'Definir como modelo', 'success' => [ 'set' => 'Entidade :name definida como um modelo.', 'unset' => 'A entidade :name não está mais definida como um modelo.', ], 'toggle' => 'Status do modelo alternado.', 'unset' => 'Remover como modelo', ], ]; ================================================ FILE: lang/pt-BR/entities/aliases.php ================================================ [ 'add' => 'Adicionar um pseudônimo', ], 'create' => [ 'helper' => 'Crie um pseudônimo para :name, que o tornará encontrável na pesquisa global e por meio de menções de :code.', 'success' => 'Pseudônimo :name adicionado a :entity.', 'title' => 'Novo pseudônimo', ], 'destroy' => [ 'success' => 'Pseudônimo :name removido.', ], 'fields' => [ 'name' => 'Nome', ], 'helpers' => [ 'primary' => 'Definir um ou mais pseudônimos na entidade fará com que ela seja encontrada na pesquisa global (barra superior) e por meio de menções :code.', ], 'limit' => 'Limite de aliases atingido para campanhas padrão (:amount/:max). :upgrade para aliases ilimitados nesta campanha.', 'pitch' => 'Adicione pseudônimos a esta entidade para facilitar a localização em pesquisas e menções. Ideal para apelidos, títulos ou grafias alternativas.', 'placeholders' => [ 'name' => 'Novo pseudônimo', ], 'unboosted' => [], 'update' => [ 'success' => 'Pseudônimo :name atualizado para :entity.', 'title' => 'Atualizar pseudônimo para :name', ], ]; ================================================ FILE: lang/pt-BR/entities/assets.php ================================================ [ 'alias' => 'Pseudônimo ou Identidade secreta', 'file' => 'Adicione um arquivo', 'link' => 'Link externo', ], 'copy_alias' => [ 'success' => 'Menção de pseudônimo copiada para a área de transferência.', 'title' => 'Clique para copiar a menção do alias para a área de transferência.', ], 'show' => [ 'title' => 'Recursos de :name', ], ]; ================================================ FILE: lang/pt-BR/entities/attributes.php ================================================ [ 'load' => 'Carregar', 'manage' => 'Gerenciar', 'more' => 'Mais opções', 'remove_all' => 'Remover Tudo', 'save_and_edit' => 'Aplicar e Editar', 'save_and_story'=> 'Aplicar e Visualizar', 'show_hidden' => 'Mostrar atributos ocultos', 'toggle_privacy'=> 'Privado/Público', ], 'errors' => [ 'loop' => 'Existe um loop infinito no cálculo desse atributo!', 'no_attribute_selected' => 'Selecione um ou mais atributos primeiro.', 'too_many_v2' => 'Campos máximos atingidos (:count/:max). Exclua alguns atributos primeiro antes de poder adicionar mais.', ], 'fields' => [ 'community_templates' => 'Modelos da Comunidade', 'is_private' => 'Atributos Privados', 'is_star' => 'Fixado', 'preferences' => 'Preferências', 'value' => 'Valor', ], 'filters' => [ 'name' => 'Nome do atributo', 'value' => 'Valor do atributo', ], 'helpers' => [ 'delete_all' => 'Tem certeza de que deseja excluir todos os atributos desta entidade?', 'is_private' => 'Permita apenas que membros do cargo :admin-role vejam os atributos desta entidade.', 'setup' => 'Você pode representar elementos como PV ou inteligência de uma entidade com atributos. Adicione atributos manualmente clicando no botão :manage, ou aplique aqueles de um modelo de atributo.', ], 'hints' => [], 'index' => [ 'success' => 'Atributos para :entity atualizados.', 'title' => 'Atributos para :name', ], 'labels' => [ 'checkbox' => 'Nome da caixa de seleção', 'name' => 'Nome do atributo', 'section' => 'Nome da seção', 'value' => 'Valor do atributo', ], 'live' => [ 'success' => 'Atributo :attribute atualizado.', 'title' => 'Atualizando :attribute', ], 'placeholders' => [ 'attribute' => 'Número de conquistas, Nível de Desafio, Iniciativa, População', 'block' => 'Nome do texto multilinhas', 'checkbox' => 'Nome da caixa de seleção', 'icon' => [ 'class' => 'FontAwesome ou RPG Awesome class: fas fa-users', 'name' => 'Nome do Ícone', ], 'number' => 'Valor do número', 'random' => [ 'name' => 'Nome do atributo', 'value' => '1-100 ou lista de valores separados por vírgula', ], 'section' => 'Nome da seção', 'value' => 'Valor do atributo', ], 'ranges' => [ 'text' => 'Opções disponíveis :options', ], 'sections' => [ 'unorganised' => 'Desorganizado', ], 'show' => [ 'hidden' => 'Atributos Ocultos', 'title' => ':name Atributos', ], 'template' => [ 'load' => [ 'success' => 'Modelo carregado', 'title' => 'Carregar do modelo', ], 'pitch' => 'Carregue atributos de um modelo de atributo ou plugins instalados do :plugin.', 'success' => 'Modelo de Atributo :name aplicado em :entity', 'title' => 'Aplicar um modelo de atributo para :name', ], 'title' => 'Atributos', 'toasts' => [ 'bulk_deleted' => 'Atributos removidos', 'bulk_privacy' => 'Privacidade dos atributos alternada', 'lock' => 'Atributo bloqueado', 'pin' => 'Atributo fixado', 'unlock' => 'Atributo desbloqueado', 'unpin' => 'Atributo desafixado', ], 'tutorials' => [], 'types' => [ 'attribute' => 'Atributo', 'block' => 'Bloco', 'checkbox' => 'Caixa de Seleção', 'icon' => 'Ícone', 'number' => 'Número', 'random' => 'Aleatório', 'section' => 'Seção', 'text' => 'Texto Multilinhas', ], 'update' => [ 'success' => 'Atributos para :entity atualizados.', ], 'visibility' => [ 'entry' => 'O atributo é exibido no menu da entidade.', 'private' => 'Atributo visível apenas para membros do cargo de "Administrador".', 'public' => 'Atributo visível a todos os membros', 'tab' => 'Atributo é exibido somente no menu Atributos.', ], ]; ================================================ FILE: lang/pt-BR/entities/children.php ================================================ 'Filho', ]; ================================================ FILE: lang/pt-BR/entities/events.php ================================================ [ 'helper' => 'Crie um lembrete para vincular :name a um calendário.', ], 'fields' => [ 'type' => 'Tipo de Lembrete', ], 'helpers' => [ 'characters' => 'Definir o tipo como data de nascimento ou de morte para este personagem irá calcular automaticamente sua idade. :more.', 'founding' => 'Definir o tipo como :type calculará automaticamente a idade da entidade desde a sua fundação.', ], 'show' => [ 'actions' => [ 'add' => 'Adicionar lembrete', ], 'title' => 'Lembretes :name', ], 'types' => [ 'birth' => 'Nascimento', 'birthday' => 'Aniversário', 'death' => 'Morte', 'founded' => 'Fundado', 'primary' => 'Primário', ], 'years-ago' => '{1} :count ano atrás|[2,*] :count anos atrás', ]; ================================================ FILE: lang/pt-BR/entities/files.php ================================================ [ 'max' => [ 'helper' => 'Não é possível anexar mais arquivos a menos que você remova um existente.', 'limit' => 'Esta entidade atingiu seu limite de arquivos', ], 'upgrade' => [ 'limit' => 'Você atingiu o limite de :limit arquivos para esta entidade', 'upgrade' => 'Atualize para uma campanha premium para anexar até :limit arquivos e desbloquear ainda mais flexibilidade criativa.', ], ], 'create' => [ 'helper' => 'Adicione um arquivo a :name. O arquivo será contabilizado no limite de armazenamento da galeria.', 'success_plural' => '{1} Arquivo :name adicionado.|[2,*] :count arquivos adicionados.', 'title' => 'Novo arquivo para :entity', ], 'destroy' => [ 'success' => 'Arquivo :file removido.', ], 'fields' => [ 'file' => 'Arquivo', 'files' => 'Arquivos', 'name' => 'Nome do arquivo', ], 'max' => [ 'title' => 'Limite alcançado', ], 'update' => [ 'success' => 'Arquivo :file atualizado.', 'title' => 'Atualizar arquivo', ], ]; ================================================ FILE: lang/pt-BR/entities/image.php ================================================ [ 'change_focus' => 'Alterar ponto de foco', 'change_visibility' => 'Alterar visibilidade', 'replace_image' => 'Substituir imagem', 'save-replace' => 'Substituir imagem', 'save_focus' => 'Salvar ponto de foco', 'view' => 'Visualizar imagem', ], 'call-to-action' => 'Clique na imagem da entidade para definir seu ponto de foco ao invés de usar um palpite automatizado.', 'focus' => [ 'breadcrumb' => 'Foco da imagem', 'helper' => 'Clique na imagem para configurar o ponto de foco. Clique no ponto de foco para removê-lo.', 'panel_title' => 'Foco da imagem', 'success' => 'Foco da imagem atualizado.', 'title' => 'Foco da imagem da entidade :name', 'unboosted' => 'Definir um ponto de foco da imagem é reservado a :boosted-campaigns.', 'warning' => 'O ponto de foco para imagens da :gallery é compartilhado por todas as entidades que usam a mesma imagem.', ], 'gallery_permissions' => [ 'admin' => 'Esta imagem da galeria só é visível para os membros do cargo :admin da campanha.', 'adminself' => 'Esta imagem da galeria é visível somente para :creator e os membros do cargo :admin da campanha.', 'member' => 'Esta imagem da galeria é visível apenas para os membros da campanha.', 'self' => 'Esta imagem da galeria só é visível para você.', ], 'replace' => [ 'breadcrumb' => 'Substituição da imagem', 'panel_title' => 'Substituição da imagem da entidade', 'success' => 'Imagem substituída.', 'title' => 'Substituição da imagem da entidade :name', ], 'visibility' => [ 'helper' => 'Altere a visibilidade da imagem da galeria, controlando quem pode visualizá-la.', 'updated' => 'Visibilidade da imagem atualizada.', ], ]; ================================================ FILE: lang/pt-BR/entities/inventories.php ================================================ [ 'copy_inventory' => 'Copiar inventário', ], 'copy' => [ 'helper' => 'Copiar o inventário inteiro de uma entidade para :name', ], 'create' => [ 'helper' => 'Adicione um item ao inventário de :name. Opcionalmente, ele pode ser vinculado a um objeto existente da campanha.', 'success' => 'Item :item adicionado a :entity.', 'success_bulk' => '{0} Nenhum item adicionado à :entity.|{1} :count item adicionado à :entity.|[2,*] :count itens adicionados à :entity.', 'title' => 'Adicionar um item a :name', ], 'default_position' => 'Desorganizado', 'destroy' => [ 'success' => 'Item :item removido de :entuty.', 'success_position' => 'Itens em :position removidos de :entity.', ], 'fields' => [ 'amount' => 'Quantidade', 'copy_entity_entry_v2' => 'Usar introdução do objeto', 'description' => 'Descrição', 'is_equipped' => 'Equipado', 'name' => 'Nome', 'position' => 'Posição', 'qty' => 'Qtd', ], 'helpers' => [ 'amount' => 'Número de itens', 'copy_entity_entry_v2' => 'Exiba a introdução do objeto em vez da descrição personalizada.', 'description' => 'Adicione uma descrição personalizada ao item', 'is_equipped' => 'Marque estes itens como equipados.', 'name' => 'Dê o nome ao item. Um nome é necessário se nenhum objeto for selecionado', ], 'placeholders' => [ 'amount' => 'Qualquer quantidade', 'description' => 'Usado, Danificado, Sintonizado', 'name' => 'Obrigatório se nenhum item for selecionado', 'position' => 'Equipado, Mochila, Estoque, Banco', ], 'show' => [ 'helper' => 'Para criar o inventário desta entidade, comece adicionando um item a ele.', 'title' => 'Inventário :name', 'unsorted' => 'Não classificado', ], 'tooltips' => [ 'equipped' => 'Este item está equipado', ], 'tutorials' => [], 'update' => [ 'success' => 'Item :item de :entity atualizado.', 'title' => 'Atualizar o item de :name', ], ]; ================================================ FILE: lang/pt-BR/entities/links.php ================================================ [ 'add' => 'Adicionar um link', ], 'call-to-action' => 'Adicione links para fontes externas nesta entidade, como DnDBeyond, e eles serão exibidos diretamente na visão geral da entidade.', 'create' => [ 'helper' => 'Adicione um link externo para :name, por exemplo, para a página DnDBeyond.', 'success' => 'Link :name adicionado para :entity.', 'title' => 'Adicionar um link para :name', ], 'destroy' => [ 'success' => 'Link :name removido.', ], 'fields' => [ 'icon' => 'Ícone', 'name' => 'Nome', 'position' => 'Posição', 'url' => 'URL', ], 'go' => [ 'actions' => [ 'confirm' => 'Tenho certeza', 'trust' => 'Não me pergunte novamente', ], 'description' => 'Este link irá levá-lo para :link. Tem certeza que quer ir para lá?', 'title' => 'Saindo de Kanka', ], 'helpers' => [ 'icon' => 'Você pode personalizar o ícone exibido para o link. Use qualquer um dos ícones gratuitos de :fontawesome ou deixe este campo em branco para o padrão. Encontre mais em nossa :docs.', 'parent' => 'Exiba este link rápido após um elemento da barra lateral, ao invés na seção de links rápidos da barra lateral.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Campanhas impulsionadas podem adicionar links a entidades que apontam para sites externos.', 'title' => 'Links para :name', ], 'unboosted' => [], 'update' => [ 'success' => 'Link :name atualizado para :entity.', 'title' => 'Atualizar link para :name', ], ]; ================================================ FILE: lang/pt-BR/entities/logs.php ================================================ [ 'create' => 'Criar', 'create_post' => 'Post ":post" criado', 'delete' => 'Remover', 'delete_post' => 'Post removido', 'reorder_post' => 'Posts reordenados', 'restore' => 'Restaurar', 'reveal' => 'Mostrar detalhes', 'update' => 'Atualizar', 'update_post' => 'Post ":post" atualizado', 'view' => 'Ver mudanças', ], 'call-to-action' => 'Os logs completos de alterações para até :amount dias estão disponíveis para campanhas superimpulsionadas.', 'fields' => [ 'action' => 'Ação', 'date' => 'Data', ], 'filters' => [ 'keywords' => 'Palavras-chave', ], 'impersonated' => 'Personificado por :name', 'none' => 'Nenhum', 'show' => [ 'title' => 'Histórico de :name', ], 'tooltips' => [ 'post' => 'Vá para o post', ], ]; ================================================ FILE: lang/pt-BR/entities/map-points.php ================================================ 'Esta entidade está fixada nos seguintes mapas.', 'title' => 'Pontos do Mapa :name', ]; ================================================ FILE: lang/pt-BR/entities/mentions.php ================================================ [ 'element' => 'Elemento', 'type' => 'Tipo', ], 'helper' => 'Esta entidade é mencionada nas seguintes outras entidades, posts ou descrição de campanha.', 'mentioned_in_v2' => 'Esta entidade é mencionada em :count elementos. :more.', 'see_more' => 'Ver detalhes', 'show' => [ 'title' => 'Menções da Entidade :name', ], 'title' => 'Entidade mencionada', ]; ================================================ FILE: lang/pt-BR/entities/move.php ================================================ [ 'copy' => 'Copiar', ], 'errors' => [ 'permission' => 'Você não tem permissão para criar entidades desse tipo na campanha alvo.', 'permission_update' => 'Você não tem permissão para mover essa entidade.', 'same_campaign' => 'Você precisa selecionar outra campanha para mover a entidade.', 'unknown_campaign' => 'Campanha desconhecida.', ], 'fields' => [ 'campaign' => 'Campanha alvo', 'copy' => 'Criar uma cópia na campanha alvo', 'select_one' => 'Selecionar uma campanha', ], 'helpers' => [ 'copy' => 'Crie uma cópia da entidade na campanha de destino.', ], 'panel' => [ 'description' => 'Selecione uma campanha que você deseja mover ou fazer uma cópia dessa entidade.', 'description_bulk_copy' => 'Selecione uma campanha para qual você deseja copiar as entidades selecionadas.', 'title' => 'Mover ou copiar uma entidade para outra campanha', ], 'success' => 'Entidade :name movida.', 'success_copy' => 'Entidade :entity copiada.', 'title' => 'Mover :name', 'warnings' => [ 'custom' => 'Esta entidade não é de um módulo padrão, mas sim de um tipo de entidade personalizado ":module". Ela será criada como uma entidade de Nota na campanha de destino.', ], ]; ================================================ FILE: lang/pt-BR/entities/notes.php ================================================ [ 'add' => 'Novo Post', 'add_role' => 'Adicionar cargo', 'add_user' => 'Adicionar usuário', ], 'collapsed' => [ 'closed' => 'Post recolhido no cabeçalho', 'open' => 'Post expandido', ], 'copy_mention' => [ 'copy' => 'Copiar menção avançada', 'copy_with_name' => 'Copiar menção avançada com o nome do post', 'success' => 'Menção avançada do post copiada para a área de transferência.', ], 'create' => [ 'success' => 'Post :name adicionado para :entity', ], 'destroy' => [ 'success' => 'Post :name de :entity removido.', ], 'edit' => [ 'success' => 'Post :name de :entity atualizado.', ], 'fields' => [ 'creator' => 'Criador', 'display' => 'Exibir', 'name' => 'Nome', 'position' => 'Posição', ], 'footer' => [ 'created' => 'Criado pelo :user em :date', 'updated' => 'Atualizado pelo :user em :date', ], 'hint' => 'As informações que não se enquadram nos campos padrão de uma entidade ou que devem ser mantidas em sigilo podem ser adicionadas como posts.', 'hints' => [ 'reorder' => 'Você pode reordenar os posts de uma entidade clicando no ícone :icon ao lado da visão geral no menu da entidade.', ], 'index' => [], 'move' => [ 'copy' => 'Criar uma cópia na entidade de destino', 'copy_success' => 'Post :name copiado com sucesso para :entity.', 'copy_title' => 'Mantenha uma cópia', 'description' => 'Selecione uma entidade para a qual você deseja mover ou fazer uma cópia deste post.', 'entity' => 'Entidade de destino', 'move_success' => 'Post :name movido com sucesso para :entity.', ], 'placeholders' => [ 'name' => 'Nome do post, observação ou comentário', ], 'show' => [ 'advanced' => 'Permissões Avançadas', 'title' => 'Post :name de :entity', ], 'states' => [ 'collapsed' => 'Recolhido', 'expanded' => 'Expandido', ], 'warning' => [], ]; ================================================ FILE: lang/pt-BR/entities/permissions.php ================================================ [ 'text' => 'Esta entidade está definida como privada. Permissões personalizadas ainda podem ser definidas, mas enquanto a entidade for privada, elas serão ignoradas e somente os membros do cargo de :admin da campanha poderão ver a entidade.', 'warning' => 'Aviso', ], 'quick' => [ 'empty-permissions' => 'Nenhum cargo ou usuário além dos administradores da campanha tem acesso a esta entidade.', 'manage' => 'Gerenciar Permissões', 'screen-reader' => 'Abrir as configurações de privacidade', 'success' => [ 'private' => ':entity está agora escondida.', 'public' => ':entity está agora visível.', ], 'title' => 'Visão geral da permissão', 'viewable-by' => 'Visível por', ], ]; ================================================ FILE: lang/pt-BR/entities/pins.php ================================================ 'Links', 'title' => 'Fixados', ]; ================================================ FILE: lang/pt-BR/entities/profile.php ================================================ [ 'edit_profile' => 'Editar perfil', ], 'history' => 'Histórico', 'show' => [ 'tab_name' => 'Perfil', 'title' => 'Perfil de :name', ], ]; ================================================ FILE: lang/pt-BR/entities/quests.php ================================================ 'Essa entidade faz parte das seguintes missões.', 'title' => 'Missões de :name', ]; ================================================ FILE: lang/pt-BR/entities/relations.php ================================================ [ 'mode-map' => 'Ferramenta de exploração das conexões', 'mode-table' => 'Tabela de conexões e elementos relacionados', ], 'bulk' => [ 'delete' => '{1} :count conexão removida. |[2,*] :count conexões removidas.', 'fields' => [ 'delete_mirrored' => 'Excluir espelhado', 'unmirror' => 'Desvincular espelhado', 'update_mirrored' => 'Atualizar espelhado', ], 'helpers' => [ 'delete_mirrored' => 'Exclua também as conexões espelhadas.', 'unmirror' => 'Desvincula conexões espelhadas.', 'update_mirrored' => 'Atualiza conexões espelhadas.', ], 'success' => [ 'editing' => '{1} :count conexão foi atualizada. |[2,*] :count conexões foram atualizadas.', 'editing_partial' => '{1} :count/:total conexão foi atualizada. |[2,*] :count/:total conexões foram atualizadas.', ], ], 'call-to-action' => 'Explore visualmente as conexões dessa entidade e como ela está conectada ao restante da campanha.', 'connections' => [ 'map_point' => 'Ponto do mapa', 'mention' => 'Menção', 'quest_element' => 'Elemento de missão', 'timeline_element' => 'Elemento de linha do tempo', ], 'create' => [ 'helper' => 'Crie uma conexão entre :name e uma ou várias entidades.', 'new_title' => 'Nova conexão', 'success_bulk' => '{1} Adicionada :count conexão a :entity.|[2,*] Adicionadas :count conexões a :entity.', ], 'delete_mirrored' => [ 'helper' => 'Essa conexão é espelhada na entidade alvo. Selecione essa opção para também remover a conexão espelhada.', 'option' => 'Remover conexão espelhada', ], 'destroy' => [ 'mirrored' => 'Isso também removerá a conexão espelhada e é permanente.', 'success' => 'Conexão de :target removida para :entity.', ], 'fields' => [ 'attitude' => 'Atitude', 'is_pinned' => 'Fixado', 'owner' => 'Fonte', 'target' => 'Entidade alvo', 'targets' => 'Entidades alvo', 'two_way' => 'Conexão espelhada', 'unmirror' => 'Desespelhe esta conexão.', ], 'filters' => [ 'connection' => 'Relação da conexão', 'name' => 'Conexão alvo', ], 'helper' => 'Estabeleça conexões entre entidades com atitudes e visibilidade. Conexões também podem ser fixadas no menu da entidade.', 'helpers' => [ 'description' => 'Detalhe a natureza da conexão entre as duas entidades.', 'no_relations' => 'Essa entidade atualmente não tem quaisquer outras conexões com outras entidades da campanha.', ], 'hints' => [ 'attitude' => 'Este campo opcional pode ser usado para definir a ordem padrão em que as conexões aparecem em ordem decrescente.', 'two_way' => 'Crie uma conexão no alvo selecionado e espelhe-os. Atualizar uma relação espelhada não atualiza a conexão original.', ], 'index' => [ 'title' => 'Conexões', ], 'options' => [ 'mentions' => 'Padrão + relacionados + menções', 'only_relations' => 'Apenas conexões diretas', 'related' => 'Padrão + relacionados', 'relations' => 'Padrão', 'show' => 'Mostrar', ], 'panels' => [ 'related' => 'Relacionados', ], 'placeholders' => [ 'attitude' => '-100 a 100, 100 sendo muito positiva', ], 'show' => [ 'title' => 'Conexões de :name', ], 'types' => [ 'family_member' => 'Membro da família', 'organisation_member' => 'Membro da Organização', ], 'update' => [ 'success' => 'Conexão :target atualizada para :entity.', 'title' => 'Atualizar conexões para :name', ], ]; ================================================ FILE: lang/pt-BR/entities/reminders.php ================================================ [ 'add' => 'Vincular a um calendário', 'remove' => 'Remover data do calendário', ], 'helpers' => [ 'pitch' => 'Deixe o calendário do mundo real para trás e vincule essa entidade a um calendário do seu mundo para permanecer imerso na sua linha do tempo fictícia.', ], ]; ================================================ FILE: lang/pt-BR/entities/story.php ================================================ [ 'collapse_all' => 'Recolher todos', 'expand_all' => 'Expandir todos', 'load_more' => 'Carregar mais', 'login_for_more' => 'Entre para ver mais posts', ], 'reorder' => [ 'helper' => 'Arraste e solte os posts para reordená-los na página de visão geral da entidade.', 'icon_tooltip' => 'Reordenar posts', 'panel_title' => 'Reordenar posts', 'save' => 'Salvar nova ordem', 'success' => 'Posts reordenados.', ], 'update' => [ 'title' => 'Atualizar introdução da :entity', ], 'warning' => [], ]; ================================================ FILE: lang/pt-BR/entities/tags.php ================================================ [ 'helper' => 'Adicione ou remova tags em :name.', 'title' => 'Adicionar ou remover tags', ], ]; ================================================ FILE: lang/pt-BR/entities/timelines.php ================================================ 'As linhas do tempo que possuem elementos vinculados a esta entidade são mostradas abaixo.', 'show' => [ 'title' => 'Linhas do Tempo de :name', ], ]; ================================================ FILE: lang/pt-BR/entities/transform.php ================================================ [], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Tipo de entidade desconhecida ou inválida.', ], 'success' => '{1} :count entidade transformada ao novo tipo: :type.|[2,*] :count entidades transformadas ao novo tipo: :type', ], 'fields' => [ 'current' => 'Módulo atual', 'select_one' => 'Selecione um', 'target' => 'Novo tipo de entidade', ], 'panel' => [ 'bulk_description' => 'Altere o tipo de entidade de várias entidades. Esteja ciente de que alguns dados podem ser perdidos devido aos diferentes campos entre os tipos de entidade.', 'bulk_title' => 'Transformar entidades em massa', 'title' => 'Transformar uma entidade', ], 'success' => 'Entidade :name transformada.', 'title' => 'Transformar :name', ]; ================================================ FILE: lang/pt-BR/entities.php ================================================ 'Habilidades', 'ability' => 'Habilidade', 'attribute_template' => 'Modelo de Atributo', 'attribute_templates' => 'Modelos de Atributo', 'bookmark' => 'Favorito', 'bookmarks' => 'Favoritos', 'calendar' => 'Calendário', 'calendars' => 'Calendários', 'campaign' => 'Campanha', 'campaigns' => 'Campanhas', 'character' => 'Personagem', 'characters' => 'Personagens', 'conversation' => 'Diálogo', 'conversations' => 'Diálogos', 'creator' => [ 'actions' => [ 'create' => 'Criar :type', 'full' => 'Acesse o formulário completo', 'more' => 'Adicionar mais detalhes', ], 'back' => 'Voltar à seleção', 'bulk_names' => 'Adicione um nome por linha', 'duplicate' => 'Atenção! Já existem outras entidades deste tipo com um nome semelhante.', 'helper_v2' => 'Crie rapidamente a base de uma nova entidade sem interromper seu fluxo atual.', 'missing_v2' => 'Apenas os módulos habilitados e que você tem permissão para criar estão disponíveis nesta interface. :learn-more.', 'modes' => [ 'bulk' => 'Adição em massa', 'default' => 'Adição rápida', ], 'name' => [ 'new' => 'Novo nome', 'remove' => 'Remover', ], 'success_multiple' => '{1} Nova entidade :link criada.|[2,*] Novas entidades :link criadas.', 'success_multiple_posts' => '{1} Novo post :link criado.|[2,*] Novos posts: link criados.', 'title' => 'Nova Entidade', 'titles' => [ 'everything' => 'Tudo', 'quick-access' => 'Acesso rápido', ], 'tooltip' => 'Crie uma nova entidade sem deixar a página atual.', 'tooltips' => [ 'create' => 'Crie a entidade e volte para a tela de seleção de entidade', 'create_more' => 'Crie a entidade e comece a criar outra do mesmo tipo', 'edit' => 'Crie a entidade e comece a editá-la', ], ], 'creature' => 'Criatura', 'creatures' => 'Criaturas', 'dice_roll' => 'Rolagem de Dado', 'dice_rolls' => 'Rolagem de Dados', 'event' => 'Evento', 'events' => 'Eventos', 'families' => 'Famílias', 'family' => 'Família', 'inventories' => 'Inventários', 'item' => 'Item', 'items' => 'Itens', 'journal' => 'Diário', 'journals' => 'Diários', 'location' => 'Local', 'locations' => 'Locais', 'map' => 'Mapa', 'maps' => 'Mapas', 'new' => [], 'note' => 'Nota', 'notes' => 'Notas', 'organisation' => 'Organização', 'organisations' => 'Organizações', 'quest' => 'Missão', 'quest_element' => 'Elemento da missão', 'quests' => 'Missões', 'race' => 'Raça', 'races' => 'Raças', 'relation' => 'Relação', 'relations' => 'Relações', 'reminders' => 'Lembretes', 'tag' => 'Tag', 'tags' => 'Tags', 'templates' => 'Modelos', 'timeline' => 'Linha do Tempo', 'timeline_element' => 'Elemento da linha do tempo', 'timelines' => 'Linhas do Tempo', ]; ================================================ FILE: lang/pt-BR/entries/tabs.php ================================================ 'Pseudônimos', 'identity' => 'Identidade', 'media' => 'Mídia', 'properties' => 'Propriedades', 'relations' => 'Relações', ]; ================================================ FILE: lang/pt-BR/errors.php ================================================ [ 'body' => 'Parece que você não tem permissão para acessar esta páginaQ', 'title' => 'Acesso Negado.', ], '403-form' => [ 'help' => 'Isso pode ser devido sua sessão ter atingido o tempo limite. Tente fazer login novamente em outra janela antes de salvar.', ], '404' => [ 'body' => 'Desculpe, a página que você está procurando não pode ser encotrada', 'title' => 'Página Não Encontrada', ], '500' => [ 'body' => [ '1' => 'Eita, parece que algo deu errado!', '2' => 'Um relatório com o erro encontrado foi enviado para nós, mas às vezes ajuda se pudermos saber um pouco mais sobre o que você estava fazendo.', ], 'title' => 'Erro', ], '503' => [ 'body' => [ '1' => 'Kanka está em manutenção, o que geralmente significa que uma nova versão está a caminho!', '2' => 'Desculpe pela inconveniência. Tudo voltará ao normal em apenas alguns minutos.', ], 'json' => 'Kanka está atualmente sob manutenção, por favor tente novamente dentro de alguns minutos.', 'title' => 'Em manutenção', ], '503-form' => [], 'back-to-campaigns' => 'Voltar para uma de suas campanhas', 'footer' => 'Se precisar de mais ajuda, entre em contato conosco em :email ou no :discord', 'log-in' => 'Fazer login na sua conta pode revelar o que você está procurando.', 'post_layout' => 'Layout de post inválido.', 'private-campaign' => [ 'auth' => [ 'helper' => 'Você não tem acesso a esta campanha.', ], 'guest' => [ 'helper' => 'A campanha que você está tentando acessar é privada e você não está conectado.', 'login' => 'Fazer login pode permitir que você acesse o conteúdo.', ], 'title' => 'Campanha privada', ], ]; ================================================ FILE: lang/pt-BR/events.php ================================================ [ 'title' => 'Novo Evento', ], 'destroy' => [], 'edit' => [], 'events' => [ 'helper' => 'Eventos que tem essa entidade como seu evento primário são exibidos aqui.', ], 'fields' => [ 'date' => 'Data', ], 'helpers' => [ 'date' => 'Este campo pode conter qualquer coisa e não está vinculado aos calendários da campanha. Para vincular este evento a um calendário, adicione-o no calendário ou no sub-menu de lembretes deste evento.', ], 'index' => [], 'placeholders' => [ 'date' => 'Uma data para o seu evento', 'type' => 'Cerimonia, Festival, Desastre, Batalha, Nascimento', ], 'show' => [], 'tabs' => [ 'calendars' => 'Introduções de Calendário', ], ]; ================================================ FILE: lang/pt-BR/families/trees.php ================================================ [ 'clear' => 'Apaga tudo', 'first' => 'Adiconar um fundador', 'founder' => 'Adicionar um novo fundador', 'rename-relation' => 'Renomear relação', 'reset' => 'Descartar mudanças', 'save' => 'Salvar', ], 'modal' => [ 'first-title' => 'Selecione uma entidade', 'helper' => 'Substitua a entidade por outra da campanha', 'relation' => 'Relação', 'title' => 'Substituir entidade', ], 'modals' => [ 'clear' => [ 'confirm' => 'Tem certeza de que deseja reinicializar todos os dados da árvore genealógica?', ], 'entity' => [ 'add' => [ 'founder' => 'Fundador', 'member' => 'Membro', 'success' => 'Entidade adicionada.', 'title' => 'Adicionar uma entidade', ], 'child' => [ 'success' => 'Filho adicionado.', 'title' => 'Adicionar um filho', ], 'edit' => [ 'helper' => 'Marque esta opção se a relação for desconhecida. Um personagem pode ser adicionado mais tarde.', 'success' => 'Entidade atualizada.', 'title' => 'Atualizar uma entidade', ], 'founder' => [ 'title' => 'Adicionar um novo fundador', ], 'remove' => [ 'confirm' => 'Tem certeza que deseja remover essa entidade da árvore genealógica?', 'success' => 'Entidade removida.', ], ], 'relations' => [ 'add' => [ 'success' => 'Relação adicionada.', 'title' => 'Adicionar uma relação', ], 'edit' => [ 'success' => 'Relação atualizada.', 'title' => 'Atualizar uma relação', ], 'unknown' => 'Desconhecida', ], 'reset' => [ 'confirm' => 'Tem certeza que deseja descartar quaisquer mudanças feitas na árvore genealógica?', ], ], 'pitch' => 'Crie uma árvore genealógica detalhada para as famílias da campanha.', 'success' => [ 'cleared' => 'Árvore genealógica apagada.', 'reseted' => 'Árvore genealógica foi redefinida.', 'saved' => 'Árvore genealógica salva.', ], 'title' => 'Árvore Genealógica :name', 'unknown' => 'não estabelecido', ]; ================================================ FILE: lang/pt-BR/families.php ================================================ [ 'title' => 'Nova Família', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Essa família está extinta.', 'members' => 'Os membros de uma família estão listados aqui. Um personagem pode ser adicionado a uma família editando o personagem desejado e usando a lista suspensa "Família".', ], 'index' => [], 'members' => [ 'create' => [ 'helper' => 'Adicione um ou vários membros a :name.', 'success' => '{0} Nenhum membro foi adicionado.|{1} 1 membro foi adicionado.|[2,*] :count membros foram adicionados.', 'title' => 'Novos Membros', ], ], 'placeholders' => [ 'name' => 'Nome da família', 'type' => 'Realeza, Nobres, Extinta', ], 'show' => [ 'tabs' => [ 'tree' => 'Árvore genealógica', ], ], ]; ================================================ FILE: lang/pt-BR/faq.php ================================================ [ 'account_settings' => 'Configurações da conta', 'answer' => 'Para excluir sua conta, vá para a página da sua :account e role para baixo até a seção de exclusão da conta. Isso excluirá sua conta e todas as campanhas nas quais você é o único membro.', 'question' => 'Como posso deletar a minha conta?', ], 'app_backup' => [ 'answer' => 'Fazemos dois backups por dia para evitar qualquer perda de dados. Nossas próprias campanhas estão no servidor, então não queremos correr riscos!', 'question' => 'Com que frequência é feito backup dos dados em Kanka?', ], 'attribute-templates' => [ 'answer' => <<<'TEXT' A melhor maneira de explicar os Modelos de Atributo é com um exemplo. Vamos imaginar que seu mundo tem muitos locais e, em muitos desses locais, você deseja se lembrar de criar um atributo personalizado para "População", "Clima", "Nível de crime". Agora, você poderia fazer isso facilmente em todos os locais, mas pode se tornar tedioso e às vezes você pode se esquecer de criar o atributo "Nível de crime". É aqui que os Modelos de Atributo entram em jogo. Você pode criar um Modelo de Atributo com esses atributos (População, Clima, Nível de Crime, etc) e, posteriormente, aplicar esse modelo às suas localizações. Isso criará os atributos do modelo nos locais, então tudo que você precisa fazer é alterar os valores e não precisa se lembrar dos atributos! TEXT , 'question' => 'Modelos de Atributo, o que são eles?', ], 'backup' => [ 'answer' => 'Uma vez por dia, você pode exportar todos os dados de sua campanha como um arquivo ZIP. No aplicativo, clique em "Campanha" no menu à esquerda e clique em "Exportar". Isso criará uma exportação que ficará disponível por 30 minutos. Você não pode fazer upload desta exportação para Kanka; ela é destinada apenas para sua própria tranquilidade ou se você não planeja mais usar o aplicativo.', 'question' => 'Como posso fazer backup ou exportar minha campanha?', ], 'bugs' => [ 'answer' => 'Basta entrar em nosso servidor do :discord e relatar seu bug no canal #error-and-bugs.', 'question' => 'Como posso relatar um bug?', ], 'campaign-sync' => [ 'answer' => 'Kanka não tem esse recurso. No entanto, se você está tentando ter vários grupos de jogo no mesmo mundo, considere usar a mesma campanha e separar seus grupos por meio de uma combinação de missões, tags e permissões', 'question' => 'Posso sincronizar entidades em várias campanhas?', ], 'custom' => [ 'answer' => 'Kanka vem com um conjunto de tipos de entidades predefinidas que interagem entre si. Permitir tipos de entidade personalizados exigiria reconstruir o aplicativo do zero e anular o propósito de uma ferramenta com tipos predefinidos para ajudar as pessoas a construir um mundo em vez de descobrir como organizar as coisas. Além disso, Kanka é flexível com tags que podem representar a maioria dos cenários de tipo de entidade personalizado.', 'question' => 'Posso criar tipos de entidade personalizados?', ], 'delete-campaign' => [ 'answer' => 'Vá para o painel de sua campanha e clique em "Campanha" no menu à esquerda. Um botão "Excluir" campanha aparecerá se você for o último membro da campanha. Excluir uma campanha é uma ação permanente que irá excluir todos os dados armazenados em nossos servidores, incluindo imagens.', 'question' => 'Como posso deletar uma campanha?', ], 'discord' => [ 'answer' => 'Para vincular sua conta Kanka ao :discord, primeiro você precisa clicar em seu avatar no canto superior direito do aplicativo e clicar no botão Perfil. A partir daí, navegue até a subpágina de aplicativos e clique em Conectar.', 'question' => 'Como posso linkar minha conta do Kanka com o Discord?', ], 'early-access' => [ 'answer' => 'O Acesso Antecipado é uma forma de recompensar nossos incríveis assinantes, dando-lhes um período exclusivo de 30 dias, onde podem experimentar os módulos mais recentes antes de qualquer outra pessoa.', 'question' => 'O que é Acesso Antecipado?', ], 'entity-notes' => [ 'answer' => 'Todas as entidades têm uma guia \'Notas de Entidade\' que são pequenos trechos de texto que podem ser configurados para serem visíveis apenas por você (ótimo para campanhas com mais de um Mestre), apenas para membros da com o cargo de administrador ou visíveis para todos. Você também pode dar a seus jogadores permissão para criar e editar notas de entidade em entidades sem ter que permitir que eles editem uma entidade inteira.', 'question' => 'Como Kanka lida com informações parcialmente ocultas?', ], 'fields' => [ 'answer' => 'Resposta', 'category' => 'Categoria', 'locale' => 'Localidade', 'order' => 'Ordem', 'question' => 'Questão', ], 'free' => [ 'answer' => <<<'TEXT' Sim! Acreditamos fortemente que sua situação financeira não deve afetar sua diversão com RPGs ou construção de mundos e sempre manteremos o aplicativo principal gratuito. No entanto, se você deseja ter um papel mais ativo nesta jornada, nos apoiar e votar nos recursos que mais lhe importam, você pode fazê-lo por meio de nossas assinaturas. Além de votar na direção que Kanka toma, nos apoiar permite que você ganhe acesso a :boosters, aumentar o limite do tamanho de upload de arquivos, adicionar seu nome ao Hall da Fama, ter ícones padrão mais legais e muito mais! TEXT , 'question' => 'O aplicativo vai continuar sendo gratuito?', ], 'gods-and-religions' => [ 'answer' => 'Recomendamos a criação de Deuses como Personagens e a criação de religiões como Organizações. Se você deseja encontrar rapidamente suas divindades, recomendamos marcá-las com uma tag e/ou tipo apropriado.', 'question' => 'Onde criar Deuses e Religiões?', ], 'help' => [ 'answer' => 'Em primeiro lugar, obrigado por querer ajudar! Estamos sempre interessados em pessoas que possam ajudar com traduções, que possam testar novos recursos ou que possam ajudar novos usuários. Também adoramos quando as pessoas promovem o Kanka para alcançar novos usuários em lugares que não tínhamos pensado. Seu melhor curso de ação é juntar-se a nós no :discord, onde há um canal dedicado a ajudar.', 'question' => 'Como posso ajudar?', ], 'map' => [ 'answer' => 'O módulo de Mapas suporta imagens PNG, JPG e SVG. Esses mapas podem ter camadas, grupos e marcadores apontando de várias formas e tamanhos que apontam para outras entidades em uma campanha.', 'question' => 'Posso fazer upload de mapas no Kanka?', ], 'mobile' => [ 'answer' => 'Atualmente, não há um aplicativo para celular do Kanka, mas a maior parte do aplicativo funciona em um dispositivo móvel. Esperamos que o suporte por meio de assinaturas nos permita pagar alguém para construir um aplicativo móvel um dia, mas não prevemos isso no futuro próximo.', 'question' => 'Há um aplicativo para celular? Há algum planejado?', ], 'monsters' => [ 'answer' => 'Recomendamos usar o módulo Raças para povos, espécies, monstros e qualquer coisa viva que não seja um personagem.', 'question' => 'Onde criar monstros?', ], 'multiworld' => [ 'answer' => 'Você pode fazer parte de quantas campanhas quiser, incluindo as que criou. Para mudar ou criar uma nova campanha, vá para o painel de sua campanha e no canto superior direito você pode clicar em sua campanha atual para exibir a interface do alternador de campanha.', 'question' => 'Posso ter mais de uma campanha?', ], 'nested' => [ 'answer' => 'Se você preferir visualizar suas entidades em uma visualização aninhada por padrão (por exemplo, o botão Visualização aninhada na lista de locais), você pode fazer isso acessando as opções de Perfil e Layout. Lá você pode marcar a opção Visualização aninhada. Isso é apenas para sua conta e não para suas campanhas.', 'question' => 'Posso definir as listas a serem aninhadas por padrão?', ], 'permissions' => [ 'answer' => 'Com certeza, é por isso que construímos o Kanka! Você pode convidar todos os seus jogadores para suas campanhas e dar-lhes funções e permissões. Construímos o sistema para ser extremamente flexível (você pode usar uma configuração opt-in e opt-out) para cobrir o máximo de necessidades e situações possíveis.', 'question' => 'Posso limitar as informações que meus jogadores veem em minha campanha?', ], 'plans' => [ 'answer' => <<<'TEXT' O plano de longo prazo para Kanka é construir uma ferramenta versátil de construção de mundos e gerenciamento de campanha que seja independente do sistema com conteúdo gerenciado pela comunidade na forma de "Modelos de Comunidade". Outro objetivo nosso é construir ferramentas que se integrem com outras plataformas, como aplicativos de mesa virtuais. Nós mesmos usamos o Kanka, então temos o plano de jamais parar de desenvolvê-lo e aprimorá-lo. No entanto, apenas por segurança, o projeto também é de código aberto e pode ser continuado pela comunidade se algo acontecer conosco. TEXT , 'question' => 'Quais são os planos de longo prazo?', ], 'public-campaigns' => [ 'answer' => 'Você pode navegar na página :public-campaigns para ver como outras pessoas usam o Kanka em suas campanhas.', 'question' => 'Como outras pessoas usam o Kanka?', ], 'renaming-modules' => [ 'answer' => 'Embora isso seja fácil de fazer para o inglês e outros idiomas que não usam nomes de gênero, ser capaz de alterar o nome dos módulos quebraria a correção gramatical e a experiência do usuário para a maioria dos idiomas em que o Kanka também está disponível.', 'question' => 'Posso renomear módulos? Por exemplo, famílias para clãs ou organizações para facções?', ], 'sections' => [ 'community' => 'Comunidade', 'general' => 'Geral', 'other' => 'Outros', 'permissions' => 'Permissões', 'pricing' => 'preços', 'worldbuilding' => 'Construção de Mundo', ], 'show' => [ 'return' => 'Voltar pas Perguntas Frequentes', 'timestamp' => 'Atualizado pela última vez em :date', 'title' => 'Perguntas Frequentes :name', ], 'unboost' => [ 'answer' => 'Deixar de impulsionar uma campanha não exclui nenhum dos dados que foram criados enquanto ela era impulsionada, mas simplesmente oculta as informações e recursos. Se você impulsionar a campanha novamente, as informações e recursos voltarão a estar disponíveis com a mesma configuração de antes do impulso ser removido.', 'question' => 'O que acontece se a campanha não for mais impulsionada?', ], 'user-switch' => [ 'answer' => 'As permissões podem ser complicadas, especialmente com grandes campanhas. Como administrador da campanha, você pode navegar até a página dos membros da campanha e clicar no botão "Alternar" que aparecerá ao lado dos membros não administradores da campanha. Isso fará com que você se conecte como esse usuário e permitirá que você veja a campanha como eles o veriam. Esta é a maneira mais fácil de verificar as permissões de sua campanha.', 'question' => 'Minhas permissões de campanha estão configuradas, como posso testá-las?', ], 'visibility' => [ 'answer' => 'Apenas as pessoas que você convida para sua campanha podem ver e interagir com o que você criou. Seus dados são privados e sempre sob seu controle. Você também pode definir sua campanha como pública para permitir que usuários não registrados a visualizem.', 'question' => 'Alguém pode ver meu mundo?', ], ]; ================================================ FILE: lang/pt-BR/fields.php ================================================ [ 'placeholder' => 'Escolha uma imagem da galeria da campanha', ], 'gallery-header' => [ 'description' => 'Se a entidade não tiver uma imagem de cabeçalho, exiba uma imagem da galeria da campanha.', ], 'gallery-image' => [ 'description' => 'Se a entidade não tiver imagem, exiba uma imagem da galeria da campanha.', ], 'header-image' => [ 'boosted-description' => 'Exiba uma imagem de fundo no cabeçalho da entidade com um :boosted-campaign.', 'description' => 'Exiba uma imagem de fundo no cabeçalho da entidade. Para melhores resultados, use uma imagem muito grande.', 'title' => 'Imagem de Cabeçalho', ], 'tooltip' => [], ]; ================================================ FILE: lang/pt-BR/filters.php ================================================ [ 'bookmark' => 'Marcador', ], 'alerts' => [ 'copy' => 'Os filtros foram copiados para sua área de transferência.', ], 'bookmark' => [ 'helper' => 'Crie um novo marcador para esta visualização usando os filtros atuais.', 'name' => ':module (filtrado)', 'premium' => 'Adicionar mais marcadores requer a ativação de recursos premium da campanha.', 'success' => 'Marcador criado.', ], 'helpers' => [ 'guest' => 'Por favor entre em sua conta para filtrar resultados.', 'icon' => 'Dê a este marcador um ícone :fontawesome especial, por exemplo :example.', 'icon-premium' => 'Dê a este marcador um ícone especial :fontawesome, como :example, com um :premium.', ], ]; ================================================ FILE: lang/pt-BR/footer.php ================================================ 'Sobre nós', 'blog' => 'Blog', 'boosters' => 'Impulsionadores', 'community' => 'Comunidade', 'company' => 'Companhia', 'contact' => 'Contato', 'copyright' => 'Direitos autorais :copy :year Owlchester SNC', 'documentation' => 'Documentação', 'features' => 'Recursos', 'kb' => 'Base de conhecimento', 'language-switcher' => [ 'other' => 'Outros idiomas', 'title' => 'Selecione seu idioma', ], 'made' => 'Feito com ❤️ em Genebra, Suíça', 'newsletter' => 'Informativos', 'platform' => 'Plataforma', 'plugins' => 'Biblioteca de plugins', 'premium' => 'Campanhas premium', 'press-kit' => 'Kit de imprensa', 'pricing' => 'Preços', 'privacy' => 'Privacidade', 'public-campaigns' => 'Campanhas públicas', 'resources' => 'Recursos', 'roadmap' => 'Roadmap', 'security' => 'Segurança', 'server-time' => 'Este é o horário do nosso servidor (:server)', 'status' => 'Status do serviço', 'terms' => 'Termos', 'thanks' => 'Só é possível graças aos nossos assinantes.', 'translator_call' => 'Kanka é traduzido em outros idiomas graças à nossa incrível comunidade. Se você quiser ajudar a traduzir Kanka para o seu idioma, entre em contato conosco no :discord!', 'whats-new' => 'Novidades', ]; ================================================ FILE: lang/pt-BR/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Votos da Comunidade', ]; ================================================ FILE: lang/pt-BR/front/hall-of-fame.php ================================================ 'Salão da Fama', ]; ================================================ FILE: lang/pt-BR/front/kb.php ================================================ [], 'show' => [], 'title' => 'Base de conhecimento', ]; ================================================ FILE: lang/pt-BR/front/newsletter.php ================================================ [ 'learn_more' => 'Saiba mais', 'subscribe' => 'Assinar', ], 'fields' => [ 'firstname' => 'Nome', 'lastname' => 'Sobrenome', 'notifications' => 'Notificações', ], 'groups' => [ 'all' => 'Receba atualizações ocasionais sobre novos recursos, votos da comunidade, promoções e eventos.', 'newsletter' => 'Newsletter', ], 'headline' => 'Assine uma ou todas nossas newsletter para se manter atualizado sobre o Kanka.', 'title' => 'Atualizações pelo E-mail', ]; ================================================ FILE: lang/pt-BR/front.php ================================================ [], 'actions' => [], 'campaigns' => [ 'public' => [ 'filters' => [ 'is-premium' => 'Esta é uma campanha premium!', ], ], ], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => 'Entendido!', 'link' => 'Saiba mais', 'message' => 'Este site usa cookies para garantir que você obtenha a melhor experiência em nosso site.', ], 'faq' => [], 'featured_campaigns' => [], 'features' => [ 'api' => [ 'link' => 'Documentos da API', ], 'patreon' => [ 'api_calls' => 'Aumento de requisições de API (90 por minuto)', 'boosts' => 'Impulsos de Campanha', 'default_image' => 'Imagens padrão mais agradáveis para entidades em listas', 'discord' => 'Canal privado no :discord', 'free' => 'Grátis', 'hall_of_fame' => 'Nome no :link', 'impact' => 'Alto impacto de recursos futuros (através do Discord)', 'monthly_vote' => 'Participe das votações da comunidade', 'pagination' => 'Resultados de paginação aumentados', 'upload_limit' => 'Tamanho de upload', 'upload_limit_map' => 'Tamanho de upload de mapas', ], ], 'first_block' => [], 'footer' => [], 'goodbye' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Você é um mestre do jogo, criador de mundos ou um contador de histórias? Oferecemos um gerenciador de campanha de mesa e uma ferramenta de criação de mundo que facilita a organização, o planejamento e o aproveitamento de suas campanhas TTRPG. Somos movidos pela comunidade e, o melhor de tudo, nossos principais recursos são gratuitos!', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Dashboard', 'login' => 'Conecte-se', 'register' => 'Registre-se', 'register_free' => 'Registre-se gratuitamente', ], 'meta' => [ 'description' => ':kanka é um construtor de mundo digital flexível e gerencimanto de campanha de RPG de mesa online', 'title' => ':kanka - Gerenciador de campanha de RPG de mesa online e ferramenta de construção de mundo', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Grátis', 'month' => 'mês', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Construção de mundo, RPG de mesa, gerenciamento de campanha de RPG', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/pt-BR/gallery.php ================================================ [ 'gallery' => 'Da galeria', 'url' => 'Carregar uma imagem de uma URL', ], 'browse' => [ 'layouts' => [ 'large' => 'Pré-visualizações grandes', 'small' => 'Pré-visualizações pequenas', ], 'search' => [ 'placeholder' => 'Procurar uma imagem na galeria', ], 'title' => 'Galeria', 'unauthorized' => 'Nenhum de seus cargos tem a permissão "navegar pela galeria".', ], 'cta' => [ 'action' => 'Desbloqueie mais espaço de armazenamento', 'helper' => 'Desbloqueie até :size GB de espaço de armazenamento com uma :premium-campaign.', 'title' => 'Armazenamento cheio', ], 'delete' => [ 'success' => '[0] Excluído 0 elementos|[1] Excluído um elemento|{2,*} Excluídos :count elementos', ], 'download' => [ 'errors' => [ 'copy_failed' => 'Nossos servidores não conseguiram baixar a imagem fornecida.', 'gallery_full_free' => 'O espaço de armazenamento da galeria está cheio. Habilite recursos premium para mais armazenamento.', 'gallery_full_premium' => 'O espaço de armazenamento da galeria está cheio. Remova os arquivos não utilizados primeiro.', 'invalid_format' => 'O arquivo não está num formato válido.', 'too_big' => 'O arquivo é muito grande.', 'unauthorized' => 'Nenhum dos seus cargos tem a permissão "carregar para a galeria".', ], ], 'file' => [ 'saved' => 'Salvo', ], 'filters' => [ 'only_unused' => 'Somente mostrar arquivos não salvos', 'sort' => 'Ordenar por', ], 'move' => [ 'success' => '[0] Movido 0 elementos|[1] Movido um elemento|{2,*} Movidos :count elementos', ], 'update' => [ 'home' => 'Pasta inicial', 'success' => '[0] Atualizado 0 elementos|[1] Atualizado um elemento|{2,*} Atualizados :count elementos', ], ]; ================================================ FILE: lang/pt-BR/general.php ================================================ 'Desmarcar Todos', 'documentation' => 'Saiba mais sobre esse recurso em nossa documentação', 'done' => 'Feito', 'learn-more' => 'Saiba mais', 'no' => 'Não', 'required' => 'Obrigatório', 'select_all' => 'Selecionar Todos', 'success' => [ 'created' => ':name criado.', 'deleted' => ':name removido.', 'deleted-cancel' => ':name removido. :cancel', 'updated' => ':name atualizado.', ], 'tutorial' => 'Assistir tutorial', 'yes' => 'Sim', ]; ================================================ FILE: lang/pt-BR/genres.php ================================================ 'História alternativa', 'cyberpunk' => 'Cyberpunk', 'fantasy' => 'Fantasia', 'historical' => 'Histórico', 'many_worlds' => 'Muitos mundos', 'modern' => 'Moderno', 'occult' => 'Oculto', 'post_apocalyptic' => 'Pós-apocalíptico', 'pulp' => 'Pulp', 'science_fantasy' => 'Fantasia científica', 'science_fiction' => 'Ficção científica', 'space_opera' => 'Ópera espacial', 'steampunk' => 'Steampunk', 'superhero' => 'Super-herói', 'urban_fantasy' => 'Fantasia urbana', 'western' => 'Velho-oeste', ]; ================================================ FILE: lang/pt-BR/header.php ================================================ [ 'title' => 'Novidades do Kanka', ], 'notifications' => [ 'dismiss' => 'Dispensar', 'no-unread' => 'Nenhuma notificação não lida', 'read_all' => 'Ver tudo', ], 'qq' => [ 'tooltip' => 'Criar uma entidade ou artigo', ], 'toggle_navigation' => 'Alterar navegação', 'user' => [ 'settings' => 'Configurações', 'sign-out' => 'Sair', 'upgrade' => 'Aprimorar', 'your-profile' => 'Seu perfil', ], ]; ================================================ FILE: lang/pt-BR/helpers.php ================================================ [], 'api-filters' => [ 'description' => 'Os seguintes filtros estão disponíveis para o endpoint :name da API.', 'title' => 'Filtros da API', ], 'attributes' => [ 'link' => 'Opções de atributo', ], 'calendar-widget' => [ 'info' => 'Por que esses lembretes estão sendo exibidos?', 'title' => 'Widget do Calendário', ], 'dice' => [], 'entity_templates' => [], 'filters' => [ 'title' => 'Como usar filtros', ], 'link' => [ 'description' => 'Você pode facilmente criar um link para outras entidades quando estiver criando ou editando personagens, locais e etc. Apenas escreva os seguintes códigos com o nome da entidade que você gostaria de vincular.', ], 'map' => [], 'pins' => [], 'public' => 'Assista a um tutorial em vídeo no Youtube explicando as campanhas públicas.', 'troubleshooting' => [ 'description' => 'Um membro da equipe de Kanka enviou você para esta página. Selecione uma campanha no menu suspenso para gerar um token para que possamos ingressar temporariamente em sua campanha como um administrador.', 'errors' => [ 'token_exists' => 'Um token já existe para :campaign.', ], 'save_btn' => 'Gerar token', 'select_campaign' => 'Selecionar uma campanha', 'subtitle' => 'Por favor, envie ajuda!', 'success' => 'Copie o seguinte token e envie-o para alguém da equipe de Kanka.', 'title' => 'Solução de Problemas', ], 'widget-filters' => [ 'description' => 'Você pode filtrar entidades exibidas no widget modificado recentemente, fornecendo uma lista de campos da entidade e valores. Por exemplo, você pode usar :example para filtrar personagens mortos do tipo PdM.', 'link' => 'filtros de widget', 'title' => 'Filtros dos Widgets do Dashboard', ], ]; ================================================ FILE: lang/pt-BR/history.php ================================================ [ 'show-old' => 'Alterações', ], 'cta' => 'Exiba um log de todas as alterações recentes na campanha.', 'empty' => 'Vazio', 'fields' => [ 'action' => 'Ação', 'category' => 'Categoria', 'details' => 'Detalhes', 'when' => 'Quando', 'who' => 'Quem', ], 'filters' => [ 'all-actions' => 'Todas as ações', 'all-users' => 'Todos os membros', 'no-results' => 'Nenhum resultado para exibir. Tente com outros filtros ou volte depois de fazer alterações nas entidades da campanha.', ], 'helpers' => [ 'base' => 'Essa interface contém alterações recentes nas entidades da campanha por até :amount meses, mostrando as alterações mais recentes primeiro.', 'changes' => 'Os campos a seguir tinham anteriormente esses valores.', ], 'log' => [ 'create' => ':user criou :entity', 'create_post' => ':user criou o post ":post" em :entity', 'delete' => ':user removeu :entity', 'delete_post' => ':user removeu um post em :entity', 'reorder_post' => ':user reordenou os posts de :entity', 'restore' => ':user restaurou :entity', 'update' => ':user atualizou :entity', 'update_post' => ':user atualizou o post ":post" em :entity', 'update_tree' => ':user atualizou a árvore genealógica de :entity', ], 'title' => 'Histórico', 'unknown' => [ 'entity' => 'uma entidade desconhecida', ], ]; ================================================ FILE: lang/pt-BR/items.php ================================================ [ 'creators' => [ 'action' => 'Ação para criadores', 'remove' => 'Remover todos os criadores', ], ], 'create' => [ 'title' => 'Novo Objeto', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'creators' => 'Criadores', 'is_equipped' => 'Equipado', 'price' => 'Preço', 'size' => 'Tamanho', 'weight' => 'Peso', ], 'helpers' => [], 'hints' => [], 'index' => [], 'inventories' => [], 'lists' => [ 'empty' => 'Adicione armas, artefatos ou itens importantes ao seu mundo.', ], 'placeholders' => [ 'price' => 'Preço do objeto', 'size' => 'Tamanho, Peso, Dimensões', 'type' => 'Arma, Poção, Artefato', 'weight'=> 'Peso do objeto', ], 'show' => [ 'tabs' => [ 'inventories' => 'Inventários', ], ], ]; ================================================ FILE: lang/pt-BR/journals.php ================================================ [ 'title' => 'Novo Diário', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Autor', 'date' => 'Data', ], 'helpers' => [], 'index' => [], 'journals' => [], 'lists' => [ 'empty' => 'Crie entidades de diário para registrar aventuras, pensamentos dos personagens ou resumos e preparativos das sessões.', ], 'placeholders' => [ 'author' => 'Quem escreveu o diário', 'date' => 'Data do mundo real do diário', 'type' => 'Sessão, One Shot, Rascunho', ], 'show' => [], ]; ================================================ FILE: lang/pt-BR/languages.php ================================================ [ 'ca' => 'Catalão', 'cs' => 'Tcheco', 'de' => 'Alemão', 'el' => 'Grego', 'en' => 'Inglês', 'en-US' => 'Inglês Americano', 'es' => 'Espanhol', 'fr' => 'Francês', 'gl' => 'Galego', 'he' => 'Hebraico', 'hr' => 'Croata', 'hu' => 'Húngaro', 'it' => 'Italiano', 'nb' => 'Norueguês (Bokmal)', 'nl' => 'Holandês', 'pl' => 'Polonês', 'pt-BR' => 'Português do Brasil', 'ru' => 'Russo', 'sk' => 'Eslovaco', 'tr' => 'Turco', ], 'header'=> 'Idiomas', ]; ================================================ FILE: lang/pt-BR/locations.php ================================================ [], 'create' => [ 'title' => 'Novo Local', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [ 'is_destroyed' => 'Destruído', 'title' => 'Título', ], 'helpers' => [ 'characters' => 'Ver todos personagens neste local e em seus locais secundários, ou apenas aqueles localizados diretamente aqui.', ], 'hints' => [ 'is_destroyed' => 'Esse local está destruído.', ], 'index' => [], 'items' => [], 'journals' => [], 'lists' => [ 'empty' => 'Adicione sua primeira cidade, taverna ou ruína escondida para dar sustentação ao seu mundo.', ], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'title' => 'T[itulo', 'type' => 'Cidade, Reino, Ruína', ], 'show' => [], ]; ================================================ FILE: lang/pt-BR/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Entrar no modo de edição', 'exit-edit-mode' => 'Sair do modo de edição', 'finish-drawing' => 'Concluir o desenho do polígono', ], 'notifications' => [ 'start-drawing' => 'Clique no mapa para começar a desenhar um polígono', ], 'toggle' => 'Abrir/fechas todos os grupos', ]; ================================================ FILE: lang/pt-BR/maps/groups.php ================================================ [ 'add' => 'Adicionar um novo grupo', ], 'bulks' => [ 'delete' => '{1} Removido :count grupo.|[2,*] Removidos :count grupos.', 'patch' => '{1} Atualizado :count grupo.|[2,*] Atualizados :count grupos.', ], 'create' => [ 'helper' => 'Adicione um novo grupo a :name. Os marcadores poderão então ser atribuídos a este grupo.', 'success' => 'Grupo :name criado.', 'title' => 'Novo Grupo', ], 'delete' => [ 'success' => 'Grupo :name removido.', ], 'edit' => [ 'success' => 'Grupo :name atualizado.', 'title' => 'Editar Grupo :name', ], 'fields' => [ 'is_shown' => 'Exibir marcadores do grupo', 'parent' => 'Grupo Primário', 'position' => 'Posição', ], 'helper' => [ 'amount_v3' => 'Os marcadores podem ser agrupados usando grupos de mapas. Cada grupo pode ser clicado ao explorar um mapa para exibir ou ocultar rapidamente todos os marcadores nele.', ], 'hints' => [ 'is_shown' => 'Se assinalado, os marcadores do grupo serão exibidos no mapa por padrão.', ], 'index' => [ 'title' => 'Grupos de :name', ], 'pitch' => [ 'max' => [ 'helper' => 'Você não pode adicionar mais grupos a menos que remova um existente.', 'limit' => 'Este mapa atingiu seu limite de grupo', ], 'upgrade' => [ 'limit' => 'Você atingiu o limite de :limit grupos para este mapa', 'upgrade' => 'Faça upgrade para uma campanha premium para adicionar até :limit grupos e desbloquear ainda mais flexibilidade criativa.', ], ], 'placeholders' => [ 'name' => 'Lojas, Tesouros, PdMs', 'position' => 'Primeiro', 'position_list' => 'Depois de :name', ], 'reorder' => [ 'save' => 'Salvar nova ordem', 'success' => '{1} Reordenado :count grupo.|[2,*] Reordenados :count grupos.', 'title' => 'Reordenar grupos', ], ]; ================================================ FILE: lang/pt-BR/maps/layers.php ================================================ [ 'add' => 'Adicionar uma nova camada', ], 'base' => 'Camada Base', 'bulks' => [ 'delete' => '{1} Removida :count camada.|[2,*] Removidas :count camadas.', 'patch' => '{1} Atualizada :count camada.|[2,*] Atualizadas :count camadas.', ], 'create' => [ 'success' => 'Camada :name criada.', 'title' => 'Nova Camada', ], 'delete' => [ 'success' => 'Camada :name removida.', ], 'edit' => [ 'success' => 'Camada :name atualizada.', 'title' => 'Editar Camada :name', ], 'fields' => [ 'position' => 'Posição', 'type' => 'Tipo de camada', ], 'helper' => [ 'amount_v2' => 'Carregue camadas em um mapa para alternar a imagem de plano de fundo exibida abaixo dos marcadores ou como sobreposições acima do mapa, mas abaixo dos marcadores.', 'is_real' => 'Camadas não estão disponíveis ao usar OpenStreetMaps.', ], 'index' => [ 'title' => 'Camadas de :name', ], 'pitch' => [ 'max' => [ 'helper' => 'Não é possível adicionar mais camadas a menos que você remova uma existente.', 'limit' => 'Este mapa atingiu seu limite de camadas', ], 'upgrade' => [ 'limit' => 'Você atingiu o limite de :limit camadas para este mapa', 'upgrade' => 'Atualize para uma campanha premium para adicionar até :limit camadas e desbloquear ainda mais flexibilidade criativa.', ], ], 'placeholders' => [ 'name' => 'Subterrãneo, Nível 2, Navio Naufragado', 'position' => 'Primeiro', 'position_list' => 'Depois de :name', ], 'reorder' => [ 'save' => 'Salvar nova ordem', 'success' => '{1} Reordenada :count camada.|[2,*] Reordenadas :count camadas.', 'title' => 'Reordenar camadas', ], 'short_types' => [ 'overlay' => 'Sobreposição', 'overlay_shown' => 'Sobreposição (mostrar automaticamente)', 'standard' => 'Padrão', ], 'types' => [ 'overlay' => 'Sobreposição (exibido acima da camada ativa)', 'overlay_shown' => 'Sobreposição exibida por padrão', 'standard' => 'Camada padrão (alternar entre camadas)', ], ]; ================================================ FILE: lang/pt-BR/maps/markers.php ================================================ [ 'entry' => 'Escreva um campo de introdução personalizado para esse marcador.', 'remove' => 'Remover marcador', 'reset-polygon' => 'Redefinir posições', 'save_and_explore' => 'Salvar e Explorar', 'start-drawing' => 'Começar a desenhar', 'update' => 'Editar marcador', ], 'bulks' => [ 'delete' => '{1} Removido :count marcador.|[2,*] Removidos :count marcadores.', 'patch' => '{1} Atualizado :count marcador.|[2,*] Atualizados :count marcadores.', ], 'circle_sizes' => [ 'custom' => 'Personalizado', 'huge' => 'Enorme', 'large' => 'Grande', 'small' => 'Pequeno', 'standard' => 'Padrão', 'tiny' => 'Minúsculo', ], 'create' => [ 'success' => 'Marcador :name criado.', 'title' => 'Novo Marcador', ], 'delete' => [ 'success' => 'Marcador :name removido.', ], 'details' => [ 'from-entity' => 'Da entidade', ], 'edit' => [ 'success' => 'Marcador :name atualizado.', 'title' => 'Editar Marcador :name', ], 'fields' => [ 'bg_colour' => 'Cor do Background', 'circle_radius' => 'Raio do círculo', 'copy_elements' => 'Copiar elementos', 'custom_icon' => 'Ícone personalizado', 'custom_shape' => 'Forma personalizada', 'font_colour' => 'Cor do Ícone', 'group' => 'Grupo de Marcador', 'icon' => 'Ícone', 'is_draggable' => 'Arrastável', 'latitude' => 'Latitude', 'longitude' => 'Longitude', 'opacity' => 'Opacidade', 'pin_size' => 'Tamanho do Pino', 'polygon_style' => [ 'stroke' => 'Cor do traço', 'stroke-opacity' => 'Opacidade do traço', 'stroke-width' => 'Largura do traço', ], 'popupless' => 'Popup de dica de contexto', 'size' => 'Tamanho', ], 'helpers' => [ 'base' => 'Adicione marcadores no mapa clicando em qulquer lugar.', 'copy_elements' => 'Copiar grupos, camadas e marcadores.', 'copy_elements_to_campaign' => 'Copie grupos, camadas e marcadores dos mapas. Os marcadores vinculados a uma entidade serão convertidos em um marcador padrão.', 'css' => 'Defina uma classe CSS personalizada adicionada ao marcador.', 'custom_icon_v2' => 'Use ícones de :fontawesome, :rpgawesome ou um ícone SVG personalizado. Descubra como em :docs.', 'custom_radius' => 'Selecione a opção de tamanho personalizado no menu suspenso para definir um tamanho.', 'draggable' => 'Ative para permitir mover um marcador no modo de exploração.', 'is_popupless' => 'Desative a dica de contexto do marcador que aparece ao passar o mouse.', 'label' => 'Um rótulo é exibido como um bloco de texto no mapa. O conteúdo será o nome do marcador ou o nome da entidade.', 'polygon' => [ 'edit' => 'Clique no mapa para adicionar essa posição às coordenadas do polígono.', ], ], 'hints' => [ 'entry' => 'Edite o marcador para escrever uma introdução personalizada para ele.', ], 'icons' => [ 'custom' => 'Ícone personalizado', 'entity' => 'Imagem da entidade', 'exclamation' => 'Ícone de exclamação', 'marker' => 'Ícone de marcador', 'question' => 'Ícone de interrogação', ], 'index' => [ 'title' => 'Marcadores de :name', ], 'pitches' => [ 'poly' => 'Desenhe formas poligonais personalizadas para representar bordas e outras formas irregulares.', ], 'placeholders' => [ 'custom_icon' => 'Tente :example1 ou :example2', 'custom_shape' => '100,100 200,240 340,110', 'name' => 'Obrigatório se nenhuma entidade estiver selecionada', ], 'presets' => [ 'helper' => 'Clique sobre uma predefinição para carregá-la ou crie uma nova.', ], 'shapes' => [ '0' => 'Círculo', '1' => 'Quadrado', '2' => 'Triângulo', '3' => 'Personalizado', ], 'sizes' => [ '0' => 'Minúsculo', '1' => 'Padrão', '2' => 'Pequeno', '3' => 'Grande', '4' => 'Enorme', ], 'tabs' => [ 'circle' => 'Círculo', 'label' => 'Letreiro', 'marker' => 'Marcador', 'polygon' => 'Polígono', 'preset' => 'Predefinir', ], ]; ================================================ FILE: lang/pt-BR/maps.php ================================================ [ 'back' => 'Voltar para :name', 'edit' => 'Editar mapa', 'explore' => 'Explorar', ], 'create' => [ 'title' => 'Novo Mapa', ], 'destroy' => [], 'edit' => [], 'errors' => [ 'chunking' => [ 'error' => 'Ocorreu um erro ao fragmentar o mapa. Entre em contato com a equipe em :discord para obter suporte.', 'running' => [ 'edit' => 'O mapa não pode ser editado enquanto estiver sendo fragmentado.', 'explore' => 'O mapa não pode ser exibido enquanto estiver sendo fragmentado.', 'time' => 'Isso pode levar de vários minutos a várias horas, dependendo do tamanho do mapa.', ], ], 'dashboard' => [ 'missing' => 'Este mapa precisa de uma imagem para poder aparecer no dashboard.', ], 'explore' => [ 'missing' => 'Por favor adicione uma imagem ao mapa para poder explorá-lo', ], ], 'fields' => [ 'center_marker' => 'Marcador', 'center_x' => 'Posição de Longitude Padrão', 'center_y' => 'Posição de Latitude Padrão', 'centering' => 'Centralização', 'distance_measure' => 'Medição de distância', 'distance_name' => 'Rótulo da unidade de distância', 'grid' => 'Grid', 'has_clustering' => 'Agrupar marcadores', 'initial_zoom' => 'Zoom inicial', 'is_real' => 'Usar OpenStreetMaps', 'max_zoom' => 'Zoom máximo', 'min_zoom' => 'Zoom mínimo', 'tabs' => [ 'coordinates' => 'Coordenadas', 'marker' => 'Marcador', ], ], 'helpers' => [ 'center' => 'A alteração dos valores a seguir controlará em qual área do mapa o foco será centralizado. Deixar esses valores vazios resultará no foco sendo o centro do mapa.', 'centering' => 'Centralizar em um marcador terá prioridade sobre as coordenadas padrão.', 'chunked_zoom' => 'Agrupe automaticamente os marcadores quando estiverem próximos uns dos outros.', 'distance_measure' => 'Dar ao mapa uma medida de distância habilitará a ferramenta de medição no modo de exploração.', 'distance_measure_2' => 'Para que 100 pixels meçam 1 quilômetro, insira um valor de 0,0041.', 'grid' => 'Defina o tamanho do grid que será mostrado no modo exploração.', 'has_clustering' => 'Agrupe automaticamente os marcadores quando estiverem próximos uns dos outros.', 'initial_zoom' => 'O nível de zoom inicial com o qual um mapa é carregado. O valor padrão é :default, enquanto o maior valor permitido é :max e o menor valor permitido é :min.', 'is_real' => 'Selecione esta opção se quiser usar um mapa do mundo real em vez da imagem carregada. Esta opção desativa as camadas.', 'max_zoom' => 'O máximo que um mapa pode ser ampliado o zoom. O valor padrão é :default, enquanto o maior valor permitido é :max.', 'min_zoom' => 'O máximo que um mapa pode ser diminuído o zoom. O valor padrão é :default, enquanto o menor valor permitido é :max.', 'missing_image' => 'Você precisa salvar o mapa com uma imagem antes de poder adicionar camadas e marcadores.', ], 'index' => [], 'maps' => [], 'panels' => [ 'groups' => 'Grupos', 'layers' => 'Camadas', 'legend' => 'Legenda', 'markers' => 'Marcadores', 'settings' => 'Configurações', ], 'placeholders' => [ 'center_marker' => 'Deixe vazio para carregar o mapa centralizado', 'center_x' => 'Deixe vazio para carregar o mapa centralizado', 'center_y' => 'Deixe vazio para carregar o mapa centralizado', 'distance_name' => 'Km, milhas, pés, hambúrgueres', 'grid' => 'Distância em pixels entre os elementos da grid. Deixe vazio para esconder a grid.', 'name' => 'Nome do mapa', 'type' => 'Masmorra, Cidade, Galáxia', ], 'show' => [ 'tabs' => [ 'maps' => 'Mapas', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'Mapa está sendo fragmentado. Esse processo pode levar vários minutos ou horas.', ], ], ]; ================================================ FILE: lang/pt-BR/misc.php ================================================ [ 'member' => 'Torne-se um membro.', 'remove_v5' => 'O Kanka foi criado por nós dois. Apoie nossa jornada e desfrute de uma experiência sem anúncios por menos do que o preço de um café especial.', ], ]; ================================================ FILE: lang/pt-BR/notes.php ================================================ [ 'title' => 'Nova Nota', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'Sub-Notas', ], 'helpers' => [], 'hints' => [], 'index' => [], 'lists' => [ 'empty' => 'Armazene ideias, referências, regras ou informações que não se encaixam em nenhum outro lugar.', ], 'placeholders' => [ 'note' => 'Escolha uma nota primária', 'type' => 'Religião, Raça, Sistema Político', ], 'show' => [], ]; ================================================ FILE: lang/pt-BR/notifications.php ================================================ [ 'discord' => [ 'invalid' => 'Seu token do Discord expirou. Por favor, sincronize novamente suas contas do Discord e do Kanka.', ], ], 'campaign' => [ 'application' => [ 'approved' => 'Sua inscrição para a campanha :campaign foi aprovada.', 'approved_message' => 'Sua inscrição para a campanha :campaign foi aprovada. Mensagem fornecida: :reason', 'new' => 'Nova solicitação para :campaign.', 'rejected' => 'Sua solicitação para a campanha :campaign foi rejeitada. Razão fornecida: :reason', 'rejected_no_message' => 'Sua inscrição para a campanha :campaign foi rejeitada.', ], 'asset_export' => 'Uma exportação de recursos da campanha está disponível. O link está disponível por :time minutos.', 'boost' => [ 'add' => 'A campanha :campaign está sendo impulsionada por :user', 'remove' => ':user não está mais impulsionando a campanha :campaign', 'superboost' => 'A campanha :campaign está sendo super-impulsionada por :user', ], 'created' => 'Você criou :campaign.', 'deleted' => 'A campanha :campaign foi excluída.', 'export' => 'A exportação da campanha está disponível. O link estará disponível por :time minutos.', 'export_error' => 'Ocorreu um erro enquanto sua campanha era exportada. Por favor, contate-nos se o problema persistir,', 'hidden' => 'A campanha :campaign agora está oculta na página de campanhas públicas.', 'import' => [ 'failed' => 'A importação da campanha :campaign falhou.', 'success' => 'A campanha :campaign foi importada.', ], 'join' => ':user se juntou à campanha :campaign', 'leave' => ':user saiu da campanha :campaign', 'new_owner' => 'Você foi nomeado administrador de :campaign.', 'plugin' => [ 'deleted' => 'O plugin :plugin foi deletado do mercado e removido de sua campanha :campaign.', ], 'premium' => [ 'add' => 'Os recursos premium foram desbloqueados para a campanha :campaign pelo :user.', 'remove' => ':user não está mais desbloqueando recursos premium para a campanha :campaign.', ], 'removed-image' => 'A imagem ou cabeçalho de :entity foi removido devido a uma reivindicação de direitos autorais.', 'role' => [ 'add' => 'Você ganhou o cargo de :role na campanha :campaign', 'remove' => 'Você foi removido do cargo :role na campanha :campaign.', ], 'troubleshooting' => [ 'joined' => 'O membro da equipe Kanka :user juntou-se a campanha :campaign.', ], ], 'clear' => [ 'action' => 'Limpar tudo', 'success' => 'Notificações removidas.', 'title' => 'Limpar notificações', ], 'features' => [ 'approved' => 'Sua ideia :feature foi aprovada', 'finished' => 'Sua ideia :feature agora está disponível em Kanka!', 'rejected' => 'Sua ideia :feature foi rejeita, motivo :reason', ], 'header' => 'Você tem :count notificações.', 'index' => [ 'title' => 'Notificações', ], 'map' => [ 'chunked' => 'Mapa :name terminou de fragmentar e agora está pronto para uso.', ], 'no_notifications' => 'As notificações aparecerão aqui assim que você tiver algumas.', 'plugins' => [ 'comments' => [ 'new_comment' => ':user deixou um novo comentário no plugin :plugin.', 'new_reply' => ':user respondeu ao seu comentário em :plugin.', ], ], 'subscriptions' => [ 'charge_fail' => 'Ocorreu um erro ao tentar processar seu pagamento. Por favor, aguarde alguns momentos enquanto tentamos novamente. Se nada mudar, por favor entre em contato conosco.', 'deleted' => 'Sua assinatura do Kanka foi cancelada após muitas tentativas malsucedidas de cobrar seu cartão. Por favor, vá até suas configurações de Assinatura e tente atualizar os detalhes de sua forma de pagamento.', 'ended' => 'Sua assinatura do Kanka foi encerrada. Seus cargos do Discord e impulsionamentos de campanha foram removidos. Esperamos ver você novamente!', 'failed' => 'Não foi possível processar seus detalhes de pagamento. Por favor, atualize-os em suas configurações de Métodos de Pagamento.', 'started' => 'Sua assinatura do kanka foi iniciada.', ], 'unread' => 'Nova notificação', ]; ================================================ FILE: lang/pt-BR/onboarding/characters.php ================================================ 'Isso é o suficiente para começar.', 'text' => 'Concentre-se no básico: nome, uma breve descrição e uma ou duas características marcantes. Você pode adicionar detalhes como relações, propriedades e retratos posteriormente.', 'title' => 'Crie seu primeiro personagem', ]; ================================================ FILE: lang/pt-BR/organisations.php ================================================ [ 'title' => 'Nova Organização', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_defunct' => 'Extinta', 'members' => 'Membros', ], 'helpers' => [], 'hints' => [ 'is_defunct' => 'Esta organização está extinta.', ], 'index' => [], 'lists' => [ 'empty' => 'Crie guildas, facções ou sociedades secretas para moldar a estrutura de poder do seu mundo.', ], 'members' => [ 'actions' => [ 'add_multiple' => 'Adicionar membros', ], 'create' => [ 'helper' => 'Adicionar um ou vários membros à :name.', 'success_multiple' => '{1} Adicionado :count membro a :name.|[2,*] Adicionados :count membros a :name.', ], 'destroy' => [ 'success' => 'Membro removido da organização.', ], 'edit' => [ 'helper' => 'Alterar o status de afiliação para :name.', 'title' => 'Atualizar Membro para :name', ], 'fields' => [ 'parent' => 'Superior', 'pinned' => 'Fixado', 'role' => 'Função', 'status' => 'Status de Afiliação', ], 'helpers' => [ 'all_members' => 'Todos personagens que são membros desta organização e suas sub-organizações.', 'members' => 'Todos personagens que são membros desta organização.', 'pinned' => 'Escolha se este membro deve ser exibido na seção fixada da visão geral de suas entidades associadas.', ], 'pinned' => [ 'both' => 'Fixado em ambos', 'none' => 'Fixado em nenhum lugar', ], 'placeholders' => [ 'parent' => 'Quem é o superior desse membro', 'role' => 'Líder, Membro, Alto Septão, Mestre em Espionagem', ], 'status' => [ 'active' => 'Membro ativo', 'inactive' => 'Membro inativo', 'unknown' => 'Status desconhecido', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'Culto, Gangue, Rebelião, Fanáticos', ], 'show' => [], ]; ================================================ FILE: lang/pt-BR/partials.php ================================================ [ 'description' => 'Houveram alguns problemas com a sua entrada.', 'title' => 'Ooops!', ], ]; ================================================ FILE: lang/pt-BR/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/pt-BR/permissions.php ================================================ [ 'delete' => 'Permissão para excluir este elemento', 'edit' => 'Permissão para editar este elemento', 'view' => 'Permissão para visualizar este elemento', ], 'members' => [ 'inherited' => ':member já pode fazer isso sendo parte do cargo :role.', ], 'roles' => [ 'inherited' => 'O cargo :role já pode fazer isso em todo o módulo :module.', ], ]; ================================================ FILE: lang/pt-BR/pins.php ================================================ 'Aprenda mais sobre fixados em nossa documentação.', 'options' => [ 'no' => 'Desafixado', 'yes' => 'Fixado na página de visão geral da entidade', ], ]; ================================================ FILE: lang/pt-BR/playstyles.php ================================================ 'Casual / Fácil de Entrar', 'character-focused' => 'Focado nos personagens', 'combat-focused' => 'Focado em combate', 'episodic-one-shot-friendly' => 'Episódico / Ideal para One Shot', 'exploration-focused' => 'Focado em exploração', 'linear-gm-led' => 'Linear / Guiado pelo mestre', 'long-term-campaign' => 'Campanha de longo prazo', 'narrative-first' => 'Narrativa em primeiro lugar', 'story-driven' => 'Guiado pela história', 'tactical-crunchy' => 'Tático / Complexo', ]; ================================================ FILE: lang/pt-BR/post_layouts.php ================================================ 'Organizações do personagem', 'connection_map' => 'Mapa de conexão', 'helper' => 'Este post está configurado para exibir a subpágina :subpage da entidade.', 'location_characters' => 'Local dos personagens', 'location_events' => 'Local dos eventos', 'location_quests' => 'Local das missões', 'quest_elements' => 'Elementos da Missão', ]; ================================================ FILE: lang/pt-BR/posts/templates.php ================================================ [ 'set' => 'Definir como um modelo reutilizável', 'unset' => 'Remover como modelo reutilizável', ], 'helper' => 'Os artigos a seguir foram definidos como modelos que podem ser reutilizados.', 'tab' => 'Carregar a partir de modelos', 'tooltips' => [ 'click-to-edit' => 'Editar este artigo modelo', ], ]; ================================================ FILE: lang/pt-BR/posts.php ================================================ [ 'title' => 'Novo Artigo', ], 'fields' => [ 'description' => 'Descrição', 'layout' => 'Layout do artigo', 'name' => 'Nome do artigo', ], 'helpers' => [ 'new' => 'Adicione um novo artigo a essa entidade.', 'visibility' => 'Altere a visibilidade do artigo :name.', ], 'move' => [ 'copy' => [ 'helper' => 'Mantenha uma cópia do artigo em :name.', ], 'helper' => 'Mova ou copie o artigo :name para uma entidade diferente.', 'title' => 'Mover artigo', ], 'permissions' => [ 'actions' => [ 'members' => 'Adicionar membros', 'roles' => 'Adicionar funções', ], 'helpers' => [ 'members' => 'Adicione um ou vários membros para ter permissões especiais neste artigo.', 'roles' => 'Adicione uma ou várias funções para ter permissões especiais neste artigo.', ], ], 'placeholders' => [ 'name' => 'Nome do artigo', ], 'position' => [ 'dont_change' => 'Não mude', 'first' => 'Primeiro', 'last' => 'Último', ], 'remove' => [ 'title' => 'Excluir artigo', ], 'visibility' => [ 'helper' => 'Alterar a visibilidade para o artigo :name', 'title' => 'Visibilidade do artigo', ], ]; ================================================ FILE: lang/pt-BR/presets.php ================================================ [ 'create' => 'Criar uma nova predefinição', ], 'create' => [ 'success' => 'Predefinição :name criada.', 'title' => 'Nova predefinição', ], 'destroy' => [ 'success' => 'Predefinição :name destruída.', ], 'edit' => [ 'success' => 'Predefinição :name modifcada.', 'title' => 'Editar predefinição :name', ], 'fields' => [ 'name' => 'Predefinir o nome', ], 'lists' => [ 'empty' => 'Atualmente não tem predefinições disponíveis na campanha.', ], 'placeholders' => [ 'name' => 'Nome da predefinição', ], ]; ================================================ FILE: lang/pt-BR/profiles.php ================================================ [], 'avatar' => [ 'success' => 'Avatar atualizado.', ], 'campaign_switcher_order_by' => [], 'edit' => [ 'success' => 'Perfil atualizado.', ], 'editors' => [], 'fields' => [ 'avatar' => 'Avatar', 'bio' => 'Biografia', 'email' => 'Email', 'hide_subscription' => 'Esconder meu nome do :hall_of_fame.', 'last_login_share' => 'Mostrar a outros membros da campanha a última vez que estive online.', 'link' => 'Link social', 'login_sharing' => 'Último login compartilhado', 'name' => 'Nome', 'new_password' => 'Nova Senha', 'new_password_confirmation' => 'Confirmação da Nova Senha', 'newsletter' => 'Eu desejo ser contatado via email esporadicamente.', 'password' => 'Senha atual', 'profile-name' => 'Nome do perfil', 'pronouns' => 'Pronomes', 'settings' => 'Configurações', 'subscription_hiding' => 'Ocultação de assinatura', 'theme' => 'Tema', ], 'helpers' => [ 'link' => 'Altere a forma como um link para o seu perfil social aparece no seu :perfil e no :marketplace. Se deixado em branco, nenhum link será exibido.', 'profile-name' => 'Altere a forma como o seu nome aparece no seu :profile e no :marketplace. Se deixado em branco, o nome da sua conta será usado.', 'pronouns' => 'Altere a forma como seus pronomes aparecem no seu :profile e no :marketplace. Se deixado em branco, nenhum pronome será exibido.', ], 'link' => [ 'button' => 'perfil social de :name', ], 'newsletter' => [ 'helpers' => [ 'header' => 'Assine os seguintes boletins informativos por e-mail para ser notificado sobre o que está acontecendo com Kanka.', ], 'options' => [ 'monthly' => 'Newsletter do Kanka', ], 'title' => 'Boletim de Notícias', 'updated' => 'Preferências do boletim de notícias atualizadas.', ], 'password' => [ 'success' => 'Senha atualizada', ], 'placeholders' => [ 'bio' => 'Uma pequena biografia sua exibida em seu perfil público.', 'email' => 'Seu endereço de email', 'name' => 'Seu nome como exibido', 'new_password' => 'Sua nova senha', 'new_password_confirmation' => 'Confirme sua nova senha', 'password' => 'Forneça sua senha atual para qualquer mudança', ], 'sections' => [ 'dangerzone' => 'Zona de Perigo', 'delete' => [ 'confirm' => 'Excluir minha conta agora', 'delete' => 'Excluir minha conta', 'goodbye' => 'Em caso afirmativo, escreva :code na caixa abaixo.', 'helper' => 'Deletar sua conta também deletará quaisquer campanhas que você é o único membro dela. Essa ação é permanente e não pode ser desfeita.', 'subscribed' => 'Cancele sua :subscription antes de poder excluir sua conta.', 'title' => 'Remover sua conta', 'warning' => 'Ao remover sua conta todos os seus dados serão perdidos. Você tem certeza?', ], 'password' => [ 'title' => 'Alterar sua senha', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'A biografia está visível no seu :link.', 'profile' => 'perfil público', ], 'success' => 'Configurações alteradas.', ], 'theme' => [ 'success' => 'Tema alterado.', 'themes' => [ 'dark' => 'Escuro', 'default' => 'Padrão', 'future' => 'Futurista', 'midnight' => 'Azul Meia-Noite', ], ], 'title' => 'Atualizar seu perfil', 'workflows' => [ 'created' => 'Visualizar a entidade recém-criada', 'default' => 'Visualizar a lista de entidades', ], ]; ================================================ FILE: lang/pt-BR/quests.php ================================================ [ 'title' => 'Nova Missão', ], 'destroy' => [], 'edit' => [], 'elements' => [ 'create' => [ 'success' => 'Elemento :entity adicionado à missão.', 'title' => 'Novo elemento para :name', ], 'destroy' => [ 'success' => 'Elemento :entity removido.', ], 'edit' => [ 'success' => 'Elemento :entity atualizado.', 'title' => 'Atualizar elemento para :name', ], 'fields' => [ 'copy_entity_entry' => 'Usar a descrição da entidade', 'entity_or_name' => 'Selecione uma entidade da campanha ou dê um nome para este elemento.', ], 'helpers' => [ 'copy_entity_entry' => 'Exibir a descrição da entidade vinculada em vez da descrição personalizada.', ], 'placeholders' => [ 'name' => 'Nome do elemento', ], ], 'fields' => [ 'copy_elements' => 'Copiar elementos anexados à missão', 'date' => 'Data', 'element_role' => 'Função', 'instigator' => 'Instigador', 'is_completed' => 'Concluída', 'location' => 'Local inicial', 'role' => 'Função', 'status' => 'Status', ], 'helpers' => [ 'is_completed' => 'A missão é considerada concluída.', 'status' => 'O status atual da missão.', ], 'hints' => [ 'is_abandoned' => 'Esta missão foi abandonada.', 'is_completed' => 'Esta missão está concluída.', 'is_ongoing' => 'Essa missão está em andamento.', ], 'index' => [], 'lists' => [ 'empty' => 'Crie missões para registrar objetivos, enredos ou motivações de personagens.', ], 'placeholders' => [ 'date' => 'Data do mundo real para a missão', 'entity' => 'Nome de um elemento da missão', 'location' => 'O local de início da missão', 'role' => 'A função desta entidade na missão', 'type' => 'Arco de Personagem, Missão Secundária, Missão Principal', ], 'show' => [ 'actions' => [ 'add_element' => 'Adicionar um elemento', ], 'tabs' => [ 'elements' => 'Elementos', ], ], 'status' => [ 'abandoned' => 'Abandonada', 'completed' => 'Concluída', 'not_started' => 'Não Iniciada', 'ongoing' => 'Em Andamento', ], ]; ================================================ FILE: lang/pt-BR/races.php ================================================ [], 'create' => [ 'title' => 'Nova Raça', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'members' => 'Membros', ], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Essa raça está extinta.', ], 'index' => [], 'lists' => [ 'empty' => 'Defina as espécies, culturas ou povos que habitam o seu mundo.', ], 'members' => [ 'create' => [ 'helper' => 'Adicionar um ou vários personagens à :name', 'submit' => 'Adicionar membros', 'success' => '{0} Nenhum membro foi adicionado.|{1} 1 membro foi adicionado.|[2,*] :count membros foram adicionados.', 'title' => 'Novos Membros', ], ], 'placeholders' => [ 'type' => 'Humano, Fada, Ciborgue', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/pt-BR/redirects.php ================================================ 'Sua sessão expirou. Por favor tente novamente', 'unknown_entity' => 'Desculpa, nós não sabemos o que uma \':entity\' é.', ]; ================================================ FILE: lang/pt-BR/releases.php ================================================ [ 'event' => 'Evento', 'livestream' => 'Transmissão ao vivo', 'other' => 'Outro', 'release' => 'Atualização', 'vote' => 'Voto da Comunidade', ], 'index' => [ 'description' => 'As ultimas atualizações do kanka.io', 'title' => 'Atualizações', ], 'post' => [ 'footer' => 'Por :name em :date', ], 'show' => [ 'return' => 'Voltar para atualizações', 'title' => 'Atualização :name', ], ]; ================================================ FILE: lang/pt-BR/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chronicles of Darkness', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/pt-BR/search/fulltext.php ================================================ 'Pesquisando entidades, posts, atributos e muito mais pelo termo :term.', 'title' => 'Pesquisa de texto completo', ]; ================================================ FILE: lang/pt-BR/search.php ================================================ 'Pesquise em todos os lugares', 'lookup' => [ 'empty' => 'Nenhum resultado', 'hint' => 'Digite pelo menos 3 letras para pesquisar entidades na campanha.', 'keyboard' => 'pressione :k para pesquisar, :esc para descartar', 'lists' => 'Listas', 'recents' => 'Recentes', 'results' => 'Resultados', ], 'no_results' => 'Nenhum resultado.', 'placeholder' => 'BUSCAR', 'placeholders' => [ 'entry' => 'Procure uma entrada', ], 'preview' => [ 'links' => 'Vínculos', 'no-connections' => 'Nenhuma relação fixada para exibir', ], 'title' => 'Buscar', ]; ================================================ FILE: lang/pt-BR/seo.php ================================================ 'Dashboard de :campaign', 'entity-list' => 'Explore o :module de :campaign', ]; ================================================ FILE: lang/pt-BR/settings/account.php ================================================ 'Controle seu e-mail, senha, segurança e outras configurações da conta.', 'title' => 'Informações da conta', ]; ================================================ FILE: lang/pt-BR/settings/appearance.php ================================================ [ 'learn-more' => 'Saiba mais sobre essa configuração em nossa documentação.', 'save' => 'Salvar configurações', ], 'campaign-switcher' => [ 'alphabetical' => 'Alfabeticamente (A-Z)', 'date_created' => 'Data de criação (mais antigo primeiro)', 'date_joined' => 'Data de ingresso (mais antigo primeiro)', 'r_alphabetical' => 'Alfabeticamente (Z-A)', 'r_date_created' => 'Data de criação (mais novo primeiro)', 'r_date_joined' => 'Data de ingresso (mais novo primeiro)', ], 'dismissible' => [ 'main' => 'Controle a aparência e o comportamento de Kanka. Observe que as campanhas podem substituir algumas dessas configurações.', ], 'editors' => [ 'default' => 'Padrão (:name)', 'helpers' => [ 'feedback' => 'Ajude-nos a melhorar, enviando seu feedback (2 min)', 'legacy' => 'O editor de texto antigo (TinyMCE) não oferece suporte a menções em dispositivos móveis, galerias de campanhas ou outros recursos avançados.', 'tiptap' => 'Este é o nosso novo editor de texto experimental, que está sendo ativamente desenvolvido e atualizado regularmente. Ele ainda não contém todos os recursos aos quais você pode estar acostumado.', ], 'legacy' => 'Herdado (:name)', 'tiptap' => 'Experimental 2026', ], 'explore' => [ 'grid' => 'Grade (padrão)', 'table' => 'Tabela', ], 'fields' => [ 'campaign-order' => 'Ordem da lista de campanhas', 'date-format' => 'Formatação de data', 'editor' => 'Editor de texto', 'entity-explore' => 'Listas de entidade', 'mentions' => 'Menções ao editar', 'new-entity-workflow' => 'Novo fluxo de trabalho de entidade', 'pagination' => 'Resultados por página', 'theme' => 'Preferência de tema', ], 'helpers' => [ 'advanced-mentions' => 'Ao editar textos, controle se as menções são renderizadas como o nome da entidade ou como menção avançada.', 'campaign-order' => 'Altere a ordem em que as campanhas são listadas no alternador de campanha.', 'date-format' => 'Quando disponível, controle o formato no qual exibir as datas do mundo real.', 'editors' => 'Alterne entre editores de texto ao criar ou editar campos de texto grandes.', 'entity-explore' => 'Controle a maneira como as listas de entidades são exibidas nas campanhas.', 'new-entity-workflow' => 'Controle para qual interface você será levado depois de criar uma nova entidade.', 'overridable' => 'Campanhas individuais podem substituir essa preferência.', 'pagination' => 'Para listas que abrangem várias páginas, defina quantas são visíveis em cada página.', 'theme' => 'Escolha como Kanka parece para você.', ], 'mentions' => [ 'advanced' => 'Exibir menções avançadas :code', 'default' => 'Menções como o nome da entidade', ], 'nested' => [], 'success' => 'Opções de aparência salvas.', 'values' => [ 'pagination' => ':amount resultados por página', 'pagination-sub' => ':amount (disponível para assinantes)', ], ]; ================================================ FILE: lang/pt-BR/settings/boosters.php ================================================ [ 'boost_name' => 'Impulsionar :name', ], 'available' => 'Impulsões disponíveis :amount/:total', 'benefits' => [ 'boosted' => 'Impulsionar uma campanha com :one impulso desbloqueará o acesso ao :marketplace, opções de temas, uploads maiores para todos os membros, recuperação de entidades excluídas e :more.', 'more' => 'mais recursos incríveis', 'superboosted' => 'Superimpulsionar uma campanha com :amount impulsos desbloqueará todos os benefícios de uma campanha impulsionada, bem como uma galeria de campanha, alterações completas de logs que são feitas para entidades e :more.', ], 'boost' => [ 'actions' => [ 'confirm' => 'Impulsione-a!', 'remove' => 'Parar de impulsionar :campaign', 'subscribe' => 'Inscreva-se no Kanka', 'upgrade' => 'Atualize sua assinatura', ], 'confirm' => 'Que legal! Você está prestes a impulsionar :campaign. Isso atribuirá um (:custo) de seus impulsos disponíveis de campanha.', 'duration' => 'Impulsos atribuídos permanecem atribuídos até que você os remova manualmente ou quando sua assinatura terminar.', 'errors' => [ 'boosted' => 'Oh oh, parece que :campaign já foi impulsionada!', 'out-of-boosters' => 'Oh não! Você não tem impulsos suficientes disponíveis. Você tem :available e precisa de :cost. Pare de impulsionar outras campanhas ou :upgrade.', ], 'pitch' => 'Torne-se um assinante para desbloquear impulsos de campanha.', 'success' => 'A campanha :campaign agora está impulsionada. Aproveite todos os novos recursos incríveis!', 'title' => 'Impulsionar :campaign', 'upgrade' => 'atualize sua assinatura', ], 'campaign' => [ 'boosted' => 'Impulsionado por :user desde :time', 'premium' => 'Premium graças a :user desde :time', 'standard' => 'Padrão', 'superboosted' => 'Superimpulsionado por :user desde :time', 'unboosted' => 'Desimpulsionado', ], 'intro' => [ 'anyone' => 'Você não está limitado a impulsionar apenas as campanhas que criou. Você pode impulsionar qualquer campanha da qual faça parte ou possa visualizar. Isso inclui campanhas em que você é um jogador ou as :public de que gosta.', 'data' => 'Quando uma campanha não é mais impulsionada, o acesso aos recursos impulsionados é removido. No entanto, nenhum conteúdo é excluído, portanto, impulsionar a campanha novamente no futuro restaura o acesso a isto.', 'first' => 'Os recursos avançados são desbloqueados atribuindo seus impulsos para impulsionar ou superimpulsionar uma campanha. A quantidade de impulsos que você tem é determinada pela sua :subscription. Este número está disponível para você o tempo todo enquanto você for um assinante. Impulsionar uma campanha atribuirá um de seus impulsos a ela, enquanto superimpulsionar uma campanha atribuirá três deles.', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Recupere uma entidade excluída anteriormente por até :amount dias', 'customisable' => 'Personalização total da aparência de uma campanha', 'icons' => 'Acesso a milhares de belos ícones para mapas e linhas do tempo', 'title' => 'As campanhas impulsionadas obtém', 'upload' => 'Maior tamanho de upload para todos os membros da campanha', ], 'description' => 'Atribua impulsos a campanhas e ajude a desbloquear recursos incríveis para todos os envolvidos. Não está impressionado com as campanhas impulsionadas? Nós o cobrimos com campanhas superimpulsionadas!', 'more' => 'Confira a lista completa de vantagens em nossa página :boosters.', 'title' => 'Leve uma campanha para o próximo nível com personalização e vantagens para todos os seus membros', ], 'ready' => [ 'available' => 'Seus impulsos de campanha disponíveis.', 'pricing' => 'Todos os nossos níveis de assinatura incluem pelo menos um impulso de campanha e começa com :amount por mês.', 'pricing-amount' => ':currency:amount', 'title' => 'Impulsione uma campanha', ], 'superboost'=> [ 'actions' => [ 'confirm' => 'Superimpulsione-a!', 'instead' => 'Superimpulsione-a por :count!', 'remove' => 'Parar de superimpulsionar :campaign', ], 'confirm' => 'Que legal! Você está prestes a dar uma superimpulsão em :campaign. Isso atribuirá três (:cost) de seus impulsos disponíveis de campanha.', 'errors' => [ 'boosted' => 'Oh oh, parece que :campaign já está superimpulsionada!', ], 'success' => 'A campanha :campaign agora está superimpulsionada. Aproveite todos os novos recursos incríveis!', 'title' => 'Superimpulsionar :campaign', 'upgrade' => 'Pronto para uma melhor experiência no Kanka? Superimpulsionar :campaign atribuirá :cost impulsos de campanha adicionais.', ], 'title' => 'Impulsos de Campanha', 'unboost' => [ 'confirm' => 'Sim, tenho certeza', 'status' => [ 'boosting' => 'impulsionando', 'superboosting' => 'superimpulsionando', ], 'success' => 'A campanha :campaign não é mais impulsionada e seus impulsos estão disponíveis novamente.', 'title' => 'Desimpulsionar uma campanha', 'warning' => 'Tem certeza de que deseja interromper :action :campaign? Isso liberará seus impulsos atribuídos e ocultará todo o conteúdo e recursos relacionados às vantagens até que a campanha seja impulsionada novamente.', ], ]; ================================================ FILE: lang/pt-BR/settings/premium.php ================================================ [ 'remove' => 'Remover premium', 'unlock' => 'Torne-se premium', ], 'create' => [ 'actions' => [ 'confirm' => 'Torne-se premium!', ], 'confirm' => 'Que legal! Você está prestes a desbloquear recursos premium para :campaign. Isso usará uma de suas campanhas premium disponíveis.', 'duration' => 'As campanhas remium permanecem assim até removê-las manualmente ou quando sua assinatura terminar.', 'success' => 'A campanha :campaign agora é premium. Aproveite todos os novos recursos incríveis!', ], 'exceptions' => [ 'already' => 'Os recursos premium já foram desbloqueados para esta campanha.', 'out-of-stock' => 'Você não tem campanhas premium suficientes disponíveis para desbloquear esta campanha. Remova o status premium de outra campanha ou :upgrade.', ], 'pitch' => [ 'description' => 'Seja premium em campanhas e ajude a desbloquear recursos incríveis para todos os envolvidos.', 'title' => 'Campanhas premium recebem', ], 'ready' => [ 'available' => 'Suas campanhas premium disponíveis.', 'pricing' => 'Todos os nossos níveis de assinatura incluem pelo menos uma campanha premium e iniciam por :amount por mês.', 'pricing-amount' => ':currency:amount', 'title' => 'Torne-se premium', ], 'remove' => [ 'confirm' => 'Sim, tenho certeza', 'cooldown' => 'Os recursos premium de :campaign podem ser removidos após :date.', 'success' => 'Os recursos premium foram removidos da campanha :campaign. Agora você pode desbloquear recursos premium em outra campanha.', 'title' => 'Removendo recursos premium', 'warning' => 'Tem certeza de que deseja remover os recursos premium de :campaign? Isso permitirá que você desbloqueie outra campanha e oculte todo o conteúdo e recursos relacionados às vantagens até que o status premium da campanha seja reativado.', ], ]; ================================================ FILE: lang/pt-BR/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Desativar autenticação de dois fatores', 'disable-confirm' => 'Clique novamente para confirmar', 'finish' => 'Conclua a configuração e faça login', ], 'activation_helper' => 'Para concluir a configuração da autenticação de dois fatores da sua conta, siga estas instruções.', 'disable' => [ 'helper' => 'Se você deseja desativar a autenticação de dois fatores, clique no botão abaixo. Lembre-se de que isso deixará sua conta vulnerável a qualquer pessoa que conheça suas informações de login.', 'title' => 'Desativar autenticação de dois fatores', ], 'enable_instructions' => 'Para iniciar o processo de ativação, gere seu QR Code de autenticação e, em seguida, digitalize-o no aplicativo Google Authenticator (:ios, :android) ou outro aplicativo autenticador semelhante.', 'enabled' => 'Autenticação de dois-fatores está atualmente ativada em sua conta.', 'error_enable' => 'Código Inválido, tente novamente', 'fields' => [ 'otp' => 'Digite a Senha de Uso Único (OTP) fornecida pelo aplicativo autenticador', 'qrcode' => 'Digitalize o seguinte QR Code com seu aplicativo autenticador para gerar uma Senha de Uso Único (OTP)', ], 'generate_qr' => 'Gerar QR code', 'helper' => 'A autenticação de dois fatores (2FA) fortalece a segurança de acesso ao exigir dois métodos (também conhecidos como fatores) para verificar sua identidade em cada login.', 'learn_more' => 'Saiba mais sobre a autenticação de dois fatores.', 'social' => 'A autenticação de dois fatores do Kanka é habilitada apenas para usuários que fazem login usando seu e-mail e senha. Altere seu método de login nas configurações da sua conta antes de habilitar esta opção.', 'success_disable' => 'Autenticação de dois fatores desativada com sucesso.', 'success_enable' => 'Autenticação de dois fatores habilitada com sucesso. Faça login novamente para concluir a configuração.', 'success_key' => 'Seu QR Code de segurança foi gerado com sucesso. Conclua sua configuração para ativar a autenticação de dois fatores.', 'title' => 'Autenticação de dois fatores', ], 'actions' => [ 'social' => 'Trocar para o Login do Kanka', 'update_email' => 'Atualizar e-mail', 'update_password' => 'Atualizar senha', ], 'email' => 'Alterar e-mail', 'email_success' => 'E-mail atualizado.', 'password' => 'Alterar senha', 'password_success' => 'Senha atualizada.', 'social' => [ 'error' => 'Você já está usando o login do Kanka para essa conta.', 'helper' => 'Atualmente sua conta está sendo gerenciada pelo :provider. Você pode mudar isto e trocar para o login padrão do Kanka configurando uma senha.', 'success' => 'Sua conta agora usa o login do Kanka.', 'title' => 'Social para Kanka', ], 'title' => 'Conta', ], 'api' => [ 'helper' => 'Bem-vindo às APIs do Kanka. Gere um Token de Acesso Pessoal para usar em sua requisição de API para coletar informações sobre as campanhas das quais você faz parte.', 'link' => 'Leia a documentação da API', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Conectar', 'remove' => 'Remover', ], 'benefits' => 'Kanka oferece integração com alguns serviços de terceiros. Mais integrações de terceiros estão planejadas para o futuro.', 'discord' => [ 'confirm' => 'Tem certeza de que deseja desconectar sua conta do Discord? Isso removerá todas as funções com as quais você foi sincronizado.', 'errors' => [ 'add' => 'Ocorreu um erro ao vincular sua conta do Discord ao Kanka. Por favor, tente novamente. Se isso continuar acontecendo, saiba que o Discord tem um limite de 100 servidores associados ao usar suas APIs.', ], 'success' => [ 'add' => 'Sua conta do Discord foi vinculada.', 'remove' => 'Sua conta do Discord foi desvinculada.', ], 'text' => 'Acesse seus cargos de assinatura automaticamente.', 'unlock' => 'Desbloquear cargos do Discord', ], 'title' => 'Integração de App', ], 'billing' => [ 'placeholder' => 'Se você precisar de contatos adicionais ou informações fiscais aos seus recibos (endereço comercial, número de IVA, etc.), insira-as abaixo e elas aparecerão em todos os seus recibos.', 'save' => 'Salvar informações de cobrança', 'title' => 'Informações de Cobrança', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'Campanha :name já está sendo impulsionada.', 'exhausted_boosts' => 'Você está sem impulsos para dar. Remova o impulso de uma campanha antes de dar a outra.', 'exhausted_superboosts' => 'Você está sem impulsionamentos. Você precisa de 3 impulsos para tornar uma campanha Super Impulsionada.', ], ], 'countries' => [ 'austria' => 'Áustria', 'belgium' => 'Bélgica', 'france' => 'França', 'germany' => 'Alemanha', 'italy' => 'Itália', 'netherlands' => 'Holanda', 'spain' => 'Espanha', ], 'invoices' => [], 'layout' => [ 'title' => 'Layout', ], 'marketplace' => [], 'menu' => [ 'account' => 'Conta', 'api' => 'API', 'appearance' => 'Aparência', 'apps' => 'Apps', 'boosters' => 'Impulsionamentos', 'notifications' => 'Notificações', 'other' => 'Outros', 'patreon' => 'Patreon', 'payment_options' => 'Opções de Pagamento', 'personal_settings' => 'Configurações Pessoais', 'premium' => 'Campanhas premium', 'profile' => 'Perfil público', 'settings' => 'Configurações', 'subscription' => 'Assitatura', 'subscription_status' => 'Status da assinatura', ], 'patreon' => [ 'deprecated' => 'Recurso obsoleto - se você deseja oferecer suporte ao Kanka, faça-o com uma :subscription. A vinculação do Patreon ainda está ativa para nossos clientes que vincularam suas contas antes de termos deixado o Patreon.', 'pledge' => 'Pledge :name', 'remove' => [ 'button' => 'Desvincular sua conta Patreon', 'success' => 'Sua conta Patreon foi desvinculada.', 'text' => 'Desvincular sua conta Patreon com Kanka removerá seus bônus, nome no hall da fama, incentivos de campanha e outros recursos vinculados ao apoio a Kanka. Nenhum conteúdo impulsionado será perdido (por exemplo, cabeçalhos de entidade). Ao assinar novamente, você terá acesso a todos os seus dados anteriores, incluindo a capacidade de impulsionar suas campanhas impulsionadas anteriormente.', 'title' => 'Desvincule sua conta Patreon com Kanka', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Atualizar perfil', ], 'avatar' => 'Foto de Perfil', 'success' => 'Perfil atualizado.', 'title' => 'Perfil público', ], 'referrals' => [ 'title' => 'Referências', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Cancelar assinatura', 'subscribe' => 'Assinar', 'update_currency' => 'Salvar moeda preferida', ], 'billing' => [ 'helper' => 'Suas informações de faturamento são processadas e armazenadas com segurança através de :stripe. Este método de pagamento é usado para todas as suas assinaturas.', 'saved' => 'Método de pagamento salvo', ], 'cancel' => [ 'grace' => [ 'text' => 'Sua assinatura já está definida para terminar em :data, após a qual suas campanhas premium voltarão a ser campanhas padrão e outros benefícios relacionados ao suporte ao Kanka serão desativados.', 'title' => 'Período de carência', ], 'options' => [ 'competitor' => 'Alterar para um concorrente', 'financial' => 'A assinatura é muito cara', 'missing_features' => 'Falta de recursos', 'not_for' => 'Assinatura não é para mim', 'not_playing' => 'Não está mais jogando ou fazendo campanha em hiato', 'not_using' => 'Não estou usando o Kanka no momento', 'other' => 'Outro', 'testing' => 'Apenas testando Kanka', ], 'text' => 'Lamento ver você ir! Cancelar sua assinatura a manterá ativa até :date, após a qual você perderá seus impulsos de campanha e outros benefícios relacionados ao apoio a Kanka. Sinta-se à vontade para preencher o seguinte formulário para nos informar o que podemos fazer melhor ou o que levou à sua decisão.', 'title' => 'Cancelando a assinatura', ], 'cancelled' => 'Sua assinatura foi cancelada. Você pode renovar uma assinatura assim que sua assinatura atual terminar depois de :date.', 'change' => [ 'text' => [ 'downgrade_monthly' => 'Você está fazendo um downgrade para o nível :tier pelo valor de :downgrade, e a partir daí será cobrado mensalmente pelo valor de :amount.', 'downgrade_yearly' => 'Você está fazendo um downgrade para o nível :tier pelo valor de :downgrade, e a partir daí será cobrado anualmente pelo valor de :amount.', 'monthly' => 'Você está se inscrevendo no nível :tier, cobrado mensalmente em :amount.', 'upgrade_monthly' => 'Você está atualizando para o nível :tier para :upgrade e, posteriormente, será cobrado mensalmente por :amount.', 'upgrade_paypal' => 'Você está atualizando para o nível :tier para :upgrade até :date.', 'upgrade_yearly' => 'Você está atualizando para o nível :tier para :upgrade, sendo posteriormente cobrado anualmente por :amount.', 'yearly' => 'Você está se inscrevendo no nível :tier, cobrado anualmente em :amount.', ], 'title' => 'Alterar Nível de Assinatura', ], 'coupon' => [ 'check' => 'Verifique o código promocional', 'invalid' => 'Código promocional inválido.', 'label' => 'Código promocional', 'percent_off' => 'Iremos descontar sua primeira assinatura anual em :percent%!', ], 'currencies' => [ 'brl' => 'BRL', 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Altere sua moeda de cobrança preferida', ], 'errors' => [ 'callback' => 'Nosso provedor de pagamento relatou um erro. Tente novamente ou entre em contato conosco se o problema persistir.', 'failed' => 'No momento, estamos enfrentando problemas com nosso sistema de cobrança. Entre em contato conosco em :email para obter assistência.', 'subscribed' => 'Não foi possível processar sua assinatura. Stripe forneceu a seguinte sugestão.', ], 'fields' => [ 'active_since' => 'Ativa desde', 'active_until' => 'Ativa até', 'billing' => 'Cobrança', 'currency' => 'Moeda de Cobrança', 'payment_method' => 'Método de pagamento', 'plan' => 'Plano atual', 'reason' => 'Razão', 'reset' => 'Redefinir informações de cobrança', 'reset_billing' => 'Entendo que alterar a moeda fará com que eu perca meu histórico de cobrança e precise inserir novamente meu método de pagamento.', ], 'helpers' => [ 'alternatives' => 'Pague sua assinatura usando :method. Este método de pagamento não será renovado automaticamente no final da sua assinatura. :method disponível apenas em Euros.', 'alternatives-2' => 'Pague sua assinatura usando :method. Este é um pagamento único que não é renovado automaticamente no final da assinatura.', 'alternatives_warning' => 'Não é possível atualizar sua assinatura ao usar este método. Faça uma nova assinatura quando a atual terminar.', 'alternatives_yearly' => 'Devido às restrições em torno dos pagamentos recorrentes, :method está disponível apenas para assinaturas anuais', 'currency_block' => 'Não é possível alterar a moeda enquanto você tiver uma assinatura Kanka ativa; você poderá alterar sua moeda quando sua assinatura atual terminar.', 'currency_reset' => 'Alterar a moeda de sua escolha excluirá seu histórico de cobrança e exigirá que você insira novamente um método de pagamento.', 'paypal_v3' => 'Pague com segurança pela sua assinatura anual usando o PayPal.', 'stripe' => 'Suas informações de cobrança são processadas e armazenadas com segurança por meio de :stripe.', ], 'manage_subscription' => 'Gerenciar assinatura', 'payment_method' => [ 'actions' => [ 'add' => 'Adicionar', 'add_new' => 'Adicionar um novo método de pagamento', 'change' => 'Alterar método de pagamento', 'save' => 'Salvar método de pagamento', 'show_alternatives' => 'Métodos de pagamento alternativos', ], 'add_one' => 'No momento, você não tem um método de pagamento salvo.', 'alternatives' => 'Você pode se inscrever usando essas opções alternativas de pagamento. Esta ação cobrará sua conta uma vez e não renovará automaticamente sua assinatura todos os meses.', 'card' => 'Cartão', 'card_name' => 'Nome no cartão', 'country' => 'País de moradia', 'ending' => 'Válido até', 'helper' => 'Este cartão será usado para todas suas assinaturas.', 'new_card' => 'Adicionar um novo método de pagamento', 'saved' => ':brand terminando em :last4', ], 'periods' => [ 'monthly' => 'Mensalmente', 'yearly' => 'Anualmente', ], 'placeholders' => [ 'downgrade_reason' => 'Opcionalmente, diga-nos por que você está fazendo o downgrade de sua assinatura.', 'reason' => 'Opcionalmente, diga-nos por que você não está mais apoiando o Kanka. Estava faltando algum recurso? Sua situação financeira mudou?', ], 'plans' => [ 'cost_monthly' => ':currency :amount cobrado mensalmente', 'cost_yearly' => ':currency :amount cobrado anualmente', ], 'sub_status' => 'Informação da assinatura', 'subscription' => [ 'actions' => [ 'cancel' => 'Cancelar assinatura', 'downgrading' => 'Entre em contato conosco para fazer o downgrade', 'rollback' => 'Mudar para Kobold', 'subscribe' => 'Mudar para :tier mensalmente', 'subscribe_annual' => 'Mudar para :tier anualmente', ], ], 'success' => [ 'alternative' => 'Seu pagamento foi registrado. Você receberá uma notificação assim que for processado e sua assinatura estiver ativa.', 'callback' => 'Sua assinatura foi realizada com sucesso. Sua conta será atualizada assim que nosso provedor de pagamento nos informar sobre a mudança (isso pode levar alguns minutos).', 'currency' => 'Sua configuração de moeda preferida foi atualizada.', 'subscribed' => 'Sua assinatura foi bem-sucedida! Não se esqueça de assinar o boletim informativo Votação da Comunidade para ser notificado quando uma votação for ao ar. Além disso, você pode conferir nosso discord e fazer parte da comunidade', ], 'tiers' => 'Níveis de Assinatura', 'trial_period' => 'As assinaturas anuais têm uma política de cancelamento de 14 dias. Entre em contato conosco por :email se desejar cancelar sua assinatura anual e obter um reembolso.', 'upgrade_downgrade' => [ 'button' => 'Informação de Upgrade e Downgrade', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Seus bônus permanecem ativados até o final do período de pagamento.', 'boosts' => 'O mesmo acontece com suas campanhas impulsionadas. Os recursos impulsionados se tornam invisíveis, mas não são excluídos quando uma campanha não é mais impulsionada.', 'kobold' => 'Para cancelar sua assinatura, mude para o nível Kobold', 'premium' => 'O mesmo acontece com suas campanhas premium. Os recursos premium ficam invisíveis, mas não são excluídos quando uma campanha não é mais premium.', ], 'title' => 'Ao cancelar sua assinatura', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Seu nível atual permanecerá ativo até o final do seu ciclo de faturamento atual, após o qual você será rebaixado para o seu novo nível.', ], 'provide_reason' => 'Se puder, compartilhe conosco por que está fazendo o downgrade de sua assinatura.', 'title' => 'Ao fazer downgrade para um nível menor', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Seu método de pagamento será cobrado imediatamente e você terá acesso ao seu novo nível.', 'prorate' => 'Ao fazer upgrade de Urso-Coruja para Elemental, você só será cobrado pela diferença de seu novo nível.', ], 'title' => 'Ao fazer upgrade para um nível maior', ], ], 'warnings' => [ 'incomplete' => 'Não foi possível cobrar seu cartão de crédito. Atualize as informações do seu cartão de crédito e tentaremos cobrar novamente nos próximos dias. Se falhar novamente, sua assinatura será cancelada.', 'patreon' => 'Sua conta está atualmente vinculada ao Patreon. Desvincule sua conta nas configurações de :patreon antes de mudar para uma assinatura Kanka.', ], ], ]; ================================================ FILE: lang/pt-BR/sidebar.php ================================================ [ 'count' => 'Membro de :member', 'created_campaigns' => 'Suas Campanhas', 'follow_more' => 'Encontrar campanhas', 'followed_campaigns'=> 'Campanhas Seguidas', 'new_campaign' => 'Nova Campanha', 'public_campaigns' => 'Campanhas Públicas', 'reorder' => 'Reordenar', 'updated' => 'Atualizado', ], 'dashboard' => 'Dashboard', 'entity-creator' => 'Criação Rápida', 'gallery' => 'Galeria', 'game' => 'Jogo', 'other' => 'Outros', 'recent' => 'Mudanças recentes', 'relations' => 'Relações', 'settings' => 'Configurações', 'time' => 'Tempo', 'world' => 'Mundo', ]; ================================================ FILE: lang/pt-BR/starter.php ================================================ [ 'name' => 'mundo do :user', ], 'character1' => [], 'character2' => [], 'item1' => [], 'kingdom1' => [], 'kingdom2' => [], 'note1' => [], ]; ================================================ FILE: lang/pt-BR/subscription.php ================================================ [ 'main' => 'Inscreva-se no Kanka para desbloquear uploads de imagens mais altos, uma experiência sem anúncios, :boosters e :more. Usamos :stripe para lidar com todas as cobranças, sem informações de cartão de crédito armazenadas ou transitando por nossos servidores.', 'more' => 'mais recursos incríveis', ], 'errors' => [ 'grace' => 'Sua assinatura atual termina em :date, após o qual você pode assinar novamente.', 'invalid_card_country' => [ 'brl' => 'Lamentamos, mas atualmente só aceitamos pagamentos em BRL para clientes com cartões de crédito brasileiros. Se você acha que isso é um engano, entre em contato conosco em :email.', ], 'invalid_currency' => 'Você tinha uma assinatura anteriormente em :old, impedindo que você tenha uma nova assinatura em :new. Por favor, troque sua moeda para :old, ou entre em contato conosco em :email se você deseja trocar moedas.', ], ]; ================================================ FILE: lang/pt-BR/subscriptions/cancelled.php ================================================ [ 'adfree' => 'Uma experiência sem anúncios', 'discord' => 'Cargos exclusivos no :discord para assinantes', 'helper' => 'Sua assinatura permanecerá ativa até :date. Até lá, você continuará aproveitando todos os benefícios da assinatura, incluindo:', 'limit' => 'Limites de upload aumentados', 'more' => 'E muito mais!', 'premium' => 'Campanhas premium e seus recursos', 'title' => 'Suas vantagens ainda estão ativas', ], 'change' => [ 'action' => 'Inscreva-se novamente agora', 'helper' => 'Adoraríamos ter você de volta! Você pode assinar novamente a qualquer momento para continuar de onde parou.', 'title' => 'Mudou de ideia?', ], 'contact' => [ 'feedback' => 'Se houver algo que poderíamos ter feito melhor, gostaríamos de ouvir de você:', 'helper' => 'Mesmo sem uma assinatura, você ainda faz parte da comunidade Kanka. Continue construindo seus mundos e sinta-se à vontade para se reconectar quando for a hora certa.', 'send' => 'Entre em contato conosco com seu feedback', 'title' => 'Mantenha contato', ], 'next' => [ 'data' => 'Seus dados permanecerão seguros, nada será excluído e você poderá assinar novamente a qualquer momento', 'discord' => 'Você não terá mais acesso a recursos e canais exclusivos para assinantes', 'helper' => 'Após o término da sua assinatura:', 'premium' => 'Qualquer campanha com premium habilitado perderá seu status premium', 'title' => 'O que acontece depois disso?', ], 'seo_title' => 'Sinto muito ver você partir', 'subtitle' => 'Obrigado por ser nosso assinante, seu apoio foi muito importante para nós. Veja o que esperar agora:', 'title' => 'Sinto muito ver você partir, :name', ]; ================================================ FILE: lang/pt-BR/subscriptions/confirm.php ================================================ [ 'pay' => 'Pagar :amount :currency agora', 'paypal' => 'Pagar :amount :currency com PayPal', ], 'helpers' => [ 'auto-renew' => [ 'monthly' => 'Sua assinatura é renovada automaticamente todo mês. Sua próxima data de cobrança é :date.', 'none' => 'O pagamento com PayPal é único e não é renovado automaticamente. Você pode assinar novamente após o término da sua assinatura, após :date.', 'yearly' => 'Sua assinatura é renovada automaticamente a cada 12 meses. Sua próxima data de cobrança é :date.', ], 'paypal' => 'Você será redirecionado ao PayPal para concluir esta transação.', 'refund' => 'Oferecemos uma política de reembolso de 14 dias, sem perguntas, para todas as assinaturas anuais. Basta nos enviar um e-mail para :email para iniciar o processo de reembolso.', ], 'title' => ':nome da assinatura', ]; ================================================ FILE: lang/pt-BR/subscriptions/faq.php ================================================ [ 'answer' => 'Com certeza! Você encontrará um botão de cancelamento nesta página se já estiver inscrito. Todos os benefícios da assinatura permanecem ativos até o final do seu período de cobrança. Observe que as assinaturas do PayPal são encerradas automaticamente ao final do período, pois não oferecem renovação automática.', 'question' => 'Posso cancelar minha assinatura a qualquer momento?', ], 'cost' => [ 'answer' => 'A Kanka oferece três níveis de assinatura com nomes de monstros de D&D: Owlbear, Wyvern e Elemental. Os preços variam de acordo com a sua moeda preferida (USD, EUR ou BRL). Você terá dois meses grátis ao escolher uma assinatura anual em vez da mensal.', 'question' => 'Quanto custa uma assinatura?', ], 'data' => [ 'answer' => 'Fique tranquilo, nunca excluímos seus dados quando uma assinatura termina. As campanhas premium simplesmente retornam à funcionalidade padrão, com os recursos premium temporariamente desativados. Ao assinar novamente, todas as suas configurações e dados premium são restaurados imediatamente, exatamente como você os deixou.', 'question' => 'O que acontece com meus dados se eu cancelar minha assinatura?', ], 'discount' => [ 'answer' => 'Sim! Recompensamos nossos assinantes anuais com dois meses grátis em vez da cobrança mensal. Esta é a nossa forma de agradecer pelo seu compromisso de longa data com o Kanka.', 'question' => 'Há algum desconto para assinaturas anuais?', ], 'downgrade' => [ 'answer' => 'Você pode alterar seu plano de assinatura a qualquer momento. Ao fazer um upgrade, cobraremos apenas a diferença entre o seu plano atual e o novo pelo restante do seu período de cobrança. Ao fazer o downgrade, sua nova tarifa mais baixa entrará em vigor na próxima data de renovação, sem interrupção dos seus benefícios atuais.', 'question' => 'Como faço para atualizar/rebaixar minha assinatura?', ], 'fail' => [ 'answer' => 'Se o pagamento não for efetuado, notificaremos você por e-mail imediatamente e tentaremos automaticamente cobrar seu cartão até três vezes adicionais. Se essas tentativas não forem bem-sucedidas, sua assinatura será pausada. Você pode resolver isso facilmente atualizando suas informações de cobrança com um método de pagamento válido.', 'question' => 'O que acontece se meu pagamento falhar?', ], 'methods' => [ 'answer' => 'Aceitamos pagamentos com cartão de crédito e PayPal em USD, EUR e BRL. A segurança do seu pagamento é importante para nós; todo o processamento de cartão de crédito é feito com segurança pelo nosso provedor de pagamento confiável, :stripe.', 'question' => 'Quais métodos de pagamento são aceitos?', ], 'refund' => [ 'answer' => 'Sim! Oferecemos uma política de reembolso de 100% por 14 dias, sem perguntas, para todas as assinaturas anuais. Basta nos enviar um e-mail para :email solicitando seu reembolso e nós cuidaremos de tudo para você.', 'question' => 'Vocês oferecem reembolsos?', ], 'renewal' => [ 'answer' => 'Sim, para assinaturas com cartão de crédito, renovaremos automaticamente o seu plano com a mesma tarifa quando o período de cobrança terminar. As assinaturas do PayPal são a exceção, pois exigem renovação manual, pois o PayPal não oferece suporte à continuação automática da cobrança do nosso serviço.', 'question' => 'Serei cobrado automaticamente quando minha assinatura for renovada?', ], 'security' => [ 'answer' => 'Sua segurança financeira é nossa prioridade. Temos parceria com a :stripe, uma processadora de pagamentos em conformidade com o PCI que mantém os mais altos padrões de segurança em pagamentos. Todos os dados confidenciais de pagamento são processados e armazenados pela Stripe sob protocolos em conformidade com o GDPR, e não em nossos servidores.', 'question' => 'Quão seguras são minhas informações de pagamento?', ], 'sharing' => [ 'answer' => 'Com certeza! Sua assinatura permite que você habilite campanhas premium que beneficiam a todos os envolvidos. Todos os membros da campanha desfrutam de recursos premium dentro dela, independentemente do status da assinatura, tornando Kanka perfeito para a construção colaborativa de mundos.', 'question' => 'Posso compartilhar minha conta/assinatura com outras pessoas?', ], 'title' => 'Perguntas frequentes', 'trial' => [ 'answer' => 'Embora não ofereçamos um teste tradicional, a versão gratuita do Kanka oferece ferramentas robustas de construção de mundos e gerenciamento de campanhas para você começar. Quando estiver pronto para mais, a assinatura desbloqueia recursos premium, como limites de upload de imagens aumentados, uma experiência sem anúncios, funções exclusivas do Discord e melhorias adicionais ao seu kit de ferramentas de construção de mundos.', 'question' => 'Existe um teste gratuito disponível?', ], 'update' => [ 'answer' => 'Atualizar seus dados de cobrança é simples: basta acessar a página :billing nas configurações da sua conta. Lá, você pode modificar os métodos de pagamento, atualizar as informações do cartão ou alterar os endereços de cobrança, conforme necessário.', 'question' => 'Como atualizo minhas informações de cobrança?', ], ]; ================================================ FILE: lang/pt-BR/subscriptions/finish.php ================================================ [ 'action' => 'Conecte sua conta do Discord', 'enjoy' => 'Acesse o Discord para aproveitar suas novas vantagens', 'helper' => 'Como assinante, você desbloqueia funções exclusivas e canais privados em nossa comunidade do Discord, mas primeiro você precisa conectar sua conta do Discord.', 'title' => 'Obtenha suas vantagens do Discord', ], 'header' => 'Você se inscreveu com sucesso, bem-vindo ao círculo interno dos construtores de mundos!', 'help' => [ 'contact-us' => 'contate-nos', 'helper' => 'Se tiver alguma dúvida ou feedback, entre em contato conosco ou visite nossa documentação. Estamos aqui para tornar sua experiência de construção de mundos mágica.', 'title' => 'Precisa de ajuda?', ], 'next' => 'Seu apoio nos ajuda a crescer e continuar melhorando o Kanka para todos. Veja o que você pode fazer para desbloquear todos os benefícios da sua assinatura:', 'premium' => [ 'action' => 'Desbloquear', 'helper' => 'Desbloqueie recursos premium como módulos personalizados, acesso aos :plugins e muito mais!', 'title' => 'Habilitar recursos premium em uma campanha', ], 'roadmap' => [ 'action' => 'Veja o roadmap e sugira recursos', 'helper' => 'Estamos construindo o Kanka para você. Confira o roadmap público e sugira ou vote nos recursos que você gostaria de ver!', 'title' => 'Ajude a moldar o futuro de Kanka', ], 'title' => 'Obrigado por apoiar Kanka!', ]; ================================================ FILE: lang/pt-BR/subscriptions/free-trial.php ================================================ [ 'accept' => 'Comece seu teste gratuito', 'magic' => 'Não é necessário cartão de crédito. Apenas pura magia.', ], 'final' => [ 'magic' => 'Sem compromissos, sem armadilhas. Só mais magia para sua campanha.', 'title' => 'Comece agora meu teste de 15 dias', ], 'header' => 'Vimos sua dedicação nos reinos de Kanka. Para honrar sua jornada, estamos lhe concedendo um :what. Não é necessário ouro, gemas ou cartão de crédito.', 'included' => [ 'title' => 'O que está incluído', 'upsell'=> [ 'action' => 'Veja todos os níveis de assinatura', 'pitch' => 'Este é apenas o nível :tier. Pronto para desbloquear ainda mais?', ], ], 'pitch' => [ 'title' => 'Aproveite um teste gratuito de 15 dias conosco! Desbloqueie todos os recursos premium e descubra o que você estava perdendo.', ], 'started' => [ 'header' => 'Você iniciou seu teste gratuito com sucesso. Bem-vindo ao círculo interno dos construtores de mundos!', 'title' => 'Bem-vindo ao seu teste gratuito!', ], 'tease' => [ 'helper' => 'Quer desbloquear mais uploads de arquivos e mais vagas em campanhas premium? Visite a página de assinatura para ver o que mais te espera.', 'title' => 'Mas você quer mais', ], 'title' => 'Uma Nova Missão Aguarda, Aventureiro!', 'what' => ':amount-day de teste gratuito do nível :tier', 'why' => [ 'helper' => 'Você criou, explorou e expandiu sua campanha. Sua lealdade não passou despercebida. Pense nisso como um presente de agradecimento da equipe Kanka.', 'title' => 'Você ganhou esta recompensa', ], ]; ================================================ FILE: lang/pt-BR/subscriptions/promos.php ================================================ [ 'inactive' => 'Esta promoção não está mais ativa.', 'invalid' => 'Promoção desconhecida.', 'only-new' => 'Esta promoção está disponível apenas para novos assinantes.', ], ]; ================================================ FILE: lang/pt-BR/subscriptions/renew.php ================================================ [ 'renew' => 'Renovar assinatura', ], 'helper' => 'No entanto, você pode optar por renovar sua assinatura para aproveitar os benefícios sem interrupções.', 'title' => 'Renovação de assinatura', ]; ================================================ FILE: lang/pt-BR/subscriptions.php ================================================ [ 'failed' => 'Stripe não conseguiu processar seu método de pagamento. Consequentemente, sua assinatura foi desativada.', ], ]; ================================================ FILE: lang/pt-BR/tags.php ================================================ [ 'actions' => [ 'add' => 'Adicionar à tag', 'add_entity' => 'Adicionar à entidade', ], 'create' => [ 'attach_success' => '{1} Adicionada :count entidade à tag :name.|[2,*] Adicionadas :count entidades à tag :name.', 'attach_success_entity' => 'Tags atualizadas com sucesso para :name', 'entity' => 'Adicionar tags a :name', 'helper' => 'Marque uma ou várias entidades com :name', 'title' => 'Entidades de tags', ], ], 'create' => [ 'title' => 'Nova Tag', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Filhos', 'is_auto_applied' => 'Aplicar automaticamente a novas entidades', 'is_hidden' => 'Ocultar do cabeçalho e da dica de contexto', ], 'helpers' => [ 'no_children' => 'No momento não há entidades marcadas com esta tag.', 'no_posts' => 'Atualmente não há nenhuma post marcado com esta tag.', ], 'hints' => [ 'children' => 'Esta lista contém todas entidades diretamente relacionadas a esta tag ou às tags secundárias.', 'is_auto_applied' => 'Marque esta opção para aplicar automaticamente esta tag a entidades recém-criadas.', 'is_hidden' => 'Se marcada. esta tag não será exibida no cabeçalho ou dica de contexto de uma entidade.', 'tag' => 'Esta lista contém todas as tags secundárias desta tag ou de suas tags secundárias.', ], 'index' => [], 'placeholders' => [ 'type' => 'Tradições, Guerras, História, Religião, Vexilologia', ], 'show' => [ 'tabs' => [ 'children' => 'Filhos', ], ], 'tags' => [], 'transfer' => [ 'entities' => [ 'helper' => 'Transferir entidades marcadas com :name para outra tag.', 'title' => 'Transferir entidades', ], 'fail' => 'Falha ao transferir entidades de :tag para :newTag', 'fail_post' => 'Falha ao transferir posts de :tag para :newTag', 'posts' => [ 'helper' => 'Transfira posts marcados com :name para outra tag.', 'title' => 'Transferir posts', ], 'success' => 'Entidades transferidas com sucesso de :tag para :newTag', 'success_post' => 'Posts transferidos com sucesso de :tag para :newTag', 'transfer' => 'Transferir', ], ]; ================================================ FILE: lang/pt-BR/teams.php ================================================ [ 'lead' => 'Tornando a construção de mundo divertida e confiável', 'translations' => 'Traduções', ], 'leads' => [ 'translators' => 'Kanka é traduzido para vários idiomas graças a esses incríveis colaboradores.', ], 'people'=> [ 'itzamna' => [ 'title' => 'Desenvolvedor Junior', ], 'jay' => [ 'title' => 'Fundador & Desenvolvedor Líder', ], 'jon' => [ 'title' => 'Co-Fundador e Gerente de Negócios', ], 'kaz' => [ 'title' => 'Destruidor de Bugs', ], 'laura' => [ 'title' => 'Mídia Social', ], ], ]; ================================================ FILE: lang/pt-BR/tiers.php ================================================ [ 'pay' => [ 'monthly' => 'Pagar mensalmente', 'save' => 'economize 2 meses', 'yearly' => 'Pagar anualmente', ], 'subscribe' => [ 'choose' => 'Escolha :tier', 'monthly' => ':tier mensalmente', 'yearly' => ':tier anualmente', ], ], 'current' => 'Sua assinatura atual', 'features' => [ 'api_requests' => ':amount de solicitações de API / min', 'boosters' => 'Impulsionamentos de Campanha', 'discord' => 'Função e canal exclusivos do :discord', 'feature_influence' => 'Influência em novos recursos', 'file_size' => ':size Tamanho de upload de arquivo', 'import' => 'Importador de campanha', 'nice_image' => 'Imagens de entidade padrão', 'no_ads' => 'Sem anúncios', 'pagination' => ':amount Máximo de resultados paginados (entidades exibidas por página)', 'roadmap' => 'Vote em ideias no roadmap', ], 'periods' => [ 'billed_monthly' => 'cobrado mensalmente', 'billed_yearly' => 'cobrado anualmente', ], 'pricing' => ':currency :amount / mês', 'ribbons' => [ 'best-value' => 'Melhor valor', 'current' => 'Assinatura atual', ], 'target' => [ 'elemental' => 'Para profissionais de construção de mundos que gerenciam múltiplos cenários épicos e campanhas expansivas', 'owlbear' => 'Perfeito para construtores de mundos solo que desejam turbinar sua campanha principal', 'wyvern' => 'Ideal para mestres de jogo que executam múltiplas aventuras ou contadores de histórias colaborativos', ], 'toggle' => [], ]; ================================================ FILE: lang/pt-BR/timelines/elements.php ================================================ [ 'copy_with_name' => 'Copiar menção avançada com o nome do elemento', 'success' => 'Menção avançada ao elemento copiada para a área de transferência.', ], 'create' => [ 'success' => 'Elemento adicionado à linha do tempo.', 'title' => 'Novo Elemento da Linha do Tempo', ], 'delete' => [ 'success' => 'Elemento :name removido.', ], 'edit' => [ 'success' => 'Elemento atualizado', 'title' => 'Editar Elemento da Linha do Tempo', ], 'fields' => [ 'date' => 'Data', 'era' => 'Era', 'icon' => 'Ícone', 'use_entity_entry' => 'Exiba a introdução da entidade anexada abaixo. O texto deste elemento será exibido primeiro se estiver presente.', 'use_event_date' => 'Use a data do evento vinculado.', ], 'helpers' => [ 'date' => 'Se o elemento estiver vinculado a uma entidade de evento, exiba a data do evento.', 'entity_is_private' => 'O elemento da entidade está privada.', 'icon' => 'Copiar o HTML de um ícone de :fontawesome ou :rpgawesome.', 'is_collapsed' => 'O elemento é exibido recolhido por padrão.', ], 'placeholders' => [ 'date' => 'ex: 42 de Março ou 1332-1337', 'name' => 'Obrigatório se nenhuma entidade for selecionada', 'position' => 'Posição na lista de elementos da era. Deixe em branco para adicionar ao final.', ], 'warning' => [], ]; ================================================ FILE: lang/pt-BR/timelines/eras.php ================================================ [ 'add' => 'Adicionar uma nova era', ], 'bulks' => [ 'delete' => '{0} Removida :count era.|{1} Removida :count era.|[2,*] Removidas :count eras.', ], 'create' => [ 'success' => 'Era :name criada.', 'title' => 'Nova Era', ], 'delete' => [ 'success' => 'Era :name removida.', ], 'edit' => [ 'success' => 'Era :name atualizada.', 'title' => 'Editar Era :name', ], 'fields' => [ 'abbreviation' => 'Sigla', 'end_year' => 'Ano Final', 'is_collapsed' => 'Recolhido', 'start_year' => 'Ano Inicial', ], 'helpers' => [ 'eras' => 'Uma linha do tempo precisa ser criada antes que eras possam ser adicionadas a ela.', 'is_collapsed' => 'Era é recolhida (minimizada) por padrão.', 'primary' => 'Separe sua linha do tempo em eras. Uma linha do tempo precisa de pelo menos uma era para funcionar corretamente.', ], 'index' => [ 'title' => 'Eras de :name', ], 'placeholders' => [ 'abbreviation' => 'AC, DC, AEC', 'end_year' => 'Ano em que a era termina. Deixe em branco se esta for a era atual.', 'name' => 'Era Moderna, Idade do Bronze, Guerras Galácticas', 'start_year' => 'Ano em que a era começa. Deixe em branco se esta for a primeira era.', ], 'reorder' => [], ]; ================================================ FILE: lang/pt-BR/timelines.php ================================================ [ 'add_element' => 'Adicionar elemento à :era', 'back' => 'Voltar para :name', 'save_order' => 'Salvar nova ordem', ], 'create' => [ 'title' => 'Nova Linha do Tempo', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_elements' => 'Copiar Elementos', 'copy_eras' => 'Copiar Eras', 'eras' => 'Eras', 'reverse_order' => 'Ordem reversa da era', ], 'helpers' => [ 'no_era_v2' => 'Esta linha do tempo atualmente não tem nenhuma era. Adicione uma ou várias eras, após o qual você pode adicionar elementos às eras aqui.', 'reverse_order' => 'Habilite para exibir linhas do tempo em ordem cronológica reversa (eras antigas primeiro)', ], 'index' => [], 'lists' => [ 'empty' => 'Crie uma linha do tempo visual para registrar os principais eventos e acompanhar a evolução do seu mundo.', ], 'placeholders' => [ 'type' => 'Primária, Crônica Mundial, Legado do Reino', ], 'reorder' => [ 'empty' => 'Adicione eras e elementos à linha do tempo para poder reordená-la.', 'success' => ':name reordenada com sucesso.', 'title' => 'Reordenar :name', ], 'show' => [ 'tabs' => [ 'reorder-elements' => 'Reordenar elementos', ], ], 'timelines' => [], ]; ================================================ FILE: lang/pt-BR/tiptap.php ================================================ 'Experimentando o novo editor? Compartilhe (leva apenas 2 minutos)', ]; ================================================ FILE: lang/pt-BR/users/profile.php ================================================ [ 'wordsmith' => 'Um verdadeiro forjador de palavras! Vencedor de um evento de construção de mundo.', ], 'fields' => [ 'achievements' => 'Conquistas', 'banned' => 'Este usuário foi banido', 'entities_created' => 'Entidades criadas :help :count', 'member_since' => 'Membro desde :date', 'public_campaigns' => 'Campanhas públicas', 'subscriber_since' => 'Inscrito desde :date', ], 'helpers' => [ 'entities_created' => 'Esse valor é recalculado todo dia.', ], 'title' => 'Perfil :name', ]; ================================================ FILE: lang/pt-BR/validation.php ================================================ 'O campo :attribute deve ser aceito.', 'active_url' => 'O campo :attribute deve conter uma URL válida.', 'after' => 'O campo :attribute deve conter uma data posterior a :date.', 'after_or_equal' => 'O campo :attribute deve conter uma data superior ou igual a :date.', 'alpha' => 'O campo :attribute deve conter apenas letras.', 'alpha_dash' => 'O campo :attribute deve conter apenas letras, números e traços.', 'alpha_num' => 'O campo :attribute deve conter apenas letras e números .', 'array' => 'O campo :attribute deve conter um array.', 'before' => 'O campo :attribute deve conter uma data anterior a :date.', 'before_or_equal' => 'O campo :attribute deve conter uma data inferior ou igual a :date.', 'between' => [ 'numeric' => 'O campo :attribute deve conter um número entre :min e :max.', 'file' => 'O campo :attribute deve conter um arquivo de :min a :max kilobytes.', 'string' => 'O campo :attribute deve conter entre :min a :max caracteres.', 'array' => 'O campo :attribute deve conter de :min a :max itens.', ], 'boolean' => 'O campo :attribute deve conter o valor verdadeiro ou falso.', 'confirmed' => 'A confirmação para o campo :attribute não coincide.', 'date' => 'O campo :attribute não contém uma data válida.', 'date_equals' => 'O campo :attribute deve ser uma data igual a :date.', 'date_format' => 'A data informada para o campo :attribute não respeita o formato :format.', 'different' => 'Os campos :attribute e :other devem conter valores diferentes.', 'digits' => 'O campo :attribute deve conter :digits dígitos.', 'digits_between' => 'O campo :attribute deve conter entre :min a :max dígitos.', 'dimensions' => 'O valor informado para o campo :attribute não é uma dimensão de imagem válida.', 'distinct' => 'O campo :attribute contém um valor duplicado.', 'email' => 'O campo :attribute não contém um endereço de email válido.', 'exists' => 'O valor selecionado para o campo :attribute é inválido.', 'file' => 'O campo :attribute deve conter um arquivo.', 'filled' => 'O campo :attribute é obrigatório.', 'gt' => [ 'numeric' => 'O campo :attribute deve ser maior que :value.', 'file' => 'O arquivo :attribute deve ser maior que :value kilobytes.', 'string' => 'O campo :attribute deve ser maior que :value caracteres.', 'array' => 'O campo :attribute deve ter mais que :value itens.', ], 'gte' => [ 'numeric' => 'O campo :attribute deve ser maior ou igual a :value.', 'file' => 'O arquivo :attribute deve ser maior ou igual a :value kilobytes.', 'string' => 'O campo :attribute deve ser maior ou igual a :value caracteres.', 'array' => 'O campo :attribute deve ter :value itens ou mais.', ], 'image' => 'O campo :attribute deve conter uma imagem.', 'in' => 'O campo :attribute não contém um valor válido.', 'in_array' => 'O campo :attribute não existe em :other.', 'integer' => 'O campo :attribute deve conter um número inteiro.', 'ip' => 'O campo :attribute deve conter um IP válido.', 'ipv4' => 'O campo :attribute deve conter um IPv4 válido.', 'ipv6' => 'O campo :attribute deve conter um IPv6 válido.', 'json' => 'O campo :attribute deve conter uma string JSON válida.', 'lt' => [ 'numeric' => 'O campo :attribute deve ser menor que :value.', 'file' => 'O arquivo :attribute ser menor que :value kilobytes.', 'string' => 'O campo :attribute deve ser menor que :value caracteres.', 'array' => 'O campo :attribute deve ter menos que :value itens.', ], 'lte' => [ 'numeric' => 'O campo :attribute deve ser menor ou igual a :value.', 'file' => 'O arquivo :attribute ser menor ou igual a :value kilobytes.', 'string' => 'O campo :attribute deve ser menor ou igual a :value caracteres.', 'array' => 'O campo :attribute não deve ter mais que :value itens.', ], 'max' => [ 'numeric' => 'O campo :attribute não pode conter um valor superior a :max.', 'file' => 'O campo :attribute não pode conter um arquivo com mais de :max kilobytes.', 'string' => 'O campo :attribute não pode conter mais de :max caracteres.', 'array' => 'O campo :attribute deve conter no máximo :max itens.', ], 'mimes' => 'O campo :attribute deve conter um arquivo do tipo: :values.', 'mimetypes' => 'O campo :attribute deve conter um arquivo do tipo: :values.', 'min' => [ 'numeric' => 'O campo :attribute deve conter um número superior ou igual a :min.', 'file' => 'O campo :attribute deve conter um arquivo com no mínimo :min kilobytes.', 'string' => 'O campo :attribute deve conter no mínimo :min caracteres.', 'array' => 'O campo :attribute deve conter no mínimo :min itens.', ], 'not_in' => 'O campo :attribute contém um valor inválido.', 'not_regex' => 'O formato do valor :attribute é inválido.', 'numeric' => 'O campo :attribute deve conter um valor numérico.', 'present' => 'O campo :attribute deve estar presente.', 'regex' => 'O formato do valor informado no campo :attribute é inválido.', 'required' => 'O campo :attribute é obrigatório.', 'required_if' => 'O campo :attribute é obrigatório quando o valor do campo :other é igual a :value.', 'required_unless' => 'O campo :attribute é obrigatório a menos que :other esteja presente em :values.', 'required_with' => 'O campo :attribute é obrigatório quando :values está presente.', 'required_with_all' => 'O campo :attribute é obrigatório quando um dos :values está presente.', 'required_without' => 'O campo :attribute é obrigatório quando :values não está presente.', 'required_without_all' => 'O campo :attribute é obrigatório quando nenhum dos :values está presente.', 'same' => 'Os campos :attribute e :other devem conter valores iguais.', 'size' => [ 'numeric' => 'O campo :attribute deve conter o número :size.', 'file' => 'O campo :attribute deve conter um arquivo com o tamanho de :size kilobytes.', 'string' => 'O campo :attribute deve conter :size caracteres.', 'array' => 'O campo :attribute deve conter :size itens.', ], 'starts_with' => 'O campo :attribute deve começar com um dos seguintes valores: :values', 'string' => 'O campo :attribute deve ser uma string.', 'timezone' => 'O campo :attribute deve conter um fuso horário válido.', 'unique' => 'O valor informado para o campo :attribute já está em uso.', 'uploaded' => 'Falha no Upload do arquivo :attribute.', 'url' => 'O formato da URL informada para o campo :attribute é inválido.', 'uuid' => 'O campo :attribute deve ser um UUID válido.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ 'address' => 'endereço', 'age' => 'idade', 'body' => 'conteúdo', 'city' => 'cidade', 'country' => 'país', 'date' => 'data', 'day' => 'dia', 'description' => 'descrição', 'excerpt' => 'resumo', 'first_name' => 'primeiro nome', 'gender' => 'gênero', 'hour' => 'hora', 'last_name' => 'sobrenome', 'message' => 'mensagem', 'minute' => 'minuto', 'mobile' => 'celular', 'month' => 'mês', 'name' => 'nome', 'password_confirmation' => 'confirmação da senha', 'password' => 'senha', 'phone' => 'telefone', 'second' => 'segundo', 'sex' => 'sexo', 'state' => 'estado', 'subject' => 'assunto', 'text' => 'texto', 'time' => 'hora', 'title' => 'título', 'username' => 'usuário', 'year' => 'ano', 'email' => 'e-mail', 'remember' => 'lembrar-se de mim', ], ]; ================================================ FILE: lang/pt-BR/visibilities.php ================================================ [ 'admin' => 'Somente membros do cargo Admin podem visualizar este elemento.', 'admin-self' => 'Somente você e membros do cargo Admin podem visualizar este elemento.', 'all' => 'Todos podem visualizar esse elemento.', 'members' => 'Somente membros da campanha podem visualizar este elemento.', 'self' => 'Somente você pode ver esse elemento.', ], 'picker' => [ 'admin' => 'Visível apenas para membros com o cargo :admin.', 'admin-self' => 'Somente você e os membros com o cargo :admin podem ver isso.', 'all' => 'Qualquer pessoa que possa ver :entity pode ver isto.', 'failed' => 'Falha ao atualizar a visibilidade.', 'member' => 'Visível apenas para membros da campanha. Útil para campanhas públicas.', 'self' => 'Só você pode ver isso.', ], 'title' => 'Atualizando visibilidade', 'toast' => 'Visibilidade atualizada com sucesso.', 'tooltip' => 'Clique para saber mais sobre as diferentes opções de visibilidade e seus significados em nossa documentação.', ]; ================================================ FILE: lang/pt-BR/whiteboards.php ================================================ [ 'draw' => 'Desenhar', ], 'create' => [ 'title' => 'Novo quadro branco', ], 'cta' => [ 'text' => 'Para desbloquear os quadros brancos, uma campanha precisa ser tornada premium por um membro do nível :wyvern ou :elemental.', 'title' => 'Os quadros brancos são um recurso premium especial', ], 'lists' => [ 'empty' => 'Use um quadro branco para organizar visualmente ideias, relações ou a estrutura da história.', ], ]; ================================================ FILE: lang/ru/abilities.php ================================================ [], 'children' => [ 'actions' => [ 'attach' => 'Добавить объекты', ], 'create' => [ 'attach_success' => '{1} Способность :name добавлена к :count объекту.|[2,*] Способность :name добавлена к :count объектам.', 'helper' => 'Добавьте :name к одному или нескольким объектам.', 'title' => 'Добавление объектов', ], 'description' => 'Объекты с этой способностью', 'title' => 'Объекты способности :name', ], 'create' => [ 'title' => 'Новая способность', ], 'destroy' => [], 'edit' => [], 'entities' => [], 'fields' => [ 'charges' => 'Заряды', ], 'helpers' => [], 'index' => [], 'lists' => [ 'empty' => 'Добавьте сюда силы, заклинания или таланты. Многие используют этот модуль для моделирования классов D&D.', ], 'placeholders' => [ 'charges' => 'Число зарядов. На атрибуты можно ссылаться так: {Уровень}*{ХАР}.', 'name' => 'Огненный шар, сигнал тревоги, коварный удар', 'type' => 'Заклинание, навык, атака', ], 'reorder' => [ 'parentless' => 'Без родителя', 'success' => 'Порядок способностей успешно изменен.', 'title' => 'Изменение порядка способностей', ], 'show' => [ 'tabs' => [ 'reorder' => 'Порядок способностей', ], ], ]; ================================================ FILE: lang/ru/account/email.php ================================================ [ 'update' => 'Обновить почту', ], 'fields' => [ 'email' => 'Новый адрес электронной почты', ], 'helpers' => [ 'email' => 'Убедитесь, что не допустили ошибок.', ], 'subtitle' => 'Измените адрес электронной почты, привязанной к вашему аккаунту.', 'title' => 'Обновление вашей электронной почты', ]; ================================================ FILE: lang/ru/account/password.php ================================================ [ 'update' => 'Обновить пароль', ], 'fields' => [ 'password' => 'Новый пароль', ], 'helpers' => [ 'password' => 'Можете воспользоваться менеджером паролей, чтобы создать надежный пароль.', 'password_confirmation' => 'Не ошибитесь!', ], 'subtitle' => 'Измените свой пароль. Вам придется вводить его заново на всех остальных устройствах.', 'title' => 'Обновление пароля аккаунта', ]; ================================================ FILE: lang/ru/account/social.php ================================================ 'Вход через :provider', 'subtitle' => 'Переключитесь с входа через :provider на вход через Kanka, при котором вы входите по своей электронной почте и паролю.', 'title' => 'Переключение на вход через Kanka', ]; ================================================ FILE: lang/ru/assistance.php ================================================ [ 'campaign' => 'Кампания', ], 'opening' => 'Член команды Kanka направил вас на эту страницу с целью оказать вам помощь.', 'placeholders' => [ 'campaign' => 'Выберите кампанию, в которой вы админ', ], 'select' => 'Выберите кампанию, в которой вы админ, из меню ниже, чтобы сгенерировать специальный одноразовый токен, который позволит члену команды Kanka временно присоединиться к кампании как админ.', 'success' => [ 'opening' => 'Ваш токен оказания помощи успешно сгенерирован. Команда Kanka была уведомлена и вскоре присоединится к вашей помощи и поможет вам. Обычно мы связываемся с вами в :discord, если есть необходимость скоординировать что-то напрямую.', 'secret' => 'Только подтвержденные члены команды Kanka могут воспользоваться этим токеном, он бесполезен для всех остальных, поэтому можете не хранить его в секрете.', 'token' => 'Ваш токен помощи:', ], 'title' => 'Оказание помощи', ]; ================================================ FILE: lang/ru/attribute_templates.php ================================================ [], 'create' => [ 'title' => 'Новый шаблон атрибутов', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'auto_apply' => 'Автоприменение', 'is_enabled' => 'Включен', ], 'hints' => [ 'automatic' => 'Атрибуты (:count) добавлены автоматически по шаблону :link.', 'automatic_apply' => '{1} Атрибут добавлен автоматически по шаблону :link.|[2,] Атрибуты (:count) добавлены автоматически по шаблону :link.', 'entity_type' => 'Этот шаблон атрибутов будет автоматически применяться к новым объектам указанного типа.', 'is_disabled' => 'Этот шаблон отключен.', 'is_enabled' => 'Включите этот шаблон, чтобы использовать его в кампании.', 'parent_attribute_template' => 'Этот шаблон атрибутов можно сделать дочерним другому шаблону атрибутов. При применении этого шаблона, будут также применены все его родительские шаблоны.', ], 'index' => [], 'lists' => [ 'empty' => 'Создайте шаблоны, чтобы легко добавлять общие атрибуты к разным объектам.', ], 'placeholders' => [ 'name' => 'Название шаблона атрибутов', ], 'show' => [], ]; ================================================ FILE: lang/ru/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Ошибка', 'rendering' => 'Произошла ошибка при отображении плагина из каталога. Пожалуйста, свяжитесь с создателем плагина.', ], ], 'helpers' => [], 'list' => [ 'sheets' => 'Листы персонажей', ], 'pitch' => ':marketplace позволяет искать листы персонажей и добавлять их в кампанию с помощью :boosted-campaign.', ]; ================================================ FILE: lang/ru/auth.php ================================================ [ 'permanent' => 'Вы были заблокированы навсегда.', 'temporary' => '{1} Вы были заблокированы на :days день.|[2,4] Вы были заблокированы на :days дня.|[5,20] Вы были заблокированы на :days дней.|[21,*] Вы были заблокированы на :days дн.', ], 'confirm' => [ 'confirm' => 'Подтвердить', 'error' => 'Неверный пароль. Пожалуйста, попробуйте еще раз.', 'helper' => 'Пожалуйста, подтвердите свой пароль, прежде чем вы сможете продолжить.', 'title' => 'Подтверждение пароля', ], 'continue' => [ 'facebook' => 'Продолжить через Facebook', 'google' => 'Продолжить через Google', 'x' => 'Продолжить через X', ], 'failed' => 'Недействительные учетные данные.', 'helpers' => [ 'password' => 'Показать / Скрыть пароль', ], 'login' => [ 'fields' => [ '2fa' => 'Одноразовый пароль', 'email' => 'Электронная почта', 'password' => 'Пароль', ], 'no-account' => 'Еще нет аккаунта?', 'or' => 'ИЛИ', 'password_forgotten' => 'Забыли пароль?', 'sign-up' => 'Регистрация', 'submit' => 'Войти', 'title' => 'Вход', ], 'register' => [ 'already' => 'Уже есть аккаунт? :login', 'errors' => [ 'email_already_taken' => 'Аккаунт с такой электронной почтой уже зарегистрирован.', 'general_error' => 'При создании вашего аккаунта произошла ошибка. Пожалуйста, попробуйте еще раз.', ], 'fields' => [ 'email' => 'Электронная почта', 'name' => 'Имя пользователя', 'password' => 'Пароль', ], 'log-in' => 'Войти', 'submit' => 'Зарегистрироваться', 'title' => 'Регистрация', 'tos' => 'Регистрируясь, вы подтверждаете, что прочли :terms и :privacy и согласны с ними.', ], 'reset' => [ 'fields' => [ 'email' => 'Адрес электронной почты', 'password' => 'Пароль', 'password_confirmation' => 'Подтвердите ваш пароль', ], 'send' => 'Отправить ссылку на восстановление пароля', 'submit' => 'Восстановить пароль', 'title' => 'Восстановление пароля', ], 'tfa' => [ 'helper' => 'Двухфакторная аутентификация включена. Пожалуйста, предоставьте одноразовый пароль (OTP), предоставленный вашим приложением-аутентификатором.', 'title' => 'Двухфакторная аутентификация', ], 'throttle' => 'Лимит попыток входа. Пожалуйста, попробуйте снова через :seconds сек.', 'x-twitter' => 'X ранее известный как Twitter', ]; ================================================ FILE: lang/ru/banners.php ================================================ 'Используйте промокод :code, чтобы получить скидку 20% на вашу первую годовую подписку!', ]; ================================================ FILE: lang/ru/billing/information.php ================================================ [ 'update' => 'Обновить информацию', ], 'helper' => 'Адрес вашего бизнеса, номер НДС и т.д. могут быть добавлены во все ваши счета.', 'title' => 'Обновление платежной информации', ]; ================================================ FILE: lang/ru/billing/invoices.php ================================================ [ 'download' => 'Скачать PDF', ], 'description' => 'Показаны счета за последние 24 месяца.', 'empty' => 'Счетов не найдено.', 'fields' => [ 'amount' => 'Сумма', 'date' => 'Дата', 'invoice' => 'Счет', 'status' => 'Статус', ], 'paypal' => 'Примите во внимание, что здесь показаны только платежи через Stripe, но не через PayPal.', 'status' => [ 'paid' => 'Оплачен', 'pending' => 'Обрабатывается', ], 'title' => 'История платежей', ]; ================================================ FILE: lang/ru/billing/menu.php ================================================ 'История платежей', 'overview' => 'Основное', 'payment-method' => 'Способ оплаты', ]; ================================================ FILE: lang/ru/billing/payment_methods.php ================================================ 'Способ оплаты', 'types' => [ 'card' => 'Банковская карта', ], ]; ================================================ FILE: lang/ru/bookmarks.php ================================================ [ 'customise' => 'Настроить боковую панель', ], 'create' => [ 'title' => 'Новая закладка', ], 'destroy' => [], 'edit' => [ 'title' => 'Закладка :name', ], 'fields' => [ 'active' => 'Активная', 'dashboard' => 'Домашняя панель', 'default_dashboard' => 'Основная домашняя панель', 'filters' => 'Фильтры', 'menu' => 'Страница', 'position' => 'Позиция', 'random_type' => 'Тип случайного объекта', 'selector' => 'Настройка закладки', 'target' => 'Назначение', ], 'helpers' => [ 'active' => 'Неактивные закладки не будут показаны.', 'css' => 'Добавьте CSS класс, который будет применен к ссылке закладки в боковой панели.', 'dashboard' => 'Создает закладку для одной из дополнительных домашних панелей кампании.', 'default_dashboard' => 'Ссылаться к основной домашней панели кампании. Выбрать один из дополнительных домашних панелей все равно необходимо.', 'entity' => 'Создает закладку для объекта. Поле :menu определяет, какая страница объекта будет открыта.', 'position' => 'Закладки в боковой панели отображаются в порядке возрастания значения этого поля.', 'random' => 'Создает закладку для случайного объекта. Закладку можно настроить так, чтобы она выбирала объекты только одного определенного типа.', 'selector' => 'Настройте, куда закладка отправляет пользователя при нажатии на нее.', 'type' => 'Создает закладку на один из списков объектов. Чтобы добавить фильтры, скопируйте часть URL отфильтрованного списка, стоящую после знака :?, и вставьте ее в поле :filter.', ], 'index' => [], 'lists' => [ 'empty' => 'Создавайте закладки для ваших самых часто открываемых объектов или фильтрованных списков для вашего удобства.', ], 'placeholders' => [ 'filters' => 'location_id=15&type=город', 'menu' => 'Страница меню (используйте последнее слово из URL нужной страницы)', 'tab' => '(устаревшее)', ], 'random_no_entity' => 'Невозможно выбрать случайный объект.', 'random_types' => [ 'any' => 'Любой тип', ], 'reorder' => [ 'success' => 'Порядок закладок изменен.', 'title' => 'Изменение порядка закладок', ], 'show' => [], 'targets' => [ 'dashboard' => 'Одна из домашних панелей кампании', 'entity' => 'Конкретный объект', 'random' => 'Случайный объект', 'select' => 'Выберите назначение', 'type' => 'Список объектов конкретного типа/модуля', ], 'visibilities' => [ 'is_active' => 'Показывать закладку в боковой панели', ], ]; ================================================ FILE: lang/ru/bragi.php ================================================ [ 'generate' => 'Сгенерировать', 'insert' => 'Вставить', ], 'errors' => [ 'invalid-sub' => 'Чтобы получить доступ к этой функции, вам нужно обладать подпиской Wyvern или Elemental.', 'out-of-tokens' => 'У вас закончились токены! Они автоматически пополнятся :date.', ], 'here' => 'здесь', 'intro' => 'Привет! Я :name, ИИ готовый помочь вам сгенерировать предыстории для ваших персонажей на Kanka. Вы можете узнать обо мне больше :here.', 'kankappy' => 'Тайный ученик Канкапыша.', 'loading' => 'Пожалуйста, подождите, я напряженно думаю, и это может занять вплоть до минуты!', 'placeholders' => [ 'prompt' => 'Введите запрос, который будет преобразован в предысторию.', ], 'token-limit' => 'В данный момент у вас :amount токенов. Каждый раз, когда я генерирую предысторию, расходуется токен, так что используйте их мудро!', ]; ================================================ FILE: lang/ru/calendars/weather.php ================================================ [], 'create' => [ 'helper' => 'Добавьте информацию о погоде, которая будет показана в календаре.', 'success' => 'Погода добавлена.', 'title' => 'Новое погодное явление', ], 'destroy' => [ 'success' => 'Погода удалена.', ], 'edit' => [ 'success' => 'Погода обновлена.', 'title' => 'Редактирование погоды', ], 'fields' => [ 'effect' => 'Погодное явление', 'name' => 'Название', 'precipitation' => 'Осадки', 'temperature' => 'Температура', 'weather' => 'Иконка', 'wind' => 'Ветер', ], 'options' => [ 'weather' => [ 'bolt' => 'Гроза', 'cloud' => 'Облака', 'cloud-rain' => 'Дождь', 'cloud-showers-heavy' => 'Ливень', 'cloud-sun' => 'Облака и солнце', 'cloud-sun-rain' => 'Облака, дождь и солнце', 'meteor' => 'Метеорит', 'smog' => 'Туман', 'snowflake' => 'Снег', 'sun' => 'Солнце', 'wind' => 'Ветер', ], ], 'placeholders' => [ 'effect' => 'Магическое или природное явление', 'name' => 'Необязательно, для особых погодных явлений', 'precipitation' => 'Количество осадков', 'temperature' => 'Дневной максимум и минимум', 'wind' => 'Скорость ветра', ], ]; ================================================ FILE: lang/ru/calendars.php ================================================ [ 'add_epoch' => 'Эпоха', 'add_intercalary' => 'Добавить промежуточные дни', 'add_month' => 'Месяц', 'add_moon' => 'Луна', 'add_reminder' => 'Добавить напоминание', 'add_season' => 'Время года', 'add_weather' => 'Настроить погодные явления', 'add_week' => 'Названная неделя', 'add_weekday' => 'День недели', 'add_year' => 'Названный год', 'set_today' => 'Сделать текущим днем', 'today' => 'Сегодня', 'update_weather' => 'Редактировать погоду', ], 'checkboxes' => [ 'is_recurring' => 'Происходит каждый год', ], 'create' => [ 'title' => 'Новый календарь', ], 'destroy' => [], 'edit' => [ 'today' => 'Дата календаря обновлена.', ], 'event' => [ 'create' => [ 'success' => 'Напоминание создано.', 'title' => 'Новое напоминание', ], 'destroy' => 'Напоминание удалено из календаря ":name".', 'edit' => [ 'success' => 'Напоминание обновлено.', 'title' => 'Редактирование напоминание календаря :name', ], 'errors' => [ 'invalid_entity' => 'Недействительный выбор объекта.', ], 'helpers' => [ 'other_calendar' => 'Вы редактируете напоминание календаря :calendar. Модификация этого напоминания изменит его и там, и там.', ], 'success' => 'Напоминание ":event" добавлено в календарь ":calendar".', ], 'events' => [ 'bulks' => [ 'delete' => '{1} Удалено :count напоминание.|[2,4] Удалено :count напоминания.|[5,*] Удалено :count напоминаний.', 'patch' => '{1} Обновлено :count напоминание.|[2,4] Обновлено :count напоминания.|[5,*] Обновлено :count напоминаний.', ], 'end' => '(конец)', 'filters' => [ 'show_after' => 'Сегодня и позже', 'show_all' => 'Все время', 'show_before' => 'Вчера и раньше', ], 'start' => '(начало)', ], 'fields' => [ 'comment' => 'Комментарий', 'current_day' => 'Текущий день', 'current_month' => 'Текущий месяц', 'current_year' => 'Текущий год', 'date' => 'Текущая дата', 'day' => 'День', 'default_layout' => 'Стандартный вид', 'format' => 'Формат', 'is_incrementing' => 'Автообновление даты', 'is_recurring' => 'Повторяющееся', 'leap_year' => 'Високосные года', 'leap_year_amount' => 'Лишние дни', 'leap_year_month' => 'Месяц', 'leap_year_offset' => 'Каждые сколько лет', 'leap_year_start' => 'Первый високосный год', 'length' => 'Продолжительность', 'length_days' => '{1} :count день|[2,4] :count дня|[5,20] :count дней|[21,*] :count дн.', 'month' => 'Месяц', 'months' => 'Месяцы', 'moons' => 'Луны', 'parameters' => 'Параметры', 'recurring_until' => 'Повторяется до какого года', 'reset' => 'Сброс дня недели', 'seasons' => 'Времена года', 'show_birthdays' => 'Показать дни рождения', 'skip_year_zero' => 'Пропустить нулевой год', 'start_offset' => 'Смещение первого дня', 'suffix' => 'Обозначение эры', 'week_names' => 'Названия недель', 'weekdays' => 'Дни недели', 'year' => 'Год', ], 'helpers' => [ 'default_layout' => 'Вид, в котором календарь отображается по умолчанию.', 'format' => 'Настройте формат даты календаря. По умолчанию "d M, y s". Значения букв: d - день, M - месяц, m - номер месяца, y - год, s - обозначение эры. Также разрешены пробел ( ), дефис (-) и запятая (,).', 'month_type' => 'Промежуточные месяцы влияют на луны и времена года, но дни в них не являются определенными днями недели.', 'moon_offset' => 'По умолчанию первое полнолуние происходит в первый день 0-го года. Изменение этого поля влияет на положение первого полнолуния. Это значение может быть отрицательным или положительным, но по модулю не может превышать длину первого месяца.', 'start_offset' => 'По умолчанию календарь начинается с первого дня 0-го года. Изменение этого поля влияет на положение первого дня календаря.', ], 'hints' => [ 'event_length' => 'Сколько дней событие длится. Напоминание будет скрыто по истечению первых двух лет.', 'is_incrementing' => 'Каждый день в 00:00 по UTC будет наступать новый день.', 'leap_year' => 'Добавить високосные года в календарь.', 'months' => 'В вашем календаре должно быть не меньше 2 месяцев.', 'moons' => 'Если добавить в календарь луны, они будут отображаться в календаре во время своих полнолуний и новолуний. Если период лунного цикла длится дольше 10 дней, то первая и последняя четверти тоже будут отображаться.', 'parent_calendar' => 'Календарь наследует напоминания и погодные явления от родительского календаря.', 'reset' => 'Каждый месяц или год будет начинаться с первого дня недели.', 'seasons' => 'Создайте времена года, просто указав, когда они наступают. Kanka позаботится обо всем остальном.', 'show_birthdays' => 'Дни рождения персонажей, у которых есть напоминания о днях рождения в этом календаре, будут отображаться в этом календаре вплоть до даты их смерти.', 'skip_year_zero' => 'По умолчанию, календарь начинается с нулевого года. Включите это, чтобы пропустить нулевой год.', 'weekdays' => 'Дайте названия дням недели. Их должно быть не меньше двух.', 'weeks' => 'Дайте названия наиболее важным неделям вашего календаря.', 'years' => 'Некоторые года настолько важны, что у них есть отдельные названия.', ], 'index' => [], 'layouts' => [ 'month' => 'Месяц', 'monthly' => 'Месяц', 'year' => 'Год', 'yearly' => 'Год', ], 'lists' => [ 'empty' => 'Создайте календарь для ведения учета датам, фестивалям или внтуриигровым событиям по ходу времени.', ], 'modals' => [ 'switcher' => [ 'title' => 'Переключатель лет', ], ], 'month_types' => [ 'intercalary' => 'Промежуточный', 'standard' => 'Обычный', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'По полнолуниям', 'fullmoon_name' => 'Полнолуние луны :moon', 'month' => 'Ежемесячно', 'newmoon' => 'По новолуниям', 'newmoon_name' => 'Новолуние луны :moon', 'none' => 'Нет', 'unnamed_moon' => 'Луна :number', 'year' => 'Ежегодно', ], ], 'resets' => [ '' => 'Никогда', 'month' => 'Каждый месяц', 'year' => 'Каждый год', ], ], 'panels' => [ 'intercalary' => 'Промежуточные дни', 'leap_year' => 'Високосный год', 'months' => 'Месяцы', 'weeks' => 'Недели', 'years' => 'Названия годов', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Продолжительность в днях', 'month' => 'В конце какого месяца', 'name' => 'Название промежуточного периода', ], 'month' => [ 'alias' => 'Другое название', 'length'=> 'Число дней', 'name' => 'Название месяца', 'type' => 'Тип', ], 'moon' => [ 'fullmoon' => 'Дней между полнолуниями', 'name' => 'Название луны', 'offset' => 'Смещение первого полнолуния', ], 'seasons' => [ 'day' => 'Первый день', 'month' => 'Первый месяц', 'name' => 'Название времени года', ], 'weeks' => [ 'name' => 'Название недели', 'number' => 'Номер недели', ], 'year' => [ 'name' => 'Название года', 'number' => 'Год', ], ], 'placeholders' => [ 'colour' => 'Цвет', 'comment' => 'День рожденья, фестиваль, солнцестояние', 'date' => 'Текущая дата', 'leap_year_amount' => 'Число лишних дней в високосном году', 'leap_year_month' => 'Месяц, в который входят лишние дни', 'leap_year_offset' => 'Каждые сколько лет год становится високосным', 'leap_year_start' => 'Первый год, являющийся високосным', 'length' => 'Продолжительность события в днях', 'months' => 'Число месяцев в году', 'recurring_until' => 'Последний год повторения (если здесь пусто, будет повторяться вечно)', 'seasons' => 'Число времен года', 'suffix' => 'Обозначение текущей эры (Например: н.э., до н.э.)', 'type' => 'Тип календаря', 'weekdays' => 'Число дней в неделе', ], 'show' => [ 'missing_details' => 'Этот календарь не может быть размещен. Для нормальной работы календаря в нем должно быть не меньше 2 месяцев и 2 дней недели.', 'moon_1first_quarter' => 'Первая четверть луны :moon', 'moon_full' => 'Полнолуние луны :moon', 'moon_last_quarter' => 'Последняя четверть луны :moon', 'moon_new' => 'Новолуние луны :moon', 'tabs' => [ 'events' => 'Напоминания', 'weather' => 'Погода', ], ], 'sorters' => [ 'after' => 'Сегодня и позже', 'before'=> 'Сегодня и раньше', ], 'validators' => [ 'format' => 'Формат даты недействителен.', 'moon_offset' => 'Смещение первого полнолуния не может превышать длину первого месяца календаря.', ], 'warnings' => [ 'event_length' => 'Напоминания, длящиеся несколько лет, видны только в течение первых двух лет. Подробнее об этом рассказывает :documentation.', ], ]; ================================================ FILE: lang/ru/callouts.php ================================================ [ 'subscription' => 'Посмотреть премиум планы', ], 'booster' => [ 'actions' => [ 'boost' => 'Усилить :campaign', 'superboost' => 'Супер-усилить :campaign', ], 'learn-more' => 'Что такое усиление?', 'limitation' => 'Чтобы получить доступ к этой функции, кампания должна быть усилена.', 'limitations' => [ 'boosted' => 'Чтобы получить доступ к этой функции, кампания должна быть усилена.', 'superboosted' => 'Чтобы получить доступ к этой функции, кампания должна быть супер-усилена.', ], 'multiple' => 'Чтобы получить доступ к этим функциям, кампания должна быть усилена.', 'pitches' => [ 'element-class' => 'Присвойте этому элементу уникальный CSS класс с помощью :boosted-campaign.', 'icon' => 'Откройте доступ к миллионам иконок от FontAwesome с помощью :boosted-campaign.', ], 'titles' => [ 'boosted' => 'Функция усиления', 'superboosted' => 'Функция супер-усиления', ], ], 'premium' => [ 'learn-more' => 'Посмотреть все премиум функции', 'limitation' => 'Чтобы получить доступ к этой функции, должны быть открыты премиум функции.', 'multiple' => 'Чтобы получить доступ к этим функциям, должны быть открыты премиум функции.', 'title' => 'Функция премиум кампаний', 'unlock' => 'Открыть премиум функции для :campaign', ], 'subscribe' => [ 'pitch-image' => 'Оформите подписку, чтобы загружать файлы размером до :max МБ.', 'share-booster' => 'Усильте :campaign, чтобы увеличить максимальный размер загрузок для всех участников кампании.', 'share-premium' => 'Увеличьте максимальный размер загрузок файлов для всех участников кампании с помощью премиум кампании.', ], ]; ================================================ FILE: lang/ru/campaigns/achievements.php ================================================ 'Поздравляем!', 'goal_reached' => 'Получено достижение', 'level' => 'уровень :number', 'pitch' => 'Достижения кампании предоставляют интересный способ отмечать новые рубежи, которых вы достигли на вашем пути по созданию нового мира. Следите за прогрессом, давайте игрок возможность оказать влияние, и демонстрируйте ваши успехи с помощью премиум кампании.', 'remaining' => [ 'generic' => 'и будет достигнут следующий уровень.', ], 'tagged' => '{0} Ни одному объекту не добавлены тэги.|[2,*] :amount объектам добавлены тэги.', 'titles' => [ 'calendars' => 'Хранитель времени', 'characters' => 'Именователь', 'connections' => 'Купидон', 'creatures' => 'Животновод', 'dead' => 'Убийца', 'events' => 'Историограф', 'families' => 'Родословная', 'locations' => 'Градостроитель', 'markers' => 'Навигатор', 'organisations' => 'Лидер', 'plugins' => 'Кастомизатор', 'quests' => 'Путеводитель', 'tags' => 'Архивариус', 'themes' => 'Художник', ], 'tutorial' => 'Достижения помогают отслеживать примечательную активность в кампании, такие как создание объектов или использование ключевых функций. Они имеют чисто информативную пользу и обновляются автоматически с ростом кампании.', ]; ================================================ FILE: lang/ru/campaigns/applications.php ================================================ [ 'accept' => 'Принять', 'reject' => 'Отклонить', ], 'apply' => [ 'apply' => 'Отправить', 'help' => 'Эта кампания открыта для новых участников. Чтобы вступить в нее, заполните эту форму. Вы получите уведомление, когда админы кампании рассмотрят вашу заявку.', 'remove_text' => 'вашу заявку', 'success' => [ 'apply' => 'Ваша заявка сохранена. Вы в любой момент можете ее изменить или удалить. Вы получите уведомление, когда админы кампании ее рассмотрят.', 'remove'=> 'Ваша заявка удалена.', 'update'=> 'Ваша заявка обновлена. Вы в любой момент можете ее изменить или удалить. Вы получите уведомление, когда админы кампании ее рассмотрят.', ], 'title' => 'Вступление в :name', ], 'errors' => [], 'fields' => [ 'application' => 'Заявка', ], 'helpers' => [ 'modal' => 'Публичные кампании с открытыми заявками позволяют пользователям подавать заявки на вступление в них.', ], 'placeholders' => [ 'note' => 'Напишите свою заявку на вступление в кампанию.', ], 'statuses' => [], 'toggle' => [ 'closed' => 'Не принимать заявки', 'label' => 'Статус', 'open' => 'Принимать заявки', 'success' => 'Статус принятия заявок на вступление в кампанию обновлен.', 'title' => 'Принятие заявок', ], 'update' => [ 'approve' => 'Выберите роль, в которую будет добавлен пользователь.', 'approved' => 'Заявка принята.', 'reject' => 'Вы можете написать пользователю пояснение причины отклонения его заявки.', 'rejected' => 'Заявка отклонена.', ], ]; ================================================ FILE: lang/ru/campaigns/dashboard-header.php ================================================ [ 'success' => 'Заголовок обзора кампании обновлен.', 'title' => 'Редактирование заголовка обзора кампании', ], ]; ================================================ FILE: lang/ru/campaigns/default-images.php ================================================ [ 'add' => 'Загрузить новый эскиз', ], 'call-to-action' => 'Загрузите собственные эскизы для всех персонажей, локаций или других объектов кампании. Эти изображения используются в различных списках.', 'create' => [ 'error' => 'Ошибка при сохранении эскизов. Возможно, они уже есть у типа :type.', 'success' => 'Эскиз для типа ":type" создан.', 'title' => 'Новый эскиз', ], 'destroy' => [ 'success' => 'Эскиз для типа ":type" удален.', ], 'index' => [], ]; ================================================ FILE: lang/ru/campaigns/export.php ================================================ [ 'export' => 'Экспортировать данные кампании', ], 'errors' => [ 'limit' => 'Кампания уже была экспортирована сегодня. Пожалуйста, попробуйте снова завтра.', ], 'helpers' => [], 'success' => 'Идет подготовка экспорта кампании. Вы получите уведомление на Kanka, как только он будет готов к скачиванию.', 'title' => 'Экспорт кампании', ]; ================================================ FILE: lang/ru/campaigns/gallery.php ================================================ [ 'close' => 'Закрыть', 'save' => 'Сохранить', ], 'breadcrumb' => 'Галерея', 'destroy' => [ 'success' => 'Картинка ":name" удалена.', ], 'fields' => [ 'created_by' => 'Загружена', 'ext' => 'Расширение', 'folder' => 'Папка', 'image_used_in' => '{1}Является изображением 1 объекта.|[2,*]Является изображением :count объектов.', 'name' => 'Название', 'size' => 'Размер', ], 'new_folder' => [ 'title' => 'Новая папка', ], 'no_folder' => 'Без папки', 'pitch' => 'Загружайте изображения в галерею кампании прямо из текстового редактора.', 'placeholders' => [ 'search' => 'Искать по названию...', ], 'title' => 'Галерея кампании :campaign', 'update' => [ 'success' => 'Картинка обновлена.', ], 'uploader' => [ 'add' => 'Добавить новую', 'new_folder' => 'Новая папка', 'or' => 'или', 'select_file' => 'Выберите файл', 'well' => 'Перетащите файл для загрузки', ], ]; ================================================ FILE: lang/ru/campaigns/limits.php ================================================ 'Превышение лимита', ]; ================================================ FILE: lang/ru/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'Отключить плагины', 'enable' => 'Включить плагины', 'update' => 'Обновить плагины', ], 'changelog' => 'История версий', 'disable' => 'Отключить плагин', 'enable' => 'Включить плагин', 'import' => 'Импортировать', 'update' => 'Обновить плагин', 'update_available' => 'Доступно обновление!', ], 'bulks' => [ 'delete' => '{1} Удален :count плагин.|[2,4] Удалено :count плагина.|[5,*] Удалено :count плагинов.', 'disable' => '{1} Отключен :count плагин.|[2,4] Отключено :count плагина.|[5,*] Отключено :count плагинов.', 'enable' => '{1} Включен :count плагин.|[2,4] Включено :count плагина.|[5,*] Включено :count плагинов.', 'update' => '{1} Обновлен :count плагин.|[2,4] Обновлено :count плагина.|[5,*] Обновлено :count плагинов.', ], 'destroy' => [ 'success' => 'Плагин ":plugin" удален.', ], 'disabled' => [ 'success' => 'Плагин ":plugin" отключен.', ], 'empty_list' => 'В данный момент в кампании нет плагинов. Перейдите в каталог, чтобы выбрать и установить плагины, а затем вернитесь сюда и активируйте их.', 'enabled' => [ 'success' => 'Плагин ":plugin" включен.', ], 'errors' => [ 'invalid_plugin' => 'Недействительный плагин.', ], 'fields' => [ 'name' => 'Название плагина', 'status' => 'Статус', 'type' => 'Тип плагина', ], 'import' => [ 'button' => 'Импортировать', 'created' => 'Созданы следующие объекты:', 'helper' => 'Вы собираетесь импортировать объекты из плагина :plugin в количестве :count. Если плагин ранее уже импортировался, ваши изменения импортированных объектов могут быть утеряны.', 'no_new_entities' => 'Не импортированных объектов не обнаружено.', 'option_only_import' => 'Импортировать только новые объекты, пропуская существующие.', 'option_private' => 'Импортировать все объекты приватными.', 'success' => '{1} Импортирован :count объект из плагина ":plugin".|[2,4] Импортировано :count объекта из плагина ":plugin".|[5,*] Импортировано :count объектов из плагина ":plugin".', 'title' => 'Импортирование :plugin', 'updated' => 'Обновлены следующие объекты:', ], 'info' => [ 'helper' => 'При выходе новой версии плагина кампании, его можно будет обновить.', 'title' => 'Обновления плагина :plugin', 'updates' => 'Обновления', ], 'pitch' => 'Устанавливайте и настраивайте плагины, загруженные в :marketplace.', 'status' => [ 'disabled' => 'Отключен', 'enabled' => 'Включен', ], 'templates' => [ 'name' => ':name от :user', ], 'title' => 'Плагины - :name', 'types' => [ 'attribute' => 'Шаблон атрибутов', 'pack' => 'Пакет объектов', 'theme' => 'Тема', ], 'update' => [ 'success' => 'Плагин ":plugin" обновлен.', ], ]; ================================================ FILE: lang/ru/campaigns/public.php ================================================ [], 'title' => 'Изменение доступности кампании', 'update' => [ 'private' => 'Теперь это приватная кампания. Она доступна только ее участникам.', 'public' => 'Теперь эта публичная кампания. Прежде, чем она появится на странице :public-campaigns, может пройти некоторое время.', ], ]; ================================================ FILE: lang/ru/campaigns/recovery.php ================================================ [ 'recover' => 'Восстановить', ], 'error' => 'При попытке восстановления объектов произошла ошибка.', 'fields' => [ 'deleted' => 'Удален', ], 'title' => 'Восстановление объектов - :campaign', ]; ================================================ FILE: lang/ru/campaigns/roles.php ================================================ [ 'status' => 'Статус: :status', ], 'public' => [], 'show' => [ 'title' => 'Разрешения роли :role - :campaign', ], 'toggle' => [ 'disabled' => 'Участники роли :role больше не могут :action :entities.', 'enabled' => 'Участники роли :role теперь могут :action :entities.', ], ]; ================================================ FILE: lang/ru/campaigns/sidebar.php ================================================ [ 'reset' => 'Восстановить умолчания', ], 'call-to-action' => 'Настройте порядок, иконки и названия элементов боковой панели кампании.', 'helpers' => [ 'reordering' => 'Перетаскивайте иконки, чтобы изменить порядок элементов.', ], 'reset' => [ 'success' => 'Настройки по умолчанию для боковой панели восстановлены.', 'title' => 'Восстановление настроек по умолчанию', 'warning' => 'Вы уверены, что хотите восстановить значения по умолчанию для настроек боковой панели?', ], 'success' => 'Настройки боковой панели сохранены.', 'title' => 'Боковая панель - :campaign', ]; ================================================ FILE: lang/ru/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Календари', 'title' => 'Хранитель Времени', ], 'murderer' => [ 'goal' => 'Мертвые персонажи', 'title' => 'Убийца', ], ], 'targets' => [], 'titles' => [ 'calendars' => 'Хранитель Времени уровень :level', 'characters'=> 'Дающий Имена уровень :level', 'dead' => 'Убийца уровень :level', 'families' => 'Семьянин уровень :level', 'locations' => 'Строитель уровень :level', 'quests' => 'Вдохновитель уровень :level', 'races' => 'Животновод уровень :level', ], ]; ================================================ FILE: lang/ru/campaigns/styles.php ================================================ [ 'current' => 'Текущая тема: :theme', 'disable' => 'Отключить', 'enable' => 'Включить', 'new' => 'Новый стиль', ], 'bulks' => [ 'delete' => '{1} Удален :count стиль.|[2,4] Удалено :count стиля.|[5,20] Удалено :count стилей.|{21} Удален :count стиль.|[22,24] Удалено :count стиля.|[25,*] Удалено :count стилей.', 'disable' => '{1} Отключен :count стиль.|[2,4] Отключено count стиля.|[5,20] Отключено :count стилей.|{21} Отключен :count стиль.|[22,24] Отключено :count стиля.|[25,*] Отключено :count стилей.', 'enable' => '{1} Включен :count стиль.|[2,4] Включено count стиля.|[5,20] Включено :count стилей.|{21} Включен :count стиль.|[22,24] Включено :count стиля.|[25,*] Включено :count стилей.', ], 'create' => [ 'success' => 'Новый стиль создан.', 'title' => 'Новый стиль', ], 'delete' => [ 'success' => 'Стиль ":name" удален.', ], 'errors' => [ 'max_content' => 'CSS правило не может быть содержать более :amount символов.', 'max_reached' => 'Достигнуто максимальное количество стилей (:max).', ], 'fields' => [ 'content' => 'CSS', 'is_enabled' => 'Включен', 'length' => 'Длина', 'modified' => 'Изменен', 'name' => 'Название', 'order' => 'Порядок', ], 'helpers' => [ 'here' => 'в нашем блоге', 'main' => 'Вы можете создавать собственные CSS стили для своих усиленных кампаний. Эти стили применяются после всех тем из Каталога, примененных к кампании. Больше о стилях кампаний можно узнать :here.', ], 'pitch' => 'Напишите собственный код на CSS, для полного контроля над видом и атмосферой кампании.', 'reorder' => [ 'save' => 'Сохранить новый порядок', 'success' => '{1} Изменен порядок :count стиля.|[2,20] Изменен порядок :count стилей.|{21} Изменен порядок :count стиля.|[22,*] Изменен порядок :count стилей.', 'title' => 'Изменение порядка стилей', ], 'theme' => [ 'success' => 'Тема кампании обновлена.', 'title' => 'Изменение темы кампании', ], 'title' => 'Стили кампании', 'update' => [ 'success' => 'Стиль ":name" обновлен.', 'title' => 'Редактирование стиля', ], ]; ================================================ FILE: lang/ru/campaigns.php ================================================ [], 'create' => [ 'success' => 'Кампания ":name" создана.', 'title' => 'Новая кампания', ], 'destroy' => [], 'edit' => [ 'success' => 'Кампания ":name" обновлена.', ], 'entity_note_visibility' => [], 'entity_personality_visibilities' => [ 'private' => 'Скрывать личные качества новых персонажей', ], 'entity_visibilities' => [ 'private' => 'Скрывать новые объекты', ], 'errors' => [ 'access' => 'У вас нет доступа к этой кампании.', 'premium' => 'Эта функция доступна только премиум кампаниям.', 'unknown_id' => 'Неизвестная кампания.', ], 'export' => [], 'fields' => [ 'boosted' => 'Усиливает', 'entity_count' => 'Объекты', 'entry' => 'Описание мира', 'followers' => 'Отслеживают', 'genre' => 'Жанр(ы)', 'header_image' => 'Фон заголовка домашней панели', 'image' => 'Изображение боковой панели', 'locale' => 'Язык', 'name' => 'Название', 'open' => 'Принимать заявки на вступление', 'premium' => 'Премиум открыт :name', 'public' => 'Доступность кампании', 'public_campaign_filters' => 'Фильтры для публичной кампании', 'superboosted' => 'Супер-усиливает', 'system' => 'Система', 'theme' => 'Тема', 'vanity' => 'Уникальный URL', ], 'following' => 'Отслеживаемые', 'helpers' => [ 'boosted' => 'Некоторые функции стали доступны благодаря усилению кампании. Подробнее здесь: :settings.', 'css' => 'Напишите здесь собственный код на CSS, и он будет использоваться на страницах вашей кампании. Пожалуйста, примите во внимание, что любое злоупотребление этой функцией может привести к удалению вашего CSS. Повторные или серьезные нарушения могут привести к удалению вашей кампании.', 'dashboard' => 'Настройте вид виджета заголовка кампании на домашней панели с помощью следующих полей.', 'excerpt' => 'Содержимое этого поля будет отображено в виджете заголовка кампании на домашней панели. Напишите пару строк, знакомящих с вашим миром. Если оставить это поле пустым, то будет использоваться первая 1000 символов статьи кампании.', 'header_image' => 'Изображение для фона заголовка кампании на домашней панели.', 'hide_history' => 'Доступ к истории объектов (журналу изменений) будут иметь только участники с ролью :admin.', 'hide_members' => 'Доступ к списку участников кампании будут иметь только участники с ролью :admin.', 'locale' => 'Язык, на котором написана ваша кампания. Это нужно для генерации контента и группировки публичных кампаний.', 'name' => 'Название кампании или мира может быть любым, но оно должно содержать не меньше 4 букв или цифр.', 'no_entry' => 'Похоже у этой кампании еще нет описания! Давайте это исправим.', 'premium' => 'Некоторые функции доступны только премиум кампаниям. Подробнее здесь: :settings.', 'public_campaign_filters' => 'Помогите другим найти эту кампанию среди других публичных кампаний, предоставив следующую информацию.', 'public_no_visibility' => 'Внимание! Ваша кампания является публичной, но у роли "Посетитель" нет доступа к каким-либо объектам. :fix.', 'system' => 'Если ваша кампания публичная, то ее система будет показана здесь: :link.', 'systems' => 'Чтобы не заваливать пользователей настройками, некоторые функции Kanka (статблок монстра D&D 5 ред.) доступны только определенным ролевым системам. Добавьте сюда поддерживаемые системы, чтобы открыть эти функции.', 'theme' => 'Установите тему кампании, не зависящую от предпочтений пользователя.', 'view_public' => 'Чтобы увидеть вашу кампанию, как ее видит посетитель, откройте следующую ссылку в режиме инкогнито: :link.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Скопировать ссылку в буфер обмена', 'link' => 'Пригласить', ], 'create' => [ 'buttons' => [ 'create' => 'Создать ссылку', ], 'success_link' => 'Ссылка создана: :link', 'title' => 'Приглашение в :campaign', ], 'destroy' => [ 'success' => 'Приглашение удалено.', ], 'error' => [ 'inactive_token' => 'Этот токен уже использовался или этой кампании больше не существует.', 'invalid_token' => 'Этот токен больше не действителен.', 'join' => 'Пожалуйста войдите или зарегистрируйтесь, чтобы присоединиться к кампании ":campaign".', ], 'fields' => [ 'created' => 'Создана', 'role' => 'Роль', 'token' => 'Токен', 'type' => 'Тип', 'usage' => 'Лимит на число переходов', ], 'helpers' => [ 'role' => 'Пользователь должен присоединиться, прежде чем стать админом.', 'usage' => 'Сколько раз по ссылке можно перейти до ее деактивации.', ], 'unlimited_validity' => 'Без лимита', 'usages' => [ 'five' => '5 переходов', 'no_limit' => 'Без лимита', 'once' => '1 переход', 'ten' => '10 переходов', ], ], 'leave' => [ 'action' => 'Покинуть кампанию', 'confirm' => 'Вы уверены, что хотите покинуть кампанию :name? Вы не сможете снова получить к ней доступ, пока ее админ не пригласит вас обратно.', 'confirm-button' => 'Да, покинуть кампанию', 'error' => 'Невозможно покинуть кампанию.', 'fix' => 'К участникам кампании', 'no-admin-left' => 'Покинуть кампанию невозможно, поскольку в этом случае в ней не останется админов. Добавьте еще одного участника в роль админа и попробуйте снова.', 'success' => 'Вы покинули кампанию ":campaign".', 'title' => 'Выход из кампании', ], 'members' => [ 'actions' => [ 'remove' => 'Удалить из кампании', 'switch' => 'Тестировать', 'switch-back' => 'Конец тестирования', 'switch-entity' => 'Тестировать', ], 'fields' => [ 'banned' => 'Участник заблокирован', 'joined' => 'Дата присоединения', 'last_login' => 'Последний вход', 'name' => 'Пользователь', 'role' => 'Роль', 'roles' => 'Роли', ], 'helpers' => [ 'switch' => 'Тестировать разрешения пользователя', ], 'impersonating' => [ 'message' => 'Вы просматриваете кампанию как другой пользователь. Некоторые функции отключены, но остальные работают именно так, как их видит этот пользователь.', 'title' => 'Тестирование :name', ], 'invite' => [ 'description' => 'Чтобы добавить участников в кампанию, создайте пригласительную ссылку и отправьте им полученный URL. После принятия приглашения, они будут добавлены в кампанию как участники указанной роли.', 'more' => 'Вы можете создать больше ролей на :link.', 'title' => 'Приглашения', ], 'removal' => 'Вы удаляете участника ":member" из кампании.', 'roles' => [ 'member' => 'Участник', 'owner' => 'Админ', 'player' => 'Игрок', 'public' => 'Посетитель', 'viewer' => 'Наблюдатель', ], 'switch_back_success' => 'Теперь вы снова просматриваете кампанию от своего лица.', ], 'modules' => [], 'open_campaign' => [], 'options' => [], 'overview' => [ 'entity-count' => '{0} Нет объектов|{1} :amount объект|[2,4] :amount объекта|[5,*] :amount объектов', 'follower-count' => '{0} Нет отслеживающих|{1} :amount отслеживает|[2,*] :amount отслеживают', ], 'panels' => [ 'dashboard' => 'Домашняя панель', 'privacy' => 'Настройки доступа', 'setup' => 'Настройка', 'sharing' => 'Доступность', 'systems' => 'Системы', 'ui' => 'Интерфейс', ], 'placeholders' => [ 'locale' => 'Название языка', 'name' => 'Название вашей кампании', 'system' => 'D&D, Pathfinder, Fate, DSA', ], 'privacy' => [ 'hidden' => 'Не показывать', 'private' => 'Скрывать', 'visible' => 'Показывать', ], 'public' => [ 'helpers' => [ 'introduction' => 'По умолчанию кампании являются приватными, но их можно сделать публичными. Это делает их открытыми для всех, а если в них есть объекты доступные роли :public-role, то и доступными здесь: :public-campaigns. Публичные кампании видны всем, но чтобы ее содержимое было открытым, роли :public-role нужны соответствующие разрешения.', ], ], 'roles' => [ 'actions' => [ 'add' => 'Создать роль', 'duplicate' => 'Дубликат роли', 'permissions' => 'Настройка разрешений', 'rename' => 'Переименовать', 'save' => 'Сохранить роль', ], 'admin_role' => 'ролью админа', 'bulks' => [ 'delete' => '{1} Удалена :count роль.|[2,4] Удалено :count роли.|[5,*] Удалено :count ролей.', 'edit' => '{1} Обновлена :count роль.|[2,4] Обновлено :count роли.|[5,*] Обновлено :count ролей.', ], 'create' => [ 'success' => 'Роль ":name" создана.', 'title' => 'Новая роль', ], 'destroy' => [ 'success' => 'Роль ":name" удалена.', ], 'edit' => [ 'success' => 'Роль ":name" обновлена.', 'title' => 'Редактирование роли :name', ], 'fields' => [ 'copy_permissions' => 'Скопировать разрешения', 'name' => 'Название', 'permissions' => 'Разрешения', 'type' => 'Тип', 'users' => 'Пользователи', ], 'helper' => [ '1' => 'Ролей в кампании может быть сколько угодно. Роль :admin автоматически дает доступ ко всему, что есть в кампании. Остальным ролям можно задать отдельные разрешения для каждого типа объектов (персонажи, локации и т. д.).', '2' => 'Разрешения отдельным объектам можно задавать во вкладке "Разрешения" при их редактировании. Эта вкладка отображается, если в кампании несколько ролей или участников.', '3' => 'Можно либо последовать "принципу исключений", открыв ролям доступ ко всем объектам, и скрывать отдельные объекты от всех кроме админов, либо не давать ролям много разрешений, но сделать все объекты видимыми.', '4' => 'Ролей в премиум кампании может быть сколько угодно.', 'permissions_helper' => 'Скопировать все разрешения этой роли: как к модулям, так и к объектам.', ], 'hints' => [ 'campaign_not_public' => 'У роли "Посетитель" есть разрешения, хотя это приватная кампания. Это можно изменить во вкладке "Доступность" при редактировании кампании.', 'empty_role' => 'В этой роли пока нет участников.', 'role_admin' => 'Роль :name автоматически дает участнику доступ ко всему в кампании.', 'role_permissions' => 'Выберите действия, к которым будет иметь доступ роль ":name", для каждого типа объектов.', ], 'members' => 'Участники', 'modals' => [ 'details' => [ 'campaign' => 'Разрешения раздела "Кампания" позволяют следующее:', 'entities' => 'Вот краткое пояснение того, что участники этой роли могут делать при каждом разрешении.', 'more' => 'Для более подробного объяснения, смотрите наше видео на YouTube.', 'title' => 'Пояснение разрешений', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Создать', 'dashboard' => 'Домашняя панель', 'delete' => 'Удалить', 'edit' => 'Изменить', 'gallery' => [ 'browse' => 'Просматривать', 'manage' => 'Полный контроль', 'upload' => 'Загружать изображения', ], 'manage' => 'Управление', 'members' => 'Участники', 'permission'=> 'Разрешения', 'read' => 'Смотреть', 'toggle' => 'Изменить для всех', ], 'helpers' => [ 'add' => 'Позволяет создавать объекты этого типа. Участникам роли будет автоматически разрешено редактировать созданные ими объекты и просматривать их, если у роли нет разрешений на просмотр и редактирование.', 'dashboard' => 'Позволяет редактировать домашние панели и виджеты домашних панелей.', 'delete' => 'Позволяет удалять все объекты этого типа.', 'edit' => 'Позволяет редактировать все объекты этого типа.', 'gallery' => [ 'browse' => 'Позволяет просматривать галерею и использовать изображения из галереи.', 'manage' => 'Позволяет делать в галерее все то, что могут админы, включая редактирование и удаление изображений.', 'upload' => 'Позволяет загружать новые изображения в галерею. Участник будет видеть только загруженные им изображения, если у него нет разрешения на просмотр галереи.', ], 'manage' => 'Позволяет редактировать кампанию как админ, но не позволяет удалять ее.', 'members' => 'Позволяет приглашать новых участников в кампанию.', 'not_public'=> 'Эта кампания не публичная. Разрешения посетительской роли можно редактировать, но они ни на что не повлияют. Сначала сделайте кампанию публичной.', 'permission'=> 'Позволяет настраивать разрешения объектов этого типа, которые участник может редактировать.', 'read' => 'Позволяет просматривать все объекты этого типа, кроме скрытых.', ], ], 'placeholders' => [ 'name' => 'Название роли', ], 'title' => 'Роли - :name', 'types' => [ 'owner' => 'Админ', 'public' => 'Посетитель', 'standard' => 'Основной', ], 'users' => [ 'actions' => [ 'add' => 'Добавить участника', 'remove' => ':user из роли ":role"', 'remove_user' => 'Удалить пользователя из роли', ], 'create' => [ 'success' => 'Пользователь добавлен в роль.', 'title' => 'Добавление участника в роль :name', ], 'destroy' => [ 'success' => 'Пользователь удален из роли.', ], 'errors' => [ 'cant_kick_admins' => 'Во избежание злоупотребления лишить другого участника роли :admin невозможно. Если возникнут проблемы, свяжитесь с нами на :discord или напишите на :email.', 'needs_more_roles' => 'Вам нужно получить другую роль в этой кампании, прежде чем вы сможете лишить себя роли ":admin".', ], 'fields' => [ 'name' => 'Имя', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Подключить', ], 'boosted' => 'Эта функция находится в предварительном доступе и пока доступна только для :boosted.', 'deprecated' => [ 'help' => 'Этот модуль устарел, то есть больше не обновляется и не тестируется на баги при обновлениях Kanka. Пользуйтесь им, принимая во внимание, что рано им поздно он будет удален из Kanka.', 'title' => 'Устаревший', ], 'disabled' => 'Модуль :module отключен.', 'enabled' => 'Модуль :module подключен.', 'errors' => [ 'module-disabled' => 'Запрошенный модуль в данный момент отключен в настройках кампании. :fix.', ], 'helpers' => [ 'abilities' => 'Создавайте способности, будь то навыки, заклинания или силы, и присваивайте их объектам.', 'assets' => 'Загружайте файлы, добавляйте ссылки и псевдонимы для отдельных объектов.', 'bookmarks' => 'Создавайте закладки для объектов или фильтрованных списков, которые будут отображаться в боковой панели.', 'calendars' => 'Здесь можно создавать календари для вашего мира.', 'characters' => 'Создавайте и отслеживайте личностей, населяющих ваш мир.', 'conversations' => 'Вымышленные разговоры между персонажами или пользователями кампании.', 'creatures' => 'Создавайте существ, животных и монстров с помощью модуля существ.', 'dice_rolls' => 'Упрощает работу с бросками костей тем, кто использует Kanka для RPG кампаний.', 'entity_attributes' => 'Следите за атрибутами объектов этого мира, например HP или СКОРОСТЬ.', 'events' => 'Праздники, фестивали, катастрофы, дни рожденья, войны.', 'families' => 'Кланы и семьи, их члены и связи.', 'inventories' => 'Создавайте инвентари для своих объектов.', 'items' => 'Оружие, транспорт, реликвии, зелья.', 'journals' => 'Наблюдения, записанные персонажами, или подготовка ДМа к сессии.', 'locations' => 'Планеты, схемы, континенты, реки, государства, поселения, храмы, таверны.', 'maps' => 'Создавайте карты со слоями и метками, указывающими на другие объекты кампании.', 'notes' => 'Наука, природа, история, магия, культура.', 'organisations' => 'Культы, религии, фракции, гильдии.', 'quests' => 'Создавайте разнообразные квесты с персонажами и локациями.', 'races' => 'Описывайте происхождение, национальность и расовые качества персонажей вашего мира с помощью модуля рас.', 'tags' => 'У каждого объекта могут быть тэги. У тэгов могут быть свои тэги, а статьи можно фильтровать по тэгам.', 'timelines' => 'Расскажите историю вашего мира с помощью хронологий.', 'whiteboards' => 'Рисуйте и пишите на досках, чтобы наглядно распланировать свои мир и цели.', ], ], 'sharing' => [ 'filters' => 'Помогите другим найти вашу кампанию на странице :public-campaigns, добавив подходящие тэги. Это важно только для публичных кампаний.', 'language' => 'Язык статей кампании.', 'system' => 'Используемая вами система (например D&D 5e, Pathfinder)', ], 'show' => [ 'actions' => [ 'edit' => 'Редактировать', ], 'tabs' => [ 'achievements' => 'Достижения', 'customisation' => 'Настройка', 'danger' => 'Опасное', 'data' => 'Данные', 'default-images' => 'Эскизы', 'defaults' => 'Умолчания', 'deletion' => 'Удаление', 'export' => 'Экспорт', 'import' => 'Импорт', 'logs' => 'Журналы', 'management' => 'Сообщество', 'members' => 'Участники', 'plugins' => 'Плагины', 'recovery' => 'Восстановление', 'roles' => 'Роли', 'sidebar' => 'Боковая панель', 'stats' => 'Статистика', 'styles' => 'Стили', 'webhooks' => 'Webhooks', ], 'title' => 'Основное - :name', ], 'status' => [ 'free' => 'Премиум функции не доступны.', 'legacy' => [ 'title' => 'Функции усиления (старое)', ], 'premium' => 'Премиум функции открыты :name.', 'title' => 'Премиум функции', ], 'superboosted' => [], 'themes' => [ 'none' => 'Не важно (по выбору пользователя)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Видны только админам кампании', 'visible' => 'Видны участникам', ], 'fields' => [ 'entity_history' => 'Журналы изменений объектов', 'member_list' => 'Список участников кампании', ], 'helpers' => [ 'entity-history' => 'Настройте, кто может видеть, просматривать и редактировать объекты кампании.', 'member-list' => 'Настройте, кому виден список участников кампании.', 'theme' => 'Назначьте тему, не зависящую от предпочтений участника, или позвольте каждому участнику выбирать свою тему.', ], 'members' => [ 'hidden' => 'Виден только админам кампании', 'visible' => 'Виден участникам', ], ], 'visibilities' => [ 'private' => 'Приватная', 'public' => 'Публичная', 'unlisted' => 'Публичная (скрытая)', ], 'warning' => [], ]; ================================================ FILE: lang/ru/characters.php ================================================ [ 'add_appearance' => 'Добавить внешнее качество', 'add_personality' => 'Добавить личное качество', ], 'conversations' => [], 'create' => [ 'title' => 'Новый персонаж', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'fields' => [ 'age' => 'Возраст', 'is_appearance_pinned' => 'Закрепить внешность', 'is_dead' => 'Мертв(а)', 'is_personality_pinned' => 'Закрепить личность', 'is_personality_visible' => 'Показывать личные качества', 'life' => 'Жизнь', 'physical' => 'Физиология', 'pronouns' => 'Местоимения', 'sex' => 'Гендер', 'title' => 'Титул', 'traits' => 'Черты', ], 'helpers' => [ 'age' => 'Этот объект можно связать с календарем вашей кампании, чтобы его возраст вычислялся автоматически. :more.', ], 'hints' => [ 'is_appearance_pinned' => 'Внешние качества персонажа будут показаны ниже статьи во вкладке "Основное".', 'is_dead' => 'Укажите, мертв ли этот персонаж.', 'is_personality_visible' => 'Вы можете скрыть раздел "Личные качества" от всех пользователей, кроме админов.', 'personality_not_visible' => 'Личные качества этого персонажа могут видеть только админы.', 'personality_visible' => 'Личные качества этого персонажа могут видеть все.', ], 'index' => [], 'items' => [], 'journals' => [], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => 'Персонаж добавлен в организацию.', 'title' => 'Новая организация персонажа :name', ], 'destroy' => [ 'success' => 'Организация персонажа удалена.', ], 'edit' => [ 'success' => 'Организация персонажа обновлена.', 'title' => 'Редактирование организации персонажа :name', ], 'fields' => [ 'role' => 'Роль', ], ], 'placeholders' => [ 'age' => 'Возраст', 'appearance_entry' => 'Описание', 'appearance_name' => 'Глаза, волосы, кожа, рост', 'name' => 'Полное имя персонажа', 'personality_entry' => 'Описание', 'personality_name' => 'Тип (цели, поведение, страхи, принципы)', 'physical' => 'Физиология', 'pronouns' => 'Он, она, они', 'sex' => 'Гендер', 'title' => 'Титул', 'traits' => 'Черты', 'type' => 'Персонаж игрока, Божество, NPC', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Квесты, которые этот персонаж предлагает.', 'quest_member' => 'Квесты, в которых этот персонаж участвует.', ], ], 'sections' => [ 'appearance' => 'Внешность', 'personality' => 'Личность', ], 'show' => [], 'warnings' => [ 'personality_hidden' => 'Вы не можете редактировать личные качества этого персонажа.', ], ]; ================================================ FILE: lang/ru/colours.php ================================================ 'Голубой', 'black' => 'Черный', 'blue' => 'Синий', 'brown' => 'Коричневый', 'green' => 'Зеленый', 'grey' => 'Серый', 'light-blue' => 'Синий', 'maroon' => 'Малиновый', 'navy' => 'Темно-синий', 'none' => 'Нет', 'orange' => 'Оранжевый', 'pink' => 'Розовый', 'purple' => 'Фиолетовый', 'red' => 'Красный', 'teal' => 'Бирюзовый', 'white' => 'Белый', 'yellow' => 'Желтый', ]; ================================================ FILE: lang/ru/concept.php ================================================ 'усиления кампании', 'superboosted-campaign' => 'супер-усиления кампании', ]; ================================================ FILE: lang/ru/conversations.php ================================================ [ 'title' => 'Новый разговор', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_closed' => 'Закрыт', 'messages' => 'Сообщения', 'participants' => 'Участники', ], 'hints' => [ 'participants' => 'Добавьте в разговор участников, нажав на иконку :icon справа вверху.', ], 'index' => [], 'messages' => [ 'destroy' => [ 'success' => 'Сообщение удалено.', ], 'is_updated' => 'Обновлено', 'load_previous' => 'Загрузить предыдущие сообщения', 'placeholders' => [ 'message' => 'Ваше сообщение', ], ], 'participants' => [ 'create' => [ 'success' => 'Участник ":entity" добавлен в разговор.', ], 'destroy' => [ 'success' => 'Участник ":entity" удален из разговора.', ], 'modal' => 'Участники', 'title' => 'Участники :name', ], 'placeholders' => [ 'name' => 'Название разговора', 'type' => 'Игра, подготовка, сюжет', ], 'show' => [ 'is_closed' => 'Разговор закрыт.', ], 'tabs' => [ 'participants' => 'Участники', ], 'targets' => [ 'characters' => 'Персонажи', 'members' => 'Участники кампании', ], ]; ================================================ FILE: lang/ru/creatures.php ================================================ [ 'title' => 'Новое существо', ], 'creatures' => [], 'fields' => [], 'helpers' => [], 'placeholders' => [ 'type' => 'Травоядное, водное, мифическое', ], 'show' => [], ]; ================================================ FILE: lang/ru/crud.php ================================================ [ 'actions' => 'Действия', 'apply' => 'Применить', 'back' => 'Назад', 'change' => 'Изменить', 'copy' => 'Копировать', 'copy_mention' => 'Копировать [упоминание]', 'copy_to_campaign' => 'Копировать в кампанию', 'disable' => 'Выключить', 'enable' => 'Включить', 'explore_view' => 'Свернутый вид', 'export' => 'Экспортировать (PDF)', 'find_out_more' => 'Узнать больше', 'go_to' => 'Перейти к :name', 'help' => 'Справка', 'json-export' => 'Экспортировать (JSON)', 'move' => 'Переместить', 'new' => 'Создать', 'new_child' => 'Потомок', 'new_post' => 'Новый пост', 'next' => 'Далее', 'print' => 'Распечатать', 'reset' => 'Сброс', 'transform' => 'Трансформировать', ], 'add' => 'Добавить', 'alerts' => [ 'copy_attribute' => 'Упоминание атрибута скопировано в буфер обмена.', 'copy_invite' => 'Пригласительная ссылка скопирована в буфер обмена.', 'copy_mention' => 'Продвинутое упоминание объекта скопировано в буфер обмена.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Редактор и тэги', 'permissions' => 'Изменить разрешения', ], 'age' => [ 'helper' => 'Перед числом можно поставить + или - , чтобы увеличить или уменьшить возраст на это число.', ], 'buttons' => [ 'label' => 'Выбранное', ], 'edit' => [ 'tagging' => 'Действие с тэгами', 'tags' => [ 'add' => 'Добавить', 'remove' => 'Удалить', ], 'title' => 'Редактирование нескольких объектов', ], 'errors' => [ 'admin' => 'Скрывать и открывать объекты могут только админы кампании.', 'general' => 'При обработке вашего действия произошла ошибка. Пожалуйста, попробуйте снова и свяжитесь с нами, если проблема сохранится. Сообщение ошибки: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Перезапись', ], 'helpers' => [ 'override' => 'Разрешения выбранных объектов будут перезаписаны. Если не включать, то выбранные разрешения будут добавлены к уже существующим.', ], 'title' => 'Изменение разрешений нескольких объектов', ], ], 'bulk_templates' => [ 'bulk_title' => 'Применение шаблона к нескольким объектам', ], 'cancel' => 'Отмена', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Копирование объектов в другую кампанию', 'panel' => 'Копировать', 'title' => 'Копирование :name в другую кампанию', ], 'create' => 'Создать', 'datagrid' => [ 'empty' => 'Здесь пока ничего нет.', ], 'delete_modal' => [ 'callout' => 'Псст!', 'permanent' => 'Это действие необратимо.', 'recoverable' => 'Объекты можно восстанавливать в течение :day дней с помощью :boosted-campaign.', 'title' => 'Подтверждение удаления', ], 'destroy_many' => [], 'edit' => 'Редактировать', 'errors' => [ 'boosted_campaigns' => 'Этой функцией обладают только :boosted.', 'unavailable_feature' => 'Функция недоступна.', ], 'events' => [], 'fields' => [ 'calendar_date' => 'Дата календаря', 'child' => 'Потомок', 'closed' => 'Закрыт', 'colour' => 'Цвет', 'copy_abilities' => 'Копировать способности', 'copy_inventory' => 'Копировать инвентарь', 'copy_links' => 'Копировать ссылки объекта', 'copy_permissions' => 'Копировать разрешения (и заменить значения во вкладке разрешений)', 'copy_posts' => 'Копировать посты (и их разрешения)', 'creator' => 'Создатель', 'date_range' => 'Диапазон дат', 'excerpt' => 'Краткое описание', 'has_entity_files' => 'Есть загруженные файлы', 'has_image' => 'Есть изображение', 'header_image' => 'Изображение заголовка', 'image' => 'Изображение', 'is_closed' => 'Разговор будет закрыт и перестанет принимать новые сообщения.', 'is_private' => 'Скрытый', 'is_private_v3' => 'Показывать этот объект только участникам кампании с ролью :admin-role независимо от любых разрешений.', 'is_star' => 'Закреплено', 'locations' => ':first в :second', 'name' => 'Название', 'position' => 'Позиция', 'replace_mentions' => 'Заменить упоминания атрибутов в статье атрибутами нового объекта.', 'template' => 'Шаблон', 'tooltip' => 'Подсказки', 'type' => 'Тип', 'visibility' => 'Доступ', ], 'files' => [ 'errors' => [ 'max' => 'Вы достигли максимального количества файлов (:max) для этого объекта.', 'no_files' => 'Нет файлов.', ], 'hints' => [ 'limit' => 'Каждому объекту можно загрузить не больше :max файлов.', 'limitations' => 'Форматы: :formats. Размер файла: до :size.', ], ], 'filter' => 'Фильтровать', 'filters' => [ 'all' => 'Фильтр всех потомков', 'clear' => 'Очистить фильтры', 'copy_helper' => 'Используйте скопированные фильтры в буфере обмена в качестве значений для фильтров виджетов обзоров и быстрых ссылок.', 'copy_to_clipboard' => 'Копировать фильтры', 'direct' => 'Фильтр непосредственных потомков', 'filtered' => 'Показано :count из :total объектов типа :entity.', 'lists' => [ 'desktop' => [ 'all' => 'Показать всех членов (:count)', 'filtered' => 'Показать непосредственных членов (:count)', ], ], 'mobile' => [ 'clear' => 'Очистить', 'copy' => 'Копировать', ], 'options' => [ 'children' => 'С потомками', 'exclude' => 'Отсутствует', 'hide' => 'Скрыть', 'include' => 'Присутствует', 'none' => 'Не важно', 'show' => 'Показать', ], 'show' => 'Показать фильтры', 'sorting' => [ 'asc' => ':field - возрастание', 'desc' => ':field - убывание', 'helper' => 'Управление порядком показа результатов.', ], 'title' => 'Фильтры', ], 'fix-this-issue' => 'Исправьте это', 'forms' => [ 'actions' => [ 'calendar' => 'Добавить дату календаря', ], 'copy_options' => 'Параметры копирования', ], 'helpers' => [ 'copy_options' => 'Следующие элементы объекта будут скопированы в новый объект.', 'linking' => 'Ссылки на другие объекты', ], 'hidden' => 'Скрыт', 'hints' => [ 'calendar_date' => 'Дата календаря позволяет легко фильтровать списки, а также добавляет событие в выбранный календарь.', 'image_limitations' => 'Форматы: :formats. Размер файла: до :size.', 'image_recommendation' => 'Рекомендованный размер: :width на :height пикселей.', 'is_star' => 'Закрепленные элементы отображаются в меню объекта.', 'tooltip' => 'Автоматическая подсказка будет заменена на содержание этого поля. HTML код не поддерживается, но вы можете упоминать другие объекты с помощью продвинутых упоминаний.', ], 'history' => [ 'created_clean' => 'Создано :name :date', 'created_date_clean' => 'Создано :date', 'unknown' => 'Неизвестно', 'updated_clean' => 'Изменено :name :date', 'updated_date_clean' => 'Изменено :date', 'view' => 'Показать историю объекта', ], 'image' => [ 'error' => 'Нам не удалось получить данное изображение. Возможно, сайт не позволяет нам скачать изображение (такое случается со Squarespace и DeviantArt) или эта ссылка больше не действительна. Пожалуйста, убедитесь также, что изображение не превышает :size.', ], 'is_private' => 'Этот объект скрыт и виден только участникам роли "Админ".', 'keyboard-shortcut' => 'Горячая клавиша :code', 'move' => [], 'navigation' => [ 'cancel' => 'Отменить', 'or_cancel' => 'или :cancel', 'skip_to_content' => 'Пропустить меню', ], 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Разрешить', 'deny' => 'Запретить', 'ignore' => 'Не изменять', 'remove' => 'Удалить', ], 'bulk_entity' => [ 'allow' => 'Разрешить', 'deny' => 'Запретить', 'inherit' => 'Наследовать', ], 'delete' => 'Удалить', 'edit' => 'Редактировать', 'toggle' => 'Изменить', ], 'fields' => [ 'member' => 'Участник', 'role' => 'Роль', ], 'helpers' => [ 'setup' => 'Используйте эту страницу, чтобы назначить, как пользователи и роли могут взаимодействовать с этим объектом. :allow позволяет пользователю или роли совершать это действие. :deny запрещает это действие. :inherit будет использовать то же разрешение, что роль пользователя или основная роль. Пользователь с :allow может совершать действие, которое запрещено для его роли.', ], 'success' => 'Разрешения сохранены.', 'title' => 'Разрешения', 'too_many_members' => 'В этой кампании слишком много участников (>:number) для отображения этой страницы. Пожалуйста, используйте кнопку "Разрешения" на странице объекта для детальной настройки разрешений.', ], 'placeholders' => [ 'calendar' => 'Выберите календарь', 'gallery_image' => 'Выберите изображение из галереи', 'image_url' => 'Вы также можете ввести URL изображения.', 'journal' => 'Выберите журнал', 'location' => 'Выберите локацию', 'organisation' => 'Выберите организацию', 'tag' => 'Выберите тэг', 'timeline' => 'Выберите хронологию', ], 'relations' => [], 'remove' => 'Удалить', 'save' => 'Сохранить', 'save_and_close' => 'Сохранить и Закрыть', 'save_and_copy' => 'Сохранить и Копировать', 'save_and_new' => 'Сохранить и Создать', 'save_and_update' => 'Сохранить и Изменить', 'save_and_view' => 'Сохранить и Открыть', 'search' => 'Искать', 'select' => 'Выбрать', 'tabs' => [ 'abilities' => 'Способности', 'inventory' => 'Инвентарь', 'overview' => 'Основное', 'permissions' => 'Разрешения', 'profile' => 'Профиль', 'reminders' => 'Напоминания', ], 'titles' => [ 'editing' => 'Редактирование :name', ], 'tooltips' => [], 'update' => 'Редактировать', 'users' => [ 'unknown' => 'Неизвестный', ], 'view' => 'Показать', 'visibilities' => [ 'admin' => 'Админ', 'admin-self' => 'Вы и Админ', 'all' => 'Все', 'members' => 'Участники', 'self' => 'Вы', ], ]; ================================================ FILE: lang/ru/dashboard.php ================================================ [ 'follow' => 'Отслеживать', 'join' => 'Вступить', 'unfollow' => 'Перестать отслеживать', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Изменить параметры', 'new' => 'Новый обзор', ], 'create' => [ 'success' => 'Обзор ":name" создан.', 'title' => 'Новый обзор кампании', ], 'custom' => [ 'text' => 'Вы редактируете обзор :name этой кампании.', ], 'default' => [ 'text' => 'Вы редактируете основной обзор этой кампании.', 'title' => 'Основной обзор', ], 'delete' => [ 'success' => 'Обзор ":name" удален.', ], 'fields' => [ 'copy_widgets' => 'Копировать виджеты', 'name' => 'Название обзора', 'visibility' => 'Доступ', ], 'helpers' => [ 'copy_widgets' => 'Копировать виджеты из обзора :name в новый обзор.', ], 'pitch' => 'Создавайте дополнительные обзоры с разрешениями для каждой роли кампании.', 'placeholders' => [ 'name' => 'Название обзора', ], 'update' => [ 'success' => 'Обзор ":name" обновлен.', 'title' => 'Редактирование обзора :name', ], 'visibility' => [ 'default' => 'Основной', 'none' => 'Недоступный', 'visible' => 'Дополнительный', ], ], 'helpers' => [ 'follow' => 'Кампании, которые вы отслеживаете, находятся в переключателе кампаний (слева вверху) ниже ваших кампаний.', 'join' => 'Эта кампания открыта для новых участников. Нажмите, чтобы отправить заявку на вступление.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Добавить виджет', 'back_to_dashboard' => 'Назад к обзору', 'edit' => 'Редактирование виджета', ], 'reorder' => [ 'success' => 'Порядок виджетов изменен.', ], 'title' => 'Настройка обзора кампании', 'tutorial' => [ 'blog' => 'наше руководство', 'text' => 'Нужна помощь с настройкой обзора кампании? Читайте :blog для пояснения и вдохновения.', ], ], 'title' => 'Обзор кампании', 'widgets' => [ 'calendar' => [ 'actions' => [ 'next' => 'Изменить дату на следующий день', 'previous' => 'Изменить дату на предыдущий день', ], 'previous_events' => 'Прошедшие', 'upcoming_events' => 'Будущие', ], 'campaign' => [ 'helper' => 'Этот виджет отображает заголовок кампании. Он всегда находится в основном обзоре кампании.', ], 'create' => [ 'success' => 'Виджет добавлен в обзор.', ], 'delete' => [ 'success' => 'Виджет удален из обзора.', ], 'fields' => [ 'class' => 'CSS класс', 'dashboard' => 'Обзор', 'name' => 'Заголовок виджета', 'optional-entity' => 'Ссылка на объект', 'order' => 'Сортировка', 'size' => 'Размер', 'width' => 'Ширина', ], 'helpers' => [ 'class' => 'Задайте CSS класс для этого виджета.', 'filters' => 'Нажмите, чтобы посмотреть доступные параметры фильтрации.', ], 'orders' => [ 'name_asc' => 'По названию (А - Я)', 'name_desc' => 'По названию (Я - А)', 'recent' => 'По дате изменения', ], 'random' => [ 'helpers' => [ 'name' => 'Чтобы вставить название случайного объекта, напишите "{name}".', ], ], 'recent' => [ 'advanced_filter' => 'Дополнительный фильтр', 'advanced_filters' => [ 'mentionless' => 'Неупомянающие (объекты, в статьях которых нет упоминаний других объектов)', 'unmentioned' => 'Неупомянутые (объекты, упоминаний которых нет в статьях других объектов)', ], 'entity-header' => 'Использовать изображение заголовка объекта', 'filters' => 'Фильтры', 'help' => 'Показывать только первый элемент, а не список.', 'helpers' => [ 'entity-header' => 'Если у объекта есть изображение заголовка (функция усиленных кампаний), этот виджет будет использовать его, а не основное изображение объекта.', 'show_attributes' => 'Показывать закрепленные атрибуты объекта ниже текста статьи.', 'show_members' => 'Если объект - семья или организация, показывать его членов ниже текста статьи.', 'show_relations' => 'Показывать закрепленные связи объекта ниже текста статьи.', ], 'show_attributes' => 'Показывать закреп. атрибуты', 'show_members' => 'Показывать членов', 'show_relations' => 'Показывать закреп. связи', 'singular' => 'Один объект', 'tags' => 'В списке будут показаны только объекты с этими тэгами.', 'title' => 'Список объектов', ], 'tabs' => [ 'advanced' => 'Дополнительно', 'setup' => 'Основное', ], 'unmentioned' => [ 'title' => 'Неупомянутые объекты', ], 'update' => [ 'success' => 'Виджет обновлен.', ], 'widths' => [ '0' => 'Авто', '12'=> 'Вся страница (100%)', '3' => 'Маленькая (25%)', '4' => 'Средняя (33%)', '6' => 'Половина страницы (50%)', '8' => 'Широкая (66%)', '9' => 'Большая (75%)', ], ], ]; ================================================ FILE: lang/ru/datetime.php ================================================ 'день', 'days' => 'дней', 'elapsed_ago' => ':duration назад', 'hour' => 'час', 'hours' => 'часов', 'just_now' => 'только что', 'minute' => 'минуту', 'minutes' => 'минут', 'month' => 'месяц', 'months' => 'месяцев', 'second' => 'секунду', 'seconds' => 'секунд', 'week' => 'неделю', 'weeks' => 'недель', 'year' => 'год', 'years' => 'лет', ]; ================================================ FILE: lang/ru/default.php ================================================ 'Заголовок страницы', ]; ================================================ FILE: lang/ru/dice_roll_results.php ================================================ [ 'title' => 'Результаты бросков костей', ], ]; ================================================ FILE: lang/ru/dice_rolls.php ================================================ [ 'title' => 'Новый бросок костей', ], 'destroy' => [ 'dice_roll' => 'Бросок костей удален.', ], 'edit' => [], 'fields' => [ 'created_at' => 'Бросок совершен в', 'parameters' => 'Параметры броска', 'results' => 'Результаты', 'rolls' => 'Броски', ], 'hints' => [ 'parameters' => 'Какие параметры есть у бросков?', ], 'index' => [ 'actions' => [ 'results' => 'Результаты', ], ], 'placeholders' => [ 'name' => 'Название броска костей', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Бросить кости', ], 'error' => 'Не удалось выполнить бросок. Недействительные параметры.', 'fields' => [ 'creator' => 'Пользователь', 'date' => 'Дата', 'result' => 'Результат', ], 'hint' => 'Результаты всех бросков.', 'success' => 'Кости брошены.', ], 'show' => [ 'tabs' => [ 'results' => 'Результаты', ], ], ]; ================================================ FILE: lang/ru/emails/subscriptions/expiring.php ================================================ 'обновите данные Вашей карты', 'primary' => 'Это автоматическое предупреждение о том, что срок действия Вашей карты :brand **** :last скоро истечет.', 'title' => 'Срок действия карты Вашей подписки истекает.', 'valid' => 'Если Вы хотите сохранить Вашу подписку, пожалуйста :action.', ]; ================================================ FILE: lang/ru/emails/subscriptions/upcoming.php ================================================ 'Если Вы не хотите продлевать свою подписку, просим Вас войти в свой аккаунт и :link.', 'closing' => 'С уважением,', 'dear' => 'Уважаемый пользователь :name!', 'link' => 'отменить свою подписку', 'notice' => 'Важное уведомление о продлении:', 'primary' => 'Это автоматическое напоминание о том, что :date мы автоматически спишем средства с Вашей карты :brand **** :last для оплаты Вашей подписки Kanka.', 'title' => 'Годовая оплата Вашей подписки Kanka', 'valid' => 'Просим убедиться в том, что Ваша кредитная карта будет действительна в день оплаты.', ]; ================================================ FILE: lang/ru/emails/welcome.php ================================================ 'Добро пожаловать в Kanka, :name!', 'header_sub' => 'Поздравляем, вы сделали первый шаг в создании своего мира на :kanka!', 'pricing' => 'здесь', 'section_1' => 'Куда теперь?', 'section_11' => 'Создайте свой Мир', 'section_2' => 'Самый важный ресурс - это :discord, где вы найдете множество наших преданных пользователей, команду помощи новичкам, а также автора Kanka, который может ответить на любые ваши вопросы.', 'section_4' => 'На нашем :youtube есть несколько видео по основам Kanka. И хотя пока охвачены не все темы, мы регулярно добавляем новые видео.', 'section_4_v2' => 'Наша :knowledge-base охватывает самые базовые вопросы, которые могут у вас возникнуть, а более специфичную помощь вам окажет наша :documentation!', 'section_6' => 'Свяжитесь с нами', 'section_7' => 'Если вы не нашли ответы на свои вопросы или просто хотите с нами связаться, можете найти нас на :facebook или отправить письмо на :email. Мы маленькая команда из 2 друзей, но мы обязательно ответим на каждое полученное письмо, так что, пишите без колебаний!', 'section_8' => 'И напоследок', 'section_9_v2' => 'Мы сделали все основные функции Kanka бесплатными, и мы всегда будем следовать этому пути. Однако, если вы хотите поддержать нас в этом проекте, вы можете оформить подписку и получить доступ к дополнительным функциям, а также нашу вечную благодарность. Узнайте больше :pricing.', 'social_account' => 'Если при входе в аккаунт возникают проблемы, напоминаем, что вы используете вход через :provider. Это можно изменить в настройках вашего аккаунта.', 'title' => 'Знакомство с Kanka', ]; ================================================ FILE: lang/ru/entities/abilities.php ================================================ [ 'add' => 'Добавить способности', 'reset' => 'Сбросить заряды', ], 'create' => [ 'success' => 'Способность ":ability" добавлена объекту ":entity".', 'success_multiple' => 'Способности ":abilities" добавлены объекту ":entity".', 'title' => 'Добавление способностей :name', ], 'fields' => [ 'note' => 'Описание', 'position' => 'Позиция', ], 'helpers' => [ 'note' => 'В этом поле можно ссылаться на объекты с помощью продвинутых упоминаний (:code) и на атрибуты этого объекта ({Сила}).', ], 'import' => [ 'errors' => [ 'no_race' => 'У этого персонажа нет расы.', 'not_character' => 'Этот объект не является персонажем.', ], 'success' => '{0} Добавлено :count способностей.|{1} Добавлена :count способность.|[2,4] Добавлено :count способности.|[5,*] Добавлено :count способностей.', ], 'show' => [ 'helper' => 'Присвойте этому объекту способности. Способность всегда можно скрыть, открыть или удалить. Способности-потомки одной родительской способности будут отображаться при нажатии на прямоугольник с названием родителя.', 'title' => 'Способности объекта :name', ], 'update' => [ 'title' => 'Cпособность объекта :name', ], ]; ================================================ FILE: lang/ru/entities/actions.php ================================================ [ 'set' => 'Назначить статус шаблона', 'success' => [ 'set' => 'Объект ":entity" получил статус шаблона.', 'unset' => 'Объект ":entity" потерял статус шаблона.', ], 'unset' => 'Снять статус шаблона', ], ]; ================================================ FILE: lang/ru/entities/aliases.php ================================================ [ 'add' => 'Добавить название', ], 'create' => [ 'success' => 'Название ":name" добавлено объекту ":entity".', 'title' => 'Дополнительное название объекта :name', ], 'destroy' => [ 'success' => 'Название ":name" удалено.', ], 'fields' => [ 'name' => 'Название', ], 'helpers' => [ 'primary' => 'Объект будет проще найти с помощью поисковой строки или упоминаний, если добавить ему дополнительные имена.', ], 'pitch' => 'Добавьте объекту дополнительные имена, чтобы его было проще найти.', 'placeholders' => [ 'name' => 'Новое название', ], 'unboosted' => [], 'update' => [ 'success' => 'Название ":name" объекта ":entity" обновлено.', 'title' => 'Редактирование названия объекта :name', ], ]; ================================================ FILE: lang/ru/entities/assets.php ================================================ [ 'alias' => 'Псевдонимы', 'file' => 'Файл', 'link' => 'Ссылка', ], 'show' => [ 'title' => 'Ресурсы объекта :name', ], ]; ================================================ FILE: lang/ru/entities/attributes.php ================================================ [ 'manage' => 'Управление', 'more' => 'Другое', 'remove_all' => 'Удалить все', ], 'errors' => [ 'loop' => 'В вычислении этого атрибута обнаружен бесконечный цикл!', ], 'fields' => [ 'community_templates' => 'Шаблоны сообщества', 'is_private' => 'Скрыть атрибуты', 'is_star' => 'Закреплен', 'value' => 'Значение', ], 'filters' => [ 'name' => 'Название атрибута', 'value' => 'Значение атрибута', ], 'helpers' => [ 'delete_all' => 'Вы уверены, что хотите удалить все атрибуты этого объекта?', 'setup' => 'Элементы объекта, такие как HP или интеллект, можно отображать с помощью атрибутов. Добавьте атрибуты в ручную с помощью кнопки :manage или примените шаблон атрибутов.', ], 'hints' => [], 'index' => [ 'success' => 'Атрибуты объекта ":entity" обновлены.', 'title' => 'Атрибуты объекта :name', ], 'live' => [ 'success' => 'Атрибут ":attribute" обновлен.', 'title' => 'Редактирование :attribute', ], 'placeholders' => [ 'attribute' => 'Число побед, рейтинг опасности, инициатива, население', 'block' => 'Название блока', 'checkbox' => 'Название флажка', 'icon' => [ 'class' => 'Класс FontAwesome или RPG Awesome: fas fa-users', 'name' => 'Название иконки', ], 'number' => 'Название числа', 'random' => [ 'name' => 'Название атрибута', 'value' => 'Диапазон вида 1-100 или список значений через запятую', ], 'section' => 'Название раздела', 'value' => 'Значение атрибута', ], 'show' => [ 'title' => 'Атрибуты объекта :name', ], 'template' => [ 'success' => 'Шаблон атрибутов ":name" применен к объекту ":entity".', 'title' => 'Применение шаблона атрибутов к объекту :name', ], 'types' => [ 'attribute' => 'Атрибут', 'block' => 'Блок текста', 'checkbox' => 'Флажок', 'icon' => 'Иконка', 'number' => 'Число', 'random' => 'Случайный', 'section' => 'Раздел', 'text' => 'Блок текста', ], 'update' => [ 'success' => 'Атрибуты объекта ":entity" обновлены.', ], 'visibility' => [ 'entry' => 'Этот атрибут закреплен на странице истории объекта.', 'private' => 'Этот атрибут виден только участникам роли "Админ".', 'public' => 'Этот атрибут виден всем участникам.', 'tab' => 'Этот атрибут отображается только во вкладке атрибутов.', ], ]; ================================================ FILE: lang/ru/entities/events.php ================================================ [ 'type' => 'Тип события', ], 'helpers' => [ 'characters' => 'Если задать тип как дату рождения или смерти этого персонажа, то его возраст будет вычисляться автоматически. :more.', 'founding' => 'Если задать тип :type, то время с момента основания этого объекта будет вычисляться автоматически.', ], 'show' => [ 'actions' => [ 'add' => 'Новое напоминание', ], 'title' => 'Напоминания объекта :name', ], 'types' => [ 'birth' => 'Рождение', 'death' => 'Смерть', 'founded' => 'Основание', 'primary' => 'Обычное', ], 'years-ago' => '{1} :count год назад|[2,4] :count года назад|[5,*] :count лет назад', ]; ================================================ FILE: lang/ru/entities/files.php ================================================ [], 'create' => [ 'title' => 'Новый файл объекта :entity', ], 'destroy' => [ 'success' => 'Файл ":file" удален.', ], 'fields' => [ 'file' => 'Файл', 'name' => 'Название файла', ], 'update' => [ 'success' => 'Файл ":file" обновлен.', ], ]; ================================================ FILE: lang/ru/entities/image.php ================================================ [ 'change_focus' => 'Изменить фокус', 'replace_image' => 'Заменить', 'save-replace' => 'Заменить', 'save_focus' => 'Сохранить фокус', 'view' => 'Открыть', ], 'call-to-action' => 'Нажмите на изображение, чтобы назначить ему точку фокуса.', 'focus' => [ 'breadcrumb' => 'Фокус изображения', 'helper' => 'Нажмите на изображение, чтобы задать точку фокуса. Чтобы убрать точку фокуса, нажмите на нее.', 'panel_title' => 'Фокус изображения', 'success' => 'Фокус изображения обновлен.', 'title' => 'Фокус изображения объекта :name', 'unboosted' => 'Возможность изменять фокус изображений дают :boosted-campaigns.', ], 'replace' => [ 'breadcrumb' => 'Замена изображения', 'panel_title' => 'Замена изображения объекта', 'success' => 'Изображение заменено.', 'title' => 'Замена изображения объекта :name', ], ]; ================================================ FILE: lang/ru/entities/inventories.php ================================================ [], 'create' => [ 'success' => 'Предмет ":item" добавлен объекту ":entity".', 'title' => 'Добавление предмета объекту :name', ], 'destroy' => [ 'success' => 'Предмет ":item" объекта ":entity" удален.', ], 'fields' => [ 'amount' => 'Количество', 'description' => 'Описание', 'is_equipped' => 'Экипирован', 'name' => 'Название', 'position' => 'Нахождение', 'qty' => 'Кол-во', ], 'helpers' => [], 'placeholders' => [ 'amount' => 'Любое количество', 'description' => 'Использован, поврежден, настроен', 'name' => 'Укажите, если не выбран объект.', 'position' => 'На персонаже, в рюкзаке, на складе, в банке', ], 'show' => [ 'helper' => 'Объектам можно присвоить предметы, чтобы создать инвентарь.', 'title' => 'Инвентарь объекта :name', 'unsorted' => 'Несортированный', ], 'update' => [ 'success' => 'Предмет ":name" объекта ":entity" обновлен.', 'title' => 'Редактирование предмета :name', ], ]; ================================================ FILE: lang/ru/entities/links.php ================================================ [ 'add' => 'Добавить ссылку', ], 'call-to-action' => 'Добавьте ссылки на внешние ресурсы объекта, например на DnDBeyond, и они появятся прямо на основной вкладке объекта.', 'create' => [ 'success' => 'Ссылка ":name" добавлена объекту ":entity".', 'title' => 'Новая ссылка объекта :name', ], 'destroy' => [ 'success' => 'Ссылка ":name" удалена.', ], 'fields' => [ 'icon' => 'Иконка', 'name' => 'Название', 'position' => 'Позиция', 'url' => 'URL адрес', ], 'go' => [ 'actions' => [ 'confirm' => 'Да', 'trust' => 'Больше не спрашивать', ], 'description' => 'Эта ссылка отправит вас по адресу :link. Вы уверены, что хотите перейти туда?', 'title' => 'Уход с Kanka', ], 'helpers' => [ 'icon' => 'Вы можете назначить иконку для ссылки. Используйте любые бесплатные иконки с :fontawesome или оставьте поле пустым, чтобы использовать стандартную.', 'parent' => 'Поместите ссылку под одним из элементов боковой панели, а не в разделе быстрых ссылок.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'В усиленных кампаниях объектам можно добавлять ссылки на другие сайты.', 'title' => 'Ссылки объекта :name', ], 'unboosted' => [], 'update' => [ 'success' => 'Ссылка ":name" объекта ":entity" обновлена.', 'title' => 'Редактирование ссылки объекта :name', ], ]; ================================================ FILE: lang/ru/entities/logs.php ================================================ [ 'create' => 'Создание', 'delete' => 'Удаление', 'restore' => 'Восстановление', 'update' => 'Редактирование', 'view' => 'Показать изменения', ], 'call-to-action' => 'Полная история изменений объекта за последние :amount дней доступна для супер-усиленных кампаний.', 'fields' => [ 'action' => 'Действие', 'date' => 'Дата', ], 'impersonated' => 'Во время тестирования :name', 'show' => [ 'title' => 'История объекта :name', ], ]; ================================================ FILE: lang/ru/entities/map-points.php ================================================ 'Этот объект отмечен на следующих картах.', 'title' => 'Точки на картах объекта :name', ]; ================================================ FILE: lang/ru/entities/mentions.php ================================================ [ 'element' => 'Элемент', 'type' => 'Тип', ], 'helper' => 'Этот объект упомянут в следующих объектах, постах или описаниях кампаний.', 'mentioned_in_v2' => 'Этот объект упомянут в :count объектах, постах или кампаниях. :more', 'see_more' => 'Подробнее', 'show' => [ 'title' => 'Упоминания объекта :name', ], 'title' => 'Упоминания объекта', ]; ================================================ FILE: lang/ru/entities/move.php ================================================ [ 'copy' => 'Копировать', ], 'errors' => [ 'permission' => 'У вас нет разрешения на создание объектов этого типа в данной кампании.', 'permission_update' => 'У вас нет разрешения на перемещение этого объекта.', 'same_campaign' => 'Выберите кампанию, в которую нужно переместить объект.', 'unknown_campaign' => 'Неизвестная кампания.', ], 'fields' => [ 'campaign' => 'Кампания', 'copy' => 'Создать копию', 'select_one' => 'Выберите кампанию', ], 'panel' => [ 'description' => 'Выберите кампанию, в которую вы хотите переместить или копировать этот объект.', 'description_bulk_copy' => 'Выберите кампанию, в которую вы хотите копировать выбранные объекты.', 'title' => 'Перемещение или копирование объекта в другую кампанию', ], 'success' => 'Объект ":name" перемещен.', 'success_copy' => 'Объект ":name" скопирован.', 'title' => 'Перемещение :name', ]; ================================================ FILE: lang/ru/entities/notes.php ================================================ [ 'add' => 'Новый пост', 'add_role' => 'Добавить роль', 'add_user' => 'Добавить пользователя', ], 'copy_mention' => [ 'copy' => 'Копировать продвинутое упоминание', 'copy_with_name' => 'Копировать продвинутое упоминание с названием поста', 'success' => 'Продвинутое упоминание поста скопировано в буфер обмена.', ], 'create' => [ 'success' => 'Пост ":name" добавлен объекту ":entity".', ], 'destroy' => [ 'success' => 'Пост ":name" объекта ":entity" удален.', ], 'edit' => [ 'success' => 'Пост ":name" объекта ":entity" обновлен.', ], 'fields' => [ 'creator' => 'Создатель', 'name' => 'Название', 'position' => 'Куда добавить', ], 'footer' => [ 'created' => 'Создано :user :date.', 'updated' => 'Обновлено :user :date.', ], 'hint' => 'Информацию, которая не подходит под стандартные поля объекта или должна быть скрытой, можно добавить в виде постов.', 'hints' => [ 'reorder' => 'Порядок постов можно изменить, нажав на иконку :icon в заголовке объекта.', ], 'index' => [], 'move' => [ 'copy' => 'Создать копию', 'copy_success' => 'Пост ":name" скопирован к объекту ":entity".', 'description' => 'Выберите объект, к которому вы хотите переместить или копировать этот пост.', 'entity' => 'Объект', 'move_success' => 'Пост ":name" перемещен к объекту ":entity".', ], 'placeholders' => [ 'name' => 'Название поста, наблюдения или заметки', ], 'show' => [ 'advanced' => 'Дополнительные разрешения', 'title' => 'Пост :name объекта :entity', ], 'states' => [ 'collapsed' => 'Свернутый', 'expanded' => 'Развернут', ], 'warning' => [], ]; ================================================ FILE: lang/ru/entities/permissions.php ================================================ [ 'text' => 'Этот объект приватен. Разрешения все еще можно настраивать, но пока объект приватен, они ни на что не влияют, объект будет доступен только участникам кампании с ролью :admin.', 'warning' => 'Внимание', ], 'quick' => [ 'empty-permissions' => 'Ни одна роль или пользователь кроме админов не имеет доступа к этому объекту.', 'success' => [ 'private' => 'Объект ":entity" скрыт.', 'public' => 'Объект ":enity" открыт.', ], 'title' => 'Разрешения', 'viewable-by' => 'Просмотр доступен:', ], ]; ================================================ FILE: lang/ru/entities/pins.php ================================================ 'Ссылки', 'title' => 'Закреплено', ]; ================================================ FILE: lang/ru/entities/profile.php ================================================ [ 'edit_profile' => 'Редактировать профиль', ], 'show' => [ 'tab_name' => 'Профиль', 'title' => 'Профиль объекта :name', ], ]; ================================================ FILE: lang/ru/entities/quests.php ================================================ 'Этот объект участвует в следующих квестах.', 'title' => 'Квесты объекта :name', ]; ================================================ FILE: lang/ru/entities/relations.php ================================================ [ 'mode-map' => 'Карта связей', 'mode-table' => 'Таблица связей', ], 'bulk' => [ 'delete' => '{1} Удалена :count связь.|[2,4] Удалено :count связи.|[5,*] Удалено :count связей.', 'success' => [ 'editing' => '{1} Обновлена :count связь.|[2,4] Обновлено :count связи.|[5,*] Обновлено :count связей.', 'editing_partial' => '{1} Обновлена :count из :total связей.|[2,*] Обновлено :count из :total связей.', ], ], 'call-to-action' => 'Исследуйте связи объекта и его отношение к другим объектам кампании наглядно.', 'connections' => [ 'map_point' => 'Точка на карте', 'mention' => 'Упоминание', 'quest_element' => 'Элемент квеста', 'timeline_element' => 'Элемент хронологии', ], 'create' => [ 'new_title' => 'Новая связь', ], 'delete_mirrored' => [ 'helper' => 'Выберите, чтобы также удалить зеркальную связь у объекта связи.', 'option' => 'Удалить зеркальную связь', ], 'destroy' => [ 'mirrored' => 'Зеркальная связь также будет удалена. Это действие необратимо.', 'success' => 'Связь объекта ":entity" с объектом ":target" удалена.', ], 'fields' => [ 'attitude' => 'Отношение', 'owner' => 'Обладатель', 'target' => 'Объект связи', 'two_way' => 'Создать зеркальную связь', 'unmirror' => 'Отвязать от зеркальной связи', ], 'helper' => 'Соединяйте объекты связями, настраивайте отношения и доступ. Также связи можно закрепить в меню объекта.', 'helpers' => [ 'no_relations' => 'Сейчас у этого объекта нет связей с другими объектами кампании.', ], 'hints' => [ 'attitude' => 'Это необязательное поле можно использовать для сортировки списков.', 'two_way' => 'Если сделать связь зеркальной, то такая же связь будет создана для объекта связи. Но, если одну из связей отредактировать, зеркальная не изменится.', ], 'index' => [ 'title' => 'Связи', ], 'options' => [ 'mentions' => 'Связи + связанное + упоминания', 'only_relations' => 'Только прямые связи', 'related' => 'Связи + связанное', 'relations' => 'Связи', 'show' => 'Показать', ], 'panels' => [ 'related' => 'Связанное', ], 'placeholders' => [ 'attitude' => 'От -100 до 100, где 100 это очень позитивное', ], 'show' => [ 'title' => 'Связи объекта :name', ], 'types' => [ 'family_member' => 'Член семьи', 'organisation_member' => 'Член организации', ], 'update' => [ 'success' => 'Связь объекта ":entity" с объектом ":target" обновлена.', 'title' => 'Редактирование связи объекта :name', ], ]; ================================================ FILE: lang/ru/entities/story.php ================================================ [ 'collapse_all' => 'Свернуть все', 'expand_all' => 'Развернуть все', 'load_more' => 'Показать еще', ], 'reorder' => [ 'icon_tooltip' => 'Изменить порядок постов', 'panel_title' => 'Изменение порядка постов', 'save' => 'Сохранить новый порядок', 'success' => 'Порядок постов сохранен.', ], 'update' => [ 'title' => 'Редактирование статьи объекта :entity', ], 'warning' => [], ]; ================================================ FILE: lang/ru/entities/timelines.php ================================================ 'Ниже показаны хронологии, в которых есть элементы, ссылающиеся на этот объект.', 'show' => [ 'title' => 'Хронологии объекта :name', ], ]; ================================================ FILE: lang/ru/entities/transform.php ================================================ [], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Неизвестный или недействительный тип объекта.', ], 'success' => '{1} Тип :count объекта трансформирован в новый: :type.|[2,*] Типы :count объектов трансформированы в новый: :type.', ], 'fields' => [ 'select_one' => 'Выберите тип', 'target' => 'Новый тип объектов', ], 'panel' => [ 'bulk_description' => 'Смена типа нескольких объектов. Имейте в виду, что возможна потеря некоторых данных из-за отличий в наборах полей разных типов объектов.', 'bulk_title' => 'Трансформация нескольких объектов', 'title' => 'Трансформация объекта', ], 'success' => 'Объект ":name" трансформирован.', 'title' => 'Трансформация :name', ]; ================================================ FILE: lang/ru/entities.php ================================================ 'Способности', 'ability' => 'Способность', 'attribute_template' => 'Шаблон атрибутов', 'attribute_templates' => 'Шаблоны атрибутов', 'calendar' => 'Календарь', 'calendars' => 'Календари', 'campaign' => 'Кампания', 'campaigns' => 'Кампании', 'character' => 'Персонаж', 'characters' => 'Персонажи', 'conversation' => 'Разговор', 'conversations' => 'Разговоры', 'creator' => [ 'actions' => [ 'create' => 'Создать', 'more' => 'Добавить информацию', ], 'back' => 'Назад', 'bulk_names' => 'Каждое название на отдельной строке', 'duplicate' => 'Внимание! В кампании уже есть объект этого типа с похожим названием.', 'helper_v2' => 'Создайте основу для нового объекта быстро, не отрываясь от текущего занятия.', 'modes' => [ 'bulk' => 'Групповое создание', 'default' => 'Быстрое создание', ], 'name' => [ 'new' => 'Добавить название', 'remove' => 'Удалить', ], 'success_multiple' => '{1} Новый объект ":link" создан.|[2,*] Новые объекты ":link" созданы.', 'title' => 'Новый объект', 'tooltip' => 'Создайте новый объект, не покидая текущую страницу.', ], 'creature' => 'Существо', 'creatures' => 'Существа', 'dice_roll' => 'Бросок костей', 'dice_rolls' => 'Броски костей', 'event' => 'Событие', 'events' => 'События', 'families' => 'Семьи', 'family' => 'Семья', 'inventories' => 'Инвентари', 'item' => 'Предмет', 'items' => 'Предметы', 'journal' => 'Журнал', 'journals' => 'Журналы', 'location' => 'Локация', 'locations' => 'Локации', 'map' => 'Карта', 'maps' => 'Карты', 'new' => [], 'note' => 'Заметка', 'notes' => 'Заметки', 'organisation' => 'Организация', 'organisations' => 'Организации', 'quest' => 'Квест', 'quest_element' => 'Элемент квеста', 'quests' => 'Квесты', 'race' => 'Раса', 'races' => 'Расы', 'relation' => 'Связь', 'relations' => 'Связи', 'tag' => 'Тэг', 'tags' => 'Тэги', 'timeline' => 'Хронология', 'timeline_element' => 'Элемент хронологии', 'timelines' => 'Хронологии', ]; ================================================ FILE: lang/ru/errors.php ================================================ [ 'body' => 'Кажется, у вас нет разрешения на доступ к этой странице!', 'title' => 'В доступе отказано', ], '403-form' => [ 'help' => 'Это могло произойти из-за выхода времени сессии. Попробуйте войти в Kanka в новом окне перед сохранением.', ], '404' => [ 'body' => 'К сожалению нам не удалось найти нужную вам страницу.', 'title' => 'Страница не найдена', ], '500' => [ 'body' => [ '1' => 'Упс, кажется, что-то пошло не так.', '2' => 'Мы получили сообщение о произошедшей ошибке, но, возможно, вы поможете нам, если расскажите, что вы делали, когда она произошла.', ], 'title' => 'Ошибка', ], '503' => [ 'body' => [ '1' => 'Kanka в данный момент находится на обслуживании, а это обычно означает, что на подходе новое обновление!', '2' => 'Просим прощения за доставленные неудобства. Через пару минут все придет в норму.', ], 'json' => 'Kanka в данный момент находится на обслуживании, пожалуйста, попробуйте снова через несколько минут.', 'title' => 'Обслуживание', ], '503-form' => [], 'footer' => 'Если вам нужна дополнительная помощь, свяжитесь с нами через :email или :discord.', ]; ================================================ FILE: lang/ru/events.php ================================================ [ 'title' => 'Новое событие', ], 'destroy' => [], 'edit' => [], 'events' => [ 'helper' => 'Здесь показаны события, родительским событием которых является это событие.', ], 'fields' => [ 'date' => 'Дата', ], 'helpers' => [ 'date' => 'В это поле можно написать что угодно. Оно не связано с календарями кампании. Чтобы связать событие с календарем, добавьте его в календарь во вкладке напоминаний этого события или на странице календаря.', ], 'index' => [], 'placeholders' => [ 'date' => 'Дата вашего события', 'type' => 'Церемония, фестиваль, катастрофа, битва, рождение', ], 'show' => [], 'tabs' => [ 'calendars' => 'Записи календарей', ], ]; ================================================ FILE: lang/ru/families.php ================================================ [ 'title' => 'Новая cемья', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'members' => 'Это список членов семьи. Персонажа можно добавить в семью при его редактировании через поле "Семья".', ], 'index' => [], 'members' => [], 'placeholders' => [ 'name' => 'Название семьи', 'type' => 'Королевская, знатная, исчезнувшая', ], 'show' => [], ]; ================================================ FILE: lang/ru/faq.php ================================================ [ 'account_settings' => 'Настройки аккаунта', 'answer' => 'Чтобы удалить аккаунт, перейдите в свой :account и прокрутите вниз до раздела "Удаление аккаунта". Это удалит ваш аккаунт и все кампании, где нет других участников, кроме вас.', 'question' => 'Как удалить свой аккаунт?', ], 'app_backup' => [ 'answer' => 'Во избежание потери данных мы создаем их резервные копии два раза в сутки. Мы не хотим рисковать, ведь и наши кампании хранятся на этом сервере!', 'question' => 'Как часто данные Kanka резервируются?', ], 'attribute-templates' => [ 'answer' => <<<'TEXT' Лучший способ объяснить шаблоны атрибутов - привести пример. Давайте представим, что в вашем мире множество локаций, для многих из которых вы хотите создать собственные атрибуты "Население", "Климат", "Преступность". Вы конечно можете просто создавать их отдельно на каждой локации, но это может быть утомительным, и к тому же можно легко забыть создать один из атрибутов, например "Преступность". И здесь шаблоны атрибутов все меняют. Вы можете создать шаблон с этими атрибутами ("Население", "Климат", "Преступность" и т. д.), а затем применить его к вашим локациям. Это добавит локациям атрибуты из шаблона, и вам останется только задать им значения! TEXT , 'question' => 'Что такое шаблоны атрибутов?', ], 'backup' => [ 'answer' => 'Один раз за сутки вы можете экспортировать все данные вашей кампании в виде ZIP файла. В боковом меню выберите "Кампания" и нажмите "Экспорт" в меню "Основное". Вы получите ссылку на экспорт, открытую в течение 30 минут. Вы не сможете загрузить этот экспорт в Kanka, он нужен только для вашего спокойствия, или если вы планируете перестать использовать приложение.', 'question' => 'Как резервировать и экспортировать кампании?', ], 'bugs' => [ 'answer' => 'Просто присоединитесь к нашему :discord серверу и сообщите о баге на канале #errors-and-bugs.', 'question' => 'Как сообщить о баге?', ], 'campaign-sync' => [ 'answer' => 'В Kanka нет такой функции. Однако, если вы хотите, чтобы несколько игровых групп находились в одном мире, попробуйте использовать одну кампанию и отделить группы друг от друга с помощью квестов, тэгов и разрешений.', 'question' => 'Можно ли синхронизировать объекты между несколькими кампаниями?', ], 'conversations' => [], 'custom' => [ 'answer' => 'В Kanka по умолчанию есть определенные типы объектов, взаимодействующих между собой. Чтобы позволить пользователям создавать свои типы объектов, приложение пришлось бы полностью переделывать и отказаться от нашей цели - помогать людям творить, не ломая голову над систематизацией. К тому же, в Kanka есть тэги, выполняющие большинство функций ваших собственных типов объектов.', 'question' => 'Можно ли создавать собственные типы объектов?', ], 'delete-campaign' => [ 'answer' => 'Нажмите "Кампания" в боковом меню, находясь в вашей кампании. Кнопка "Удалить" появится на открывшейся странице, если вы последний участник в кампании. Удаление кампании это необратимое действие, которое удалит все данные кампании, хранящиеся на наших серверах, включая изображения.', 'question' => 'Как удалить кампанию?', ], 'discord' => [ 'answer' => 'Чтобы связать ваш аккаунт Kanka с :discord, сначала нажмите на ваш аватар в правом верхнем углу приложения и нажмите на кнопку "Профиль". Оттуда перейдите в :apps и нажмите "Подключить".', 'question' => 'Как связать мой аккаунт Kanka с Discord?', ], 'early-access' => [ 'answer' => 'Ранний доступ это награда для наших потрясающих подписчиков, дающая им 30 дней на то, чтобы опробовать новейшие функции Kanka раньше всех остальных участников.', 'question' => 'Что такое ранний доступ?', ], 'entity-notes' => [ 'answer' => 'У каждого объекта есть заметки объекта - небольшие фрагменты текста, которые можно скрыть от всех, кроме вас (удобно, когда ГМ не один), от всех, кроме админов, или оставить открытыми для всех. Вы также можете позволить игрокам создавать и редактировать заметки объекта, не давая разрешение на редактирование самого объекта.', 'question' => 'Как частично скрывать информацию на Kanka?', ], 'fields' => [ 'answer' => 'Ответ', 'category' => 'Категория', 'locale' => 'Язык', 'order' => 'Порядок', 'question' => 'Вопрос', ], 'free' => [ 'answer' => <<<'TEXT' Да! Мы убеждены, что ваша финансовая ситуация не должна мешать вам наслаждаться RPG или ворлд-билдингом. Поэтому основные функции приложения всегда будут бесплатными. Однако, если вы хотите играть более активную роль в этом проекте, поддерживать нас и участвовать в голосованиях по выбору функций, выбирая те, что вам важнее всего, то можете сделать это с помощью наших подписок. Помимо участия в выборе направления развития Kanka, поддержка дает вам :boosters, увеличивает максимальный размер загружаемых файлов, размещает ваше имя в Зале Славы, возможность добавить красивые иконки по умолчанию и многое другое! TEXT , 'question' => 'Останется ли приложение бесплатным?', ], 'gods-and-religions' => [ 'answer' => 'Мы советуем создавать богов как персонажей, а религии как организации. Если вы хотите быстро находить богов среди других персонажей, советуем назначить им соответствующий тэг и/или тип.', 'question' => 'Где создавать богов и религии?', ], 'help' => [ 'answer' => 'Прежде всего, спасибо, что хотите помочь! Мы всегда заинтересованы в тех, кто может помочь с переводами, тестированием новых функций, и в тех, кто может помочь новым пользователям. Мы также любим, когда вы помогаете Kanka найти новых пользователей там, где мы их не искали. Рекомендуем присоединиться к нам на :discord, где выделен отдельный канал для помощи друг другу.', 'question' => 'Как я могу помочь Kanka?', ], 'map' => [ 'answer' => 'Модуль карт поддерживает PNG, JPG, WEBP и SVG изображения. Карты могут иметь слои, группы и метки разных форм и размеров, указывающие на другие объекты кампании.', 'question' => 'Можно ли загружать на Kanka карты?', ], 'mobile' => [ 'answer' => 'На данный момент у Kanka нет мобильной версии, но большая часть сайта работает на мобильных устройствах. Мы надеемся, что поддержка через подписки однажды позволит нам нанять кого-нибудь для разработки мобильного приложения, но не стоит ждать этого в ближайшем будущем.', 'question' => 'Есть ли мобильное приложение? Будет ли?', ], 'monsters' => [ 'answer' => 'Мы советуем использовать модуль рас для создания народов, животных, растений, монстров и всего живого, кроме персонажей.', 'question' => 'Где создавать монстров?', ], 'multiworld' => [ 'answer' => 'Вы можете быть участником скольких угодно кампаний, в том числе созданных вами. Для переключения между кампаниями или создания новой, нажмите на название текущей кампании в боковой панели, чтобы открыть переключатель кампаний. Ниже списка ваших кампаний есть кнопка создания новой кампании.', 'question' => 'Можно ли создать больше одной Кампании?', ], 'nested' => [ 'answer' => 'Если вы предпочитаете, чтобы ваши объекты отображались в свернутом виде по умолчанию (как при нажатии кнопки "Свернутый вид"), вы можете сделать это в настройках профиля в разделе "Оформление". Там можно включить опцию "Свернутый вид по умолчанию". Эта опция влияет на все кампании, а не только на ваши. Она не влияет на других пользователей.', 'question' => 'Можно ли сделать списки свернутыми по умолчанию?', ], 'organise_play' => [], 'permissions' => [ 'answer' => 'Конечно, для этого мы и создали Kanka! Вы можете пригласить всех своих игроков в ваши кампании и задать им роли и разрешения. Мы сделали систему невероятно гибкой (вы можете использовать как систему правил, так и систему исключений), чтобы она подходила в максимальном количестве ситуаций.', 'question' => 'Можно ли ограничить информацию, которую видят игроки в моей кампании?', ], 'plans' => [ 'answer' => <<<'TEXT' Долгосрочный план для Kanka - создание многофункционального инструмента для ворлд-билдинга и ведения кампаний любых систем с контентом, управляемым сообществом через "Шаблоны Сообщества". Еще одна наша цель это создание инструментов, интегрируемых с другими платформами, такими как Virtual Tabletop. Мы и сами используем Kanka, так что мы не планируем останавливать разработку и развитие приложения. Однако, на всякий случай, код приложения находится в открытом доступе, и проект может быть продолжен сообществом, если с нами что-нибудь случится. TEXT , 'question' => 'Каковы долгосрочные планы?', ], 'public-campaigns' => [ 'answer' => 'Вы можете посетить :public-campaigns, чтобы посмотреть, как другие используют Kanka для своих кампаний.', 'question' => 'Как другие используют Kanka?', ], 'renaming-modules' => [ 'answer' => 'Kanka не позволяет менять названия модулей. Это сделано для избежания неудобств и грамматических ошибок на языках, слова которых различаются по роду. Однако, усиленные кампании могут менять названия модулей в боковой панели с помощью CSS кампании.', 'question' => 'Можно ли переименовывать модули? Например кланы, вместо семей, или фракции, вместо организаций?', ], 'sections' => [ 'community' => 'Сообщество', 'general' => 'Основное', 'other' => 'Другое', 'permissions' => 'Разрешения', 'pricing' => 'Цены', 'worldbuilding' => 'Ворлд-билдинг', ], 'show' => [ 'return' => 'Назад к ЧаВо', 'timestamp' => 'Обновлено: :date', 'title' => 'ЧаВо - :name', ], 'unboost' => [ 'answer' => 'Из кампании, потерявшей усиление, не удаляются данные, созданные, когда она еще была усилена. Они просто скрываются, как и функции усиления. Если снова ее усилить, данные и функции снова появятся на своих местах.', 'question' => 'Что происходит, когда кампанию перестают усиливать?', ], 'user-switch' => [ 'answer' => 'Разрешения могут запутать, особенно в больших кампаниях. Как админ кампании, вы можете перейти на страницу участников кампании и нажать кнопку "Тестировать" возле участника без роли "Админ". Сделав это, вы сможете увидеть кампанию так, как ее видит выбранный пользователь. Это самый простой способ проверить разрешения Кампании.', 'question' => 'Как проверить работу разрешений кампании?', ], 'visibility' => [ 'answer' => 'Только те, кого вы приглашаете в свою кампанию, могут видеть и взаимодействовать с тем, что вы создали. Ваши данные являются приватными и всегда находятся под вашим контролем. Вы также можете сделать кампанию публичной, чтобы ее могли просматривать незарегистрированные пользователи.', 'question' => 'Может ли кто-нибудь видеть мой мир?', ], ]; ================================================ FILE: lang/ru/fields.php ================================================ [ 'placeholder' => 'Выберите изображение из галереи кампании', ], 'gallery-header' => [ 'description' => 'Если у объекта нет изображения заголовка, можно использовать изображение из галереи кампании.', ], 'gallery-image' => [ 'description' => 'Если у объекта нет изображения, можно использовать изображение из галереи кампании.', ], 'header-image' => [ 'boosted-description' => 'Добавьте изображение для заголовка объекта с помощью :boosted-campaign.', 'description' => 'Добавьте изображение для заголовка объекта. Советуем использовать крупное изображение.', 'title' => 'Изображение заголовка', ], 'tooltip' => [], ]; ================================================ FILE: lang/ru/filters.php ================================================ [ 'copy' => 'Фильтры скопированы в буфер обмена.', ], 'helpers' => [ 'guest' => 'Пожалуйста, войдите в свой аккаунт, чтобы использовать фильтры.', ], ]; ================================================ FILE: lang/ru/footer.php ================================================ 'Блог', 'boosters' => 'Усиление', 'company' => 'Компания', 'language-switcher' => [ 'other' => 'Другие языки', 'title' => 'Выберите язык', ], 'platform' => 'Платформа', 'press-kit' => 'Пресс-кит', 'privacy' => 'Конфиденциальность', 'resources' => 'Ресурсы', 'security' => 'Безопасность', 'status' => 'Состояние сервера', 'terms' => 'Условия', 'translator_call' => 'Платформа Kanka переведена на другие языки благодаря нашему потрясающему сообществу. Если вы хотите помочь перевести Kanka на ваш язык, свяжитесь с нами на :discord!', 'whats-new' => 'Что нового', ]; ================================================ FILE: lang/ru/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Голосования', ]; ================================================ FILE: lang/ru/front/hall-of-fame.php ================================================ 'Зал Славы', ]; ================================================ FILE: lang/ru/front/kb.php ================================================ [], 'show' => [], 'title' => 'База знаний', ]; ================================================ FILE: lang/ru/front/newsletter.php ================================================ [ 'learn_more' => 'Узнать больше', 'subscribe' => 'Подписаться', ], 'fields' => [ 'firstname' => 'Имя', 'lastname' => 'Фамилия', 'notifications' => 'Уведомления', ], 'groups' => [ 'newsletter' => 'Новости', ], 'headline' => 'Подписывайтесь на наши рассылки, чтобы быть в курсе событий Kanka.', 'title' => 'Новости по электронной почте', ]; ================================================ FILE: lang/ru/front.php ================================================ [], 'campaigns' => [], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => 'Понятно', 'link' => 'Узнать больше', 'message' => 'Этот сайт использует cookie-файлы, чтобы его использование было как можно удобнее для вас.', ], 'faq' => [], 'featured_campaigns' => [], 'features' => [ 'api' => [ 'link' => 'Документация API', ], 'patreon' => [ 'api_calls' => 'Повышенный лимит API запросов (90 в минуту)', 'boosts' => 'Усилители кампаний', 'default_image' => 'Эскизы для объектов', 'discord' => 'Приватный :discord канал', 'free' => 'Бесплатно', 'hall_of_fame' => 'Внесение в :link', 'impact' => 'Влияние на будущие функции', 'monthly_vote' => 'Участие в голосованиях по выбору функций', 'pagination' => 'Увеличенная пагинация', 'upload_limit' => 'Максимальный размер файлов', 'upload_limit_map' => 'Максимальный размер карт', ], ], 'first_block' => [], 'footer' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Вы гейм-мастер, ворлд-билдер, или писатель? Мы предлагаем инструмент для ведения настольных кампаний и ворлд-билдинга позволяющий легко организовывать, планировать, и наслаждаться TTRPG кампаниями. Мы все время прислушиваемся к сообществу, а главное, все основные функции приложения бесплатны!', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Обзор кампании', 'login' => 'Вход', 'register' => 'Регистрация', 'register_free' => 'Бесплатная регистрация', ], 'meta' => [ 'description' => ':kanka это многофункциональный онлайн инструмент для ворлд-билдинга и ведения rpg кампаний онлайн', 'title' => ':kanka - Онлайн инструмент для ведения настольных RPG кампаний и ворлд-билдинга', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Бесплатно', 'month' => 'Месяц', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Ворлд-билдинг, Настольные RPG, RPG Кампании', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/ru/general.php ================================================ 'Отменить выбор всего', 'no' => 'Нет', 'required' => 'Обязательно', 'select_all' => 'Выбрать все', 'success' => [ 'created' => 'Объект ":name" создан.', 'deleted' => 'Объект ":name" удален.', 'updated' => 'Объект ":name" обновлен.', ], 'yes' => 'Да', ]; ================================================ FILE: lang/ru/header.php ================================================ [ 'read_all' => 'Показать все', ], 'toggle_navigation' => 'Показать/скрыть навигацию', 'user' => [ 'settings' => 'Настройки', 'sign-out' => 'Выйти', 'your-profile' => 'Мой профиль', ], ]; ================================================ FILE: lang/ru/helpers.php ================================================ [], 'api-filters' => [ 'description' => 'Для конечной точки API с названием :name доступны следующие фильтры.', 'title' => 'Фильтры API', ], 'attributes' => [ 'link' => 'Параметры атрибутов', ], 'calendar-widget' => [ 'info' => 'Почему здесь показаны эти события?', 'title' => 'Виджет календаря', ], 'dice' => [], 'entity_templates' => [], 'filters' => [ 'title' => 'Как использовать фильтры', ], 'link' => [ 'description' => 'На другие объекты вашей кампании можно ссылаться с помощью следующих символов.', ], 'map' => [], 'pins' => [], 'public' => 'Посмотрите видео на YouTube, объясняющее публичные кампании.', 'troubleshooting' => [ 'description' => 'На эту страницу вас направил член команды Kanka. Выберите кампанию из списка, чтобы сгенерировать токен, позволяющий нам временно присоединиться к вашей кампании в роли админа.', 'errors' => [ 'token_exists' => 'Токен для кампании :campaign уже существует.', ], 'save_btn' => 'Сгенерировать токен', 'select_campaign' => 'Выберите кампанию', 'subtitle' => 'Спасите, помогите!', 'success' => 'Пожалуйста, скопируйте следующий токен и отправьте его кому-нибудь из членов команды Kanka.', 'title' => 'Исправление неполадок', ], 'widget-filters' => [ 'description' => 'В некоторых виджетах можно фильтровать список объектов, выбрав тип искомых объектов и предоставив их "значения". Например, фильтр :example покажет только мертвых персонажей типа "NPC".', 'link' => 'фильтрах виджетов', 'title' => 'Фильтры в виджетах обзоров', ], ]; ================================================ FILE: lang/ru/items.php ================================================ [ 'title' => 'Новый предмет', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'price' => 'Цена', 'size' => 'Размеры', ], 'helpers' => [], 'hints' => [], 'index' => [], 'inventories' => [], 'placeholders' => [ 'price' => 'Цена предмета', 'size' => 'Размер, вес, габариты', 'type' => 'Оружие, зелье, артефакт', ], 'quests' => [], 'show' => [ 'tabs' => [ 'inventories' => 'Инвентари', ], ], ]; ================================================ FILE: lang/ru/journals.php ================================================ [ 'title' => 'Новый журнал', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Автор', 'date' => 'Дата', ], 'helpers' => [], 'index' => [], 'journals' => [], 'placeholders' => [ 'author' => 'Тот, кто написал этот журнал', 'date' => 'Дата реального мира для журнала', 'type' => 'Сессия, one-shot, черновик', ], 'show' => [], ]; ================================================ FILE: lang/ru/languages.php ================================================ [ 'ca' => 'Каталанский', 'cs' => 'Чешский', 'de' => 'Немецкий', 'el' => 'Греческий', 'en' => 'Английский', 'en-US' => 'Английский (США)', 'es' => 'Испанский', 'fr' => 'Французский', 'gl' => 'Галисийский', 'he' => 'Иврит', 'hr' => 'Хорватский', 'hu' => 'Венгерский', 'it' => 'Итальянский', 'nb' => 'Норвежский (Букмол)', 'nl' => 'Нидерландский', 'pl' => 'Польский', 'pt-BR' => 'Португальский (Бразилия)', 'ru' => 'Русский', 'sk' => 'Словацкий', 'tr' => 'Турецкий', ], 'header'=> 'Языки', ]; ================================================ FILE: lang/ru/locations.php ================================================ [], 'create' => [ 'title' => 'Новая локация', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [], 'helpers' => [ 'characters' => 'Выбирайте между просмотром списка всех персонажей этой локации и всех ее подлокаций и просмотром только тех, кто относится непосредственно к ней.', ], 'hints' => [], 'index' => [], 'items' => [], 'journals' => [], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'type' => 'Город, королевство, развалины', ], 'show' => [], ]; ================================================ FILE: lang/ru/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Редактировать', 'exit-edit-mode' => 'Исследовать', 'finish-drawing' => 'Закончить рисовать фигуру', ], 'notifications' => [ 'start-drawing' => 'Нажмите на карту, чтобы начать рисовать фигуру', ], ]; ================================================ FILE: lang/ru/maps/groups.php ================================================ [ 'add' => 'Добавить новую группу', ], 'bulks' => [ 'delete' => '{1} Удалена :count группа.|[2,4] Удалено :count группы.|[5,*] Удалено :count групп.', 'patch' => '{1} Обновлена :count группа.|[2,4] Обновлено :count группы.|[5,*] Обновлено :count групп.', ], 'create' => [ 'success' => 'Группа ":name" создана.', 'title' => 'Новая группа', ], 'delete' => [ 'success' => 'Группа ":name" удалена.', ], 'edit' => [ 'success' => 'Группа ":name" обновлена.', 'title' => 'Редактирование группы :name', ], 'fields' => [ 'is_shown' => 'Показывать по умолчанию', 'position' => 'Позиция', ], 'helper' => [ 'amount_v3' => 'Метки можно объединять в группы. При исследовании карты группы меток можно скрыть или показать одним нажатием.', ], 'hints' => [ 'is_shown' => 'Метки группы будут видны по умолчанию.', ], 'index' => [ 'title' => 'Группы карты :name', ], 'pitch' => [], 'placeholders' => [ 'name' => 'Магазины, клады, NPC', 'position' => 'Первая', 'position_list' => 'После :name', ], 'reorder' => [ 'save' => 'Сохранить порядок', 'success' => '{1} Изменен порядок :count группы.|[2,*] Изменен порядок :count групп.', 'title' => 'Изменение порядка групп', ], ]; ================================================ FILE: lang/ru/maps/layers.php ================================================ [ 'add' => 'Новый слой', ], 'base' => 'Основной слой', 'bulks' => [ 'delete' => '{1} Удален :count слой.|[2,4] Удалено :count слоя.|[5,*] Удалено :count слоев.', 'patch' => '{1} Обновлен :count слой.|[2,4] Обновлено :count слоя.|[5,*] Обновлено :count слоев.', ], 'create' => [ 'success' => 'Слой ":name" создан.', 'title' => 'Новый слой', ], 'delete' => [ 'success' => 'Слой ":name" удален.', ], 'edit' => [ 'success' => 'Слой ":name" обновлен.', 'title' => 'Редактирование слоя :name', ], 'fields' => [ 'position' => 'Позиция', 'type' => 'Тип слоя', ], 'helper' => [ 'amount_v2' => 'Загружайте слои карты в виде переключаемых фоновых изображений, показываемых под метками, или перекрывающих, показываемых поверх карты, но под метками.', 'is_real' => 'Слои не доступны при использовании OpenStreetMaps.', ], 'index' => [ 'title' => 'Слои карты :name', ], 'pitch' => [], 'placeholders' => [ 'name' => 'Подземный этаж, уровень 2, затонувший корабль', 'position' => 'Первая', 'position_list' => 'После :name', ], 'reorder' => [ 'save' => 'Сохранить порядок', 'success' => '{1} Изменен порядок :count слоя.|[2,*] Изменен порядок :count слоев.', 'title' => 'Изменение порядка слоев', ], 'short_types' => [ 'overlay' => 'Перекрывающий (скрытый)', 'overlay_shown' => 'Перекрывающий (видимый)', 'standard' => 'Обычный', ], 'types' => [ 'overlay' => 'Перекрывающий, скрытый по умолчанию', 'overlay_shown' => 'Перекрывающий, видимый по умолчанию', 'standard' => 'Обычный (самостоятельный)', ], ]; ================================================ FILE: lang/ru/maps/markers.php ================================================ [ 'entry' => 'Написать отдельную статью для этой метки', 'remove' => 'Удалить метку', 'reset-polygon' => 'Очистить точки', 'save_and_explore' => 'Сохранить и Исследовать', 'start-drawing' => 'Начать рисовать', 'update' => 'Редактировать метку', ], 'bulks' => [ 'delete' => '{1} Удалена :count метка.|[2,4] Удалено :count метки.|[5,*] Удалено :count меток.', 'patch' => '{1} Обновлена :count метка.|[2,4] Обновлено :count метки.|[5,*] Обновлено :count меток.', ], 'create' => [ 'success' => 'Метка ":name" создана.', 'title' => 'Новая метка', ], 'delete' => [ 'success' => 'Метка ":name" удалена.', ], 'edit' => [ 'success' => 'Метка ":name" обновлена.', 'title' => 'Редактирование метки :name', ], 'fields' => [ 'circle_radius' => 'Радиус круга', 'copy_elements' => 'Копировать элементы', 'custom_icon' => 'Другая иконка', 'custom_shape' => 'Настройка формы фигуры', 'font_colour' => 'Цвет иконки', 'group' => 'Группа меток', 'icon' => 'Иконка', 'is_draggable' => 'Подвижная', 'latitude' => 'Широта', 'longitude' => 'Долгота', 'opacity' => 'Непрозрачность', 'pin_size' => 'Размер метки', 'polygon_style' => [ 'stroke' => 'Цвет линий', 'stroke-opacity' => 'Непрозрачность линий', 'stroke-width' => 'Толщина линий', ], ], 'helpers' => [ 'base' => 'Чтобы добавить метку, нажмите в любое место на карте.', 'copy_elements' => 'Копировать группы, слои и метки.', 'copy_elements_to_campaign' => 'Копировать группы, слои и метки. Метки, связанные с объектами, станут обычными метками.', 'custom_icon_v2' => 'Используйте иконки с :fontawesome, :rpgawesome или собственную SVG иконку. Подробнее расскажет :docs.', 'custom_radius' => 'Выберите вариант "Другой" в списке размеров, чтобы задать радиус круга.', 'draggable' => 'Подвижные метки можно двигать в режиме исследования.', 'label' => 'Надпись отображается на карте в виде текста. Его содержание определяется названием метки или объекта.', 'polygon' => [ 'edit' => 'Нажмите на карту, чтобы добавить место нажатия к координатам этой фигуры.', ], ], 'icons' => [ 'custom' => 'Особая', 'entity' => 'Объект', 'exclamation' => 'Восклицание', 'marker' => 'Метка', 'question' => 'Вопрос', ], 'index' => [ 'title' => 'Метки карты :name', ], 'pitches' => [ 'poly' => 'Рисуйте какие угодно многоугольники для отображения границ и других фигур.', ], 'placeholders' => [ 'custom_icon' => 'Попробуйте :example1 или :example2', 'custom_shape' => '100,100 200,240 340,110', 'name' => 'Обязательно, если не выбран объект.', ], 'presets' => [ 'helper' => 'Нажмите на заготовку, чтобы применить ее, или создайте новую.', ], 'shapes' => [ '0' => 'Круг', '1' => 'Квадрат', '2' => 'Треугольник', '3' => 'Особая', ], 'sizes' => [ '0' => 'Крошечная', '1' => 'Обычная', '2' => 'Маленькая', '3' => 'Большая', '4' => 'Огромная', ], 'tabs' => [ 'circle' => 'Круг', 'label' => 'Надпись', 'marker' => 'Метка', 'polygon' => 'Фигура', 'preset' => 'Заготовка', ], ]; ================================================ FILE: lang/ru/maps.php ================================================ [ 'back' => 'Назад к :name', 'edit' => 'Редактировать карту', 'explore' => 'Исследовать', ], 'create' => [ 'title' => 'Новая карта', ], 'destroy' => [], 'edit' => [], 'errors' => [ 'chunking' => [ 'error' => 'При сегментировании карты произошла ошибка. Для помощи, пожалуйста, свяжитесь с командой на :discord.', 'running' => [ 'edit' => 'Карту нельзя редактировать до завершения сегментирования.', 'explore' => 'Карту нельзя просматривать до завершения сегментирования.', 'time' => 'Это может занять от нескольких минут до нескольких часов, в зависимости от размера карты.', ], ], 'dashboard' => [ 'missing' => 'Чтобы отобразить карту в обзоре кампании, ей нужно изображение.', ], 'explore' => [ 'missing' => 'Чтобы исследовать карту, добавьте ей изображение.', ], ], 'fields' => [ 'center_marker' => 'Метка', 'center_x' => 'Долгота по умолчанию', 'center_y' => 'Широта по умолчанию', 'centering' => 'Центрирование', 'distance_measure' => 'Измерение расстояния', 'distance_name' => 'Единица измерения расстояния', 'grid' => 'Сетка', 'has_clustering' => 'Объединять метки в кластеры', 'initial_zoom' => 'Изначальное приближение', 'is_real' => 'Использовать OpenStreetMaps', 'max_zoom' => 'Максимальное приближение', 'min_zoom' => 'Минимальное приближение', 'tabs' => [ 'coordinates' => 'Координаты', 'marker' => 'Метка', ], ], 'helpers' => [ 'center' => 'Следующие значения влияют на то, на какую часть карты фокус наведен изначально. Чтобы навести фокус на центр карты, оставьте эти поля пустыми.', 'centering' => 'Центрирование на метке не зависит от указанных координат по умолчанию.', 'chunked_zoom' => 'Автоматически объединять метки в кластеры, если они находятся рядом.', 'distance_measure' => 'Указание меры расстояния откроет инструмент измерения расстояний в режиме исследования. Чтобы 100 пикселей равнялись 1 километру, значение должно быть 0.0041.', 'distance_measure_2' => 'Чтобы 100 пикселей равнялись километру, введите 0.0041', 'grid' => 'Укажите размер сетки, отображаемой в режиме исследования.', 'has_clustering' => 'Автоматически объединять метки в кластеры, если они находятся рядом.', 'initial_zoom' => 'Уровень приближения карты при ее загрузке. По умолчанию он равен :default, максимальное допустимое значение :max, а минимальное :min.', 'is_real' => 'Включите это, если хотите использовать карту реального мира, а не загруженную картинку. Это сделает слои недоступными.', 'max_zoom' => 'Максимальный уровень приближения карты. По умолчанию он равен :default, максимальное допустимое значение :max.', 'min_zoom' => 'Минимальный уровень приближения карты. По умолчанию он равен :default, минимальное допустимое значение это :min.', 'missing_image' => 'Добавьте карте изображение и сохраните ее, чтобы получить возможность добавлять слои и метки.', ], 'index' => [], 'maps' => [], 'panels' => [ 'groups' => 'Группы', 'layers' => 'Слои', 'markers' => 'Метки', 'settings' => 'Настройки', ], 'placeholders' => [ 'center_marker' => 'Чтобы центрировать карту на середину, оставьте поле пустым.', 'center_x' => 'Чтобы центрировать карту на середину, оставьте поле пустым.', 'center_y' => 'Чтобы центрировать карту на середину, оставьте поле пустым.', 'distance_name' => 'Км, миль, футов, гамбургеров', 'grid' => 'Расстояние в пикселях между линиями сетки. Пусто - нет сетки.', 'name' => 'Название карты', 'type' => 'Подземелье, город, галактика', ], 'show' => [ 'tabs' => [ 'maps' => 'Карты', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'Идет сегментирование карты. Это может занять от пару минут до нескольких часов.', ], ], ]; ================================================ FILE: lang/ru/misc.php ================================================ [ 'subscribing' => 'подписки', ], ]; ================================================ FILE: lang/ru/notes.php ================================================ [ 'title' => 'Новая заметка', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'Подзаметки', ], 'helpers' => [], 'hints' => [], 'index' => [], 'placeholders' => [ 'note' => 'Выберите родительскую заметку', 'type' => 'Религия, раса, политика', ], 'show' => [], ]; ================================================ FILE: lang/ru/notifications.php ================================================ [ 'application' => [ 'approved' => 'Ваша заявка на вступление в кампанию :campaign одобрена.', 'approved_message' => 'Ваша заявка на вступление в кампанию :campaign одобрена. Сообщение: :reason', 'new' => 'Новая заявка на вступление в кампанию :campaign.', 'rejected' => 'Ваша заявка на вступление в кампанию :campaign отклонена. Причина: :reason', 'rejected_no_message' => 'Ваша заявка на вступление в кампанию :campaign отклонена.', ], 'asset_export' => 'Экспорт изображений кампании готов. Ссылка доступна в течение :time минут.', 'boost' => [ 'add' => 'Кампанию :campaign теперь усиливает :user.', 'remove' => 'Пользователь :user перестал усиливать кампанию :campaign.', 'superboost' => 'Кампанию :campaign теперь супер-усиливает :user.', ], 'deleted' => 'Кампания :campaign была удалена.', 'export' => 'Экспорт кампании готов. Ссылка доступна в течение :time минут.', 'export_error' => 'При экспорте изображений вашей кампании произошла ошибка. Пожалуйста, свяжитесь с нами, если проблема повторится.', 'hidden' => 'Кампания :campaign убрана со страницы публичных кампаний.', 'join' => 'Пользователь :user присоединился к кампании :campaign.', 'leave' => 'Пользователь :user покинул кампанию :campaign.', 'plugin' => [ 'deleted' => 'Плагин :plugin удален из каталога и из вашей кампании :campaign.', ], 'role' => [ 'add' => 'Вы были добавлены в роль :role в кампании :campaign.', 'remove' => 'Вы были удалены из роли :role в кампании :campaign.', ], 'troubleshooting' => [ 'joined' => 'Член команды Kanka :user присоединился к кампании :campaign.', ], ], 'clear' => [ 'action' => 'Очистить все', 'success' => 'Уведомления удалены.', 'title' => 'Очистить уведомления.', ], 'header' => 'У вас :count уведомлений', 'index' => [ 'title' => 'Уведомления', ], 'map' => [ 'chunked' => 'Сегментирование карты :name завершено, она готова к использованию.', ], 'no_notifications' => 'Пока уведомлений нет.', 'subscriptions' => [ 'charge_fail' => 'При обработке вашей оплаты произошла ошибка. Пожалуйста, подождите немного, пока мы попробуем обработать ее еще раз. Пожалуйста, свяжитесь с нами, если ничего не изменится.', 'deleted' => 'Ваша подписка на Kanka была отменена из-за слишком большого количества неудачных попыток снятия денег с вашей карты. Пожалуйста, перейдите в настройки подписок и попробуйте обновить параметры вашей оплаты.', 'ended' => 'Ваша подписка на Kanka закончилась. Ваши усилители кампаний и роли в Discord были удалены. Надеемся вы скоро вернетесь!', 'failed' => 'Не удалось совершить оплату по вашим параметрам оплаты. Пожалуйста обновите их настройках способа оплаты.', 'started' => 'Ваша подписка на Kanka оформлена.', ], 'unread' => 'Новое уведомление', ]; ================================================ FILE: lang/ru/onboarding/tags.php ================================================ 'NPC', ]; ================================================ FILE: lang/ru/organisations.php ================================================ [ 'title' => 'Новая организация', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_defunct' => 'Исчезнувшая', 'members' => 'Члены', ], 'helpers' => [], 'hints' => [ 'is_defunct' => 'Укажите, перестала ли эта организация существовать.', ], 'index' => [], 'members' => [ 'destroy' => [ 'success' => 'Член удален из организации.', ], 'edit' => [ 'title' => 'Редактирование члена :name', ], 'fields' => [ 'parent' => 'Руководитель', 'pinned' => 'Закрепление', 'role' => 'Роль', 'status' => 'Статус активности', ], 'helpers' => [ 'all_members' => 'Все персонажи, входящие в эту организацию или ее подорганизации.', 'members' => 'Все персонажи, входящие в эту организацию.', 'pinned' => 'Выберите, на страницах каких объектов следует закрепить это членство.', ], 'pinned' => [ 'both' => 'Везде', 'none' => 'Нигде', ], 'placeholders' => [ 'parent' => 'Руководитель этого персонажа', 'role' => 'Лидер, член, верховный септон, шпион', ], 'status' => [ 'active' => 'Активен', 'inactive' => 'Неактивен', 'unknown' => 'Статус неизвестен', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'Культ, банда, восстание, фэндом', ], 'quests' => [], 'show' => [], ]; ================================================ FILE: lang/ru/pagination.php ================================================ '« Назад', 'next' => 'Вперёд »', ]; ================================================ FILE: lang/ru/partials.php ================================================ [ 'description' => 'Небольшие проблемы с введенными данными.', 'title' => 'Упс!', ], ]; ================================================ FILE: lang/ru/passwords.php ================================================ 'Пароль должен быть не менее шести символов и совпадать с подтверждением.', 'reset' => 'Ваш пароль был сброшен!', 'sent' => 'Ссылка на сброс пароля была отправлена!', 'token' => 'Ошибочный код сброса пароля.', 'user' => 'Не удалось найти пользователя с указанным электронным адресом.', ]; ================================================ FILE: lang/ru/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/ru/posts.php ================================================ [ 'title' => 'Новый пост', ], 'fields' => [ 'name' => 'Название', ], 'placeholders' => [ 'name' => 'Название поста', ], 'position' => [ 'first' => 'В начало', 'last' => 'В конец', ], ]; ================================================ FILE: lang/ru/presets.php ================================================ [ 'create' => 'Новая заготовка', ], 'create' => [ 'success' => 'Заготовка ":name" создана.', 'title' => 'Новая заготовка', ], 'destroy' => [ 'success' => 'Заготовка ":name" уничтожена.', ], 'edit' => [ 'success' => 'Заготовка ":name" модифицирована.', 'title' => 'Редактирование заготовки :name', ], 'fields' => [ 'name' => 'Название', ], 'lists' => [ 'empty' => 'В данный момент в кампании нет доступных заготовок.', ], 'placeholders' => [ 'name' => 'Название заготовки', ], ]; ================================================ FILE: lang/ru/profiles.php ================================================ [], 'avatar' => [ 'success' => 'Аватар обновлен.', ], 'campaign_switcher_order_by' => [], 'edit' => [ 'success' => 'Профиль обновлен.', ], 'editors' => [], 'fields' => [ 'avatar' => 'Аватар', 'bio' => 'О себе', 'email' => 'Электронная почта', 'hide_subscription' => 'Скрыть мое имя в :hall_of_fame', 'last_login_share' => 'Показывать участникам кампании дату моего последнего входа в Kanka.', 'name' => 'Имя', 'new_password' => 'Новый пароль', 'new_password_confirmation' => 'Подтверждение нового пароля', 'newsletter' => 'Я хочу иногда получать электронные письма.', 'password' => 'Текущий пароль', 'profile-name' => 'Имя в профиле', 'settings' => 'Настройки', 'theme' => 'Тема', ], 'helpers' => [ 'profile-name' => 'Укажите, как вы хотите указать ваше имя в своем :profile. В таком же виде его будет отображать :marketplace. Оставьте пустым, чтобы использовать ваше обычное имя.', ], 'newsletter' => [ 'helpers' => [ 'header' => 'Подписывайтесь на наши рассылки по электронной почте, чтобы быть в курсе всего, что происходит на Kanka.', ], 'options' => [ 'monthly' => 'Новости Kanka', ], 'title' => 'Рассылки', ], 'password' => [ 'success' => 'Пароль обновлен.', ], 'placeholders' => [ 'bio' => 'Небольшой текст о себе, показываемый в публичном профиле.', 'email' => 'Ваш адрес электронной почты', 'name' => 'Ваше имя', 'new_password' => 'Ваш новый пароль', 'new_password_confirmation' => 'Подтвердите ваш новый пароль.', 'password' => 'Ваш текущий пароль', ], 'sections' => [ 'dangerzone' => 'Опасная зона', 'delete' => [ 'confirm' => 'Удалить мой аккаунт', 'delete' => 'Удалить мой аккаунт', 'goodbye' => 'Если вы уверены, пожалуйста введите код :code в поле ниже.', 'helper' => 'При удалении аккаунта будут также удалены все кампании, в которых нет других участников, кроме вас. Это действие необратимо.', 'subscribed' => 'Пожалуйста, убедитесь, что ваша :subscription отменена, прежде чем удалить аккаунт.', 'title' => 'Удаление аккаунта', 'warning' => 'При удалении вашего аккаунта, все ваши данные будут потеряны. Вы уверены?', ], 'password' => [ 'title' => 'Смена вашего пароля', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'Раздел "О себе" будет показан в вашем :link.', 'profile' => 'публичном профиле', ], 'success' => 'Настройки обновлены.', ], 'theme' => [ 'success' => 'Тема обновлена.', 'themes' => [ 'dark' => 'Темная', 'default' => 'Стандартная', 'future' => 'Будущее', 'midnight' => 'Синяя Полночь', ], ], 'title' => 'Обновление вашего профиля', 'workflows' => [ 'created' => 'К созданному объекту', 'default' => 'К списку объектов', ], ]; ================================================ FILE: lang/ru/quests.php ================================================ [ 'title' => 'Новый квест', ], 'destroy' => [], 'edit' => [], 'elements' => [ 'create' => [ 'success' => 'Объект :entity добавлен в квест.', 'title' => 'Новый элемент квеста :name', ], 'destroy' => [ 'success' => 'Элемент :entity удален из квеста.', ], 'edit' => [ 'success' => 'Элемент :entity обновлен.', 'title' => 'Редактирование элемента квеста :name', ], 'fields' => [ 'entity_or_name' => 'Нужно либо выбрать объект из кампании, либо дать название этому элементу.', ], ], 'fields' => [ 'copy_elements' => 'Копировать элементы квеста', 'date' => 'Дата', 'element_role' => 'Роль', 'is_completed' => 'Завершен', 'role' => 'Роль', ], 'helpers' => [ 'is_completed' => 'Квест будет считаться завершенным.', ], 'hints' => [], 'index' => [], 'placeholders' => [ 'date' => 'Дата квеста в реальном мире', 'entity' => 'Выберите объект', 'role' => 'Роль объекта в квесте', 'type' => 'Арка персонажа, побочный, основной', ], 'show' => [ 'actions' => [ 'add_element' => 'Добавить элемент', ], 'tabs' => [ 'elements' => 'Элементы', ], ], ]; ================================================ FILE: lang/ru/races.php ================================================ [], 'create' => [ 'title' => 'Новая раса', ], 'destroy' => [], 'edit' => [], 'fields' => [], 'helpers' => [], 'index' => [], 'placeholders' => [ 'type' => 'Человек, фея, борг', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/ru/redirects.php ================================================ 'Время вашей сессии истекло. Пожалуйста, попробуйте снова.', 'unknown_entity' => 'Извините, мы не знаем, что такое ":entity".', ]; ================================================ FILE: lang/ru/releases.php ================================================ [ 'event' => 'Событие', 'livestream' => 'Прямая трансляция', 'other' => 'Другое', 'release' => 'Обновление', 'vote' => 'Голосование', ], 'index' => [ 'description' => 'Последние обновления kanka.io', 'title' => 'Обновления', ], 'post' => [ 'footer' => 'От :name :date', ], 'show' => [ 'return' => 'Назад к обновлениям', 'title' => 'Обновление :name', ], ]; ================================================ FILE: lang/ru/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chronicles of Darkness', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/ru/search.php ================================================ 'Ничего не найдено.', 'title' => 'Поиск', ]; ================================================ FILE: lang/ru/settings/boosters.php ================================================ [ 'boost_name' => 'Усилить кампанию :name', ], 'boost' => [ 'actions' => [ 'confirm' => 'Усилить!', 'remove' => 'Перестать усиливать кампанию :campaign', 'subscribe' => 'Подписка на Kanka', 'upgrade' => 'Обновить подписку', ], 'errors' => [ 'boosted' => 'Ой, кажется, кампания :campaign уже усилена!', ], 'pitch' => 'Оформите подписку, чтобы получить усилители кампаний.', 'success' => 'Кампания :campaign теперь усилена. Пора опробовать новые потрясающие функции!', 'title' => 'Усиление :campaign', ], 'campaign' => [ 'boosted' => 'Усиливает :user с :time.', 'superboosted' => 'Супер-усиливает :user с :time.', 'unboosted' => 'Не усилена', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Возможность восстановить удаленный объект в течение :amount дней', 'customisable' => 'Возможность настроить внешний вид кампании как-угодно', 'icons' => 'Доступ к тысячам красивых иконок для карт и хронологий', 'title' => 'Усиленные кампании получают', 'upload' => 'Повышенные размеры загрузок для всех участников', ], 'description' => 'Наделите кампанию усилителями и помогите всем ее участникам открыть потрясающие функции. Не впечатляют усиленные кампании? Не беспокойтесь, ведь еще есть супер-усиленные кампании!', 'more' => 'Смотрите полный список бонусов и преимуществ здесь: :boosters.', 'title' => 'Поднимите кампанию на новый уровень с бонусами для всех участников', ], 'ready' => [ 'available' => 'Ваши свободные усилители.', 'pricing' => 'Каждый уровень подписки дает вам хотя бы один усилитель кампаний по цене от :amount в месяц.', 'pricing-amount' => ':currency :amount', 'title' => 'Усиление кампаний', ], 'superboost' => [ 'actions' => [ 'confirm' => 'Супер-усилить!', 'remove' => 'Перестать супер-усиливать кампанию :campaign', ], 'errors' => [ 'boosted' => 'Ой, кажется кампания :campaign уже супер-усилена!', ], 'success' => 'Кампания :campaign теперь супер-усилена. Пора опробовать новые потрясающие функции!', 'title' => 'Супер-усилить кампанию :campaign', ], 'title' => 'Усилители кампаний', 'unboost' => [ 'confirm' => 'Да', 'success' => 'Кампания :campaign больше не усилена, а ваши усилители снова свободны.', 'title' => 'Прекращение усиления кампании', ], ]; ================================================ FILE: lang/ru/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Отключить двухфакторную аутентификацию', 'finish' => 'Закончить и перейти ко входу', ], 'activation_helper' => 'Для завершения подключения двухфакторной аутентификации следуйте данной инструкции.', 'disable' => [ 'title' => 'Отключение двухфакторной аутентификации', ], 'enabled' => 'Двухфакторная аутентификация для вашего аккаунта включена.', 'error_enable' => 'Недействительный код, попробуйте еще раз', 'fields' => [ 'otp' => 'Введите одноразовый пароль (OTP), предоставленный вашим приложением-аутентификатором.', ], 'generate_qr' => 'Сгенерировать QR-код', 'learn_more' => 'Узнать больше о двухфакторной аутентификации.', 'success_disable' => 'Двухфакторная аутентификация отключена.', 'success_enable' => 'Двухфакторная аутентификация включена. Пожалуйста, войдите в свой аккаунт заново.', 'title' => 'Двухфакторная аутентификация', ], 'actions' => [ 'social' => 'Перейти на вход Kanka', 'update_email' => 'Обновить электронную почту', 'update_password' => 'Обновить пароль', ], 'email' => 'Смена электронной почты', 'email_success' => 'Электронная почта обновлена.', 'password' => 'Смена пароля', 'password_success' => 'Пароль обновлен.', 'social' => [ 'error' => 'Этот аккаунт уже использует вход Kanka.', 'helper' => 'Сейчас вход в ваш аккаунт управляется :provider. Вы можете перейти на стандартный вход Kanka, создав пароль.', 'success' => 'Теперь ваш аккаунт использует вход Kanka.', 'title' => 'Вход Kanka', ], 'title' => 'Аккаунт', ], 'api' => [ 'helper' => 'Добро пожаловать в Kanka API. Сгенерируйте личный маркер доступа, чтобы использовать его в API запросах для сбора информации о кампаниях, в которых вы состоите.', 'link' => 'Читать документацию API', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Подключить', 'remove' => 'Удалить', ], 'benefits' => 'Kanka предоставляет интеграцию со сторонними сервисами. В будущем планируется больше интеграций.', 'discord' => [ 'errors' => [ 'add' => 'При подключении вашего Discord аккаунта к Kanka произошла ошибка. Пожалуйста, попробуйте снова. Если ошибка повторяется, обратите внимание, что Discord API генерирует ошибку, если пользователь состоит более чем в 100 серверах.', ], 'success' => [ 'add' => 'Ваш аккаунт Discord подключен.', 'remove' => 'Ваш аккаунт Discord отключен.', ], 'text' => 'Автоматический доступ к ролям вашей подписки.', 'unlock' => 'Получите роли в Discord', ], 'title' => 'Интеграция', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'Кампания :name уже усилена.', 'exhausted_boosts' => 'У вас закончились усилители. Вы можете снять усилитель с одной из кампаний, и усилить другую.', 'exhausted_superboosts' => 'У вас закончились усилители. Вам нужно 3 усилителя, чтобы супер-усилить кампанию.', ], ], 'countries' => [ 'austria' => 'Австрия', 'belgium' => 'Бельгия', 'france' => 'Франция', 'germany' => 'Германия', 'italy' => 'Италия', 'netherlands' => 'Нидерланды', 'spain' => 'Испания', ], 'invoices' => [], 'layout' => [ 'title' => 'Оформление', ], 'marketplace' => [], 'menu' => [ 'account' => 'Аккаунт', 'api' => 'API', 'appearance' => 'Оформление', 'apps' => 'Приложения', 'boosters' => 'Усилители', 'notifications' => 'Уведомления', 'other' => 'Другое', 'patreon' => 'Patreon', 'payment_options' => 'Способы оплаты', 'personal_settings' => 'Персональные настройки', 'profile' => 'Профиль', 'settings' => 'Настройки', 'subscription' => 'Подписка', 'subscription_status' => 'Статус подписки', ], 'patreon' => [ 'deprecated' => 'Устаревшая функция - если вы хотите поддержать Kanka, пожалуйста сделайте это с помощью меню ":subscription". Ссылка на Patreon до сих пор активна для наших Patron-ов, подключивших свои аккаунты до нашего ухода с Patreon.', 'pledge' => 'Уровень: :name', 'remove' => [ 'button' => 'Отключить аккаунт Patreon', 'success' => 'Ваш аккаунт Patreon отключен.', 'text' => 'При отключении вашего аккаунта Patreon Kanka будут удалены ваши бонусы, имя в Зале Славы, усилители кампаний и другие функции, получаемые через поддержку Kanka. Ничего из того, чтобы было создано в усиленной кампании не пропадет (например, изображения заголовков объектов). Оформив подписку заново, вы получите доступ ко всем этим данным, и снова сможете усиливать ваши кампании.', 'title' => 'Отключение вашего Patreon аккаунта Kanka', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Обновить профиль', ], 'avatar' => 'Изображение профиля', 'success' => 'Профиль обновлен.', 'title' => 'Публичный профиль', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Отменить подписку', 'subscribe' => 'Подписаться', 'update_currency' => 'Сохранить предпочитаемую валюту', ], 'billing' => [ 'helper' => 'Информация о вашей оплате обрабатывается и надежно сохраняется с помощью :stripe. Этот способ оплаты будет использоваться для всех ваших подписок.', 'saved' => 'Сохраненный способ оплаты', ], 'cancel' => [ 'options' => [ 'competitor' => 'Перехожу на платформу-конкурента', 'financial' => 'Подписка слишком дорого стоит', 'missing_features' => 'Не хватает функций', 'not_for' => 'Подписка это не для меня', 'not_using' => 'Не пользуюсь Kanka', 'other' => 'Другое', ], 'text' => 'Жаль, что вы нас покидаете! После отмены ваша подписка останется активной до :date, после которого вы потеряете ваши усилители кампаний и другие преимущества, которые дает поддержка Kanka. Вы можете заполнить следующую форму, чтобы сообщить нам, что мы могли бы улучшить, или что привело вас к этому решению.', ], 'cancelled' => 'Ваша подписка отменена. Вы можете обновить подписку, когда ваша нынешняя подписка закончится :date.', 'change' => [ 'text' => [ 'monthly' => 'Вы подписываетесь на уровень :tier, стоимостью :amount в месяц.', 'yearly' => 'Вы подписываетесь на уровень :tier, стоимостью :amount в год.', ], 'title' => 'Изменение уровня подписки', ], 'coupon' => [ 'check' => 'Проверить промокод', 'invalid' => 'Недействительный промокод.', 'label' => 'Промокод', 'percent_off' => 'Вы получите скидку :percent% на вашу первую годовую подписку!', ], 'currencies' => [ 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Изменение предпочитаемой валюты оплаты', ], 'errors' => [ 'callback' => 'Наш платежный провайдер сообщил нам об ошибке. Пожалуйста, попробуйте еще раз и свяжитесь с нами, если проблема повторится.', 'subscribed' => 'Не удалось обработать вашу подписку. Stripe предоставил следующее пояснение.', ], 'fields' => [ 'active_since' => 'Активна с', 'active_until' => 'Активна до', 'billing' => 'Оплата', 'currency' => 'Валюта оплаты', 'payment_method' => 'Способ оплаты', 'plan' => 'Текущий план', 'reason' => 'Причина', ], 'helpers' => [ 'alternatives' => 'Оплатите свою подписку с помощью :method. Этот способ оплаты не будет автоматически обновляться по окончанию вашей подписки. Метод :method доступен только для евро.', 'alternatives_warning' => 'Повышение вашего уровня подписки при данном способе оплаты невозможно. Пожалуйста, оформите новую подписку, когда закончится текущая.', 'alternatives_yearly' => 'Из-за ограничений, связанных с повторяющимися оплатами, метод :method доступен только для годовых подписок.', 'stripe' => 'Ваша платежная информация безопасно хранится и обрабатывается с помощью :stripe.', ], 'manage_subscription' => 'Управление подпиской', 'payment_method' => [ 'actions' => [ 'add_new' => 'Добавить способ оплаты', 'change' => 'Изменить способ оплаты', 'save' => 'Сохранить способ оплаты', 'show_alternatives' => 'Альтернативные способы оплаты', ], 'add_one' => 'У вас нет сохраненного способа оплаты.', 'alternatives' => 'Вы можете оплатить подписку с помощью альтернативных способов оплаты. При этом подписка будет оплачена один раз и не будет автоматически обновляться каждый месяц.', 'card' => 'Карта', 'card_name' => 'Имя на карте', 'country' => 'Страна проживания', 'ending' => 'Заканчивается на', 'helper' => 'Эта карта будет использоваться для всех ваших подписок.', 'new_card' => 'Добавить новый способ оплаты.', 'saved' => ':brand заканчивается на :last4', ], 'periods' => [ 'monthly' => 'Месячный план', 'yearly' => 'Годовой план', ], 'placeholders' => [ 'downgrade_reason' => 'Если хотите, можете рассказать нам, почему вы понижаете уровень подписки.', 'reason' => 'Если хотите, можете рассказать нам, почему вы перестаете поддерживать Kanka. Может не хватает нужной вам функции? Или изменилась ваша финансовая ситуация?', ], 'plans' => [ 'cost_monthly' => ':currency :amount выплачивается ежемесячно', 'cost_yearly' => ':currency :amount выплачивается ежегодно', ], 'sub_status' => 'Информация о подписке', 'subscription' => [ 'actions' => [ 'cancel' => 'Отменить подписку', 'downgrading' => 'Свяжитесь с нами для понижения уровня', 'rollback' => 'Перейти на Kobold', 'subscribe' => 'Перейти на месячный :tier', 'subscribe_annual' => 'Перейти на годовой :tier', ], ], 'success' => [ 'alternative' => 'Ваша оплата зарегистрирована. Вы получите уведомление, как только она будет обработана и ваша подписка будет активирована.', 'callback' => 'Ваша подписка успешно оформлена. Ваш аккаунт будет обновлен, как только наш платежный провайдер сообщит нам об оплате (это может занять несколько минут).', 'currency' => 'Настройки вашей предпочитаемой валюты обновлены.', 'subscribed' => 'Ваша подписка успешно оформлена! Не забудьте подписаться на рассылку голосований, чтобы быть в курсе, когда начнется новое голосование. Также советуем заглянуть к нам на Discord и стать частью сообщества.', ], 'tiers' => 'Уровни подписки', 'trial_period' => 'Годовые подписки можно отменять в течение 14 дней. Напишите нам на :email, если вы хотите отменить вашу годовую подписку и получить деньги назад.', 'upgrade_downgrade' => [ 'button' => 'Информация о повышении и понижении уровня', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Ваши бонусы останутся доступными до окончания периода подписки.', 'boosts' => 'То же самое происходит с усиленными кампаниями. При окончании усиления функции усиления становятся невидимыми, но не удаляются из кампании.', 'kobold' => 'Чтобы отменить подписку, перейдите на уровень Kobold.', ], 'title' => 'При отмене подписки', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Ваш текущий уровень будет активен до окончания текущего периода оплаты, после чего подписка будет понижена до вашего нового уровня.', ], 'provide_reason' => 'Если вам не трудно, поделитесь с нами, почему вы решили понизить уровень.', 'title' => 'При понижении уровня', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Ваша подписка будет незамедлительно оплачена, и вы получите доступ к вашему новому уровню.', 'prorate' => 'Вы платите только разницу между вашими старым и новым уровнями.', ], 'title' => 'При повышении уровня', ], ], 'warnings' => [ 'incomplete' => 'Не удалось совершить оплату с помощью вашей карты. Пожалуйста обновите информацию вашей кредитной карты, и мы попробуем совершить оплату снова в течение нескольких дней. Если ошибка произойдет снова, ваша подписка будет отменена.', 'patreon' => 'Ваш аккаунт подключен к Patreon. Пожалуйста, отключите ваш аккаунт в настройках :patreon перед переходом на подписку Kanka.', ], ], ]; ================================================ FILE: lang/ru/sidebar.php ================================================ [ 'created_campaigns' => 'Ваши кампании', 'new_campaign' => 'Новая кампания', 'public_campaigns' => 'Публичные кампании', 'updated' => 'Обновлена', ], 'dashboard' => 'Обзор кампании', 'entity-creator' => 'Быстрый редактор', 'gallery' => 'Галерея', 'other' => 'Другое', 'relations' => 'Связи', 'world' => 'Мир', ]; ================================================ FILE: lang/ru/starter.php ================================================ [ 'name' => 'Ваш мир', ], 'character1' => [], 'character2' => [], 'item1' => [], 'kingdom1' => [], 'kingdom2' => [], 'note1' => [], ]; ================================================ FILE: lang/ru/subscription.php ================================================ [ 'main' => 'Оформите подписку Kanka, чтобы загружать изображения больших размеров, забыть о рекламах и получить :boosters и :more. Мы используем :stripe для проведения оплаты без хранения и передачи информации кредитных карт через наши сервера.', 'more' => 'другие потрясающие функции', ], ]; ================================================ FILE: lang/ru/subscriptions/promos.php ================================================ [ 'inactive' => 'Это продвижение больше не действует.', 'invalid' => 'Неизвестное продвижение.', 'only-new' => 'Это продвижение доступно только новым подписчикам.', ], ]; ================================================ FILE: lang/ru/subscriptions.php ================================================ [ 'failed' => 'Stripe не удалось выполнить оплату вашим способом. Ваша подписка была деактивирована.', ], ]; ================================================ FILE: lang/ru/tags.php ================================================ [ 'actions' => [ 'add' => 'Добавить объект', ], ], 'create' => [ 'title' => 'Новый тэг', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Потомки', 'is_auto_applied' => 'Автоматически добавлять новым объектам', ], 'helpers' => [ 'no_children' => 'В данным момент не существует объектов с этим тэгом.', ], 'hints' => [ 'children' => 'Этот список содержит все объекты с этим тэгом или его подтэгами.', 'is_auto_applied' => 'Тэг будет автоматически добавляться новым объектам кампании.', 'tag' => 'Это список всех непосредственных потомков этого тэга.', ], 'index' => [], 'placeholders' => [ 'type' => 'Знания, войны, история, религия, флаги', ], 'show' => [ 'tabs' => [ 'children' => 'Потомки', ], ], 'tags' => [], ]; ================================================ FILE: lang/ru/teams.php ================================================ [ 'lead' => 'С Kanka ворлд-билдинг веселее и надежнее', 'translations' => 'Перевод', ], 'leads' => [ 'translators' => 'Платформа Kanka переведена на ряд языков благодаря вкладу этих потрясающих людей.', ], 'patreon' => [], 'people' => [ 'itzamna' => [ 'title' => 'Младший разработчик', ], 'jay' => [ 'title' => 'Автор и ведущий разработчик', ], 'jon' => [ 'title' => 'Соавтор и бизнес-менеджер', ], 'kaz' => [ 'title' => 'Истребитель багов', ], 'laura' => [ 'title' => 'Социальные сети', ], ], ]; ================================================ FILE: lang/ru/tiers.php ================================================ [ 'subscribe' => [ 'monthly' => 'Месячная подписка :tier', 'yearly' => 'Годовая подписка :tier', ], ], 'current' => 'Это ваша текущая подписка.', 'features' => [ 'api_requests' => ':amount API запросов в минуту', 'boosters' => 'усилителей кампаний', 'discord' => 'Роли в Discord', 'feature_influence' => 'Влияние на новые функции', 'file_size' => 'Загрузка файлов размером до :size', 'nice_image' => 'Иконки объектов по умолчанию', 'no_ads' => 'Отсутствие рекламы', 'pagination' => 'До :amount результатов на странице', ], 'periods' => [], 'pricing' => ':currency :amount/месяц', 'ribbons' => [ 'best-value' => 'Выгодно', ], 'toggle' => [], ]; ================================================ FILE: lang/ru/timelines/elements.php ================================================ [ 'copy_with_name' => 'Копировать продвинутое упоминание с названием', 'success' => 'Продвинутое упоминание элемента скопировано в буфер обмена.', ], 'create' => [ 'success' => 'Элемент добавлен в хронологию.', 'title' => 'Новый элемент хронологии', ], 'delete' => [ 'success' => 'Элемент ":name" удален.', ], 'edit' => [ 'success' => 'Элемент обновлен.', 'title' => 'Редактирование элемента хронологии', ], 'fields' => [ 'date' => 'Дата', 'era' => 'Эра', 'icon' => 'Иконка', 'use_entity_entry' => 'Показывать статью связанного объекта под элементом. Если у этого элемента есть текст, он будет показан выше статьи.', 'use_event_date' => 'Использовать дату события', ], 'helpers' => [ 'date' => 'Если элемент связан с событием, то можно использовать его дату.', 'entity_is_private' => 'Объект элемента скрыт.', 'icon' => 'Скопируйте HTML иконки с :fontawesome или :rpgawesome.', 'is_collapsed' => 'Элемент будет отображен свернутым по умолчанию.', ], 'placeholders' => [ 'date' => 'Например, 42-ое марта или 1332-1337', 'name' => 'Обязательно, если не выбран объект.', 'position' => 'Позиция в списке элементов эры. Оставьте пустым, чтобы добавить в конец.', ], 'warning' => [], ]; ================================================ FILE: lang/ru/timelines/eras.php ================================================ [ 'add' => 'Новая эра', ], 'bulks' => [ 'delete' => '{0} Удалено :count эр.|{1} Удалена :count эра.|[2,4] Удалено :count эры.|[5,*] Удалено :count эр.', ], 'create' => [ 'success' => 'Эра ":name" создана.', 'title' => 'Новая эра', ], 'delete' => [ 'success' => 'Эра ":name" удалена.', ], 'edit' => [ 'success' => 'Эра ":name" обновлена.', 'title' => 'Редактирование эры :name', ], 'fields' => [ 'abbreviation' => 'Сокращение', 'end_year' => 'Год конца', 'is_collapsed' => 'Свернуть', 'start_year' => 'Год начала', ], 'helpers' => [ 'eras' => 'Прежде чем добавлять в хронологию эры, ее нужно создать.', 'is_collapsed' => 'Эра будет свернута по умолчанию.', 'primary' => 'Разделяйте ваши хронологии на эры. Для правильной работы в хронологии должна быть хотя бы одна эра.', ], 'index' => [ 'title' => 'Эры хронологии :name', ], 'placeholders' => [ 'abbreviation' => 'н. э., до н. э.', 'end_year' => 'Год, которым заканчивается эра. Оставьте пустым, если это текущая эра.', 'name' => 'Современность, бронзовый век, галактические войны', 'start_year' => 'Год, с которого начинается эра. Оставьте пустым, если это первая эра.', ], 'reorder' => [], ]; ================================================ FILE: lang/ru/timelines.php ================================================ [ 'add_element' => 'Добавить элемент в эру :era', 'back' => 'Назад к :name', 'save_order' => 'Сохранить новый порядок', ], 'create' => [ 'title' => 'Новая хронология', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_elements' => 'Копировать элементы', 'copy_eras' => 'Копировать эры', 'eras' => 'Эры', 'reverse_order' => 'Обратный порядок эр', ], 'helpers' => [ 'reverse_order' => 'Отображение эр в обратном хронологическом порядке (чем древнее эра, тем она выше).', ], 'index' => [], 'placeholders' => [ 'type' => 'Основные события, хроники мира, история королевства', ], 'reorder' => [ 'success' => 'Порядок элементов хронологии изменен.', 'title' => 'Изменение порядка элементов', ], 'show' => [], 'timelines' => [], ]; ================================================ FILE: lang/ru/users/profile.php ================================================ [ 'wordsmith' => 'Истинное мастерство слова! Победа в событии по ворлд-билдингу.', ], 'fields' => [ 'achievements' => 'Достижения', 'banned' => 'Этот пользователь был заблокирован.', 'entities_created' => 'Создано объектов :help :count', 'member_since' => 'Подписка с :date', 'public_campaigns' => 'Публичные кампании', 'subscriber_since' => 'Подписка с :date', ], 'helpers' => [ 'entities_created' => 'Это число обновляется раз в сутки.', ], 'title' => 'Профиль пользователя :name', ]; ================================================ FILE: lang/ru/validation.php ================================================ 'Вы должны принять :attribute.', 'active_url' => 'Поле :attribute содержит недействительный URL.', 'after' => 'В поле :attribute должна быть дата после :date.', 'after_or_equal' => 'В поле :attribute должна быть дата после или равняться :date.', 'alpha' => 'Поле :attribute может содержать только буквы.', 'alpha_dash' => 'Поле :attribute может содержать только буквы, цифры, дефис и нижнее подчеркивание.', 'alpha_num' => 'Поле :attribute может содержать только буквы и цифры.', 'array' => 'Поле :attribute должно быть массивом.', 'before' => 'В поле :attribute должна быть дата до :date.', 'before_or_equal' => 'В поле :attribute должна быть дата до или равняться :date.', 'between' => [ 'array' => 'Количество элементов в поле :attribute должно быть между :min и :max.', 'file' => 'Размер файла в поле :attribute должен быть между :min и :max Килобайт(а).', 'numeric' => 'Поле :attribute должно быть между :min и :max.', 'string' => 'Количество символов в поле :attribute должно быть между :min и :max.', ], 'boolean' => 'Поле :attribute должно иметь значение логического типа.', 'confirmed' => 'Поле :attribute не совпадает с подтверждением.', 'date' => 'Поле :attribute не является датой.', 'date_equals' => 'Поле :attribute дожно быть датой равной :date.', 'date_format' => 'Поле :attribute не соответствует формату :format.', 'different' => 'Поля :attribute и :other должны различаться.', 'digits' => 'Длина цифрового поля :attribute должна быть :digits.', 'digits_between' => 'Длина цифрового поля :attribute должна быть между :min и :max.', 'dimensions' => 'Поле :attribute имеет недопустимые размеры изображения.', 'distinct' => 'Поле :attribute содержит повторяющееся значение.', 'email' => 'Поле :attribute должно быть действительным электронным адресом.', 'exists' => 'Выбранное значение для :attribute некорректно.', 'file' => 'Поле :attribute должно быть файлом.', 'filled' => 'Поле :attribute обязательно для заполнения.', 'gt' => [ 'array' => 'Количество элементов в поле :attribute должно быть больше :value.', 'file' => 'Размер файла в поле :attribute должен быть больше :value Килобайт(а).', 'numeric' => 'Поле :attribute должно быть больше :value.', 'string' => 'Количество символов в поле :attribute должно быть больше :value.', ], 'gte' => [ 'array' => 'Количество элементов в поле :attribute должно быть больше или равно :value.', 'file' => 'Размер файла в поле :attribute должен быть больше или равен :value Килобайт(а).', 'numeric' => 'Поле :attribute должно быть больше или равно :value.', 'string' => 'Количество символов в поле :attribute должно быть больше или равно :value.', ], 'image' => 'Поле :attribute должно быть изображением.', 'in' => 'Выбранное значение для :attribute ошибочно.', 'in_array' => 'Поле :attribute не существует в :other.', 'integer' => 'Поле :attribute должно быть целым числом.', 'ip' => 'Поле :attribute должно быть действительным IP-адресом.', 'ipv4' => 'Поле :attribute должно быть действительным IPv4-адресом.', 'ipv6' => 'Поле :attribute должно быть действительным IPv6-адресом.', 'json' => 'Поле :attribute должно быть JSON строкой.', 'lt' => [ 'array' => 'Количество элементов в поле :attribute должно быть меньше :value.', 'file' => 'Размер файла в поле :attribute должен быть меньше :value Килобайт(а).', 'numeric' => 'Поле :attribute должно быть меньше :value.', 'string' => 'Количество символов в поле :attribute должно быть меньше :value.', ], 'lte' => [ 'array' => 'Количество элементов в поле :attribute должно быть меньше или равно :value.', 'file' => 'Размер файла в поле :attribute должен быть меньше или равен :value Килобайт(а).', 'numeric' => 'Поле :attribute должно быть меньше или равно :value.', 'string' => 'Количество символов в поле :attribute должно быть меньше или равно :value.', ], 'max' => [ 'array' => 'Количество элементов в поле :attribute не может превышать :max.', 'file' => 'Размер файла в поле :attribute не может быть более :max Килобайт(а).', 'numeric' => 'Поле :attribute не может быть более :max.', 'string' => 'Количество символов в поле :attribute не может превышать :max.', ], 'mimes' => 'Поле :attribute должно быть файлом одного из следующих типов: :values.', 'mimetypes' => 'Поле :attribute должно быть файлом одного из следующих типов: :values.', 'min' => [ 'array' => 'Количество элементов в поле :attribute должно быть не менее :min.', 'file' => 'Размер файла в поле :attribute должен быть не менее :min Килобайт(а).', 'numeric' => 'Поле :attribute должно быть не менее :min.', 'string' => 'Количество символов в поле :attribute должно быть не менее :min.', ], 'not_in' => 'Выбранное значение для :attribute ошибочно.', 'not_regex' => 'Выбранный формат для :attribute ошибочный.', 'numeric' => 'Поле :attribute должно быть числом.', 'present' => 'Поле :attribute должно присутствовать.', 'regex' => 'Поле :attribute имеет ошибочный формат.', 'required' => 'Поле :attribute обязательно для заполнения.', 'required_if' => 'Поле :attribute обязательно для заполнения, когда :other равно :value.', 'required_unless' => 'Поле :attribute обязательно для заполнения, когда :other не равно :values.', 'required_with' => 'Поле :attribute обязательно для заполнения, когда :values указано.', 'required_with_all' => 'Поле :attribute обязательно для заполнения, когда :values указано.', 'required_without' => 'Поле :attribute обязательно для заполнения, когда :values не указано.', 'required_without_all' => 'Поле :attribute обязательно для заполнения, когда ни одно из :values не указано.', 'same' => 'Значения полей :attribute и :other должны совпадать.', 'size' => [ 'array' => 'Количество элементов в поле :attribute должно быть равным :size.', 'file' => 'Размер файла в поле :attribute должен быть равен :size Килобайт(а).', 'numeric' => 'Поле :attribute должно быть равным :size.', 'string' => 'Количество символов в поле :attribute должно быть равным :size.', ], 'starts_with' => 'Поле :attribute должно начинаться из одного из следующих значений: :values', 'string' => 'Поле :attribute должно быть строкой.', 'timezone' => 'Поле :attribute должно быть действительным часовым поясом.', 'unique' => 'Такое значение поля :attribute уже существует.', 'uploaded' => 'Загрузка поля :attribute не удалась.', 'url' => 'Поле :attribute имеет ошибочный формат.', 'uuid' => 'Поле :attribute должно быть корректным UUID.', /* |-------------------------------------------------------------------------- | Собственные языковые ресурсы для проверки значений |-------------------------------------------------------------------------- | | Здесь Вы можете указать собственные сообщения для атрибутов. | Это позволяет легко указать свое сообщение для заданного правила атрибута. | | http://laravel.com/docs/validation#custom-error-messages | Пример использования | | 'custom' => [ | 'email' => [ | 'required' => 'Нам необходимо знать Ваш электронный адрес!', | ], | ], | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Собственные названия атрибутов |-------------------------------------------------------------------------- | | Последующие строки используются для подмены программных имен элементов | пользовательского интерфейса на удобочитаемые. Например, вместо имени | поля "email" в сообщениях будет выводиться "электронный адрес". | | Пример использования | | 'attributes' => [ | 'email' => 'электронный адрес', | ], | */ 'attributes' => [ 'address' => 'Адрес', 'age' => 'Возраст', 'available' => 'Доступно', 'city' => 'Город', 'content' => 'Контент', 'country' => 'Страна', 'date' => 'Дата', 'day' => 'День', 'description' => 'Описание', 'email' => 'E-Mail адрес', 'excerpt' => 'Выдержка', 'first_name' => 'Имя', 'gender' => 'Пол', 'hour' => 'Час', 'last_name' => 'Фамилия', 'minute' => 'Минута', 'mobile' => 'Моб. номер', 'month' => 'Месяц', 'name' => 'Имя', 'password' => 'Пароль', 'password_confirmation' => 'Подтверждение пароля', 'phone' => 'Телефон', 'second' => 'Секунда', 'sex' => 'Пол', 'size' => 'Размер', 'time' => 'Время', 'title' => 'Наименование', 'username' => 'Никнейм', 'year' => 'Год', ], ]; ================================================ FILE: lang/ru/visibilities.php ================================================ [ 'admin' => 'Только участники с ролью Админ могут просматривать этот элемент.', 'admin-self' => 'Только вы и участники с ролью Админ могут просматривать этот элемент.', 'all' => 'Все могут просматривать этот элемент.', 'members' => 'Только участники кампании могут просматривать этот элемент.', 'self' => 'Только вы можете просматривать этот элемент.', ], 'title' => 'Обновление доступа', 'toast' => 'Доступ успешно обновлен.', 'tooltip' => 'Нажмите сюда, чтобы узнать о различных видах доступа и о том, что они означают, в нашей документации.', ]; ================================================ FILE: lang/ru/whiteboards/draw.php ================================================ [ 'add-circle' => 'Добавить круг', 'add-entity' => 'Добавить объект', 'add-image' => 'Добавить изображение', 'add-square' => 'Добавить квадрат', 'add-text' => 'Добавить текст', 'duplicate' => 'Создать дубликат', 'end-drawing' => 'Закончить рисовать', 'lock' => 'Фиксировать', 'push-to-back' => 'На задний план', 'push-to-front' => 'На передний план', 'start-drawing' => 'Начать рисовать', 'unlock' => 'Разфиксировать', ], 'entity-search' => [ 'placeholder' => 'Введите название или псевдоним объекта', 'title' => 'Поиск объекта', ], 'fields' => [ 'color' => 'Цвет', ], 'pen' => [ 'large-stroke' => 'Широкий штрих', 'thin-stroke' => 'Тонкий штрих', ], 'reset' => [ 'helper' => 'Вы уверены, что хотите очистить эту доску? Это действие необратимо.', 'title' => 'Очистить доску', ], 'toast' => [ 'copy' => [ 'success' => 'Элементы скопированы в буфер обмена.', ], 'paste' => [ 'error' => 'Что-то пошло не так.', ], ], ]; ================================================ FILE: lang/ru/whiteboards.php ================================================ [ 'draw' => 'Рисовать', ], 'cta' => [ 'text' => 'Чтобы получить доступ к доскам, необходимо, чтобы участник роли :wyvern или :elemental сделал эту кампанию премиум кампанией.', ], 'lists' => [ 'empty' => 'Используйте доски, чтобы наглядно организовывать идеи, отношения персонажей или структуру вашей истории.', ], 'placeholders' => [ 'type' => 'Идея, отношения персонажей, структура истории', ], ]; ================================================ FILE: lang/sk/abilities.php ================================================ [], 'children' => [ 'actions' => [ 'attach' => 'Pripojiť k objektom', ], 'create' => [ 'attach_success' => '{1} Schopnosť :name bola pripojená k :count objektu.|[2,*] Schopnosť :name bola priradená ku :count objektom.', 'helper' => 'Pripojiť :name k jednému alebo viacerým objektom.', 'title' => 'Pripojiť objekty', ], 'description' => 'Objekty s touto schopnosťou', 'title' => 'Objekty schopnosti :name', ], 'create' => [ 'title' => 'Nová schopnosť', ], 'destroy' => [], 'edit' => [], 'entities' => [], 'fields' => [ 'charges' => 'Náboje', ], 'helpers' => [], 'index' => [], 'lists' => [ 'empty' => 'Pridaj sily, kúzla či talenty. Používa sa na najmä na modelovanie D&D archetypov.', ], 'placeholders' => [ 'charges' => 'Počet nábojov. Prepoj atribúty cez {Úroveň}*{CHA}', 'name' => 'Ohnivá guľa, Stále v strehu, Zákerný výpad', 'type' => 'Kúzlo, schopnosť, útočný manéver', ], 'reorder' => [ 'parentless' => 'Bez nadradenej', 'success' => 'Schopnosti úspešne usporiadané.', 'title' => 'Usporiadanie schopností', ], 'show' => [ 'tabs' => [ 'reorder' => 'Usporiadať schopnosti', ], ], ]; ================================================ FILE: lang/sk/articles.php ================================================ [ 'move' => 'Presunúť k objektu', ], 'helpers' => [ 'permissions' => 'Tieto oprávnenia môžu prepísať :visibility nastavenia v článku.', ], 'tabs' => [ 'layout' => 'Layout', 'main' => 'Hlavný', 'permissions' => 'Špeciálne oprávnenia', ], ]; ================================================ FILE: lang/sk/attribute_templates.php ================================================ [], 'bulk' => [ 'entity_type' => [ 'unset' => 'Nenastavené', ], ], 'create' => [ 'title' => 'Nová šablóna atribútov', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'auto_apply' => 'Autom. prevziať', 'is_enabled' => 'Aktívne', ], 'hints' => [ 'automatic' => 'Atribúty boli automaticky aplikované zo šablóny atribútov :link.', 'automatic_apply' => '{1} Nasledujúci :count atribút bol automaticky prevzatý z :link | [2,4] Nasledujúce :count atribúty boli automaticky prevzaté z :link. | [5,*] Nasledujúcich :count atribútov bolo automaticky prevzatých z :link.', 'entity_type' => 'Po aktivovaní bude v novom objekte tohto typu automaticky aplikovaná táto šablóna atribútov.', 'is_disabled' => 'Táto šablóna je neaktívna.', 'is_enabled' => 'Aktivuj túto šablónu na použitie v kampani.', 'parent_attribute_template' => 'Táto šablóna atribútov môže byť podradená inej šablóne atribútov. Ak bude aplikovaná táto šablóna atribútov, aplikujú sa zároveň s ňou aj všetky jej nadradené šablóny atribútov.', ], 'index' => [], 'lists' => [ 'empty' => 'Vytvor šablónu na používanie bežných vlastností vo viacerých objektoch.', ], 'placeholders' => [ 'name' => 'Názov šablóny atribútov', ], 'show' => [], ]; ================================================ FILE: lang/sk/attributes/templates.php ================================================ [ 'marketplace' => [ 'hint' => 'Chyba', 'rendering' => 'Pri vytváraní pluginu pre trhovisko sa vyskytla chyba. Prosím, kontaktuj tvorcu pluginu.', ], ], 'helpers' => [], 'list' => [ 'sheets' => 'Denníky postáv', ], 'pitch' => 'Nájdi a pridaj denníky postáv z :marketplace k :boosted-campaign.', ]; ================================================ FILE: lang/sk/auth.php ================================================ [ 'permanent' => 'Máš permanentný zákaz.', 'temporary' => '{1} Máš zákaz na :days deň.|[2,4] Máš zákaz na :days dni.|[5,*] Máš zákaz na :days dní.', ], 'confirm' => [ 'confirm' => 'Potvrdiť', 'error' => 'Nesprávne heslo, prosím skús ešte raz.', 'helper' => 'Prosím potvrď tvoje heslo, aby bolo možné pokračovať.', 'title' => 'Potvrdenie hesla', ], 'continue' => [ 'facebook' => 'Pokračovať cez Facebook', 'google' => 'Pokračovať cez Google', 'x' => 'Pokračovať cez X', ], 'failed' => 'Prihlasovacie údaje nie sú správne.', 'helpers' => [ 'password' => 'Zobraziť / Skryť heslo', ], 'login' => [ 'fields' => [ '2fa' => 'Jednorazové heslo', 'email' => 'E-mail', 'password' => 'Heslo', ], 'no-account' => 'Nemáš ešte konto?', 'or' => 'ALEBO', 'password_forgotten' => 'Zabudnuté heslo?', 'sign-up' => 'Registruj sa', 'submit' => 'Prihlásiť', 'title' => 'Prihlásenie', ], 'register' => [ 'already' => 'Máš už konto? :login', 'errors' => [ 'email_already_taken' => 'Konto s touto e-mailovou adresou už existuje.', 'general_error' => 'Nastala chyba pri registrácii tvojho konta. Prosím, skús to znovu.', ], 'fields' => [ 'email' => 'E-mail', 'name' => 'Meno užívateľa', 'password' => 'Heslo', ], 'log-in' => 'Prihlásenie', 'submit' => 'Registrovať', 'title' => 'Registrácia', 'tos' => 'Registráciou konta súhlasíš s našimi :terms a :privacy.', ], 'reset' => [ 'fields' => [ 'email' => 'E-mailová adresa', 'password' => 'Heslo', 'password_confirmation' => 'Potvrď svoje heslo', ], 'send' => 'Zaslať link na obnovenie hesla', 'submit' => 'Obnoviť heslo', 'title' => 'Obnovenie hesla', ], 'tfa' => [ 'helper' => 'Dvojstupňové overenie identity je aktívne. Zadaj prosím jednorazové heslo z tvojej autentifikačnej aplikácie.', 'title' => 'Dvojstupňové overenie identity', ], 'throttle' => 'Prekročený limit pokusov. Skús to znovu o :seconds sekúnd.', 'x-twitter' => 'X pôvodne známy ako Twitter', ]; ================================================ FILE: lang/sk/banners.php ================================================ 'Použi promo kód :code a získaj 20% zľavu z predplatného na prvý rok!', ]; ================================================ FILE: lang/sk/billing/invoices.php ================================================ [ 'download' => 'Stiahnuť PDF', ], 'description' => 'Zobrazujú sa faktúry za posledných 24 mesiacov.', 'empty' => 'Žiadne faktúry neboli nájdené', 'fields' => [ 'amount' => 'Množstvo', 'date' => 'Dátum', 'invoice' => 'Faktúra', 'status' => 'Stav', ], 'paypal' => 'Prosím, ber na vedomie, že sa tu zobrazujú iba platby uskutočnené cez Stripe, a nie cez PayPal.', 'status' => [ 'paid' => 'Zaplatená', 'pending' => 'Očakáva sa platba', ], 'title' => 'História fakturácie', ]; ================================================ FILE: lang/sk/billing/menu.php ================================================ 'História fakturácie', 'overview' => 'Prehľad', 'payment-method' => 'Platobná metóda', ]; ================================================ FILE: lang/sk/billing/payment_methods.php ================================================ 'Platobná metóda', 'types' => [ 'card' => 'Karta', ], ]; ================================================ FILE: lang/sk/bookmarks.php ================================================ [ 'customise' => 'Upraviť bočný panel', ], 'create' => [ 'title' => 'Nový menu link', ], 'destroy' => [], 'edit' => [ 'title' => 'Menu link :name', ], 'fields' => [ 'active' => 'Aktívny', 'dashboard' => 'Nástenka', 'default_dashboard' => 'Štandardná nástenka', 'filters' => 'Filtre', 'menu' => 'Menu', 'position' => 'Pozícia', 'random_type' => 'Náhodný typ objektu', 'selector' => 'Konfigurácia Menu linkov', 'target' => 'Cieľ', ], 'helpers' => [ 'active' => 'Neaktívne rýchle linky sa v bočnom menu nezobrazia.', 'css' => 'Pridaj CSS štýl, ktorý sa aplikuje na link záložiek v bočnom menu.', 'dashboard' => 'Cieľ s rýchlym linkom na jednu z vlastných násteniek kampane.', 'default_dashboard' => 'Prelinkuj namiesto toho k štandardnej nástenke kampane. Ešte stále musíš ale zvoliť aj vlastnú nástenku.', 'entity' => 'Nastav tento menu link, aby smeroval priamo na daný objekt. Pole :tab kontroluje, ktorá z kariet objektu bude zobrazená automaticky. Pole :menu kontroluje, ktorá podstránka objektu bude zobrazená.', 'position' => 'Použi toto pole na kontrolu v akom poradí sa linky zoradia v menu.', 'random' => 'Použi toto pole pre rýchly link cielený na náhodný objekt. Môžeš nastaviť filter, aby smeroval na špecifické typy objektov.', 'selector' => 'Nastav, kam tento rýchly link bude smerovať, ak na neho užívateľ klikne v bočnej lište.', 'type' => 'Nastav tento menu link, aby sa po kliknutí naň zobrazil zoznam objektov. Skopíruj časť URL na filtrovanom zozname objektov, ktorá sa nachádza po :? a vlož ju do poľa :filter.', ], 'index' => [], 'lists' => [ 'empty' => 'Ulož záložku k tvojim najpoužívanejším objektom alebo filtrovaných zoznamov pre rýchlejší prístup.', ], 'placeholders' => [ 'filters' => 'location_id=15&type=city', 'menu' => 'Podstránka (použi poslednú časť textu URL)', 'tab' => '(zastaralé)', ], 'random_no_entity' => 'Nebol nájdený žiaden náhodný objekt.', 'random_types' => [ 'any' => 'Hociktorý objekt', ], 'reorder' => [ 'success' => 'Menu linky usporiadané.', 'title' => 'Usporiadanie liniek v menu', ], 'show' => [], 'targets' => [ 'dashboard' => 'Nástenka', 'entity' => 'Jeden objekt', 'random' => 'Náhodný objekt', 'select' => 'Vyber jednu z možností', 'type' => 'Objekty kategórie', ], 'visibilities' => [ 'is_active' => 'Zobraziť rýchly link v bočnom paneli', ], ]; ================================================ FILE: lang/sk/bragi.php ================================================ [ 'generate' => 'Generovať', 'insert' => 'Použiť', ], 'errors' => [ 'invalid-sub' => 'Na prístup k tejto funkcionalite potrebuješ predplatné na úrovni Wyvern alebo Elemental.', 'out-of-tokens' => 'Už nemáš žiadny žetón! Ďalšie obdržíš dňa :date.', ], 'here' => 'tu', 'intro' => 'Ahoj! Som :name, UI, ktorá ti pomôže generovať príbehy pre tvoje postavy v Kanke. Viac o mne sa môžeš dozvedieť :here.', 'kankappy' => 'Si tajný učeň Kankappy.', 'loading' => 'Drž si klobúk, tvrdo premýšľam, môže to trvať hoc aj minútu!', 'placeholders' => [ 'prompt' => 'Zadaj kľúčové slová, ktoré mám premeniť na osobný príbeh.', ], 'token-limit' => 'Aktuálne máš :amount žetónov. Každé vygenerovanie príbehu spotrebuje jeden žetón, takže ich používaj rozumne!', ]; ================================================ FILE: lang/sk/calendars/weather.php ================================================ [], 'create' => [ 'helper' => 'Pridaj informácie o počasí, ktoré sa zobrazí v kalendári.', 'success' => 'Počasie pridané.', 'title' => 'Nový efekt počasia', ], 'destroy' => [ 'success' => 'Počasie odstránené.', ], 'edit' => [ 'success' => 'Počasie upravené.', 'title' => 'Upraviť počasie', ], 'fields' => [ 'effect' => 'Efekt', 'name' => 'Názov', 'precipitation' => 'Zrážky', 'temperature' => 'Teplota', 'weather' => 'Počasie', 'wind' => 'Vietor', ], 'options' => [ 'weather' => [ 'bolt' => 'Búrka', 'cloud' => 'Oblačno', 'cloud-rain' => 'Dážď', 'cloud-showers-heavy' => 'Silný dážď', 'cloud-sun' => 'Polooblačno', 'cloud-sun-rain' => 'Polooblačno s prehánkami', 'meteor' => 'Meteor', 'smog' => 'Smog', 'snowflake' => 'Sneženie', 'sun' => 'Jasno', 'wind' => 'Veterno', ], ], 'placeholders' => [ 'effect' => 'Magický alebo prírodný efekt', 'name' => 'Nepovinný vlastný názov počasia', 'precipitation' => 'Množstvo zrážok', 'temperature' => 'Denná najvyššia a najnižšia teplota', 'wind' => 'Rýchlosť vetra', ], ]; ================================================ FILE: lang/sk/calendars.php ================================================ [ 'add_epoch' => 'Pridať epochu', 'add_intercalary' => 'Pridať priestupný deň', 'add_month' => 'Pridať kalendárny mesiac', 'add_moon' => 'Pridať družicu', 'add_reminder' => 'Pridať pripomienku', 'add_season' => 'Pridať ročné obdobie', 'add_weather' => 'Nastaviť efekt počasia', 'add_week' => 'Pridať týždeň', 'add_weekday' => 'Pridať deň týždňa', 'add_year' => 'Pridať názov roka', 'set_today' => 'Nastaviť aktuálny deň', 'today' => 'Dnes', 'update_weather' => 'Aktualizovať počasie', ], 'checkboxes' => [ 'is_recurring' => 'Opakuje sa ročne', ], 'colours' => [], 'create' => [ 'title' => 'Nový kalendár', ], 'destroy' => [], 'edit' => [ 'today' => 'Aktuálny dátum upravený.', ], 'event' => [ 'create' => [ 'success' => 'Pripomienka vytvorená', 'title' => 'Nová pripomienka', ], 'destroy' => 'Pripomienka z kalendára ":name" odstránená.', 'edit' => [ 'success' => 'Pripomienka upravená.', 'title' => 'Upraviť pripomienku v :name', ], 'errors' => [ 'invalid_entity' => 'Neplatná voľba objektu', ], 'helpers' => [ 'other_calendar' => 'Upravuješ pripomienku, ktorá je v kalendári :calendar.', ], 'success' => 'Udalosť ":event" pridaná do kalendára.', ], 'events' => [ 'bulks' => [ 'delete' => '{1} Zmazaná :count pripomienka.|[2,4] Zmazané :count pripomienky.[5,*] Zmazaných :count pripomienok.', 'patch' => '{1} Aktualizovaná :count pripomienka.|[2,4] Aktualizované :count pripomienky.[5,*] Aktualizovaných :count pripomienok.', ], 'end' => '(koniec)', 'filters' => [ 'show_after' => 'Zobraziť od dnes ďalej', 'show_all' => 'Zobraziť všetky', 'show_before' => 'Zobraziť do dnes', ], 'start' => '(začiatok)', ], 'fields' => [ 'comment' => 'Komentár', 'current_day' => 'Aktuálny deň', 'current_month' => 'Aktuálny mesiac', 'current_year' => 'Aktuálny rok', 'date' => 'Aktuálny dátum', 'day' => 'Deň', 'default_layout' => 'Štandardné rozmiestnenie', 'format' => 'Formát', 'is_incrementing' => 'Narastajúce dni', 'is_recurring' => 'Opakujúce', 'leap_year' => 'Priestupné roky', 'leap_year_amount' => 'Pridať dni', 'leap_year_month' => 'Mesiac', 'leap_year_offset' => 'Každý', 'leap_year_start' => 'Priestupný rok', 'length' => 'Dĺžka udalosti', 'length_days' => ':count deň|:count dní', 'month' => 'Mesiac', 'months' => 'Mesiace', 'moons' => 'Družice', 'parameters' => 'Parametre', 'recurring_until' => 'Opakujúce sa do roku', 'reset' => 'Týždenný reset', 'seasons' => 'Ročné obdobia', 'show_birthdays' => 'Zobraziť narodeniny', 'skip_year_zero' => 'Preskočiť Rok Nula', 'start_offset' => 'Posun prvého dňa', 'suffix' => 'Prípona', 'week_names' => 'Názvy týždňov', 'weekdays' => 'Dni v týždni', 'year' => 'Rok', ], 'helpers' => [ 'default_layout' => 'Zvoľ, ktoré rozmiestnenie kalendára sa má štandardne zobrazovať.', 'format' => 'Pridaj vlastný formát dátumu pre objekty v kalendári.', 'month_type' => 'Priestupné mesiace nepoužívajú dni v týždni, ale ovplyvňujú družice a ročné obdobia.', 'moon_offset' => 'Štandardne začína spln prvý deň v roku 0. Nastavenie posunu ovplyvňuje, kedy sa tento spln udeje. Hodnota môže byť negatívna (do max. dĺžky prvého mesiaca) alebo pozitívna (do max. dĺžky prvého mesiaca).', 'start_offset' => 'Štandardne začína kalendár prvý deň v týždni v roku 0. Nastavenie tejto hodnoty ovplyvňuje, na ktorý deň v kalendári pripadne prvý deň.', ], 'hints' => [ 'event_length' => 'Ako dlho má trvať daná udalosť. Udalosť nemôže trvať dlhšie ako dva mesiace.', 'is_incrementing' => 'Narastajúci kalendár automaticky posunie aktuálny deň o 00:00 UTC.', 'leap_year' => 'Nastav priestupné roky pre kalendár.', 'months' => 'Kalendár by mal mať min. 2 mesiace.', 'moons' => 'Pridané družice sa zobrazia v kalendári počas ich splnu.', 'parent_calendar' => 'Ak kalendáru priradíš nadradený kalendár, priradíš mu aj pripomienky a efekty počasia z tohto nadradeného kalendáru.', 'reset' => 'Začiatok mesiaca alebo roku začína stále na prvom dni týždňa.', 'seasons' => 'Vytvor v tvojom kalendári ročné obdobia tým, že označíš, kedy sa začínajú. O ostatné sa už postará Kanka.', 'show_birthdays' => 'Zobrazí ročne narodeniny postáv, ktoré majú pripomenutie o narodeninách až po ich dátum smrti.', 'skip_year_zero' => 'Štandardne je prvý rok kalendára nultým rokom. Aktivovaním tohto nastavenia tento preskočíš.', 'weekdays' => 'Nastav názvy tvojich dní v týždni. Podmienkou je pridanie min. 2 dní v týždni.', 'weeks' => 'Definuj názvy pre najdôležitejšie týždne v tvojom kalendári.', 'years' => 'Niektoré roky sú tak dôležité, že dostali vlastné pomenovanie.', ], 'index' => [], 'layouts' => [ 'month' => 'Mesiac', 'monthly' => 'Štandardne mesačné', 'year' => 'Rok', 'yearly' => 'Štandardne ročné', ], 'lists' => [ 'empty' => 'Vytvor kalendár pre zaznamenávanie dátumov, festivalov a iných udalostí v čase.', ], 'modals' => [ 'switcher' => [ 'title' => 'Prepínač roku', ], ], 'month_types' => [ 'intercalary' => 'Priestupný', 'standard' => 'Štandardný', ], 'options' => [ 'events' => [ 'recurring_periodicity' => [ 'fullmoon' => 'Spln', 'fullmoon_name' => 'Spln :moon', 'month' => 'Mesačne', 'newmoon' => 'Nov', 'newmoon_name' => 'Nov :moon', 'none' => 'Žiadna', 'unnamed_moon' => 'Mesiac :number', 'year' => 'Ročne', ], ], 'resets' => [ '' => 'Žiadne', 'month' => 'Mesačne', 'year' => 'Ročne', ], ], 'panels' => [ 'intercalary' => 'Priestupné dni', 'leap_year' => 'Priestupný rok', 'months' => 'Mesiace', 'weeks' => 'Týždne', 'years' => 'Pomenované roky', ], 'parameters' => [ 'intercalary' => [ 'length' => 'Trvanie v dňoch', 'month' => 'Na konci ktorého mesiaca', 'name' => 'Názov priestupného mesiaca', ], 'month' => [ 'alias' => 'Skratka mesiaca', 'length'=> 'Počet dní', 'name' => 'Názov mesiaca', 'type' => 'Typ', ], 'moon' => [ 'fullmoon' => 'Spln každých (dní)', 'name' => 'Názov družice', 'offset' => 'Deň prvého splnu', ], 'seasons' => [ 'day' => 'Prvý deň', 'month' => 'Prvý mesiac', 'name' => 'Názov ročného obdobia', ], 'weeks' => [ 'name' => 'Názov týždňa', 'number' => 'Číslo', ], 'year' => [ 'name' => 'Názov roku', 'number' => 'Rok', ], ], 'placeholders' => [ 'colour' => 'Farba', 'comment' => 'Deň narodenia, festival, slnovrat', 'date' => 'Aktuálny dátum', 'leap_year_amount' => 'Počet dní pridaných v priestupnom roku', 'leap_year_month' => 'Mesiac, do ktorého budú pridané', 'leap_year_offset' => 'Každých koľko rokov sa opakuje priestupný rok', 'leap_year_start' => 'Prvý rok, ktorý je priestupný', 'length' => 'Dĺžka udalosti v dňoch', 'months' => 'Počet mesiacov v roku', 'recurring_until' => 'Posledný rok opakovania (ponechať prázdne pre opakovanie donekonečna)', 'seasons' => 'Počet ročných období', 'suffix' => 'Prípona aktuálnej éry (pnl., nl.)', 'type' => 'Typ kalendára', 'weekdays' => 'Počet dní v týždni', ], 'show' => [ 'missing_details' => 'Tento kalendár nie je možné zobraziť. Kalendár vyžaduje min. 2 mesiace a min. 2 dni v týždni, aby bol vytvorený.', 'moon_1first_quarter' => ':moon prvá štvrtina', 'moon_full' => ':moon spln', 'moon_last_quarter' => ':moon posledná štvrtina', 'moon_new' => ':moon nov', 'tabs' => [ 'events' => 'Kalendárne udalosti', 'weather' => 'Počasie', ], ], 'sorters' => [ 'after' => 'Dnes a potom', 'before'=> 'Dnes a predtým', ], 'validators' => [ 'format' => 'Formát dátumu je neplatný.', 'moon_offset' => 'Posun pre prvý spln nemôže byť väčší ako dĺžka prvého mesiaca kalendára.', ], 'warnings' => [ 'event_length' => 'Pripomienky, ktoré prechádzajú cez niekoľko rokov sú viditeľné iba v prvých dvoch rokoch. Viac sa dozvieš v našej :documentation.', ], ]; ================================================ FILE: lang/sk/callouts.php ================================================ [ 'actions' => [ 'boost' => 'Boostni :campaign', 'superboost' => 'Superboostni :campaign', ], 'learn-more' => 'Čo sú boosty?', 'limitation' => 'Pre prístup k tejto funkcionalite je nutné boostnuť kampaň.', 'limitations' => [ 'boosted' => 'Pre prístup k tejto funkcionalite je nutné boostnuť kampaň.', 'superboosted' => 'Pre prístup k tejto funkcionalite je nutné superboostnuť kampaň.', ], 'multiple' => 'Pre prístup k týmto funkcionalitám je nutné boostnuť kampaň.', 'pitches' => [ 'element-class' => 'Pridaj tomuto prvku vlastnú CSS triedu, ak máš :boosted-campaign.', 'icon' => 'Odomkni milióny ikoniek z FontAwesome, ak máš :boosted-campaign.', ], 'titles' => [ 'boosted' => 'Boostnutá funkcia', 'superboosted' => 'Superboostnutá funkcia', ], ], 'premium' => [ 'learn-more' => 'Čo sú prémiové kampane?', 'limitation' => 'Na prístup k tejto funkcionalite musia byť aktivované prémiové funkcionality.', 'title' => 'Funkcionalita prémiovej kampane', 'unlock' => 'Odomkni prémiové funkcionality pre :campaign', ], 'subscribe' => [ 'pitch-image' => 'Kúp si predplatné, aby sa odomkla :max MB veľkosť nahrávaných súborov.', 'share-booster' => 'Boostni :campaign, aby sa zvýšila veľkosť nahrávaných súborov pre všetkých členov kampane.', 'share-premium' => 'Zvýš hranicu veľkosti nahrávaných súborov pre všetkých členov kampane pomocou prémia.', ], ]; ================================================ FILE: lang/sk/campaigns/achievements.php ================================================ 'Gratulujeme!', 'connections' => '{0} Žiaden vzťah nevytvorený|{1} Jeden vzťah vytvorený|[2,4] :amount vzťahy vytvorené|[5,*] :amount vzťahov vytvorených', 'created' => '{0} Žiaden :plural nevytvorený|{1} Jeden :single vytvorený|[2,4] :amount :plural vytvorené|[5,*] :amount :plural vytvorených', 'dead' => '{0} Žiadne tajomstvo smrti|{1} Jedna tajomná smrť|[2,4] :amount tajomné usmrtenia|[5,*] :amount tajomných usmrtení', 'goal' => 'Cieľ :number', 'goal_reached' => 'Kampani sa podarilo dosiahnuť nasledujúce úspechy:', 'level' => 'úroveň :number', 'markers' => '{0} Žiadna značka na mape|{1} Jedna značka na mape|[2,4] :amount značky na mape|[5,*] :amount značiek na mape', 'painter' => '{0} Žiadna téma nevytvorená|{1} Jedna téma vytvorená|[2,4] :amount témy vytvorené|[5,*] :amount tém vytvorených', 'pitch' => 'Úspechy v kampani sú zábavný spôsob, ako osláviť určité míľniky v tvojej ceste tvorbou svetov. Sleduj svoj postup, zapoj svoje hráčstvo a zobraz tieto výsledky s prémiovou kampaňou.', 'plugins' => '{0} Žiaden plugin nenainštalovaný|{1} Jeden plugin nainštalovaný|[2,4] :amount pluginy nainštalované|[5,*] :amount pluginov nainštalovaných', 'remaining' => [ 'generic' => 'viac a dosiahneš ďalšiu úroveň.', ], 'spotlight' => [ 'active' => [ 'cta' => 'Zobraziť zvýraznené', ], 'private' => [ 'cta' => 'Skontroluj verejné nastavenia', 'helper' => 'Urob svoju kampaň verejnou, aby mohla byť zvýraznená na webe.', ], 'public' => [ 'cta' => 'Zisti, ako funguje zvýraznenie', 'helper' => 'Vybrané kampane sú uvedené v rámci Kanka Zvýraznenia a na blogu.', ], ], 'spotlighted' => '{0} Ešte nezvýraznená|[1,*] Zvýraznená', 'tagged' => '{0} Žiaden objekt nevytvorený|{1} Jeden objekt vytvorený|[2,4] :amount objekty vytvorené|[5,*] :amount objektov vytvorených', 'titles' => [ 'calendars' => 'Strážca/kyňa času', 'characters' => 'Rozdávač/ka mien', 'connections' => 'Amor', 'creatures' => 'Rodič/ka', 'dead' => 'Vrah/yňa', 'events' => 'Majster/ka legiend', 'families' => 'Rodinný plánovač/ka', 'locations' => 'Staviteľ/ka', 'markers' => 'Kartograf/ka', 'organisations' => 'Fúzie a akvizície', 'plugins' => 'Fajnšmeker/ka pluginov', 'quests' => 'Mastermind', 'spotlighted' => 'Zvýraznená', 'tags' => 'Pod kontrolou', 'themes' => 'Maliar/ka', ], 'tutorial' => 'Úspechy sledujú zaujímavé aktivity v kampani, napr. vytváranie zápisov alebo používanie kritických funkcionalít. Majú len informačnú funkciu a aktualizujú sa automaticky, ako tieto nachádzaš a využívaš.', ]; ================================================ FILE: lang/sk/campaigns/applications.php ================================================ [ 'accept' => 'Schváliť', 'reject' => 'Odmietnuť', ], 'apply' => [ 'apply' => 'Použiť', 'help' => 'Táto kampaň je otvorená pre nových členov. Prihlás sa do nej vyplnením tohto formulára. Obdržíš notifikáciu, keď sa administrátor kampane bude zaoberať tvojou prihláškou.', 'remove_text' => 'Tvoje podanie', 'success' => [ 'apply' => 'Tvoja prihláška bola uložená. Môžeš ju kedykoľvek zmeniť alebo odstrániť. Obdržíš notifikáciu, keď sa administrátor kampane bude zaoberať tvojou prihláškou.', 'remove'=> 'Tvoja prihláška bola odstránená.', 'update'=> 'Tvoja prihláška bola aktualizovaná. Môžeš ju kedykoľvek zmeniť alebo odstrániť. Obdržíš notifikáciu, keď sa administrátor kampane bude zaoberať tvojou prihláškou.', ], 'title' => 'Prihlásiť sa do :name', ], 'errors' => [], 'fields' => [ 'application' => 'Prihláška', 'reason' => 'Dôvod schválenia / odmietnutia', ], 'helpers' => [ 'modal' => 'Do kampane, ktorá je verejná a prijíma prihlášky, si môžu podať prihlášku noví užívatelia.', 'no_applications_title' => 'Žiadne prihlášky neboli nájdené', 'reason' => 'Ak vyplnený, uchádzajúca sa osoba obdrží tento dôvod.', 'role' => 'Pri schvaľovaní rola, ktorú obdrží uchádzajúca sa osoba.', ], 'open' => [ 'closed' => 'Kampaň je uzatvorená', 'open' => 'Kampaň je otvorená', 'title' => 'Otvorená kampaň', ], 'placeholders' => [ 'note' => 'Spíš tvoju prihlášku na vstup do kampane', 'reason' => 'Tvoj dôvod', ], 'public' => [ 'private' => 'Kampaň je súkromná.', 'public' => 'Kampaň je verejná.', 'title' => 'Verejná kampaň.', ], 'statuses' => [], 'toggle' => [ 'closed' => 'Neprijímať prihlášky', 'label' => 'Stav', 'open' => 'Prijíma prihlášky', 'success' => 'Stav kampane ohľadom prihlášok aktualizovaný.', 'title' => 'Stav prihlášok', ], 'update' => [ 'approve' => 'Vyber rolu užívateľa, ktorú bude mať v tvojej kampani.', 'approved' => 'Prihláška schválená', 'reject' => 'Spíš dobrovoľnú správu pre užívateľa, prečo bola prihláška odmietnutá.', 'rejected' => 'Prihláška odmietnutá', ], ]; ================================================ FILE: lang/sk/campaigns/builder.php ================================================ 'S pomocou tohto rozhrania vieš vizuálne upraviť tému tvojej kampane. Prejdi nižšie, ak chceš vidieť, ako sa zmeny prejavia v jednotlivých prvkoch v kampani. Ak zvolíš nejakú farbu, automaticky bude vybraná "kontrastná" farba pre text. Zisti viac o štýlovaní v našej sekcii :docs.', 'pitch' => 'Psst, vytvorili sme Staviteľa témy, ak by sa ti zažiadalo upraviť niektoré z farieb tvojej kampane 😉', 'pitch-go' => 'Prenes ma do Staviteľa témy', 'reset' => 'Štýlovanie Staviteľa témy resetované.', 'success' => 'Štýlovanie Staviteľa témy uložené.', 'title' => 'Staviteľ témy', ]; ================================================ FILE: lang/sk/campaigns/categories.php ================================================ [ 'permission-disabled' => 'Táto kategória nie je aktívna.', ], 'helpers' => [ 'aliases' => 'Pridaj aliasy a tajné identity k objektom v tvojom svete.', 'media' => 'Uploaduj mediálne dokumenty (obrázky, pdfká, audio) a externé linky k objektom.', ], 'tab' => 'Kategórie', ]; ================================================ FILE: lang/sk/campaigns/dashboard-header.php ================================================ [ 'success' => 'Záhlavie nástenky kampane bolo aktualizované.', 'title' => 'Aktualizovať záhlavie nástenky kampane', ], ]; ================================================ FILE: lang/sk/campaigns/default-images.php ================================================ [ 'add' => 'Pridať nový prednastavený obrázok', ], 'call-to-action' => 'Nahraj vlastný náhľad pre všetky kalendáre, miesta a ďalšie objekty kampane. Tieto obrázky sa potom zobrazujú v rôznych zoznamoch.', 'create' => [ 'error' => 'Chyba pri uložení nového prednastaveného obrázka objektu. Bol zvolený :type?', 'success' => 'Prednastavený obrázok objektu pre :type vytvorený.', 'title' => 'Nový prednastavený obrázok objektu', ], 'destroy' => [ 'success' => 'Prednastavený obrázok objektu pre :type odstránený.', ], 'index' => [], ]; ================================================ FILE: lang/sk/campaigns/delete.php ================================================ 'zálohu', 'confirm' => 'Ak máš istotu, že chceš permanentne odstrániť :campaign, prepíš :code do poľa nižšie.', 'confirm-button' => 'Permanentne odstrániť :name', 'helper' => 'Odstránenie kampane je permanentný akt, ktorý nemôže byť vrátený späť. Týmto budú zmazané všetky údaje danej kampane z našich serverov, vrátane obrázkov a materiálov. Odporúčame predtým vytvoriť :backup.', 'issue' => 'Nasledujúce záležitosti musia byť vyriešené predtým, ako bude kampaň odstránená.', 'members' => 'Všetky ostatné členstvá musia byť odstránené z kampane.', 'success' => ':name bola permanentne odstránená.', 'title' => 'Odstránenie', ]; ================================================ FILE: lang/sk/campaigns/export.php ================================================ [ 'download' => 'Stiahnuť', 'export' => 'Exportovať údaje kampane', ], 'confirm' => [ 'title' => 'Potvrdenie exportu', 'warning' => 'Plánuješ exportovať dáta kampane. Tento proces môže trvať dlhý čas v závislosti od veľkosti kampane. Môžeš naďalej používať Kanku, zatiaľ čo naše servre generujú export.', ], 'errors' => [ 'limit' => 'Kampaň už dnes bola raz exportovaná. Prosím, vyskúšaj to opäť zajtra.', ], 'expired' => 'Link vypršal', 'helpers' => [], 'progress' => 'Stav', 'size' => 'Veľkosť', 'status' => [ 'failed' => 'Neúspešné', 'finished' => 'Ukončené', 'running' => 'Prebieha', 'scheduled' => 'Plánované', ], 'success' => 'Pripravuje sa export kampane. O pripravení na stiahnutie ťa budeme informovať v Kanke.', 'title' => 'Export kampane', ]; ================================================ FILE: lang/sk/campaigns/gallery.php ================================================ [ 'close' => 'Zatvoriť', 'file-link' => 'Link k súboru', 'focus_point' => 'Nastaviť stredobod záujmu', 'image-link' => 'Link k obrázku', 'reset_focus' => 'Resetovať stredobod záujmu', 'save' => 'Uložiť', 'upgrade' => 'Navýšiť miesto na úložisku', ], 'breadcrumb' => 'Galéria', 'bulk' => [ 'destroy' => [ 'confirm' => 'Naozaj chceš natrvalo odstrániť vybrané prvky? Túto akciu nie je možné vrátiť.', 'success' => '{0}Neodstránené žiadne súbory.|{1}Jeden súbor odstránený.|{2,4} :count súbory odstránené.|{5,*} :count súborov odstránených.', ], ], 'cta' => 'Manažuj a používaj obrázky v celej kampani.', 'destroy' => [ 'folder' => 'Priečinok :name zmazaný.', 'success' => 'Obrázok :name zmazaný.', ], 'errors' => [ 'max' => 'Prosím zvoľ max. :count súborov naraz.', 'permissions' => 'Role v tvojej kampani nemajú oprávnenie :permission, aby mohli nahrávať obrázky do galérie kampane.', 'storage' => 'Nemáš dostatok miesta na nahranie vybraných obrázkov. Dostupné miesto: :available.', ], 'fields' => [ 'created_by' => 'Nahrané od', 'details' => 'Detaily', 'ext' => 'Ext', 'file_type' => 'Typ súboru', 'folder' => 'Priečinok', 'image_mentioned_in' => '{0} Tento obrázok sa nezobrazuje v žiadnom objekte kampane.|{1} Zobrazuje sa v 1 zázname/poznámke.|[2,*] Zobrazuje sa v :count záznamoch/poznámkach.', 'image_used_in' => '{1}Použitý ako obrázok jedného objektu.|[2,*]Použitý ako obrázok :count objektov.', 'link' => 'Link', 'name' => 'Názov', 'size' => 'Veľkosť', 'unused' => 'Nie je nikde použitý', 'used_in' => 'Použitý v', ], 'focus' => [ 'locked' => 'Na nastavenie stredobodu záujmu na obrázku je nutná prémiová kampaň.', 'removed' => 'Stredobod záujmu odstránený.', 'updated' => 'Stredobod záujmu aktualizovaný.', ], 'new_folder' => [ 'title' => 'Nový priečinok', ], 'no_folder' => 'Bez priečinku', 'pitch' => 'Nahraj obrázky do galérie kampane priamo z textového editoru.', 'placeholders' => [ 'search' => 'Hľadať názov obrázku...', ], 'storage' => [ 'of' => 'z', 'title' => 'úložiska', ], 'title' => 'Galéria kampane :campaign', 'update' => [ 'folder' => 'Priečinok zmenený.', 'success' => 'Obrázok zmenený.', ], 'uploader' => [ 'add' => 'Pridať nový', 'new_folder' => 'Nový priečinok', 'or' => 'alebo', 'select_file' => 'Vybrať súbor', 'well' => 'Prenes súbor sem k nahratiu', ], ]; ================================================ FILE: lang/sk/campaigns/import.php ================================================ [ 'import' => 'Nahrať export', ], 'fields' => [ 'updated' => 'Posledná aktulizácia', ], 'form' => 'Nahrať formulár', 'progress' => [ 'uploading' => 'Prebieha nahrávanie', ], 'status' => [ 'failed' => 'Zlyhaný', 'finished' => 'Ukončený', 'queued' => 'V čakačke', 'running' => 'Prebieha', ], 'title' => 'Import', ]; ================================================ FILE: lang/sk/campaigns/invites.php ================================================ [ 'helper' => 'Vytvor link s pozvánkou na zaslanie tvojmu hráčstvu, aby sa vedeli pridať do kampane.', ], ]; ================================================ FILE: lang/sk/campaigns/limits.php ================================================ 'Dosiahnutý limit', ]; ================================================ FILE: lang/sk/campaigns/members.php ================================================ [ 'limited' => ':amount z :total členstiev.', 'title' => 'Dostupné členstvá', 'unlimited' => ':amount z nekonečna členstiev.', ], 'roles' => [ 'helper' => 'Pridať alebo odobrať role členstva pre :user.', 'success' => 'Role úspešne aktualizované pre :user.', 'title' => 'Upraviť role členstva', ], ]; ================================================ FILE: lang/sk/campaigns/modules.php ================================================ [ 'create' => 'Vytvoriť modul', 'customise' => 'Prispôsobiť', ], 'create' => [ 'helper' => 'Vytvor nový vlastný modul na uloženie objektov, ktoré nepasujú do ostatných modulov.', 'success' => 'Nový modul vytvorený.', 'title' => 'Nový modul', ], 'delete' => [ 'confirm' => 'Napíš :code, ak máš istotu, že chceš permanentne zmazať :name vlastného modulu.', 'helper' => 'Naozaj chceš odstrániť vlastný modul :name? Automaticky to odstráni aj všetky objekty, záložky a widgety prepojené s týmto modulom.', 'success' => 'Modul :name odstránený.', 'title' => 'Odstrániť modul', ], 'errors' => [ 'disabled' => 'Modul :name nastavený ako neaktívny. :fix', 'limit' => 'Kampane môžu mať aktuálne :max vlastných modulov, kým táto funkcionalita prejde testom.', ], 'fields' => [ 'icon' => 'Ikona modulu', 'plural' => 'Množné meno modulu', 'singular' => 'Jednotné meno modulu', ], 'helpers' => [ 'custom' => 'Toto je vlastný modul.', 'icon' => 'Ikona :fontawesome, napr. :example.', 'plural' => 'Množné číslo mena objektu nového modulu, napr. elixíry', 'roles' => 'Zvoľ role, ktoré budú mať prístup na zobrazenie objektov nového modulu. Toto môže byť neskôr zmenené v nastavení rolí.', 'singular' => 'Jednotné číslo mena objektu nového modulu, napr. elixír', ], 'pitch' => 'Zmeň názov a ikonu asociovanú s týmto modulom pre celú kampaň.', 'pitch-custom' => 'Vytvor vlastné moduly na uloženie jedinečných objektov.', 'rename' => [ 'helper' => 'Zmeň názov a ikonu modulu v celej kampani. Ponechaj prázdne, ak chce používať pôvodné nastavenie Kanky.', 'success' => 'Modul prispôsobený.', 'title' => 'Prispôsobenie modulu :module', ], 'reset' => [ 'default' => 'Toto zresetuje iba štandardné moduly, nie vlastné.', 'success' => 'Moduly kampane boli resetované.', 'title' => 'Resetovať vlastné názvy a ikony modulov', 'warning' => 'Naozaj chceš resetovať moduly kampane na ich pôvodné názvy a ikony?', ], 'sections' => [ 'custom' => 'Vlastné moduly', 'default' => 'Štandardné moduly', 'features' => 'Funkcionality', ], 'states' => [ 'disable' => 'Deaktivovať', 'enable' => 'Aktivovať', ], ]; ================================================ FILE: lang/sk/campaigns/overview.php ================================================ [ 'title' => 'Sledovateľstvo', ], 'member' => [ 'title' => 'Členstvo', ], 'premium' => [ 'enable' => 'Aktivuj prémiové funkcionality', ], 'status' => [ 'title' => 'Viditeľnosť', ], ]; ================================================ FILE: lang/sk/campaigns/plugins.php ================================================ [ 'bulks' => [ 'disable' => 'Deaktivovať pluginy', 'enable' => 'Aktivovať pluginy', 'update' => 'Aktualizovať pluginy', ], 'changelog' => 'História zmien', 'disable' => 'Deaktivovať plugin', 'enable' => 'Aktivovať plugin', 'import' => 'Importovať', 'update' => 'Aktualizovať plugin', 'update_available' => 'Dostupná aktualizácia!', ], 'bulks' => [ 'delete' => '{1} Odstránený :count plugin.|[2,4] Odstránené :count pluginy.|[5,*] Odstránených :count pluginov.', 'disable' => '{1} Deaktivovaný :count plugin.|[2,4] Deaktivované :count pluginy.|[5,*] Deaktivovaných :count pluginov.', 'enable' => '{1} Aktivovaný :count plugin.|[2,4] Aktivované :count pluginy.|[5,*] Aktivovaných :count pluginov.', 'update' => '{1} Aktualizovaný :count plugin.|[2,4] Aktualizované :count pluginy.|[5,*] Aktualizovaných :count pluginov.', ], 'destroy' => [ 'success' => 'Plugin :plugin odstránený.', ], 'disabled' => [ 'success' => 'Plugin :plugin deaktivovaný.', ], 'empty_list' => 'Táto kampaň nemá aktuálne žiadne pluginy. Zájdi na Trhovisko, ak chceš nejaké nainštalovať a následne sa vráť sem, ich aktivovať.', 'enabled' => [ 'success' => 'Plugin :plugin aktivovaný.', ], 'errors' => [ 'invalid_plugin' => 'Neplatný plugin.', ], 'fields' => [ 'name' => 'Názov pluginu', 'obsolete' => 'Tento plugin bol tímom Kanky označený ako zastaralý, t.z. že nefunguje, ako bolo pôvodne plánované.', 'status' => 'Stav', 'type' => 'Typ pluginu', ], 'import' => [ 'button' => 'Importovať', 'created' => 'Nasledujúce objekty boli vytvorené:', 'helper' => 'Práve sa chystáš importovať :count objektov z pluginu :plugin. Ak už tento plugin bol importovaný, prepíšu sa zmeny, ktorá boli spravené v daných objektoch.', 'no_new_entities' => 'Žiadne nové objekty nebudú importované.', 'option_only_import' => 'Importovať len nové objekty a preskočiť už predtým importované.', 'option_private' => 'Importovať všetky nové objekty ako súkromné.', 'success' => '{1} Importovaný :count objekt z pluginu :plugin.|[2,4] Importované :count objekty z pluginu :plugin.|[5,*] Importovaných :count objektov z pluginu :plugin.', 'title' => 'Importovať :plugin', 'updated' => 'Nasledujúce objekty boli aktualizované:', ], 'info' => [ 'helper' => 'Ak je vydaná novšia verzia pluginu, môžeš si ho aktualizovať v tvojej kampani na poslednú verziu.', 'title' => 'Aktualizácie pluginu :plugin', 'updates' => 'Aktualizácie', ], 'pitch' => 'Inštaluj a spravuj pluginy z :marketplace.', 'status' => [ 'always' => 'Tento plugin bude stále aktívny, dokiaľ ho neodstrániš.', 'disabled' => 'Deaktivovaný', 'enabled' => 'Aktivovaný', ], 'templates' => [ 'name' => ':name od :user', ], 'title' => 'Pluginy kampane :name', 'types' => [ 'attribute' => 'Šablóna atribútov', 'pack' => 'Balík s obsahom', 'theme' => 'Téma', ], 'update' => [ 'success' => 'Plugin :plugin aktualizovaný.', ], ]; ================================================ FILE: lang/sk/campaigns/public.php ================================================ [], 'title' => 'Zmeniť viditeľnosť kampane', 'update' => [ 'private' => 'Kampaň je teraz privátna a viditeľná len pre jej členov.', 'public' => 'Kampaň je teraz verejná. Môže chvíľku trvať, kým sa zobrazí na stránke :public-campaigns.', ], ]; ================================================ FILE: lang/sk/campaigns/recovery.php ================================================ [ 'recover' => 'Obnoviť', 'recover_selected' => 'Obnoviť vybrané', ], 'error' => 'Počas obnovy objektov sa vyskytla chyba.', 'fields' => [ 'deleted' => 'Zmazané', 'deleted_at' => 'Zmazané :user dňa :date', ], 'name_link' => ':name bolo úspešne obnovené', 'order' => [ 'newest' => 'Zoradiť podľa: Najnovšie', 'newest_first' => 'Najnovšie ako prvé', 'oldest' => 'Zoradiť podľa: Najstaršie', 'oldest_first' => 'Najstaršie ako prvé', 'type' => 'Zoradiť podľa: Typu', 'type_order' => 'Typ', ], 'posts' => [], 'premium' => 'Obnovenie objektov je funkcionalita prémiovej kampane.', 'success_v2'=> '{1} :count prvok obnovený.|[2,4] :count prvky obnovené.|[5,*] :count prvkov obnovených.', 'title' => 'Obnovenie objektov', 'toggle' => [], ]; ================================================ FILE: lang/sk/campaigns/roles.php ================================================ [ 'status' => 'Stav: :status', ], 'overview' => [ 'limited' => ':amount z :total rolí vytvorených.', 'title' => 'Dostupné role', 'unlimited' => ':amount z nekonečna rolí vytvorených.', ], 'public' => [], 'show' => [ 'title' => 'Oprávnenia :role - :campaign', ], 'toggle' => [ 'disabled' => 'Členovia role :role už nemôžu :action :entities.', 'enabled' => 'Členovia role :role už môžu :action :entities.', ], 'warnings' => [ 'adding-to-admin' => 'Členovia role :name majú prístup ku všetkému v kampani a nemôžu byť odstránení inými členmi rovnakej roly. Po :amount minútach sa sami môžu odstrániť z tejto role.', ], ]; ================================================ FILE: lang/sk/campaigns/sidebar.php ================================================ [ 'reset' => 'Vrátiť na štandard', ], 'call-to-action' => 'Uprav poradie, ikonky a názvy prvkov bočného menu kampane.', 'helpers' => [ 'image' => 'Pridaj obrázok, ktorý reprezentuje kampaň. Tento obrázok bude použitý v bočnom menu a v rozhraní prepínača kampaní. Môžeš ho zmeniť hocikedy úpravou kampane.', 'reordering' => 'Uprav poradie v bočnom menu pretiahnutím symbolov vľavo na inú pozíciu.', ], 'image-success' => 'Nový obrázok kampane bol uložený. Tento obrázok môže byť zmenený opäť úpravou kampane.', 'reset' => [ 'success' => 'Štandardné nastavenie bočného menu kampane vrátené.', 'title' => 'Vrátiť nastavenie bočného menu', 'warning' => 'Naozaj chceš vrátiť nastavenie bočného menu kampane na štandard?', ], 'success' => 'Nastavenie bočného menu uložené.', 'title' => 'Nastavenie bočného menu kampane :campaign', 'tooltips' => [ 'image' => 'Zmeň tento obrázok pozadia', ], ]; ================================================ FILE: lang/sk/campaigns/stats.php ================================================ [ 'calendars' => [ 'goal' => 'Kalendáre', 'title' => 'Záznamník času', ], 'murderer' => [ 'goal' => 'Mŕtve postavy', 'title' => 'Vrah', ], ], 'fields' => [ 'created' => 'Vytvorené dňa', 'creator' => 'Vytvorené', 'general' => 'Všeobecné', ], 'targets' => [], 'title2' => 'Štatistiky', 'titles' => [ 'calendars' => 'Záznamník času úrovne :level', 'characters'=> 'Zadávateľ mien úrovne :level', 'dead' => 'Vrah úrovne :level', 'families' => 'Plánovač rodov úrovne :level', 'locations' => 'Staviteľ úrovne :level', 'quests' => 'Supermozog úrovne :level', 'races' => 'Chovateľ úrovne :level', ], ]; ================================================ FILE: lang/sk/campaigns/styles.php ================================================ [ 'current' => 'Aktuálna téma: :theme', 'disable' => 'Deaktivovať', 'enable' => 'Aktivovať', 'new' => 'Nový štýl', ], 'bulks' => [ 'delete' => '{1} Odstránený :count štýl.|[2,4] Odstránené :count štýly.|[5,*] Odstránených :count štýlov.', 'disable' => '{1} Deaktivovaný :count štýl.|[2,4] Deaktivované :count štýly.|[5,*] Deaktivovaných :count štýlov.', 'enable' => '{1} Aktivovaný :count štýl.|[2,4] Aktivované :count štýly.|[5,*] Aktivovaných :count štýlov.', ], 'create' => [ 'success' => 'Nový štýl vytvorený.', 'title' => 'Nový štýl', ], 'delete' => [ 'success' => 'Štýl :name odstránený.', ], 'errors' => [ 'max_content' => 'CSS pravidlo nemôže mať viac ako :amount znakov.', 'max_reached' => 'Dosiahnutý max. počet štýlov (:max).', ], 'fields' => [ 'content' => 'Pravidlo CSS', 'is_enabled' => 'Aktivovaný', 'length' => 'Dĺžka', 'modified' => 'Upravené', 'name' => 'Názov', 'order' => 'Poradie', ], 'helpers' => [ 'here' => 'na našom blogu', 'is_enabled' => 'Aktivovať túto tému na každej stránke.', 'main' => 'Tvojej boostnutej kampani môžeš pridať vlastné CSS štýlovanie. Tieto štýly sú nahrávané po tom, ako je nahraná téma z trhoviska, ktorú máš aktivovanú pre danú kampaň. Viac o štýloch pre tvoju kampaň nájdeš :here.', ], 'pitch' => 'Vytvor vlastný CSS štýl, ktorým si nastavíš vlastný vizuál kampane.', 'placeholders' => [ 'name' => 'Názov štýlu', ], 'reorder' => [ 'save' => 'Uložiť nové poradie', 'success' => '{1} Zmena poradia :count štýlu.|[2,4] Zmena poradia :count štýlov.|[5,*] Zmena poriadia :count štýlov.', 'title' => 'Preskupiť štýly', ], 'theme' => [ 'none' => 'Použiť preferenciu užívateľa', 'override' => 'Prepísať tému', 'success' => 'Téma kampane prepísaná.', 'title' => 'Aktualizovať tému kampane.', ], 'title' => 'Témovanie kampane', 'update' => [ 'success' => 'Štýl :name aktualizovaný.', 'title' => 'Aktualizovať štýl', ], ]; ================================================ FILE: lang/sk/campaigns/vanity.php ================================================ 'Meno :vanity je dostupné!', 'rule' => 'Pole :field vyžaduje aspoň jeden abecedný znak.', 'rule2' => 'Pole :field neumožňuje nasledujúce znaky: /.', 'set' => 'Skrátené URL kampane je trvalo nastavené na :vanity.', ]; ================================================ FILE: lang/sk/campaigns/webhooks.php ================================================ [ 'action' => 'Zmeniť stav', 'add' => 'Vytvoriť webhook', 'bulks' => [ 'delete_success' => '{1} :count webhook zmazaný.|[2,4] :count webhooky zmazané.|[5,*] :count webhookov zmazaných.', 'disable' => 'Deaktivovať', 'disable_success' => '{1} :count webhook deaktivovaný.|[2,4] :count webhooky deaktivované.|[5,*] :count webhookov deaktivovaných.', 'enable' => 'Aktivovať', 'enable_success' => '{1} :count webhook aktivovaný.|[2,4] :count webhooky aktivované.|[5,*] :count webhookov aktivovaných.', ], 'test' => 'Testovať webhook', 'update' => 'Aktualizovať webhook', ], 'create' => [ 'success' => 'Webhook úspešne vytvorený', 'title' => 'Pridať nový webhook', ], 'destroy' => [ 'success' => 'Webhook úspešne zmazaný', ], 'edit' => [ 'success' => 'Webhook úspešne aktualizovaný', 'title' => 'Aktualizovať webhook', ], 'fields' => [ 'enabled' => 'Aktivovaný', 'event' => 'Udalosť', 'events' => [ 'deleted' => 'Zmazaný objekt', 'edited' => 'Upravený objekt', 'new' => 'Nový objekt', ], 'message' => 'Správa', 'private_entities' => [ 'helper' => 'Nespustí webhook pri aktualizácii súkromných objektov.', 'skip' => 'Preskočiť súkromné objekty', ], 'type' => 'Typ', 'types' => [ 'custom' => 'Správa', 'payload' => 'Payload', ], 'url' => 'Url', ], 'helper' => [ 'active' => 'Ak je webhook práve aktívny', 'message' => 'Pridaj vlastnú správu s podporou pre mapovanie', 'status' => 'Zmeň aktívny stav webhooku', ], 'placeholders' => [ 'message' => '{who} urobil zmeny v {name}, zisti to na {url}', 'url' => 'Url cieľového webhooku', ], 'test' => [ 'success' => 'Požiadavka na test zaslaná', ], 'title' => 'Webhooky', ]; ================================================ FILE: lang/sk/campaigns.php ================================================ [], 'create' => [ 'success' => 'Kampaň vytvorená.', 'title' => 'Vytvoriť novú kampaň', ], 'destroy' => [], 'edit' => [ 'success' => 'Kampaň upravená.', ], 'entity_note_visibility' => [], 'entity_personality_visibilities' => [ 'private' => 'Nové postavy majú popis osobnosti nastavený štandardne ako súkromný.', ], 'entity_visibilities' => [ 'private' => 'Nové objekty sú súkromné', ], 'errors' => [ 'access' => 'K tejto kampani nemáš prístup.', 'premium' => 'Táto funkcionalita je dostupná pre prémiové kampane.', 'unknown_id' => 'Neznáma kampaň.', ], 'export' => [], 'fields' => [ 'boosted' => 'Boost od', 'entity_count' => 'Počet objektov', 'entry' => 'Popis kampane', 'followers' => 'Odberatelia', 'genre' => 'Žáner', 'header_image' => 'Titulný obrázok', 'image' => 'Obrázok', 'locale' => 'Jazyk', 'name' => 'Názov', 'open' => 'Otvorená pre prihlášky', 'premium' => 'Prémium poskytnuté od :name', 'public' => 'Viditeľnosť kampane', 'public_campaign_filters' => 'Filter verejných kampaní', 'superboosted' => 'Superboostnutie od', 'system' => 'Systém', 'theme' => 'Téma', 'vanity' => 'Skrátené URL', ], 'following' => 'Odber aktívny', 'helpers' => [ 'boosted' => 'Niektoré funkcie sú odomknuté, pretože táto kampaň je boostnutá. Viac nájdeš v :settings.', 'css' => 'Napíš svoj vlastný CSS, ktorý sa nahrá do stránok tvojej kampane. Prosím, uvedom si, že hociktoré zneužitie tejto funkcionality môže viesť k odstráneniu tvojho užívateľského CSS kódu. Opakované alebo závažné porušenia môžu viesť k odstráneniu tvojej kampane.', 'dashboard' => 'Prispôsob zobrazenie widgetu na nástenke vyplnením týchto údajov.', 'excerpt' => 'Krátky popis kampane sa zobrazí na nástenke, napíš teda pár pár viet ako úvod do tvojho sveta. Nemusíš sa rozpisovať, stačí pár slov.', 'header_image' => 'Obrázok, ktorý sa bude zobrazovať na pozadí widgetu pre záhlavie kampane na nástenke.', 'hide_history' => 'Aktivuj toto nastavenie, ak chceš skryť prehľad minulých zmien objektov pre neadministrátorov.', 'hide_members' => 'Aktivuj toto nastavenie, ak chceš skryť zoznam členov kampane pre neadministrátorov.', 'locale' => 'Regionálne nastavenie, ktoré sa vzťahuje na tvoju kampaň. Používa sa na vytváranie obsahu a filtrovanie verejných kampaní.', 'name' => 'Tvoja kampaň / svet môže mať ľubovoľné meno, pokiaľ sa skladá z min. 4 písmen alebo čísel.', 'no_entry' => 'Vyzerá to tak, že kampaň ešte nemá žiaden popis! Zmeňme to.', 'premium' => 'Niektoré funkcionality sú dostupné, lebo boli odomknuté prémiové funkcionality. Zisti viac na stránke :settings.', 'public_campaign_filters' => 'Pomôž iným nájsť tvoju kampaň medzi ostatnými verejnými doplnením týchto informácií.', 'public_no_visibility' => 'Hlavu hore! Tvoja kampaň je verejná, ale rola pre verejnosť nemá k ničomu prístup. :fix', 'system' => 'Ak je tvoja kampaň verejne viditeľná, systém sa zobrazuje na stránke :link.', 'systems' => 'Aby sme užívateľov nezahltili nespočetnými možnosťami, niektoré funkcionality Kanky sú prístupné len pre špecifické RPG systémy (napr. štatistický popis príšer pre D&D 5e). Priradením systému na tomto mieste aktivuješ dané funkcionality.', 'theme' => 'Nastav tému pevne pre kampaň a prepíš nastavenie užívateľov.', 'view_public' => 'Ak si chceš pozrieť tvoju kampaň ako verejnú, otvor tento :link v novom inkognito okne.', ], 'index' => [], 'invites' => [ 'actions' => [ 'copy' => 'Kopírovať link do schránky', 'link' => 'Pozvať ľudí', ], 'create' => [ 'buttons' => [ 'create' => 'Vytvoriť pozvánku', ], 'success_link' => 'Link vytvorený: :link', 'title' => 'Pozvať niekoho k tvojej kampani', ], 'destroy' => [ 'success' => 'Pozvánka odstránená.', ], 'error' => [ 'inactive_token' => 'Táto pozvánka už bola použitá alebo daná kampaň už neexistuje.', 'invalid_token' => 'Platnosť tejto pozvánky už vypršala.', 'join' => 'Prosím, prihlás sa alebo si registruj nové konto k prístupu do :campaign.', ], 'fields' => [ 'created' => 'Zaslať', 'role' => 'Rola', 'token' => 'Žetón', 'type' => 'Typ', 'usage' => 'Max. počet použití', ], 'helpers' => [ 'role' => 'Užívatelia musia byť súčasťou kampane, aby mohli obdržať rolu admina.', 'usage' => 'Koľkokrát môže byť pozvánkový link použitý, než sa stane nefunkčným.', ], 'unlimited_validity' => 'Neobmedzený', 'usages' => [ 'five' => '5 použití', 'no_limit' => 'Bez obmedzenia', 'once' => '1 použitie', 'ten' => '10 použití', ], ], 'leave' => [ 'action' => 'Opustiť kampaň', 'confirm' => 'Naozaj chceš opustiť kampaň :name? Už ku nej nebudeš mať prístup, ibaže by ťa do nej opäť pozval jej administrátor.', 'confirm-button' => 'Áno, opustiť kampaň', 'error' => 'Nemôže opustiť kampaň.', 'fix' => 'Prejsť na členstvo kampane', 'no-admin-left' => 'Nie je možné opustiť kampaň, pretože by tak ostala bez adminov. Priraď najprv inému členovi alebo členke rolu admin.', 'success' => 'Opustil/a si kampaň.', 'title' => 'Opustenie kampane', ], 'members' => [ 'actions' => [ 'remove' => 'Odstrániť z kampane', 'switch' => 'Prepnúť', 'switch-back' => 'Prepnúť späť', 'switch-entity' => 'Zobraziť ako', ], 'fields' => [ 'banned' => 'Užívateľ má zákaz', 'joined' => 'Súčasťou od', 'last_login' => 'Posledné prihlásenie', 'name' => 'Užívateľ', 'role' => 'Rola', 'roles' => 'Roly', ], 'helpers' => [ 'switch' => 'Prepnúť na tohto užívateľa', ], 'impersonating' => [ 'message' => 'Kampaň teraz vidíš ako iný užívateľ. Niektoré funkcionality boli deaktivované, ale ostatok vyzerá rovnako, ako by to videl daný užívateľ. Aby si sa prepol/a späť na tvojho užívateľa, použi tlačidlo Prepnúť, ktoré sa nachádza na mieste, kde je bežne tlačidlo Logout.', 'title' => 'Náhľad ako :name', ], 'invite' => [ 'description' => 'Do tvojej kampane môžeš pozvať priateľa/ku tým, že zadáš ich e-mailovú adresu. Po akceptovaní pozvánky bude pridaný/á ako člen s danou rolou. Zaslaná pozvánka môže byť hocikedy zrušená.', 'more' => 'Nové role môžeš pridať cez :link.', 'title' => 'Pozvať', ], 'removal' => 'Odstraňuješ ":member" z kampane.', 'roles' => [ 'member' => 'Člen', 'owner' => 'Administrátor', 'player' => 'Hráč', 'public' => 'Verejný', 'viewer' => 'Divák', ], 'switch_back_success' => 'Teraz si späť ako tvoj vlastný užívateľ.', ], 'mentions' => [], 'modules' => [], 'open_campaign' => [], 'options' => [], 'overview' => [ 'entity-count' => '{0} Žiadne objekty|{1} :amount objekt|[2,4] :amount objekty|[5,*] :amount objektov', 'follower-count' => '{0} Žiadni sledovatelia|{1} :amount sledovateľ|[2,4] :amount sledovatelia|[5,*] :amount sledovateľov', ], 'panels' => [ 'dashboard' => 'Nástenka', 'privacy' => 'Štandardy ochrany dát', 'setup' => 'Nastavenia', 'sharing' => 'Zdieľanie', 'systems' => 'Systémy', 'ui' => 'Rozhranie', ], 'placeholders' => [ 'locale' => 'Jazyk', 'name' => 'Názov tvojho sveta', 'system' => 'D&D, Pathfinder, Fate, Dračí Doupě', ], 'privacy' => [ 'hidden' => 'Skryté', 'private' => 'Súkromné', 'visible' => 'Viditeľné', ], 'public' => [ 'helpers' => [ 'introduction' => 'Kampane sú štandardne nastavené ako súkromné, no môžu byť zviditeľnené pre verejnosť. Hocikto si ich takto môže pozrieť a dostupné sú na stránke :public-campaigns, ak majú objekty, ktorým je pridelená rola :public-role. Verejná kampaň je viditeľná pre všetkých, ale aby bol viditeľný aj jej obsah, je nutné prideliť :public-role potrebné oprávnenia.', ], ], 'roles' => [ 'actions' => [ 'add' => 'Pridať rolu', 'duplicate' => 'Duplikovať rolu', 'permissions' => 'Spravovať oprávnenia', 'rename' => 'Premenovať rolu', 'save' => 'Uložiť rolu', ], 'admin_role' => 'Rola administrátora', 'bulks' => [ 'delete' => '{1} Odstránená :count rola.|[2,4] Odstránené :count roly.|[5,*] Odstránených :count rolí.', 'edit' => '{1} Aktualizovaná :count rola.|[2,4] Aktualizované :count roly.|[5,*] Aktualizovaných :count rolí.', ], 'create' => [ 'success' => 'Rola :name vytvorená.', 'title' => 'Nová rola', ], 'destroy' => [ 'success' => 'Rola odstránená.', ], 'edit' => [ 'success' => 'Rola upravená.', 'title' => 'Upraviť rolu :name', ], 'fields' => [ 'copy_permissions' => 'Kopírovať oprávnenia', 'name' => 'Názov', 'permissions' => 'Oprávnenia', 'type' => 'Typ', 'users' => 'Užívateľ', ], 'helper' => [ '1' => 'Kampani môže byť priradených viacero rolí. Rola :admin má automaticky prístup ku všetkému v kampani, ale každej inej roli môžu byť pridelené špecifické oprávnenia na rôzne typy objektov (postavy, miesta, atď.)', '2' => 'Objekty môžu mať oveľa detailnejšie nastavenie oprávnení, ktoré vieš nastaviť v karte "Oprávnenia" objektu. Táto karta sa zobrazí, ak máš v kampani viacero rolí alebo členov.', '3' => 'Môžeš použiť "opt-out" systém, v ktorom všetky roly dostanú práva na čítanie na všetky objekty a niektoré objekty potom nastavíš ako "Súkromné", čím ich skryješ. Alebo rolám nedáš veľa oprávnení a následne ich nastavíš individuálne pre každý objekt.', '4' => 'Boostnuté kampane môžu mať neobmedzený počet rolí.', 'permissions_helper' => 'Duplikuje všetky oprávnenia danej roly v moduloch a objektoch.', ], 'hints' => [ 'campaign_not_public' => 'Verejná rola má oprávnenia, ale kampaň je súkromná. Tieto nastavenia počas úpravy kampane nájdeš na karte Zdieľanie.', 'empty_role' => 'Táto rola nemá zatiaľ žiadnych členov.', 'role_admin' => 'Rola :name poskytne automaticky prístup ku všetkému v kampani pre jej členstvo.', 'role_permissions' => 'Umožniť role :name nasledujúce akcie pre všetky objekty.', ], 'members' => 'Členovia', 'modals' => [ 'details' => [ 'campaign' => 'Oprávnenia kampane umožňujú nasledovné.', 'entities' => 'Rýchle info o tom, čo obdržia členovia tejto role, ak dostanú dané oprávnenie.', 'more' => 'Viac informácií nájdeš v našom videonávode na YouTube', 'title' => 'Detaily oprávnenia', ], ], 'permissions' => [ 'actions' => [ 'add' => 'Vytvoriť', 'articles' => 'Články', 'dashboard' => 'Nástenka', 'delete' => 'Odstrániť', 'edit' => 'Upraviť', 'gallery' => [ 'browse' => 'Prehľadávať', 'manage' => 'Plná kontrola', 'upload' => 'Nahrať', ], 'manage' => 'Spravovať', 'members' => 'Členovia', 'permission' => 'Spravovať oprávnenia', 'read' => 'Zobraziť', 'toggle' => 'Zmeniť u všetkých', ], 'helpers' => [ 'add' => 'Povolí vytváranie objektov tohto typu. Automaticky budú mať povolené zobraziť a upravovať objekty, ktoré vytvoria, ak nemajú oprávnenie pre zobrazenie a editáciu.', 'articles' => 'Umožňuje pridávanie, úpravy a mazanie článkov, aj keď daný člen nemôže upravovať daný objekt.', 'dashboard' => 'Povolí úpravy násteniek a nástenkových widgetov.', 'delete' => 'Povolí odstránenia všetkých objektov tohto typu.', 'edit' => 'Povolí úpravy všetkých objektov tohto typu.', 'gallery' => [ 'browse' => 'Povoliť zobrazenie galérie a nastavení obrázku objektu z galérie.', 'manage' => 'Povoliť všetko v galérii podobne ako u adminov, vrátane úprav a mazania.', 'upload' => 'Povoliť nahrávanie obrázkov do galérie. Zobrazovať sa budú iba obrázky, ktorá nahrali, ak nie sú nastavené ďalšie povolenia.', ], 'manage' => 'Povolí úpravu kampane ako ju má admin kampane, no bez možnosti zmazať kampaň.', 'members' => 'Povolí zasielať pozvánky pre nových členov do kampane.', 'not_public' => 'Kampaň nie je verejná. Oprávnenia pre verejné role môžu byť nastavené, ale budú ignorované. Ak chceš kampaň zverejniť, uprav jej nastavenia.', 'permission' => 'Povolí nastaviť oprávnenia na objektoch typu, ktoré môže upravovať.', 'read' => 'Povolí zobrazenie všetkých objektov tohto typu, ktoré nie sú súkromné.', ], ], 'placeholders' => [ 'name' => 'Názov role', ], 'title' => 'Roly kampane :name', 'types' => [ 'owner' => 'Admin', 'public' => 'Verejný', 'standard' => 'Štandard', ], 'users' => [ 'actions' => [ 'add' => 'Pridať', 'remove' => ':user z role :role', 'remove_user' => 'Odstrániť užívateľa z role', ], 'create' => [ 'success' => 'Užívateľ bol priradený k roli.', 'title' => 'Pridať člena k roli :name', ], 'destroy' => [ 'success' => 'Užívateľ bol odstránený z role.', ], 'errors' => [ 'cant_kick_admins' => 'Aby sme predišli zneužitiu, nie je možné odstrániť iných členov kampane s rolou :admin. V prípade zmien nás kontaktuj na :discord alebo cez :email.', 'needs_more_roles' => 'Predtým, ako odstrániš svoju rolu :admin z kampane, musíš si prideliť inú rolu v kampani.', ], 'fields' => [ 'name' => 'Meno', ], ], ], 'settings' => [ 'actions' => [ 'enable' => 'Aktivovať', ], 'boosted' => 'Táto funkcia je aktuálne v beta verzii a dostupná iba pre :boosted.', 'deprecated' => [ 'help' => 'Tento modul je zastaralý, znamená to, že už nie je aktualizovaný a chyby, ktoré sa môžu vyskytnúť s novými aktualizáciami, nie sú opravované. Môžeš ho používať, ale v budúcnosti bude z Kanky odstránený.', 'title' => 'Zastaralé', ], 'disabled' => 'Modul :module je deaktivovaný.', 'enabled' => 'Modul :module je aktivovaný.', 'errors' => [ 'module-disabled' => 'Požadovaný modul je aktuálne v nastaveniach kampane deaktivovaný. :fix.', ], 'helpers' => [ 'abilities' => 'Vytvor schopnosti ako kúzla alebo sily, ktoré priradíš iným objektom.', 'assets' => 'Nahraj súbory, nastav linky a definuj aliasy pre jednotlivé objekty.', 'bookmarks' => 'Vytvor záložky k objektom alebo filtrovaným zoznamom, ktoré sa zobrazia v bočnom menu.', 'calendars' => 'Miesto, na ktorom vieš vytvoriť kalendáre tvojho sveta.', 'characters' => 'Postavy, ktoré obývajú svoj svet.', 'conversations' => 'Fiktívne diskusie medzi postavami v tvojom svete alebo užívateľmi kampane.', 'creatures' => 'Obsaď tvoj svet zvermi, bytosťami a príšerami s pomocou modulu pre bytosti.', 'dice_rolls' => 'Ak používaš Kanku na hranie, môžeš tu spravovať tvoje hody kockami.', 'entity_attributes' => 'Maj prehľad o atribútoch objektov kampane, napr. ich HP alebo Rýchlosti.', 'events' => 'Sviatky, festivaly, katastrofy, narodeniny, vojny.', 'families' => 'Klany a rody, ich vzťahy a členovia.', 'inventories' => 'Spravuj inventáre v tvojich objektoch.', 'items' => 'Zbrane, vozidlá, relikvie, elixíry.', 'journals' => 'Zistenia a pozorovania spísané postavami alebo príprava na hry pre Rozprávača.', 'locations' => 'Planéty, sféry, kontinenty, rieky, štáty, osídlia, chrámy, hostince.', 'maps' => 'Nahraj mapy s úrovňami a značkami, ktoré sú prelinkované s inými objektami tvojej kampane.', 'notes' => 'Báje, náboženstvá, dejiny, mágia, rasy.', 'organisations' => 'Kulty, vojenské jednotky, frakcie, cechy.', 'quests' => 'Aby si vedel/a sledovať plnenie úloh a cieľov postáv.', 'races' => 'Ak má kampaň viac ako jednu rasu, pomôže sa ti v nich vyznať táto funkcia.', 'tags' => 'Každý objekt môže byť priradený viacerým kategóriám. Kategórie môžu patriť pod iné kategórie a objekty môžu byť podľa kategórií filtrované.', 'timelines' => 'Zobraz dejiny tvojho sveta pomocou časových osí.', 'whiteboards' => 'Kresli a píš na tabule, ak chceš vizuálne plánovať tvoj svet či ciele.', ], ], 'sharing' => [ 'filters' => 'Verejné kampane sú viditeľné na :public-campaigns stránke. Vyplnením tohto formulára umožníš ľuďom rýchlejšie nájsť tvoju kampaň.', 'language' => 'Jazyk, v ktorom je kampaň písaná.', 'system' => 'Ak hráte stolovú RPG hru, systém, ktorý používate pri hraní.', ], 'show' => [ 'actions' => [ 'edit' => 'Upraviť kampaň', ], 'tabs' => [ 'achievements' => 'Úspechy', 'customisation' => 'Úprava', 'danger' => 'Výstrahy', 'data' => 'Údaje', 'default-images' => 'Prednastavené obrázky', 'defaults' => 'Štandardné', 'deletion' => 'Odstránenie', 'export' => 'Export', 'import' => 'Import', 'logs' => 'Logy', 'management' => 'Manažment', 'members' => 'Členovia', 'plugins' => 'Pluginy', 'recovery' => 'Obnovenie', 'roles' => 'Roly', 'sidebar' => 'Bočné menu', 'stats' => 'Štatistiky', 'styles' => 'Témy', 'webhooks' => 'Webhooky', ], 'title' => 'Kampaň :name', ], 'status' => [ 'free' => 'Prémiové funkcionality sú neaktívne.', 'legacy' => [ 'title' => 'Boostnuté funkcionality (zastaralé)', ], 'premium' => 'Vďaka :name sú prémiové funkcionality aktívne.', 'title' => 'Prémiové funkcionality', ], 'superboosted' => [], 'themes' => [ 'none' => 'Žiadna (štandardné nastavenie)', ], 'ui' => [ 'entity_history' => [ 'hidden' => 'Viditeľné len pre adminov kampane', 'visible' => 'Viditeľné pre členov', ], 'fields' => [ 'entity_history' => 'Protokol histórie objektu', 'member_list' => 'Zoznam členov kampane', ], 'helpers' => [ 'entity-history' => 'Kontroluj, kto vidí posledné zmeny v jednotlivých objektoch kampane.', 'member-list' => 'Kontroluj, kto vidí koho v kampani.', 'theme' => 'Zobraz kampaň v téme užívateľa alebo vynúť zobrazenie v jednej z nasledujúcich tém.', ], 'members' => [ 'hidden' => 'Viditeľné len pre adminov kampane', 'visible' => 'Viditeľné pre členov', ], ], 'visibilities' => [ 'private' => 'Súkromná', 'public' => 'Verejná', 'unlisted' => 'Verejná (neviditeľná)', ], 'warning' => [], ]; ================================================ FILE: lang/sk/characters.php ================================================ [ 'add_appearance' => 'Pridať výzor', 'add_personality' => 'Pridať osobnosť', ], 'conversations' => [], 'create' => [ 'title' => 'Nová postava', ], 'destroy' => [], 'dice_rolls' => [], 'edit' => [], 'families' => [ 'helper' => 'Zmeň poradie a kontroluj, ktoré rody :name sú viditeľné a skryté pre ne-administrátorov.', 'reorder' => [ 'success' => 'Rody postavy úspešne aktualizované.', ], 'title2' => 'Spravovať rody', ], 'fields' => [ 'age' => 'Vek', 'is_appearance_pinned' => 'Pripnutý výzor', 'is_dead' => 'Po smrti', 'is_personality_pinned' => 'Pripnutá osobnosť', 'is_personality_visible' => 'Osobnosť viditeľná', 'life' => 'Život', 'physical' => 'Telesné črty', 'pronouns' => 'Zámená', 'sex' => 'Pohlavie', 'status' => 'Stav', 'title' => 'Titul', 'traits' => 'Vlastnosti', ], 'helpers' => [ 'age' => 'Tento objekt môžeš referencovať v kalendári tvojej kampane a automaticky tak vypočítať vek. :more.', ], 'hints' => [ 'is_appearance_pinned' => 'Ak aktívne, črty výzoru postavy sa zobrazia pod záznamom na stránke prehľadu.', 'is_dead' => 'Táto postava je mŕtva.', 'is_missing' => 'Táto postava sa stratila.', 'is_personality_visible' => 'Celú sekciu o osobnosti vieš skryť pred užívateľmi, ktorí nemajú rolu Admin.', 'personality_not_visible' => 'Osobnostné črty tejto postavy sú aktuálne viditeľné len pre užívateľov s rolou Admin.', 'personality_visible' => 'Osobnostné črty tejto postavy sú viditeľné pre všetkých.', ], 'index' => [], 'items' => [], 'journals' => [], 'labels' => [ 'appearance' => [ 'entry' => 'Opis výzoru', 'name' => 'Názov výzoru', ], 'personality' => [ 'entry' => 'Opis osobnostnej črty', 'name' => 'Názov osobnostnej črty', ], ], 'lists' => [ 'empty' => 'Vytvor svojho prvého hrdinu, zloducha alebo pobočníka, aby tvoj svet ožil.', ], 'maps' => [], 'organisations' => [ 'create' => [ 'success' => 'Postava priradená organizácii.', 'title' => 'Nová organizácia pre :name', ], 'destroy' => [ 'success' => 'Postava odstránená z organizácie.', ], 'edit' => [ 'success' => 'Organizácia postavy upravená.', 'title' => 'Upraviť organizáciu :name', ], 'fields' => [ 'role' => 'Rola', ], ], 'personality_visibility' => [ 'admin' => 'Členovia roly Admin', 'all' => 'Viditeľné pre všetkých', ], 'placeholders' => [ 'age' => 'Vek', 'appearance_entry' => 'Popis', 'appearance_name' => 'Vlasy, oči, pokožka, výška', 'name' => 'Meno postavy', 'personality_entry' => 'Detaily', 'personality_name' => 'Ciele, maniere, slabosti, vzťahy,...', 'physical' => 'Telesné črty', 'pronouns' => 'On/Jeho, Ona/Jej, Oni/Ich', 'sex' => 'Pohlavie', 'title' => 'Titul', 'traits' => 'Vlastnosti', 'type' => 'NPC, hráčska postava, božstvo', ], 'quests' => [ 'helpers' => [ 'quest_giver' => 'Úlohy, ktorých zadávateľom je táto postava.', 'quest_member' => 'Úlohy, ktorých členom je táto postava.', ], ], 'races' => [ 'helper' => 'Zmeň poradie a skontroluj, ktoré rasy :name sú viditeľné alebo skryté pre ne-administrátorov.', 'reorder' => [ 'success' => 'Rasy postavy úspešne aktualizované.', ], 'title2' => 'Spravovať rasy', ], 'sections' => [ 'appearance' => 'Výzor', 'personality' => 'Osobnosť', ], 'show' => [], 'status' => [ 'alive' => 'Nažive', 'dead' => 'Mŕtva', 'missing' => 'Stratená', ], 'warnings' => [ 'personality_hidden' => 'Nemáš povolené upravovať črty osobnosti tejto postavy.', ], ]; ================================================ FILE: lang/sk/colours.php ================================================ 'Belasá', 'black' => 'Čierna', 'blue' => 'Modrá', 'brown' => 'Hnedá', 'green' => 'Zelená', 'grey' => 'Sivá', 'light-blue' => 'Svetlomodrá', 'maroon' => 'Gaštanová', 'navy' => 'Tmavomodrá', 'none' => 'Žiadna', 'orange' => 'Oranžová', 'pink' => 'Ružová', 'purple' => 'Fialová', 'red' => 'Červená', 'teal' => 'Tyrkysová', 'white' => 'Biela', 'yellow' => 'Žltá', ]; ================================================ FILE: lang/sk/concept.php ================================================ 'boostnutá kampaň', 'premium-campaign' => 'prémiová kampaň', 'premium-campaign-count' => '{0} Žiadne prémiové kampane |{1} 1 prémiová kampaň |[2,4] :count prémiové kampane |[5,*] :count prémiových kampaní', 'premium-campaigns' => 'prémiové kampane', 'premium-feature' => 'Prémiová funkcionalita', 'superboosted-campaign' => 'superboostnutá kampaň', ]; ================================================ FILE: lang/sk/confirm/editing.php ================================================ 'Späť', 'description' => 'Vyzerá to tak, že niekto práve upravuje túto stránku! Chceš sa vrátiť späť alebo ignorovať toto varovanie a riskovať stratu dát?', 'ignore' => 'Napriek tomu upraviť', 'members' => 'Osoby upravujúce túto stránku:', 'title' => 'Varovanie', 'user' => ':user od :since', ]; ================================================ FILE: lang/sk/conversations.php ================================================ [ 'title' => 'Nová diskusia', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_closed' => 'Uzavretá', 'messages' => 'Správy', 'participants' => 'Účastníci', ], 'hints' => [ 'empty' => 'Tejto diskusie sa nikto nezúčastnil.', 'participants' => 'Prosím, pridaj do diskusiu účastníkov tým, že klikneš na symbol :icon hore vpravo.', ], 'index' => [], 'messages' => [ 'destroy' => [ 'success' => 'Správa odstránená.', ], 'is_updated' => 'Upravená', 'load_previous' => 'Nahrať predchádzajúce správy', 'placeholders' => [ 'message' => 'Tvoja správa', ], ], 'participants' => [ 'create' => [ 'success' => 'Účastník :entity pridaný do diskusie.', ], 'destroy' => [ 'success' => 'Účastník :entity odstránený z diskusie.', ], 'modal' => 'Účastníci', 'title' => 'Účastníci :name', ], 'placeholders' => [ 'name' => 'Názov diskusie', 'type' => 'V hre, príprave, deji', ], 'show' => [ 'is_closed' => 'Diskusia je uzavretá.', ], 'tabs' => [ 'participants' => 'Účastníci', ], 'targets' => [ 'characters' => 'Postavy', 'members' => 'Členovia', ], ]; ================================================ FILE: lang/sk/cookieconsent.php ================================================ 'Súhlasím s cookies', 'dismiss' => 'Zrušiť', 'header' => 'Súhlas s cookies', 'link' => 'Dozvedieť sa viac', 'message' => 'Kanka používa cookies, aby ti umožnila čo najlepšie využívať našu webovú stránku.', 'policy' => 'Podmienky cookies', 'reject' => 'Nesúhlasím', ]; ================================================ FILE: lang/sk/creatures.php ================================================ [ 'title' => 'Nová bytosť', ], 'creatures' => [], 'fields' => [ 'is_extinct' => 'Vyhynuté', ], 'helpers' => [], 'hints' => [ 'is_dead' => 'Táto bytosť je mŕtva.', 'is_extinct' => 'Táto bytosť vyhynula.', ], 'placeholders' => [ 'type' => 'Bylinožravec, Morská, Mýtická', ], 'show' => [], ]; ================================================ FILE: lang/sk/crud.php ================================================ [ 'actions' => 'Akcie', 'apply' => 'Použiť', 'back' => 'Naspäť', 'change' => 'Zmeniť', 'close' => 'Zavrieť', 'confirm' => 'Potvrdiť', 'copy' => 'Kopírovať', 'copy_mention' => 'Kopírovať [ ] referenciu', 'copy_to_campaign' => 'Kopírovať do kampane', 'disable' => 'Deaktivovať', 'enable' => 'Aktivovať', 'explore_view' => 'Vnorené zobrazenie', 'export' => 'Exportovať (PDF)', 'find_out_more' => 'Dozvedieť sa viac', 'go_to' => 'Prejsť na :name', 'help' => 'Pomoc', 'json-export' => 'Exportovať (json)', 'markdown-export' => 'Export (Markdown)', 'move' => 'Premiestniť', 'new' => 'Nový', 'new_child' => 'Nový podobjekt', 'new_post' => 'Nová poznámka objektu', 'next' => 'Ďalej', 'open' => 'Otvoriť', 'print' => 'Tlačiť', 'reorder' => 'Preusporiadať', 'reset' => 'Resetovať', 'transform' => 'Transformovať', ], 'add' => 'Pridať', 'alerts' => [ 'copy_attribute' => 'Referencia na atribút bola skopírovaná do tvojej schránky.', 'copy_invite' => 'Link na prístup do kampane bol skopírovaný do tvojej schránky.', 'copy_mention' => 'Rozšírená referencia na objekt bola skopírovaná do tvojej schránky.', ], 'bulk' => [ 'actions' => [ 'edit' => 'Hromadná úprava a kategórie', 'permissions' => 'Zmeniť oprávnenia', ], 'age' => [ 'helper' => 'Môžeš použiť + a - pred číslom na úpravu veku o danú hodnotu.', ], 'buttons' => [ 'label' => 'Pre vybrané', ], 'edit' => [ 'locations' => 'Akcie s lokáciami', 'tagging' => 'Akcie s kategóriami', 'tags' => [ 'add' => 'Pridať', 'remove' => 'Odstrániť', ], 'title' => 'Úprava viacerých objektov', ], 'errors' => [ 'admin' => 'Iba administrátori kampane vedia zmeniť súkromný štatút objektu.', 'general' => 'Pri spracovávaní tvojej akcie došlo k chybe. Prosím, skús to opäť a kontaktuj nás, ak problém pretrváva. Hlásenie chyby: :hint.', ], 'permissions' => [ 'fields' => [ 'override' => 'Prepísať', ], 'helpers' => [ 'override' => 'Ak aktivované, oprávnenia vybratých objektov budú týmito prepísané. Ak deaktivované, vybrané oprávnenia budú pridané k predchádzajúcim.', ], 'title' => 'Zmeniť oprávnenia pre viaceré objekty', ], ], 'bulk_templates' => [ 'bulk_title' => 'Uplatniť šablónu na viaceré objekty', ], 'cancel' => 'Zrušiť', 'click_modal' => [], 'copy_to_campaign' => [ 'bulk_title' => 'Kopírovať objekty do inej kampane', 'panel' => 'Kopírovať', 'title' => 'Kopírovať :name do inej kampane', ], 'create' => 'Vytvoriť', 'datagrid' => [ 'empty' => 'Zatiaľ je tu prázdno.', ], 'delete_modal' => [ 'callout' => 'Psst!', 'confirm' => 'Potvrdiť odstránenie', 'permanent' => 'Táto akcia je natrvalo.', 'recoverable' => 'Objekty je možné obnoviť až do :day dní s :boosted-campaign.', 'title' => 'Potvrdiť odstránenie', ], 'destroy_many' => [], 'dynamic' => [ 'permission' => 'Nemáš dostatočné oprávnenia na vytvorenie objektu v module :module.', 'unknown' => 'Nesprávny objekt modulu :module.', ], 'edit' => 'Upraviť', 'errors' => [ 'boosted_campaigns' => 'Funkcionalita je dostupná iba pre :boosted.', 'unavailable_feature' => 'Funkcionalita nedostupná', ], 'events' => [], 'fields' => [ 'calendar_date' => 'Dátum', 'child' => 'Dieťa', 'closed' => 'Uzavretá', 'colour' => 'Farba', 'copy_abilities' => 'Kopírovať schopnosti', 'copy_inventory' => 'Kopírovať inventár', 'copy_links' => 'Kopírovať linky objektu', 'copy_permissions' => 'Kopírovať oprávnenia (tieto majú prioritu pred nastavenými v karte oprávnení)', 'copy_posts' => 'Kopírovať príspevky (inkl. ich oprávnení)', 'copy_reminders' => 'Kopírovať pripomienky', 'creator' => 'Autor', 'date_range' => 'Obdobie', 'excerpt' => 'Výpis', 'has_entity_files' => 'So súbormi v objektoch', 'has_image' => 'S obrázkom', 'has_posts' => 'S príspevkami', 'header_image' => 'Obrázok záhlavia', 'image' => 'Obrázok', 'is_closed' => 'Diskusia bude uzavretá a nebude možné do nej pridávaťnové správy.', 'is_private' => 'Súkromný', 'is_private_v3' => 'Zobraziť iba pre členov :admin-role role. Toto má prednosť pre ostatnými oprávneniami.', 'is_star' => 'Pripnutý', 'locations' => ':first v :second', 'name' => 'Názov', 'parent' => 'Rodič', 'position' => 'Pozícia', 'replace_mentions' => 'Zameniť referencie atribútov v zázname za tie nového objektu.', 'template' => 'Šablóna', 'tooltip' => 'Bublina', 'type' => 'Typ', 'visibility' => 'Viditeľnosť', 'word-count' => 'Počet slov: :number', ], 'files' => [ 'errors' => [ 'max' => 'Max. počet (:max) súborov v tomto objekte dosiahnutý.', 'max_size' => 'Kampaň dosiahla maximálnu kapacitu úložiska.', 'no_files' => 'Žiadne súbory.', ], 'hints' => [ 'limit' => 'Do každého objektu môže byť nahratých maximálne :max súborov.', 'limitations' => 'Podporované formáty: :formats. Max. veľkosť súboru: :size.', ], ], 'filter' => 'Filter', 'filters' => [ 'all' => 'Filter zobrazenia všetkých podobjektov', 'clear' => 'Resetovať filter', 'copy_helper' => 'Použi skopírované filtre v schránke pre hodnoty filtrov vo widgetoch na nástenke a rýchlych linkoch.', 'copy_to_clipboard' => 'Kopírovať filtre do schránky', 'direct' => 'Filter zobrazenia iba priamych podobjektov', 'filtered' => 'Zobraziť :count z :total :entity.', 'lists' => [ 'desktop' => [ 'all' => 'Zobraziť všetky podradené (:count)', 'filtered' => 'Zobraziť priamo podradené (:count)', ], ], 'mobile' => [ 'clear' => 'Vymazať', 'copy' => 'Schránka', ], 'options' => [ 'children' => 'S podradenými', 'exclude' => 'Vylúčiť', 'hide' => 'Skryť', 'include' => 'Zahrnúť', 'none' => 'Žiadne', 'show' => 'Zobraziť', ], 'show' => 'Zobraziť filtre', 'sorting' => [ 'asc' => ':field vzostupne', 'desc' => ':field zostupne', 'helper' => 'Nastav poradie zoradenia výsledkov.', ], 'title' => 'Filter', ], 'fix-this-issue' => 'Odstrániť tento problém', 'forms' => [ 'actions' => [ 'calendar' => 'Doplniť dátum', ], 'copy_options' => 'Kopírovať nastavenia', ], 'helpers' => [ 'copy_options' => 'Kopírovať nasledujúce prepojené prvky zo zdroja do nového objektu.', 'linking' => 'Prepojenia s inými objektami', ], 'hidden' => 'Skrytý', 'hints' => [ 'calendar_date' => 'Dátum umožňuje filtrovať zoznamy a zadať udalosť do vybraného kalendára.', 'image_dimension' => 'Odporúčané rozlíšenie: :dimension pixelov.', 'image_limitations' => 'Podporované formáty: :formats. Max. veľkosť súboru: :size.', 'image_recommendation' => 'Odporúčané rozmery: :width x :height px.', 'is_star' => 'Pripnuté objekty sa zobrazia v menu objektu.', 'tooltip' => 'Nahradiť automaticky generovaný obsah bubliny týmto obsahom.', ], 'history' => [ 'created_clean' => 'Vytvorené :name :date', 'created_date_clean' => 'Vytvorené :date', 'unknown' => 'Neznámy', 'updated_clean' => 'Posledná úprava :name :date', 'updated_date_clean' => 'Posledná úprava :date', 'view' => 'Zobraziť protokol objektu', ], 'image' => [ 'error' => 'Požadovaný obrázok nebolo možné stiahnuť. Zdá sa, že daná webová stránka nepovoľuje sťahovanie obrázkov (typické správanie Squarescape a DeviantArt) alebo že link už nie je platný.', ], 'is_private' => 'Tento objekt je súkromný a viditeľný len pre členov s rolou Admin.', 'keyboard-shortcut' => 'Klávesová skratka :code', 'navigation' => [ 'cancel' => 'Zrušiť', 'or_cancel' => 'alebo :cancel', 'skip_to_content' => 'Preskočiť navigáciu', ], 'new_entity' => [], 'panels' => [], 'permissions' => [ 'actions' => [ 'bulk' => [ 'add' => 'Povoliť', 'deny' => 'Zakázať', 'ignore' => 'Ignorovať', 'remove' => 'Odstrániť', ], 'bulk_entity' => [ 'allow' => 'Povoliť', 'deny' => 'Zakázať', 'inherit' => 'Zdediť', ], 'delete' => 'Zmazať', 'edit' => 'Upraviť', 'toggle' => 'Prepnúť', 'view' => 'Zobraziť', ], 'fields' => [ 'member' => 'Člen', 'role' => 'Rola', ], 'helpers' => [ 'setup' => 'Pomocou tohto rozhrania môžeš presne nastaviť ako role a užívatelia pracujú s týmto objektom. :allow dovolí užívateľovi alebo role urobiť danú akciu. :deny im túto akciu zakáže. :inherit preberie nastavenie z roly užívateľa alebo z oprávnení hlavnej roly. Užívateľ s nastavením :allow môže danú akciu vykonať, aj keď má jeho rola nastavenie :deny.', ], 'success' => 'Oprávnenia uložené.', 'title' => 'Oprávnenia', 'too_many_members' => 'Táto kampaň má príliš veľa členov (> 10), aby boli zobrazení v tomto rozhraní. Prosím, použi tlačidlo Oprávnení na danom objekte, aby sa zobrazili detaily nastavenia oprávnení.', ], 'placeholders' => [ 'calendar' => 'Vybrať kalendár', 'entry' => 'Použi @ s min. troma znakmi, ak chceš referencovať iný objekt v kampani.', 'fallback' => 'Vybrať :module', 'gallery_image' => 'Vyber obrázok z galérie kampane', 'image_url' => 'Obrázok je možné pridať aj nahratím cez URL.', 'journal' => 'Vyber denník', 'location' => 'Vyber miesto', 'multiple' => 'Vyber jedno alebo viac', 'name' => 'Názov objektu', 'organisation' => 'Vyber organizáciu', 'parent' => 'Vyber rodiča', 'tag' => 'Vyber kategóriu', 'timeline' => 'Vyber časovú os', 'type' => 'Typ objektu', 'user' => 'Vyber užívateľa', ], 'relations' => [], 'remove' => 'Zmazať', 'reorder' => [ 'empty' => 'Neexistuje obsah na preusporiadanie.', ], 'save' => 'Uložiť', 'save_and_close' => 'Uložiť a zavrieť', 'save_and_copy' => 'Uložiť a kopírovať', 'save_and_new' => 'Uložiť a nový', 'save_and_update' => 'Uložiť a upraviť', 'save_and_view' => 'Uložiť a zobraziť', 'search' => 'Hľadať', 'select' => 'Vybrať', 'tabs' => [ 'abilities' => 'Schopnosti', 'inventory' => 'Inventár', 'mentions' => 'Referencie', 'overview' => 'Prehľad', 'permissions' => 'Oprávnenia', 'premium' => 'Prémium', 'profile' => 'Profil', 'reminders' => 'Pripomienky', ], 'titles' => [ 'editing' => 'Upravuje sa :name', 'new' => 'Nový :module', ], 'tooltips' => [], 'update' => 'Upraviť', 'users' => [ 'unknown' => 'Neznámy', ], 'view' => 'Zobraziť', 'visibilities' => [ 'admin' => 'Admin', 'admin-self' => 'Iba ja a Admin', 'all' => 'Všetci', 'members' => 'Členstvá kampane', 'self' => 'Iba ja', ], ]; ================================================ FILE: lang/sk/dashboard.php ================================================ [ 'customise' => 'Upraviť nástenku', 'follow' => 'Sledovať', 'join' => 'Pridať sa', 'unfollow' => 'Zrušiť sledovanie', ], 'campaigns' => [], 'dashboards' => [ 'actions' => [ 'edit' => 'Upraviť názov a oprávnenia', 'new' => 'Nová nástenka', ], 'create' => [ 'success' => 'Nová nástenka :name kampane vytvorená.', 'title' => 'Nová nástenka kampane', ], 'custom' => [ 'text' => 'Aktuálne upravuješ nástenku :name kampane.', ], 'default' => [ 'text' => 'Aktuálne upravuješ štandardnú nástenku kampane.', 'title' => 'Štandardná nástenka', ], 'delete' => [ 'success' => 'Nástenka :name odstránená.', ], 'fields' => [ 'copy_widgets' => 'Kopírovať widgety', 'name' => 'Názov nástenky', 'visibility' => 'Viditeľnosť', ], 'helpers' => [ 'copy_widgets' => 'Duplikovať widgety z nástenky :name na túto novú.', ], 'pitch' => 'Vytvor viacero násteniek s vlastnými oprávneniami pre každú rolu v kampani.', 'placeholders' => [ 'name' => 'Pomenovanie nástenky', ], 'update' => [ 'success' => 'Nástenka kampane :name aktualizovaná.', 'title' => 'Aktualizovať nástenku kampane :name', ], 'visibility' => [ 'default' => 'Štandardná', 'none' => 'Žiadna', 'visible' => 'Viditeľná', ], ], 'helpers' => [ 'follow' => 'Keď sleduješ nejakú kampaň, bude sa ti zobrazovať v prepínači kampaní (vpravo hore) pod tvojimi kampaňami.', 'join' => 'Táto kampaň je otvorená pre nových členov. Klikni sem na pridanie sa do nej.', ], 'notifications' => [], 'recent' => [], 'settings' => [], 'setup' => [ 'actions' => [ 'add' => 'Pridať widget', 'back_to_dashboard' => 'Naspäť na nástenku', 'edit' => 'Upraviť widget', 'new' => 'Nový :type widget', ], 'reorder' => [ 'helper' => 'Potiahni ma, ak ma chceš presunúť', 'success' => 'Widgety preskupené.', ], 'title' => 'Nastavenie nástenky kampane', 'tutorial' => [ 'blog' => 'náš návod', 'text' => 'Potrebuješ nastaviť nástenku tvojej kampane? Prečítaj si :blog, ktorý ti poskytne pomoc a inšpiráciu.', ], ], 'title' => 'Nástenka', 'widgets' => [ 'advanced_options_boosted' => 'Aktivovať viac možností ako napr. zobrazovania značiek s :boosted_campaign.', 'calendar' => [ 'actions' => [ 'next' => 'Zmeniť dátum na nasledujúci deň', 'previous' => 'Zmeniť dátum na predošlý deň', ], 'previous_events' => 'Predošlé', 'upcoming_events' => 'Nasledujúce', ], 'campaign' => [ 'helper' => 'Tento widget zobrazuje záhlavie kampane. Tento widget je vždy zobrazovaný na štandardnej nástenke.', ], 'create' => [ 'success' => 'Widget bol pridaný na nástenku.', 'title' => 'Nový widget', ], 'delete' => [ 'success' => 'Widget bol odstránený z nástenky.', ], 'fields' => [ 'class' => 'Trieda CSS', 'dashboard' => 'Nástenka', 'name' => 'Vlastný názov widgetu', 'optional-entity' => 'Link k objektu', 'order' => 'Zoradenie', 'size' => 'Veľkosť', 'width' => 'Šírka', ], 'helpers' => [ 'class' => 'Definuj vlastnú triedu CSS priradenú widgetu.', 'filters' => 'Klikni sem, ak chceš spoznať možnosti filtrovania.', ], 'orders' => [ 'name_asc' => 'Názov vzostupne', 'name_desc' => 'Názov zostupne', 'oldest' => 'Najstaršie upravené', 'recent' => 'Posledne upravené', ], 'preview' => [ 'displays' => [ 'expand' => 'Rozšíriteľný záznam', 'full' => 'Celý záznam', ], 'fields' => [ 'display' => 'Zobraziť', ], ], 'random' => [ 'helpers' => [ 'name' => 'Referencie na náhodný objekt môžeš vložiť pomocou {name}', ], 'type' => [ 'all' => 'Všetko', ], ], 'recent' => [ 'advanced_filter' => 'Rozšírený filter', 'advanced_filters' => [ 'mentionless' => 'Neobsahuje referencie (Objekty, ktoré nereferencujú iné)', 'unmentioned' => 'Nereferencované (Objekty, ktoré nie sú referencované v iných)', ], 'all-entities' => 'Všetky objekty', 'entity-header' => 'Použiť záhlavie objektu ako obrázok', 'filters' => 'Filtre', 'help' => 'Zobraziť iba posledný upravený objekt, no zobraziť celý náhľad na objekt', 'helpers' => [ 'entity-header' => 'Ak má daný objekt záhlavie (funkcia boostnutých kampaní), nastav tento widget, aby použil tento obrázok namiesto obrázku objektu.', 'show_attributes' => 'Zobrazí pripnuté atribúty objektu pod záznamom.', 'show_members' => 'Ak je objekt rod alebo organizácia, pod záznamom sa zobrazia členovia.', 'show_relations' => 'Zobrazí pripnuté vzťahy objektu pod záznamom.', ], 'show_attributes' => 'Zobraziť pripnuté atribúty', 'show_members' => 'Zobraziť členov', 'show_relations' => 'Zobraziť pripnuté vzťahy', 'singular' => 'Jednotlivý objekt', 'tags' => 'Filtrovať zoznam nedávno upravených objektov podľa vybraných kategórií.', 'title' => 'Nedávno upravené', ], 'tabs' => [ 'advanced' => 'Rozšírené', 'setup' => 'Nastavenie', ], 'unmentioned' => [ 'title' => 'Objekty bez referencií', ], 'update' => [ 'success' => 'Widget bol upravený.', ], 'widths' => [ '0' => 'Automatická', '12'=> 'Plná (100%)', '3' => 'Mini (25%)', '4' => 'Malá (33%)', '6' => 'Polovičná (50%)', '8' => 'Široká (66%)', '9' => 'Veľká (75%)', ], ], ]; ================================================ FILE: lang/sk/dashboards/widgets/welcome.php ================================================ [], 'focus' => [ 'text' => 'Tu, ja som to!', 'title' => 'Ahoj', ], 'intros' => [ '1' => 'Spoznaj svoj nový domov pre tvorbu svetov, :user! Nastavili sme ti tvoju prvú kampaň a vložili do nej dve vzorové :characters a :locations. Tieto sú tiež viditeľné tu na nástenke kampane.', '2' => 'Na začiatok klikni na veľké tlačidlo :new-entity (alebo stlač :letter na tvojej klávesnici) a klikni na :characters, aby vznikla tvoja prvá postava. Je to ozaj tak jednoduché! Všetky postavy, miesta a ďalšie :entities nájdeš na bočnom menu na ľavom boku stránky.', '3' => 'Tu je našich Top 5 trikov pre používanie Kanky', ], 'title' => 'Vitaj v :kanka! 🎉', 'tricks' => [ '1' => 'Keď vytváraš popisy, nevpisuj do nej názvy prvkov v tvojej kampani. Namiesto toho napíš :code a následne tri písmená, aby vznikla :mention na iný objekt v kampani. Tieto referencie budú automaticky aktualizované, ak sa zmení ich názov.', '2' => 'Ak chceš upraviť názov, tému alebo obrázok kampane, klikni na :world v bočnom menu a následne na tlačidlo :edit.', '3' => 'Tajné informácie k objektom pridaj ako :posts namiesto zápisu do popisu v hlavnom textovom poli.', '4' => 'Pozvi do kampane tvojich priateľov a priateľky kliknutím na :world a :members. Tu si môžeš vytvoriť linky pre pozvánky.', '5' => 'Túto uvítaciu správu môžeš odstrániť a zobraziť namiesto nej inú informáciu na tejto stránke (ktorú nazývame nástenka). Nižšie nájdeš tlačidlo :button.', 'mention' => 'referencia', ], ]; ================================================ FILE: lang/sk/datagrids.php ================================================ [ 'back_to' => 'Späť na :name', ], 'modes' => [ 'flatten' => 'Prepnúť na ploché zobrazenie', 'grid' => 'Prepnúť na zobrazenie v mriežke', 'nested' => 'Prepnúť na vnorené zobrazenie', 'table' => 'Prepnúť na tabuľkové zobrazenie', ], 'tooltips' => [ 'nested' => 'Tento objekt má deti. Klikni na obrázok pre ich zobrazenie.', ], ]; ================================================ FILE: lang/sk/datetime.php ================================================ 'dňom', 'days' => 'dňami', 'elapsed_ago' => 'pred :duration', 'hour' => 'hodinou', 'hours' => 'hodinami', 'just_now' => 'teraz', 'minute' => 'minútou', 'minutes' => 'minútami', 'month' => 'mesiacom', 'months' => 'mesiacami', 'second' => 'sekundou', 'seconds' => 'sekundami', 'week' => 'týždňom', 'weeks' => 'týždňami', 'year' => 'rokom', 'years' => 'rokmi', ]; ================================================ FILE: lang/sk/default.php ================================================ 'Názov strany', ]; ================================================ FILE: lang/sk/dice_roll_results.php ================================================ [ 'title' => 'Výsledok hodu kockami', ], ]; ================================================ FILE: lang/sk/dice_rolls.php ================================================ [ 'title' => 'Nový hod kockami', ], 'destroy' => [ 'dice_roll' => 'Hod kockami odstránený.', ], 'edit' => [], 'fields' => [ 'created_at' => 'Hodený', 'parameters' => 'Parametre', 'results' => 'Výsledky', 'rolls' => 'Hody', ], 'hints' => [ 'parameters' => 'Aké možnosti kociek mám?', ], 'index' => [ 'actions' => [ 'results' => 'Výsledky', ], ], 'placeholders' => [ 'name' => 'Názov hodu kockami', 'parameters' => '4d6+3', ], 'results' => [ 'actions' => [ 'add' => 'Hod', ], 'error' => 'Hod kockami neúspešný. Nie je možné spracovať parametre.', 'fields' => [ 'creator' => 'Vytvorené', 'date' => 'Dátum', 'result' => 'Výsledok', ], 'hint' => 'Všetky hody vykonané s touto šablónou hodu kockami.', 'success' => 'Kocky boli hodené.', ], 'show' => [ 'tabs' => [ 'results' => 'Výsledky', ], ], ]; ================================================ FILE: lang/sk/emails/activity/password.php ================================================ 'Tvoje heslo do konta v Kanke bolo zmenené.', 'help' => 'Ak sa tak nestalo z tvojej vôle, prosím kontaktuj nás na :email.', 'title' => 'Heslo bolo zmenené', ]; ================================================ FILE: lang/sk/emails/purge/first.php ================================================ 'Ak svoje konto používaš pravidelne, nemáš sa čoho obávať, odstraňujeme iba kontá a kampane, ktoré sa už aktívne nevyužívajú.', 'help' => 'Potrebuješ pomôcť s používaním Kanky? Pridaj sa k nám na našom :discord alebo sa nám ozvi na :email', 'intro_account' => 'Obraciame sa na teba, lebo tvoje konto bude zmazané o :amount dní, keďže bolo posledne použité pred :duration months.', 'intro_campaigns' => 'Obraciame sa na teba, lebo tvoje konto a nasledujúce kampane budú zmazané o :amount dní, keďže bolo posledne použité pred :duration months.', 'keep' => 'Ak chceš ponechať tvoje konto aktívne, prihlás sa doň počas nasledujúcich :amount dní.', 'title' => 'Tvoje konto v Kanke bude zmazané o :amount dní.', 'warning' => [ 'account' => 'Po tom, čo sa tak udeje, budú všetky údaje spojené s tvojím kontom :email navždy zmazané.', 'campaigns' => 'Po tom, čo sa tak udeje, budú všetky údaje spojené s tvojím kontom :email, ako aj nasledujúce kampane, navždy zmazané.', ], ]; ================================================ FILE: lang/sk/emails/purge/second.php ================================================ 'Toto je posledná pripomienka, že tvoje konto v Kanke s e-mailom :email bude zmazaný o :amount dní, keďže bol posledne použitý pred :duration mesiacmi.', 'title' => 'Posledné varovanie: Tvoje konto v Kanke bude zmazaný za :amount dní.', ]; ================================================ FILE: lang/sk/emails/subscriptions/expiring.php ================================================ 'aktualizuj si údaje o platobnej karte', 'primary' => 'Toto je automatické varovanie, že platnosť tvojej :brand **** :last čoskoro vyprší.', 'title' => 'Platnosť platobnej karty čoskoro vyprší', 'valid' => 'Ak si praješ naďalej platiť predplatné, prosím :action.', ]; ================================================ FILE: lang/sk/emails/subscriptions/upcoming.php ================================================ 'Ak si nežiadaš predĺžiť predplatné, prosím prihlás sa do Tvojho konta v Kanke a :link.', 'closing' => 'S pozdravom,', 'dear' => 'Ahoj :name,', 'link' => 'zruš Tvoje predplatné', 'notice' => 'Dôležité oznámenie o predĺžení predplatného:', 'primary' => 'Toto je automatická pripomienka, že :date automaticky stiahneme z Tvojej :brand **** :last poplatok za predplatné Kanky.', 'title' => 'Ročný poplatok za Tvoje predplatné Kanky', 'valid' => 'Prosím zabezpeč, aby Tvoja platobná karta bola platná v deň realizácie platby.', ]; ================================================ FILE: lang/sk/emails/subscriptions/validation.php ================================================ 'Overenie e-mailu konta v Kanke', ]; ================================================ FILE: lang/sk/emails/validation.php ================================================ 'Overenie neúspešné, prosím skús to opäť.', 'modal' => 'Overený e-mail je potrebný kvôli predplatnému. Prosím, skontroluj si svoju elektronickú poštovú schránku a potvrď svoj e-mail pomocou linku predtým, ako budeš ďalej pokračovať.', 'success' => 'E-mail úspešne overený.', ]; ================================================ FILE: lang/sk/emails/welcome/2024.php ================================================ 'Veľa zdaru pri tvorbe svetov a ďakujeme za to, že ťa na tejto ceste môžeme sprevádzať,', 'header' => 'Vitaj na najlepšom mieste pre vytváranie tvojej kampane, :name!', 'lead_1' => 'Kanka bola vytvorená nadšencami rolových hier, ktorí si priali jednoduchý a zdieľaný prístup k tvorbe svetov bez nutnosti robiť kompromisy ohľadom funkčnosti.', 'lead_2' => 'Z tohto vzišla Kanka. Sme tu, aby sme ti pomohli pri organizácii tvojej kampane, nech sa môžeš sústrediť na prebudenie tvojho sveta k životu.', 'ps' => 'P.S. Ak sa chceš s nami spojiť, nájdeš nás na :discord alebo na :email.', 'what_1' => 'Na uľahčenie vstupu do systému, sme vytvorili tvoju prvú kampaň a pridali do nej niekoľko postáv a miest. Alebo môžeš :start, ak chceš.', 'what_2' => 'Taktiež sa ti budú hodiť nasledujúce zdroje informácií:', 'what_3' => 'Naša :kb, ak máš základné otázky ohľadom funkcií Kanky alebo tvojho konta.', 'what_4' => 'V :doc nájdeš všetko vo väčšej hĺbke.', 'what_5' => 'A nakoniec, :campaigns sú ukážkou, čo iní a iné vytvorili v Kanke.', 'what_new' => 'vytvoriť svoju vlastnú', 'what_now' => 'Čo teraz?', 'why' => 'Tento e-mail sme ti zaslali, pretože máš konto na našom webe.', ]; ================================================ FILE: lang/sk/emails/welcome.php ================================================ [ 'basics' => [ 'text_1' => 'Nástroj tak rôznorodý ako Kanka môže byť na prvý pohľad príliš komplexný. Naša :kb ti poskytne odpovede na základné otázky a pre ďalšiu pomoc sa môžeš obrátiť na našu :doc.', 'title' => 'Základné info', ], 'chat' => [ 'text_1' => 'Radi počúvame názory a pripomienky nášho užívateľstva. Sme aktívni na :discor, kde nájdeš mnohé aktívne osoby, tím moderátorov ale aj zakladateľov Kanky, ktorí zodpovedajú otázky, ktoré môžeš mať. Taktiež nám môžeš napísať e-mail na :email.', 'title' => 'Chceš si pokecať?', ], 'intro' => [ 'header' => 'Vitaj v najlepšej komunite pre tvorbu svetov, :name!', 'link' => 'Vstúp do tvojho sveta!', 'text_1' => 'Pozdrav tvoju nový domov pre tvorbu svetov, :name! Komunitu nosíme v našej DNA a sme radi, že si jej súčasťou. Kanka je dieťa nadšených RPG hráčov, ktorí veria v zjednodušený a zdieľaný spôsob prístupu k tvorbe svetov, bez kompromisov ohľadom potrebných funkcionalít.', 'text_2' => 'Nastavili sme tvoju prvú kampaň a pridali do nej vzorové postavy a miesta, aby sme ti uľahčili začiatočnú prácu.', ], 'preview' => 'Buď súčasťou najlepšej komunity tvorby svetov, :name!', ], 'header' => 'Vitaj v Kanke, :name!', 'header_sub' => 'Gratulujeme, prvé kroky vo vytváraní vlastného sveta v :kanka máš za sebou!', 'pricing' => 'Cenník', 'section_1' => 'Čo ďalej?', 'section_11' => 'Vytvor svoj svet,', 'section_2' => 'Najdôležitejší zdroj informácií je :discord, kde nájdeš veľa nadšených užívateľov a užívateľky, pomocný tím, zakladateľa Kanky, ktorí ti zodpovedajú tvoje otázky.', 'section_4' => 'Na našom :youtube nájdeš videá o základnom ovládaní Kanky. Aj keď nejdú príliš do hĺbky, nové videá sú neustále pridávané.', 'section_4_v2' => 'Naša :knowledge-base pokrýva základné otázky, ktoré môžeš mať, a pre detailnejšiu pomoc je možné navštíviť stránku s našou :documentation!', 'section_6' => 'Kontaktuj nás', 'section_7' => 'Ak nájdené odpovede neboli postačujúce alebo sa s nami proste chceš spojiť, nájdeš nás na :facebook alebo nám napíš :email. Sme malý tím dvoch priateľov, ale snažíme sa odpovedať na každý e-mail, ktorý dostaneme, takže nech ťa to neodradí!', 'section_8' => 'Ešte jedna vec nakoniec', 'section_9_v2' => 'Robíme všetko preto, aby základné funkcie Kanky boli stále voľne dostupné, a tak to aj navždy zostane. Ak nás však chceš v tomto projekte podporiť, môžeš si zakúpiť predplatné, a získať prístup k ďalším funkciám, ako aj našu večnú vďaku! Viac sa dozvieš na stránke :pricing .', 'social_account' => 'Ak máš problémy pri prihlásení do tvojho konta: chceme ťa len informovať, že používaš prihlásenie cez :provider - zmeniť si to vieš v nastaveniach konta.', 'title' => 'Začíname s Kankou', ]; ================================================ FILE: lang/sk/entities/abilities.php ================================================ [ 'add' => 'Pridať schopnosť', 'reset' => 'Resetovať vyčerpania schopností', 'sync' => 'Pridať z rás', ], 'charges' => [ 'left' => 'Ostáva :amount', ], 'create' => [ 'success' => 'Schopnosť :ability pridaná k :entity.', 'success_multiple' => 'Schopnosti :abilities boli pridané k :entity.', 'title' => 'Pridať schopnosť k :name', ], 'fields' => [ 'note' => 'Poznámka', 'position' => 'Pozícia', ], 'groups' => [ 'unorganised' => 'Nezorganizované', ], 'helpers' => [ 'note' => 'Referencie na iné objekty môžeš vytvoriť pomocou rozšírených referencií (ex :code) a atribútov objektov (ex :attr) v tomto poli.', 'recharge' => 'Resetuje všetky zmeny schopností, ktoré boli použité.', 'sync' => 'Importuje schopnosti, ktoré sú definované pri rase postavy.', ], 'import' => [ 'errors' => [ 'no_race' => 'Táto postava je bez rasy.', 'not_character' => 'Tento objekt nie je postava.', ], 'success' => '{1} :count schopnosť importovaná.|[2,4] :count schopnosti importované.|[5,*] :count schopností importovaných.', ], 'recharge' => [ 'success' => 'Všetky zmeny boli resetované.', ], 'reorder' => [ 'parentless' => 'Bez nadradenej', 'success' => 'Schopnosti úspešne preskupené.', ], 'show' => [ 'helper' => 'Pridaj schopnosti k tomuto objektu. Môžeš upraviť ich viditeľnosť alebo ich odstrániť. Schopnosti patriace pod nadradenú schopnosť sa zobrazia pod spoločným tlačidlom.', 'reorder' => 'Preskupiť schopnosti', 'title' => 'Schopnosti objektu :name', ], 'types' => [ 'unorganised' => 'Schopnosti sú zoskupené podľa nadradených polí a zvyšné sa zobrazujú tu.', ], 'update' => [ 'success' => 'Objektová schopnosť :ability bola aktualizovaná.', 'title' => 'Schopnosť objektu :name', ], ]; ================================================ FILE: lang/sk/entities/actions.php ================================================ [ 'set' => 'Nastaviť ako šablónu', 'success' => [ 'set' => 'Objekt :name nastavený ako šablóna.', 'unset' => 'Objekt :name už nie je nastavený ako šablóna.', ], 'toggle' => 'Prepnutý stav šablóny.', 'unset' => 'Odstrániť šablónu', ], ]; ================================================ FILE: lang/sk/entities/aliases.php ================================================ [ 'add' => 'Pridať alias', ], 'create' => [ 'success' => 'Alias :name pridaný k :entity.', 'title' => 'Pridať alias k :name', ], 'destroy' => [ 'success' => 'Alias :name odstránený.', ], 'fields' => [ 'name' => 'Názov', ], 'helpers' => [ 'primary' => 'Nastavením jedného alebo viacerých aliasov objektu ho umožňuje nájsť v rámci celkového hľadania (horná lišta) a cez referencie.', ], 'pitch' => 'Vytvor prezývky pre tento objekt, aby hľadanie a referencovanie bolo jednoduchšie.', 'placeholders' => [ 'name' => 'Nový alias', ], 'unboosted' => [], 'update' => [ 'success' => 'Alias :name aktualizovaný pre :entity.', 'title' => 'Aktualizovať alias pre :name', ], ]; ================================================ FILE: lang/sk/entities/assets.php ================================================ [ 'alias' => 'Alias', 'file' => 'Súbor', 'link' => 'Link', ], 'copy_alias' => [ 'success' => 'Referencia na prezývku skopírovaná do schránky.', ], 'show' => [ 'title' => 'Materiály pre :name', ], ]; ================================================ FILE: lang/sk/entities/attributes.php ================================================ [ 'load' => 'Nahrať', 'manage' => 'Spravovať', 'more' => 'Ďalšie možnosti', 'remove_all' => 'Odstrániť všetko', 'save_and_edit' => 'Použiť a Upraviť', 'save_and_story'=> 'Použiť a Zobraziť', 'show_hidden' => 'Zobraziť skryté atribúty', 'toggle_privacy'=> 'Súkromný/Verejný', ], 'errors' => [ 'loop' => 'Vo výpočte atribútu sa vyskytuje nekonečná slučka!', 'no_attribute_selected' => 'Vyber najprv jeden alebo viac atribútov.', 'too_many_v2' => 'Dosiahnuté max. počet polí (:count/:max). Zmaž najprv niektoré z atribútov pred pridaním nových.', ], 'fields' => [ 'community_templates' => 'Komunitné šablóny', 'is_private' => 'Súkromné atribúty', 'is_star' => 'Pripnutý', 'preferences' => 'Preferencie', 'value' => 'Hodnota', ], 'filters' => [ 'name' => 'Názov atribútu', 'value' => 'Hodnota atribútu', ], 'helpers' => [ 'delete_all' => 'Naozaj chceš odstrániť všetky atribúty tohto objektu?', 'is_private' => 'Povolí iba osobám s :admin-role rolou, aby videli atribúty tohto objektu.', 'setup' => 'Prvky ako HP alebo Inteligenciu nejakého objektu s atribútmi je možné referencovať. Atribúty pridáš ručne kliknutím na tlačidlo :manage alebo aplikovaním niektorej zo šablón atribútov.', ], 'hints' => [], 'index' => [ 'success' => 'Atribúty pre :entity upravené.', 'title' => 'Atribúty pre :name', ], 'labels' => [ 'checkbox' => 'Názov zaškrtávacieho políčka', 'name' => 'Názov atribútu', 'section' => 'Názov sekcie', 'value' => 'Hodnota atribútu', ], 'live' => [ 'success' => 'Atribút :attribute aktualizovaný.', 'title' => 'Aktualizácia :attribute', ], 'placeholders' => [ 'attribute' => 'Počet dobytí, úroveň obtiažnosti výzvy, iniciatíva, obyvateľstvo', 'block' => 'Názov bloku', 'checkbox' => 'Názov zaškrtávacieho políčka', 'icon' => [ 'class' => 'Trieda FontAwesome alebo RPG Awesome: fas fa-users', 'name' => 'Názov symbolu', ], 'number' => 'Názov čísla', 'random' => [ 'name' => 'Názov atribútu', 'value' => '1-100 alebo zoznam hodnôt oddelených čiarkou', ], 'section' => 'Názov sekcie', 'value' => 'Hodnota atribútu', ], 'ranges' => [ 'text' => 'Dostupné možnosti :options', ], 'sections' => [ 'unorganised' => 'Nezorganizované', ], 'show' => [ 'hidden' => 'Skryté atribúty', 'title' => 'Atribúty :name', ], 'template' => [ 'load' => [ 'success' => 'Šablóna nahraná', 'title' => 'Nahrať zo šablóny', ], 'success' => 'Šablóna atribútov :name použitá na :entity', 'title' => 'Použiť šablónu atribútov na :name', ], 'title' => 'Atribúty', 'toasts' => [ 'bulk_deleted' => 'Atribúty odstránené', 'bulk_privacy' => 'Súkromie atribútov prepnuté', 'lock' => 'Atribút uzamknutý', 'pin' => 'Atribút pripnutý', 'unlock' => 'Atribút odomknutý', 'unpin' => 'Atribút odopnutý', ], 'tutorials' => [], 'types' => [ 'attribute' => 'Atribút', 'block' => 'Blok', 'checkbox' => 'Zaškrtávacie políčko', 'icon' => 'Symbol', 'number' => 'Číslo', 'random' => 'Náhodne', 'section' => 'Sekcia', 'text' => 'Viacriadkový text', ], 'update' => [ 'success' => 'Atribúty pre :entity aktualizované.', ], 'visibility' => [ 'entry' => 'Atribút je zobrazený v menu objektu.', 'private' => 'Atribút viditeľný len pre členov s rolou Admin.', 'public' => 'Atribút viditeľný pre všetkých členov.', 'tab' => 'Atribút je zobrazený len v karte atribútov.', ], ]; ================================================ FILE: lang/sk/entities/children.php ================================================ 'Podobjekty', ]; ================================================ FILE: lang/sk/entities/events.php ================================================ [ 'type' => 'Typ udalosti', ], 'helpers' => [ 'characters' => 'Ak nastavíš typ ako dátum narodenia alebo smrti, systém vypočíta automaticky pre túto postavu jej vek. :more', 'founding' => 'Nastavením typu ako :type sa automaticky prepočíta vek objektu od jeho založenia.', ], 'show' => [ 'actions' => [ 'add' => 'Pridať pripomienku', ], 'title' => 'Pripomienky :name', ], 'types' => [ 'birth' => 'Narodenie', 'birthday' => 'Narodeniny', 'death' => 'Smrť', 'founded' => 'Založenie', 'primary' => 'Primárny', ], 'years-ago' => 'pred {1} :count rokom|[2,*] :count rokmi', ]; ================================================ FILE: lang/sk/entities/files.php ================================================ [], 'create' => [ 'success_plural' => '{1} Súbor :name pridaný.|[2,4] :count súbory pridané.|[5,*] :count súborov pridaných.', 'title' => 'Nový súbor pre :entity', ], 'destroy' => [ 'success' => 'Súbor :file odstránený.', ], 'fields' => [ 'file' => 'Súbor', 'files' => 'Súbory', 'name' => 'Meno súboru', ], 'max' => [ 'title' => 'Limit dosiahnutý', ], 'update' => [ 'success' => 'Súbor :file aktualizovaný.', 'title' => 'Aktualizovať súbor objektu', ], ]; ================================================ FILE: lang/sk/entities/image.php ================================================ [ 'change_focus' => 'Zmeniť stredobod záujmu', 'change_visibility' => 'Zmeniť viditeľnosť', 'replace_image' => 'Vymeniť obrázok', 'save-replace' => 'Vymeniť obrázok', 'save_focus' => 'Uložiť stredobod záujmu', 'view' => 'Zobraziť obrázok', ], 'call-to-action' => 'Kliknutím na obrázok objektu nastavíš jeho stredobod záujmu namiesto automatického odhadu.', 'focus' => [ 'breadcrumb' => 'Stredobod záujmu na obrázku', 'helper' => 'Klikni na obrázok pre umiestnenie stredobodu záujmu. Už umiestnený bod odstrániš kliknutím naň.', 'panel_title' => 'Stredobod záujmu na obrázku', 'success' => 'Stredobod záujmu aktualizovaný.', 'title' => 'Stredobod záujmu objektu :name', 'unboosted' => 'Nastavenie stredobodu záujmu na obrázku je rezervované pre :boosted_campaigns.', 'warning' => 'Stredobod záujmu pre obrázky v :gallery je zdieľaný všetkými objektami, ktoré používajú rovnaký obrázok.', ], 'gallery_permissions' => [ 'admin' => 'Tento obrázok galérie je iba viditeľný pre členstvo kampane s :admin rolou.', 'adminself' => 'Tento obrázok galérie je viditeľný iba pre :creator a členstvo kampane s :admin rolou.', 'member' => 'Tento obrázok galérie je viditeľný iba pre členstvo tejto kampane.', 'self' => 'Tento obrázok galérie je viditeľný iba pre teba.', ], 'replace' => [ 'breadcrumb' => 'Výmena obrázku', 'panel_title' => 'Výmena obrázku objektu', 'success' => 'Obrázok vymenený.', 'title' => 'Výmena obrázku objektu :name', ], 'visibility' => [ 'helper' => 'Zmeň viditeľnosť obrázku v galérii tým, že nastavíš, kto ho môže vidieť.', 'updated' => 'Viditeľnosť obrázku aktualizovaná.', ], ]; ================================================ FILE: lang/sk/entities/inventories.php ================================================ [ 'copy_inventory' => 'Kopírovať inventár', ], 'copy' => [], 'create' => [ 'success' => 'Predmet :item pridaný do :entity.', 'success_bulk' => '{0} Žiaden predmet nebol pridaný do :entity.|{1} Bol pridaný :count predmet do :entity.|[2,4] Boli pridané :count predmety do :entity.|[5,*] Bolo pridaných :count predmetov do :entity.', 'title' => 'Pridaj Predmet ku :name', ], 'default_position' => 'Bez zoradenia', 'destroy' => [ 'success' => 'Predmet :name odstránený z :entity.', 'success_position' => 'Predmet na :position odstránený z :entity.', ], 'fields' => [ 'amount' => 'Počet', 'copy_entity_entry_v2' => 'Použiť záznam objektu', 'description' => 'Popis', 'is_equipped' => 'Vybavený', 'name' => 'Názov', 'position' => 'Umiestnenie', 'qty' => 'Množ.', ], 'helpers' => [ 'amount' => 'Počet predmetov', 'copy_entity_entry_v2' => 'Zobraziť záznam objektu namiesto vlastného popisu.', 'description' => 'Pridať vlastný popis k predmetu', 'is_equipped' => 'Označiť predmety ako vybavené.', 'name' => 'Zadaj názov predmetu. Názov je povinný, ak nie je zvolený žiaden objekt.', ], 'placeholders' => [ 'amount' => 'Hocijaké množstvo', 'description' => 'Použitý, Zničený, Zžitý', 'name' => 'Potrebný, ak nie je zvolený žiaden objekt', 'position' => 'V rukách, V ruksaku, V sklade, V banke', ], 'show' => [ 'helper' => 'Objekty môžu mať priradené Predmety, čím sa vytvára ich Inventár.', 'title' => 'Objekt :name Inventár', 'unsorted' => 'Nezoradené', ], 'tooltips' => [ 'equipped' => 'Predmet je vo vybavení.', ], 'tutorials' => [], 'update' => [ 'success' => 'Predmet :item pre :entity upravený.', 'title' => 'Uprav Predmet :name', ], ]; ================================================ FILE: lang/sk/entities/links.php ================================================ [ 'add' => 'Pridať link', ], 'call-to-action' => 'Pridaj linky k externým zdrojom k tomuto objektu, napr. DnDBeyond a tieto sa objavia priamo v prehľade objektu.', 'create' => [ 'success' => 'Link :name pridaný k :entity.', 'title' => 'Pridať link k :name', ], 'destroy' => [ 'success' => 'Link :name odstránený z :entity.', ], 'fields' => [ 'icon' => 'Symbol', 'name' => 'Názov', 'position' => 'Pozícia', 'url' => 'URL', ], 'go' => [ 'actions' => [ 'confirm' => 'Áno', 'trust' => 'Nepýtaj sa ma znova', ], 'description' => 'Tento link ťa premiestni na :link. Ozaj sa tam chceš vybrať?', 'title' => 'Opúšťam Kanku', ], 'helpers' => [ 'icon' => 'Môžeš zmeniť zobrazovaný symbol linku. Použi jeden z voľne dostupných symbolov :fontawesome, alebo ponechaj pole prázdne pre štandardný symbol.', 'parent' => 'Zobrazí tento rýchly link po prvku v bočnom menu, namiesto v sekcii pre rýchle linky bočného menu.', ], 'placeholders' => [ 'name' => 'DNDBeyond', 'url' => 'https://dndbeyond.com/character-url', ], 'show' => [ 'helper' => 'Boostované kampane môžu pridávať k objektom linky, ktoré smerujú na externé webstránky.', 'title' => 'Linky pre :name', ], 'unboosted' => [], 'update' => [ 'success' => 'Link :name aktualizovaný pre :entity.', 'title' => 'Aktualizovať link pre :name', ], ]; ================================================ FILE: lang/sk/entities/logs.php ================================================ [ 'create' => 'Vytvoriť', 'create_post' => 'Vytvorená správa ":post"', 'delete' => 'Zmazať', 'delete_post' => 'Zmazaná správa', 'reorder_post' => 'Preusporiadané správy', 'restore' => 'Obnoviť', 'reveal' => 'Zobraziť detaily', 'update' => 'Aktualizovať', 'update_post' => 'Aktualizovaná správa ":post"', 'view' => 'Zobraziť zmeny', ], 'call-to-action' => 'Log s plným záznamom zmien za posledných :amount dní je dostupný pre superboosted kampane.', 'fields' => [ 'action' => 'Akcia', 'date' => 'Dátum', ], 'filters' => [ 'keywords' => 'Kľúčové slová', ], 'impersonated' => 'Vydáva sa za :name', 'none' => 'Žiadne', 'show' => [ 'title' => 'Objekt :name Log', ], ]; ================================================ FILE: lang/sk/entities/map-points.php ================================================ 'Tento objekt je označený na nasledujúcich mapách...', 'title' => ':name Miesta na mape', ]; ================================================ FILE: lang/sk/entities/mentions.php ================================================ [ 'element' => 'Prvok', 'type' => 'Typ', ], 'helper' => 'Toto je zoznam objektov, ktoré v položke "Záznam" referencujú tento objekt.', 'mentioned_in_v2' => 'Tento objekt je referencovaný v :count objektoch, poznámkach objektov alebo kampaniach. :more', 'see_more' => 'Zobraziť detaily', 'show' => [ 'title' => 'Referencie objektu :name', ], 'title' => 'Referencovaný objekt', ]; ================================================ FILE: lang/sk/entities/move.php ================================================ [ 'copy' => 'Kopírovať', ], 'errors' => [ 'permission' => 'V tejto kampani nemáš povolenie na vytváranie objektov tohto typu.', 'permission_update' => 'Na presun tohto objektu nemáš oprávnenie.', 'same_campaign' => 'Pre presun musíš zvoliť inú kampaň, kam tento objekt chceš presunúť.', 'unknown_campaign' => 'Neznáma kampaň.', ], 'fields' => [ 'campaign' => 'Cieľová kampaň', 'copy' => 'Vytvoriť kópiu', 'select_one' => 'Vyber kampaň', ], 'helpers' => [ 'copy' => 'Vytvoriť kópiu objektu v cieľovej kampani.', ], 'panel' => [ 'description' => 'Vyber kampaň, do ktorej chceš tento objekt presunúť alebo skopírovať.', 'description_bulk_copy' => 'Vyber kampaň, do ktorej chceš vybrané objekty skopírovať.', 'title' => 'Presun alebo kópia objektu do inej kampane', ], 'success' => 'Objekt :name presunutý.', 'success_copy' => 'Objekt :name skopírovaný.', 'title' => 'Presun :name', 'warnings' => [ 'custom' => 'Tento objekt nepatrí štandardnému modulu, ale vlastnému typu objektu :module. V cieľovej kampani bude vytvorený vo forme Poznámky.', ], ]; ================================================ FILE: lang/sk/entities/notes.php ================================================ [ 'add' => 'Nová Poznámka', 'add_role' => 'Pridať rolu', 'add_user' => 'Pridať užívateľa', ], 'collapsed' => [ 'closed' => 'Poznámka je zbalená na jej hlavičku', 'open' => 'Poznámka je rozbalená', ], 'copy_mention' => [ 'copy' => 'Kopírovať rozšírenú referenciu', 'copy_with_name' => 'Kopírovať rozšírenú referenciu s názvom príspevku', 'success' => 'Rozšírená referencia bola skopírovaná do schránky.', ], 'create' => [ 'success' => 'Poznámka :name pridaná k objektu :entity.', ], 'destroy' => [ 'success' => 'Poznámka :name odstránená z :entity.', ], 'edit' => [ 'success' => 'Poznámka :name pre :entity upravená.', ], 'fields' => [ 'creator' => 'Autor/ka', 'display' => 'Displej', 'name' => 'Názov', 'position' => 'Pozícia', ], 'footer' => [ 'created' => 'Vytvorené :user dňa :date', 'updated' => 'Aktualizované :used dňa :date', ], 'hint' => 'Informácie, ktoré nepasujú do štandardných polí objektu alebo by mali byť súkromné, môžu byť pridané v podobe poznámok.', 'hints' => [ 'reorder' => 'Môžeš zmeniť poradie poznámok daného objektu kliknutím na ikonku :icon vedľa Prehľadu v menu objektu.', ], 'index' => [], 'move' => [ 'copy' => 'Vytvoriť kópiu v cieľovom objekte', 'copy_success' => 'Komentár :name úspešne skopírovaný do :entity.', 'copy_title' => 'Ponechať kópiu', 'description' => 'Zvoľ objekt, do ktorého chceš premiestniť alebo kopírovať tento komentár.', 'entity' => 'Cieľový objekt', 'move_success' => 'Komentár :name úspešne premiestnený do :entity.', ], 'placeholders' => [ 'name' => 'Názov poznámky, zistenia alebo pripomienky', ], 'show' => [ 'advanced' => 'Rozšírené oprávnenia', 'title' => 'Poznámka :name objektu :entity', ], 'states' => [ 'collapsed' => 'Zbalené', 'expanded' => 'Rozbalené', ], 'warning' => [], ]; ================================================ FILE: lang/sk/entities/permissions.php ================================================ [ 'text' => 'Tento objekt je nastavený ako súkromný. Vlastné oprávnenia môžu byť ešte stále definované, ale pokiaľ je objekt súkromný, budú tieto ignorované a iba členstvo role :admin bude tento objekt vidieť.', 'warning' => 'Varovanie', ], 'quick' => [ 'empty-permissions' => 'Žiadna rola alebo užívateľ mimo adminov kampane nemá prístup k tomuto objektu.', 'manage' => 'Manažovať oprávnania', 'screen-reader' => 'Otvoriť nastavenia súkromia', 'success' => [ 'private' => ':entity je teraz skryté.', 'public' => ':entity je teraz viditeľné.', ], 'title' => 'Oprávnenia', 'viewable-by' => 'Viditeľné pre', ], ]; ================================================ FILE: lang/sk/entities/pins.php ================================================ 'Linky', 'title' => 'Pripnutia', ]; ================================================ FILE: lang/sk/entities/profile.php ================================================ [ 'edit_profile' => 'Upraviť profil', ], 'history' => 'História', 'show' => [ 'tab_name' => 'Profil', 'title' => 'Profil :name', ], ]; ================================================ FILE: lang/sk/entities/quests.php ================================================ 'Tento objekt je súčasťou nasledujúcich úloh.', 'title' => 'Úlohy :name', ]; ================================================ FILE: lang/sk/entities/relations.php ================================================ [ 'mode-map' => 'Nástroj zobrazenia vzťahov', 'mode-table' => 'Tabuľka vzťahov a prepojení', ], 'bulk' => [ 'delete' => '{1} :count vzťah odstránený.|[2,4] :count vzťahy odstránené.|[5,*] :count vzťahov odstránených.', 'fields' => [ 'delete_mirrored' => 'Zmazať zrkadlené', 'unmirror' => 'Rozviazať zrkadlené', 'update_mirrored' => 'Aktualizovať zrkadlené', ], 'helpers' => [ 'delete_mirrored' => 'Taktiež zmazať zrkadlené vzťahy.', 'unmirror' => 'Rozviazať zrkadlené vzťahy.', 'update_mirrored' => 'Aktualizovať zrkadlené vzťahy.', ], 'success' => [ 'editing' => '{1} :count vzťah aktualizovaný.|[2,4] :count vzťahy aktualizované.|[5,*] :count vzťahov aktualizovaných.', 'editing_partial' => '{1} :count/:total vzťah aktualizovaný.|[2,4] :count/:total vzťahy aktualizované.|[5,*] :count/:total vzťahov aktualizovaných.', ], ], 'call-to-action' => 'Vizuálne objavuj vzťahy tohto objektu a ako je prepojený s ostatkom kampane.', 'connections' => [ 'map_point' => 'Bod na mape', 'mention' => 'Referencia', 'quest_element' => 'Prvok úlohy', 'timeline_element' => 'Prvok časovej osy', ], 'create' => [ 'new_title' => 'Nový vzťah', 'success_bulk' => '{1} Pridaný :count prepojenie k :entity.|[2,4] Pridané :count prepojenia k :entity.|[5,*] Pridaných :count prepojení k :entity.', ], 'delete_mirrored' => [ 'helper' => 'Tento vzťah sa zrkadlí na cieľovom objekte. Zvoľ túto možnosť, aby bol odstránený aj zrkadlený vzťah.', 'option' => 'Odstrániť zrkadlený vzťah', ], 'destroy' => [ 'mirrored' => 'Toto tiež odstráni zrkadlené vzťahy a natrvalo.', 'success' => 'Vzťah pre :name odstránený', ], 'fields' => [ 'attitude' => 'Postoj', 'is_pinned' => 'Pripnuté', 'owner' => 'Zdroj', 'target' => 'Cieľ', 'two_way' => 'Vytvoriť zrkadlenie vzťahu', 'unmirror' => 'Zrušiť zrkadlenie tohto vzťahu.', ], 'filters' => [ 'connection' => 'Vzťah prepojenia', 'name' => 'Cieľ prepojenia', ], 'helper' => 'Vytvor vzťahy medzi objektami s postojom a viditeľnosťou. Vzťahy môžu byť tiež pripnuté k menu objektu.', 'helpers' => [ 'no_relations' => 'Tento objekt nemá aktuálne žiadne vzťahy s inými objektami v kampani.', ], 'hints' => [ 'attitude' => 'Toto nepovinné pole môže usporiadať poradie vzťahov štandardne podľa hodnoty vzťahu.', 'two_way' => 'Keď vytvoríš zrkadlenie vzťahu, vytvorí sa rovnaký vzťah aj u cieľového objektu. Ak bude neskôr upravovaný, zrkadlený vzťah nebude zmenami dotknutý.', ], 'index' => [ 'title' => 'Vzťahy', ], 'options' => [ 'mentions' => 'Vzťahy + Prepojené + Referencie', 'only_relations' => 'Iba priame vzťahy', 'related' => 'Vzťahy + Prepojené', 'relations' => 'Vzťahy', 'show' => 'Zobraziť', ], 'panels' => [ 'related' => 'Prepojené', ], 'placeholders' => [ 'attitude' => '-100 až 100, kde 100 je max. pozitívny', ], 'show' => [ 'title' => 'Vzťahy pre :name', ], 'types' => [ 'family_member' => 'Člen rodu', 'organisation_member' => 'Člen organizácie', ], 'update' => [ 'success' => 'Vzťah pre :name bol upravený', 'title' => 'Upraviť vzťahy', ], ]; ================================================ FILE: lang/sk/entities/story.php ================================================ [ 'collapse_all' => 'Zbaliť všetko', 'expand_all' => 'Rozbaliť všetko', 'load_more' => 'Načítať ďalšie', 'login_for_more' => 'Prihlásiť sa pre zobrazenie viacerých príspevkov', ], 'reorder' => [ 'icon_tooltip' => 'Preskupiť príspevky', 'panel_title' => 'Preskupiť príspevky', 'save' => 'Uložiť nové poradie', 'success' => 'Príspevky preskupené.', ], 'update' => [ 'title' => 'Aktualizovať záznam :entity', ], 'warning' => [], ]; ================================================ FILE: lang/sk/entities/timelines.php ================================================ 'Časové osi s objektami referencovanými k tomuto objektu sa zobrazia nižšie.', 'show' => [ 'title' => 'Časové osi pre :name', ], ]; ================================================ FILE: lang/sk/entities/transform.php ================================================ [], 'bulk' => [ 'errors' => [ 'unknown_type' => 'Neznámy alebo nesprávny typ objektu.', ], 'success' => '{1} :count objekt transformovaný na nový typ: :type.|[2,4] :count objekty transformované na nový typ: :type.|[5,*] :count objektov transformovaných na nový typ: :type.', ], 'fields' => [ 'select_one' => 'Vyber jeden', 'target' => 'Nový typ objektu', ], 'panel' => [ 'bulk_description' => 'Zmeň typ pre viacero objektov. Prosím, uvedom si, že niektoré údaje vzhľadom na rôzne polia v daných objektoch môžeš stratiť.', 'bulk_title' => 'Hromadne transformovať objekty', 'title' => 'Transformovať objekt', ], 'success' => 'Objekt :name transformovaný.', 'title' => 'Transformácia :name', ]; ================================================ FILE: lang/sk/entities.php ================================================ 'Schopnosti', 'ability' => 'Schopnosť', 'archetype' => 'Archetyp', 'archetypes' => 'Archetypy', 'article' => 'Článok', 'articles' => 'Články', 'attribute_template' => 'Šablóna atribútov', 'attribute_templates' => 'Šablóny atribútov', 'bookmark' => 'Záložka', 'bookmarks' => 'Záložky', 'calendar' => 'Kalendár', 'calendars' => 'Kalendáre', 'campaign' => 'Kampaň', 'campaigns' => 'Kampane', 'character' => 'Postava', 'characters' => 'Postavy', 'conversation' => 'Diskusia', 'conversations' => 'Diskusie', 'creator' => [ 'actions' => [ 'create' => 'Vytvoriť :type', 'full' => 'Prejsť na úplný formulár', 'more' => 'Pridať viac detailov', ], 'back' => 'Späť na výber', 'bulk_names' => 'Pridaj jedno meno na riadok', 'duplicate' => 'Existujú iné objekty tohto typu s rovnakým menom.', 'helper_v2' => 'Zrýchlene vytvor základ nového objektu bez prerušenia práce.', 'helpers' => [ 'archetype' => 'Zvoľ archetyp, ktorého kópiou budú nové objekty', 'template' => 'Zvoľ šablónu, ktorej kópiou budú nové objekty', ], 'missing_v2' => 'Jediné moduly, ktoré sú aktivované a ktoré máš povolenie vytvárať sú dostupné na tejto obrazovke. :learn-more', 'modes' => [ 'archetypes' => 'Výber archetypu', 'bulk' => 'Masové pridanie', 'default' => 'Rýchle pridanie', ], 'name' => [ 'new' => 'Nový názov', 'remove' => 'Odstrániť', ], 'success_multiple' => '{1} Nový objekt :link vytvorený.|[2,4] Nové objekty :link vytvorené.|[5,*] Nových objektov :link vytvorených.', 'success_multiple_posts' => '{1} Nová poznámka :link vytvorená.|[2,4] Nové poznámky :link vytvorené.|[5,*] Nových poznámok :link vytvorených.', 'title' => 'Nový objekt', 'titles' => [ 'everything' => 'Všetko', 'quick-access' => 'Rýchly prístup', ], 'tooltip' => 'Vytvoriť nový objekt bez opustenia aktuálnej stránky', 'tooltips' => [ 'create' => 'Vytvoriť objekt a prejsť späť na výber', 'create_more' => 'Vytvoriť objekt a začať vytvárať ďalší rovnakého typu', 'edit' => 'Vytvoriť objekt a začať ho upravovať', ], ], 'creature' => 'Bytosť', 'creatures' => 'Bytosti', 'dice_roll' => 'Hod kockou', 'dice_rolls' => 'Hody kockou', 'entries' => 'Zápisy', 'entry' => 'Zápis', 'event' => 'Udalosť', 'events' => 'Udalosti', 'families' => 'Rody', 'family' => 'Rod', 'inventories' => 'Inventáre', 'item' => 'Predmet', 'items' => 'Predmety', 'journal' => 'Denník', 'journals' => 'Denníky', 'location' => 'Miesto', 'locations' => 'Miesta', 'map' => 'Mapa', 'maps' => 'Mapy', 'media' => 'Médiá', 'new' => [], 'note' => 'Poznámka', 'notes' => 'Poznámky', 'organisation' => 'Organizácia', 'organisations' => 'Organizácie', 'properties' => 'Vlastnosti', 'quest' => 'Úloha', 'quest_element' => 'Prvok úlohy', 'quests' => 'Úlohy', 'race' => 'Rasa', 'races' => 'Rasy', 'relation' => 'Vzťah', 'relations' => 'Vzťahy', 'reminders' => 'Pripomenutia', 'tag' => 'Kategória', 'tags' => 'Kategórie', 'templates' => 'Šablóny', 'timeline' => 'Časová os', 'timeline_element' => 'Prvok časovej osi', 'timelines' => 'Časové osi', 'whiteboard' => 'Tabuľa', 'whiteboards' => 'Tabule', ]; ================================================ FILE: lang/sk/errors.php ================================================ [ 'body' => 'Vyzerá to tak, že nemáš oprávnenie na zobrazenie tejto stránky!', 'title' => 'Prístup zamietnutý', ], '403-form' => [ 'help' => 'Dôvod môže byť uplynutie doby prihlásenia. Prosím, skús sa opätovne prihlásiť v novom okne pred uložením zmien.', ], '404' => [ 'body' => 'Prepáč, ale hľadanú stránku sme nenašli.', 'title' => 'Stránka nebola nájdená', ], '500' => [ 'body' => [ '1' => 'Ojojoj, niečo sa pokazilo.', '2' => 'Report s popisom chyby nám už bol zaslaný, ale niekedy pomôže, ak vieme trochu viac o tom, čo sa vlastne dialo.', ], 'title' => 'Chyba', ], '503' => [ 'body' => [ '1' => 'Na Kanke sa práve pracuje, čo zvyčajne znamená, že nahrávame jej aktualizáciu!', '2' => 'Ospravedlňujeme sa za túto nepríjemnosť. Všetko bude o chvíľu zasa fungovať.', ], 'json' => 'Na Kanke sa aktuálne pracuje, prosím, skús to o pár minút.', 'title' => 'Údržba', ], '503-form' => [], 'back-to-campaigns' => 'Vráť sa k jednej z tvojich kampaní', 'footer' => 'Ak potrebuješ ďalšiu pomoc, kontaktuj nás na hello@kanka.io alebo na :discord.', 'log-in' => 'Prihlásením sa do tvojho konta sa môže zobraziť, čo hľadáš.', 'post_layout' => 'Nesprávny vizuál poznámky.', 'private-campaign' => [ 'auth' => [ 'helper' => 'K tejto kampani nemáš prístup.', ], 'guest' => [ 'helper' => 'Kampaň, do ktorej sa chceš dostať, je súkromná a ty nie si prihlásený/á.', 'login' => 'Prihlásením môžeš získať prístup k obsahu.', ], 'title' => 'Súkromná kampaň', ], ]; ================================================ FILE: lang/sk/events.php ================================================ [ 'title' => 'Nová udalosť', ], 'destroy' => [], 'edit' => [], 'events' => [ 'helper' => 'Udalosti, ktoré majú tento objekt ako ich nadradený, sa zobrazujú tu.', ], 'fields' => [ 'date' => 'Dátum', ], 'helpers' => [ 'date' => 'Toto pole môže obsahovať čokoľvek a nie je prepojené s kalendármi kampane. Na zobrazenie tejto udalosti v kalendári je nutné ju pridať do kalendára alebo do karty Pripomienky tejto udalosti.', ], 'index' => [], 'lists' => [ 'empty' => 'Pridaj kritické momenty ako bitvy, korunovácie alebo objavy do histórie tvojho sveta.', ], 'placeholders' => [ 'date' => 'Dátum tvojej udalosti', 'type' => 'ceremónia, festival, katastrofa, bitva, narodenie', ], 'show' => [], 'tabs' => [ 'calendars' => 'Záznamy v kalendári', ], ]; ================================================ FILE: lang/sk/export.php ================================================ 'Obsah', 'hidden_campaign' => 'Skrytá kampaň', 'index' => 'Index objektov', ]; ================================================ FILE: lang/sk/families/trees.php ================================================ [ 'clear' => 'Zmazať všetko', 'first' => 'Pridať zakladateľa/ku', 'founder' => 'Pridať nového/ú zakladateľa/ku', 'rename-relation' => 'Premenovať vzťah', 'reset' => 'Zahodiť zmeny', 'save' => 'Uložiť', ], 'modal' => [ 'first-title' => 'Výber objektu', 'helper' => 'Nahradiť objekt iným z kampane', 'relation' => 'Vzťah', 'title' => 'Nahradiť objekt', ], 'modals' => [ 'clear' => [ 'confirm' => 'Naozaj chceš reinicializovať všetky dáta z rodokmeňa?', ], 'entity' => [ 'add' => [ 'founder' => 'Zakladateľ/ka', 'member' => 'Člen/ka', 'success' => 'Objekt pridaný.', 'title' => 'Pridať objekt', ], 'child' => [ 'success' => 'Dieťa pridané.', 'title' => 'Pridať dieťa', ], 'edit' => [ 'helper' => 'Zaškrtni túto možnosť, ak je vzťah neznámy. Postavu môžeš pridať neskôr.', 'success' => 'Objekt aktualizovaný.', 'title' => 'Aktualizovať objekt', ], 'founder' => [ 'title' => 'Pridanie nového/ú zakladateľa/ku', ], 'remove' => [ 'confirm' => 'Naozaj chceš odstrániť tento objekt z rodokmeňa?', 'success' => 'Objekt odstránený.', ], ], 'relations' => [ 'add' => [ 'success' => 'Vzťah pridaný.', 'title' => 'Pridať vzťah', ], 'edit' => [ 'success' => 'Vzťah aktualizovaný.', 'title' => 'Aktualizovať vzťah', ], 'unknown' => 'Neznámy', ], 'reset' => [ 'confirm' => 'Naozaj chceš zahodiť všetky zmeny urobené v tomto rodokmeni?', ], ], 'pitch' => 'Vytvor detailný rokokmeň pre rody v tvojej kampani.', 'success' => [ 'cleared' => 'Rodokmeň zmazaný.', 'reseted' => 'Rodokmeň obnovený.', 'saved' => 'Rodokmeň uložený.', ], 'title' => 'Rodokmeň :name', 'unknown' => 'nezadaný', ]; ================================================ FILE: lang/sk/families.php ================================================ [ 'title' => 'Nový rod', ], 'destroy' => [], 'edit' => [], 'families' => [], 'fields' => [], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Tento rod vyhynul.', 'members' => 'Zoznam členov a členiek daného rodu sa zobrazuje na tomto mieste. Úpravou danej postavy je možné ju pridať do daného rodu v poli Rod.', ], 'index' => [], 'lists' => [ 'empty' => 'Zaznamenávaj rodové línie, klany či šľachtické rody, ktoré sú prepojené s tvojimi postavami.', ], 'members' => [ 'create' => [ 'helper' => 'Pridaj jedného alebo viacerých členov k :name.', 'success' => '{0} Žiaden člen nebol pridaný.|{1} 1 člen bol pridaný.|[2,4] :count členovia boli pridaní.|[5,*] :count členov bolo pridaných.', 'title' => 'Noví členovia', ], ], 'placeholders' => [ 'name' => 'Názov rodu', 'type' => 'Kráľovský, Šľachtický, Vyhynutý', ], 'show' => [ 'tabs' => [ 'tree' => 'Rodokmeň', ], ], ]; ================================================ FILE: lang/sk/faq.php ================================================ [ 'account_settings' => 'Nastavenia konta', 'answer' => 'Ak chceš odstrániť svoje konto, choď na stránku :account a skroluj nadol k sekcii o odstránení konta. Tu vieš odstrániť tvoje konto a všetky kampane, v ktorých si jediným členom.', 'question' => 'Ako viem odstrániť moje konto?', ], 'app_backup' => [ 'answer' => 'Denne vytvárame dve zálohy, aby sme zabezpečili ochranu proti strate dát. Naše vlastné kampane sa nachádzajú na tomto serveri, takže nechceme podstupovať žiadne riziko!', 'question' => 'Ako často sa zálohujú dáta v Kanke?', ], 'attribute-templates' => [ 'answer' => <<<'TEXT' Najlepší spôsob, ako vysvetliť podstatu Šablón atribútov, bude pomocou príkladu. Predstavme si, že tvoj svet má veľké množstvo miest. Pre mnohé z nich chceš zadať základné atribúty ako "Počet obyvateľov", "Podnebie", "Úroveň kriminality". Môžeš tieto atribúty pre každé miesto nastaviť jednotlivo, to sa však čoskoro stane zdĺhavým, popr. zabudneš niekde zadať atribút "Úroveň kriminality". V takom prípade sa ti zíde Šablóna atribútov. Vytvoríš jednu Šablónu atribútov s atribútmi "Počet obyvateľov", "Podnebie" a "Úroveň kriminality" a neskôr uplatníš túto pri tvojich miestach. Šablóna bude automaticky pridelená každému miestu a ty už len vyplníš dané hodnoty! TEXT , 'question' => 'Čo sú šablóny atribútov?', ], 'backup' => [ 'answer' => 'Raz za deň si môžeš všetky dáta tvojej kampane exportovať v podobe ZIP súboru. Klikni v aplikácii v ľavom menu na "Kampaň" a potom na "Exportovať". Tým sa vytvorí export, ktorý bude dostupný 30 minút. Tento export nemôžeš nahrať späť do Kanky, je len pre pokoj v tvojej duši, ak už aplikáciu nebudeš používať.', 'question' => 'Ako môžem zálohovať alebo exportovať moju kampaň?', ], 'bugs' => [ 'answer' => 'Pridaj sa jednoducho do nášho :discord servera a ohlás danú chybu v #error-and-bugs kanáli.', 'question' => 'Kde môžem nahlásiť chyby v aplikácii?', ], 'campaign-sync' => [ 'answer' => 'Túto funkcionalitu Kanka nepodporuje. Môžeš ale manažovať viacero herných skupín v rovnakom svete. Ak používajú rovnakú kampaň, vieš ich prístupy kontrolovať cez kombináciu Úloh, Kategórií a Oprávnení.', 'question' => 'Môžem synchronizovať objekty vo viacerých kampaniach naraz?', ], 'custom' => [ 'answer' => 'Kanka ponúka preddefinované typy navzájom integrovaných objektov. Ak by sme povolili vytváranie vlastných typov objektov, museli by sme aplikáciu úplne prerobiť a zároveň by sme prišli o zjednodušenie práce a namiesto tvorby svetov by sme riešili, ako ich organizovať. Okrem toho je Kanka dostatočne flexibilná pomocou objektu kategórií.', 'question' => 'Môžem vytvoriť mnou definované typy objektov?', ], 'delete-campaign' => [ 'answer' => 'Na nástenke tvojej kampane klikni na "Kampaň" v ľavom menu. Ak si posledným členom kampane, objaví sa tlačidlo "Zmazať". Zmazanie kampane je nevratná akcia, ktorá zmaže z našich serverov všetky údaje, vrátane obrázkov.', 'question' => 'Ako môžem kampaň zmazať?', ], 'discord' => [ 'answer' => 'Ak chceš prepojiť tvoje konto s :discord, musíš najprv kliknúť na tvojho avatara vpravo hore v aplikácii a následne na tlačidlo Profil. Tu vieš dostať na substránku :apps a kliknúť na Prepojiť.', 'question' => 'Ako prepojím moje konto v Kanke s Discordom?', ], 'early-access' => [ 'answer' => 'Early Access (Skorý prístup) je spôsob, akým môžeme odmeniť našich prispievateľov. Počas 30 dní môžu exkluzívne vyskúšať všetky najnovšie moduly predtým, než sú dostupné pre všetkých.', 'question' => 'Čo je Early Access?', ], 'entity-notes' => [ 'answer' => 'Všetky objekty majú kartu pre Poznámky objektu, čo sú krátke textové poznámky, ktoré môžu byť nastavené viditeľné pre teba (aj keď napr. spolu-GM-uješ), len pre členov s rolou Admin alebo pre všetkých. Hráčom a hráčkam vieš poskytnúť oprávnenie pridávať objektom poznámky bez toho, aby vedeli upravovať daný objekt.', 'question' => 'Ako zaobchádza Kanka s informáciami, ktoré by nemali byť všeobecne viditeľné?', ], 'fields' => [ 'answer' => 'Odpoveď', 'category' => 'Kategória', 'locale' => 'Jazyk', 'order' => 'Poradie', 'question' => 'Otázka', ], 'free' => [ 'answer' => <<<'TEXT' Áno! Sme toho názoru, že naša finančná situácia nemá mať dopad na pôžitok z hrania RPG hier alebo tvorby svetov, preto základné funkcie aplikácie budú stále dostupné zadarmo. Ale ak sa chceš aktívne zapojiť a podporiť naše ciele, zároveň hlasovať za funkcie, ktoré by ti najviac vyhovovali, môžeš si nás predplatiť. Popri hlasovaní za smer, ktorým chceš, aby sa Kanka vyvíjala, podporou získaš prístup k :boosters, vyššiemu limitu pre nahrané súbory, tvoje meno bude pridané do siene slávy, krajšie ikonky a oveľa viac! TEXT , 'question' => 'Ostane Kanka dostupná zadarmo?', ], 'gods-and-religions' => [ 'answer' => 'Odporúčame bohov vytvoriť ako postavy a náboženstvá ako organizácie. Ak chceš rýchlo nájsť nejaké božstvá, odporúčame ich vytvoriť zároveň so zodpovedajúcou kategóriou.', 'question' => 'Kde mám vytvoriť bohov a náboženstvá?', ], 'help' => [ 'answer' => 'Za prvé, vďaka, že nám chceš pomôcť! Stále radi vidíme, ak sa prihlásia ľudia, ktorí by nám chceli pomôcť s prekladmi, testovaním nových funkcionalít alebo by chceli pomáhať novým užívateľom. Tiež máme radi, ak Kanku odporúčajú ďalej na miestach, na ktoré sme doteraz nemysleli. Najlepší spôsob, ako nám pomôcť, je pridať sa k nám na našom :discord serveri, kde máme dedikovaný kanál pre výpomoc.', 'question' => 'Ako vám môžem pomôcť?', ], 'map' => [ 'answer' => 'Modul Mapy podporuje súbory vo formátoch PNG, JPG a SVG. Mapy môžu mať vrstvy, značky a skupiny značiek v rôznych tvaroch a veľkostiach, ktoré môžu referencovať ďalšie objekty v kampani.', 'question' => 'Môžem do Kanky nahrať mapy?', ], 'mobile' => [ 'answer' => 'Aktuálne neexistuje mobilná aplikácia pre Kanku, ale väčšina funkcionalít funguje aj na telefóne. Dúfame, že pomocou podpory cez predplatné sa nám niekedy v budúcnosti podarí zaplatiť niekoho, kto jedného dňa vytvorí mobilnú aplikáciu, ale v blízkej budúcnosti to nemáme v pláne.', 'question' => 'Existuje mobilná aplikácia? Plánujete nejakú?', ], 'monsters' => [ 'answer' => 'Pre vytvorenie hocijakej entity ako národy, rasy, príšery alebo hocičo iné, čo je živé (alebo nemŕtve) odporúčame modul Rasy.', 'question' => 'Kde môžem vytvoriť príšery?', ], 'multiworld' => [ 'answer' => 'Môžeš byť súčasťou toľkých kampaní, koľkých chceš, vrátane tých, ktoré vytvoríš. Ak chceš prepnúť alebo vytvoriť novú kampaň, prejdi do nástenky kampane a hore vpravo klikni na svoju aktuálnu kampaň, čím zobrazíš prepínač medzi kampaňami.', 'question' => 'Môžem mať viac ako jednu kampaň?', ], 'nested' => [ 'answer' => 'Ak preferuješ vnorené zobrazenie ako štandardné (napr. po kliknutí na Vnorené zobrazenie v zozname miest), môžeš si ho nastaviť v tvojom profile a nastaveniach zobrazenia. Tam môžeš zaškrtnúť možnosť pre vnorené zobrazenie. Toto je len pre tvoje konto, nie štandardne pre všetky kampane.', 'question' => 'Môžem nastaviť, aby sa zoznamy zobrazovali štandardne ako vnorené?', ], 'permissions' => [ 'answer' => 'Samozrejme, preto sme Kanku vytvorili! Môžeš do kampane pozvať všetkých hráčov a hráčky a zadať im role a oprávnenia. Systém sme vytvorili ako extrémne flexibilný (môžeš sa rozhodnúť pre stratégiu opt-in alebo opt-out), aby pokryl veľké množstvo požiadavok a situácií.', 'question' => 'Môžem nejak obmedziť informácie, ktoré hráči a hráčky vidia v mojej kampani?', ], 'plans' => [ 'answer' => <<<'TEXT' Dlhodobý plán je z Kanky vytvoriť všestranný nástroj pre tvorbu svetov a správu kampaní, ktorý sa neviaže na žiaden systém a jeho obsah vytvára komunita formou "Komunitných šablón". Ďalší náš cieľ je vytvoriť nástroje, ktorými by bola Kanka prepojená s inými platformami, napr. virtuálnymi aplikáciami pre hranie stolových RPG. Kanku používame aj my, takže nemáme v pláne prestať ju ďalej vyvíjať a zlepšovať. Projekt ale vedieme zároveň ako open source, takže ho komunita môže prevziať a pokračovať v ňom, ak by sa nám niečo prihodilo. TEXT , 'question' => 'Aké dlhodobé plány máte?', ], 'public-campaigns' => [ 'answer' => 'Môžeš si pozrieť stránku s :public-campaigns, kde môžeš sledovať, ako Kanku používajú ostatní užívatelia.', 'question' => 'Akým spôsobom používajú Kanku iní užívatelia?', ], 'renaming-modules' => [ 'answer' => 'Aj keby toto bolo jednoduché pre názvy v angličtine alebo iných jazykoch, ktoré nepoužívajú rodovo rozdielne názvy, možnosť zmeniť názvy modulov by porušilo gramatickú správnosť a užívateľskú skúsenosť pre väčšinu jazykov, v ktorých je Kanka dostupná.', 'question' => 'Môžem moduly premenovať? Napr. Rody na Klany, alebo Organizácie na Frakcie?', ], 'sections' => [ 'community' => 'Komunita', 'general' => 'Všeobecné', 'other' => 'Iné', 'permissions' => 'Oprávnenia', 'pricing' => 'Cenník', 'worldbuilding' => 'Tvorba svetov', ], 'show' => [ 'return' => 'Späť na FAQ', 'timestamp' => 'Posledná úprava dňa :date', 'title' => 'FAQ :name', ], 'unboost' => [ 'answer' => 'Ukončenie boostnutia kampane nezmaže žiadne údaje z nej, ktoré boli vytvorené počas boostnutia, ale jednoducho skryje tieto dodatočné informácie a funkcionality. Ak bude kampaň opätovne boostnutá, tieto informácie a funkcionality sa opätovne zobrazia s rovnakými nastaveniami.', 'question' => 'Čo sa stane, ak kampaň prestane byť boostnutá?', ], 'user-switch' => [ 'answer' => 'Oprávnenia môžu byť trochu zložitejšie, najmä vo väčších kampaniach. Ako administrátor kampane môžeš na stránke členov kampane kliknúť na "Prepnúť", ktorý sa zobrazí vedľa mena člena kampane. Po kliknutí ťa systém prihlási ako daného užívateľa a povolí ti vidieť kampaň cez jeho oči. Toto je najjednoduchší spôsob, akým môžeš skontrolovať nastavenie oprávnení tvojej kampane.', 'question' => 'Mám nastavené oprávnenia v mojej kampani. Ako ich otestujem?', ], 'visibility' => [ 'answer' => 'Iba ľudia, ktorých pozveš do kampane, ju môžu vidieť a pracovať s tvojím dielom. Tvoje údaje sú súkromné a ostávajú pod tvojou kontrolou. Kampaň ale môžeš nastaviť aj ako verejnú, aby ju mohli vidieť aj neregistrovaní užívatelia.', 'question' => 'Môže niekto vidieť mnou vytvorený svet?', ], ]; ================================================ FILE: lang/sk/fields.php ================================================ [ 'placeholder' => 'Vybrať obrázok z galérie kampane', ], 'gallery-header' => [ 'description' => 'Ak objekt nemá žiaden obrázok záhlavia, zobraziť namiesto toho obrázok z galérie kampane.', ], 'gallery-image' => [ 'description' => 'Ak objekt nemá žiaden obrázok, zobraziť namiesto toho obrázok z galérie kampane.', ], 'header-image' => [ 'boosted-description' => 'Zobraziť obrázok pozadia v záhlaví objektu pomocou :boosted-campaign.', 'description' => 'Zobraziť obrázok pozadia v záhlaví objektu. Pre lepší výsledok použi naozaj veľký obrázok.', 'title' => 'Obrázok záhlavia', ], 'tooltip' => [], ]; ================================================ FILE: lang/sk/filters.php ================================================ [ 'bookmark' => 'Záložka', ], 'alerts' => [ 'copy' => 'Filtre boli skopírované do tvojej schránky.', ], 'bookmark' => [ 'name' => ':module (filtrovaný)', 'premium' => 'Ak chceš pridať viac záložiek, budeš musieť aktivovať prémiové funkcionality kampane.', 'success' => 'Záložka vytvorená.', ], 'helpers' => [ 'guest' => 'Prosím prihlás sa do tvojho konta, ak chceš filtrovať výsledky.', ], ]; ================================================ FILE: lang/sk/footer.php ================================================ 'O nás', 'blog' => 'Blog', 'boosters' => 'Boosty', 'community' => 'Komunita', 'company' => 'Spoločnosť', 'contact' => 'Kontakt', 'copyright' => 'Copyright :copy :year Owlchester SNC', 'documentation' => 'Dokumentácia', 'features' => 'Funkcie', 'kb' => 'Vedomostná databáza', 'language-switcher' => [ 'other' => 'Iné jazyky', 'title' => 'Zvoľ tvoj jazyk', ], 'made' => 'Vytovrené s ❤️ v Ženeve, Švajčiarsko', 'newsletter' => 'Newsletter', 'platform' => 'Platforma', 'premium' => 'Prémiové kampane', 'press-kit' => 'Mediálny balíček', 'pricing' => 'Cenník', 'privacy' => 'Ochrana osobných údajov', 'public-campaigns' => 'Verejné kampane', 'resources' => 'Zdroje', 'roadmap' => 'Plán', 'security' => 'Bezpečnosť', 'server-time' => 'Toto je čas na našom serveri', 'status' => 'Stav služby', 'terms' => 'Všeobecné podmienky', 'thanks' => 'Umožnené jedine s pomocou našich podporujúcich.', 'translator_call' => 'Kanka je prekladaná do iných jazykov vďaka našej úžasnej komunite. Ak chceš pomôcť s prekladom Kanky do tvojho jazyka, ozvi sa nám na :discord!', 'whats-new' => 'Čo je nové', ]; ================================================ FILE: lang/sk/front/community-votes.php ================================================ [], 'index' => [], 'latest' => [], 'show' => [], 'title' => 'Komunitné hlasovania', ]; ================================================ FILE: lang/sk/front/hall-of-fame.php ================================================ 'Sieň slávy', ]; ================================================ FILE: lang/sk/front/kb.php ================================================ [], 'show' => [], 'title' => 'Vedomostná databáza', ]; ================================================ FILE: lang/sk/front/newsletter.php ================================================ [ 'learn_more' => 'Zisti viac', 'subscribe' => 'Prihlásiť sa k odberu', ], 'fields' => [ 'firstname' => 'Meno', 'lastname' => 'Priezvisko', 'notifications' => 'Notifikácie', ], 'groups' => [ 'all' => 'Obdrž občas oznámenia o nových funkcionalitách, komunitných hlasovaniach, udalostiach, atď.', 'newsletter' => 'Newsletter', ], 'headline' => 'Prihlás sa k odberu jedného alebo viacerých našich newsletterov k obdržaniu aktuálnych informácií o Kanke.', 'title' => 'E-mailové novinky', ]; ================================================ FILE: lang/sk/front.php ================================================ [], 'actions' => [], 'campaigns' => [ 'public' => [ 'filters' => [ 'is-premium' => 'Toto je prémiová kampaň!', ], ], ], 'community' => [], 'contact' => [], 'cookie' => [ 'dismiss' => 'Rozumiem!', 'link' => 'Dozvedieť sa viac', 'message' => 'Táto webstránka používa cookies, aby pre teba zaistila najlepšiu možnú skúsenosť s ňou.', ], 'faq' => [], 'featured_campaigns' => [], 'features' => [ 'api' => [ 'link' => 'API dokument', ], 'patreon' => [ 'api_calls' => 'Vyšší počet API volaní (90)', 'boosts' => 'Boosty pre kampane', 'default_image' => 'Pekné prednastavené obrázky pre objekty', 'discord' => 'Privátny kanál na Discorde', 'free' => 'Zadarmo', 'hall_of_fame' => 'Meno v :link', 'impact' => 'Ovplyvnenie budúcich funkcií', 'monthly_vote' => 'Účasť na komunitných hlasovaniach', 'pagination' => 'Vyšší počet zobrazených objektov na stránke', 'upload_limit' => 'Veľkosti súborov', 'upload_limit_map' => 'Veľkosť nahraných máp', ], ], 'first_block' => [], 'footer' => [], 'goodbye' => [], 'help' => [], 'home' => [ 'seo' => [ 'meta-description' => 'Kanka je komunitou riadený nástroj na spravovanie RPG kampaní a tvorbu svetov, s ktorým sa dajú tieto jednoducho organizovať, plánovať a používať', ], ], 'master' => [], 'media' => [], 'menu' => [ 'dashboard' => 'Nástenka', 'login' => 'Login', 'register' => 'Registrácia', 'register_free' => 'Bezplatná registrácia', ], 'meta' => [ 'description' => 'Kanka je flexibilný digitálny nástroj na tvorbu svetov a spravovanie tvojej RPG kampane', 'title' => 'Kanka - nástroj na správu RPG kampaní a tvorbu svetov', ], 'partners' => [], 'pricing' => [ 'tier' => [ 'free' => 'Zadarmo', 'month' => 'Mesačne', ], ], 'privacy' => [], 'release' => [], 'roadmap' => [], 'second_block' => [], 'seo' => [ 'keywords' => 'Tvorba svetov, Hry na hrdinov, Správa RPG kampaní', ], 'team' => [], 'terms' => [], ]; ================================================ FILE: lang/sk/gallery.php ================================================ [ 'gallery' => 'Z galérie', 'url' => 'Nahrať obrázok z URL', ], 'browse' => [ 'layouts' => [ 'large' => 'Veľké náhľady', 'small' => 'Malé náhľady', ], 'search' => [ 'placeholder' => 'Hľadať obrázok v galérii', ], 'title' => 'Galéria', 'unauthorized' => 'Žiadna z tvojich rolí nemá povolenie na "prehliadanie galérie".', ], 'cta' => [ 'action' => 'Odomknúť viac úložného priestoru', 'helper' => 'Odomkni až do :size GiB úložného priesotru pre :premium-campaign.', 'title' => 'Úložisko plné', ], 'delete' => [ 'success' => '[0] Odstránených 0 prvkov|[1] Odstránený 1 prvok|{2,4} Odstránené :count prvky|{5,*} Odstránených :count prvkov', ], 'download' => [ 'errors' => [ 'copy_failed' => 'Našim serverom sa nepodarilo stiahnuť daný obrázok.', 'gallery_full_free' => 'Úložisko galérie je plné. Aktivuj prémiovú funkcionalitu pre zväčšenie priestoru.', 'gallery_full_premium' => 'Úložisko galérie je plné. Odstráň najprv niektoré zo súborov.', 'invalid_format' => 'Súbor nemá platný formát.', 'too_big' => 'Súbor je príliš veľký.', 'unauthorized' => 'Žiadna z tvojich rolí nemá povolenie na "nahrávanie do galérie".', ], ], 'file' => [ 'saved' => 'Uložené', ], 'filters' => [ 'only_unused' => 'Zobraziť iba nepoužívané súbory', ], 'move' => [ 'success' => '[0] Premiestnených 0 prvkov|[1] Premiestnený 1 prvok|{2,4} Premiestnené :count prvky|{5,*} Premiestnených :count prvkov', ], 'update' => [ 'home' => 'Domovský priečinok', 'success' => '[0] Aktualizovaných 0 prvkov|[1] Aktualizovaný 1 prvok|{2,4} Aktualizované :count prvky|{5,*} Aktualizovaných :count prvkov', ], ]; ================================================ FILE: lang/sk/general.php ================================================ 'Odznačiť všetky', 'no' => 'Nie', 'required' => 'Povinné', 'select_all' => 'Označiť všetky', 'success' => [ 'created' => ':name vytvorené.', 'deleted' => ':name odstránené.', 'deleted-cancel' => ':name odstránené. :cancel.', 'updated' => ':name aktualizované.', ], 'yes' => 'Áno', ]; ================================================ FILE: lang/sk/genres.php ================================================ 'Alternatívna história', 'cyberpunk' => 'Cyberpunk', 'fantasy' => 'Fantasy', 'historical' => 'Historická', 'many_worlds' => 'Viaceré svety', 'modern' => 'Moderná', 'occult' => 'Okultná', 'post_apocalyptic' => 'Postapokalyptická', 'pulp' => 'Pulp', 'science_fantasy' => 'Science Fantasy', 'science_fiction' => 'Science Fiction', 'space_opera' => 'Space Opera', 'steampunk' => 'Steampunk', 'superhero' => 'Superhrdinská', 'urban_fantasy' => 'Mestská fantasy', 'western' => 'Western', ]; ================================================ FILE: lang/sk/header.php ================================================ [ 'title' => 'Novinky z Kanky', ], 'notifications' => [ 'dismiss' => 'Odznačiť', 'no-unread' => 'Žiadne neprečítané oznámenia', 'read_all' => 'Označiť ako prečítané', ], 'toggle_navigation' => 'Prepnúť navigáciu', 'user' => [ 'settings' => 'Nastavenia', 'sign-out' => 'Odhlásiť sa', 'upgrade' => 'Upgrade', 'your-profile' => 'Tvoj profil', ], ]; ================================================ FILE: lang/sk/helpers.php ================================================ [], 'api-filters' => [ 'description' => 'Nasledujúce filtre sú dostupné pre koncový bod API :name.', 'title' => 'API filtre', ], 'attributes' => [ 'link' => 'Možnosti atribútu', ], 'calendar-widget' => [ 'info' => 'Prečo sa zobrazujú tieto pripomenutia?', 'title' => 'Kalendárový widget', ], 'dice' => [], 'entity_templates' => [], 'filters' => [ 'title' => 'Ako používať filtre', ], 'link' => [ 'description' => 'Prepojenia medzi objektami tvojej kampane môžeš vytvoriť jednoducho pomocou nasledujúcich skratiek.', ], 'map' => [], 'pins' => [], 'public' => 'Pozri si video, ktoré vysvetľuje verejné kampane, na YouTube.', 'troubleshooting' => [ 'description' => 'Člen tímu Kanky ťa poslal na túto stránku. Vyber si kampaň zo zoznamu a vygeneruj token, aby sme vedeli dočasne pristúpiť do tvojej kampane s rolou Admin.', 'errors' => [ 'token_exists' => 'Token pre :campaign už existuje.', ], 'save_btn' => 'Vygenerovať token', 'select_campaign' => 'Zvoľ kampaň', 'subtitle' => 'Prosím, pošlite pomoc!', 'success' => 'Prosím, skopíruj nasledujúci token a zašli ho niekomu z tímu Kanky.', 'title' => 'Riešenie problémov', ], 'widget-filters' => [ 'description' => 'Filtrovať zobrazované objekty vo widgete nedávno upravených je možné použitím zoznamu polí v objektoch a ich hodnôt. Napr. môžeš použiť :example na vyfiltrovanie mŕtvych postáv typu NPC.', 'link' => 'filter widgetu', 'title' => 'Filtre pre nástenkové widgety', ], ]; ================================================ FILE: lang/sk/history.php ================================================ [ 'show-old' => 'Zmeny', ], 'cta' => 'Zobraziť report všetkých nedávnych zmien v kampani.', 'empty' => 'Bez hodnoty', 'filters' => [ 'all-actions' => 'Všetky akcie', 'all-users' => 'Všetci členovia', 'no-results' => 'Žiadne výsledky na zobrazenie. Vyskúšaj iné filtre alebo sa sem vráť, keď niečo v kampani zmeníš.', ], 'helpers' => [ 'base' => 'Obrazovka ponúka prehľad nedávnych zmien v objektoch kampane až do :amount mesiacov, pričom najaktuálnejšie sa zobrazujú ako prvé.', 'changes' => 'Nasledujúce polia obsahovali predtým tieto hodnoty.', ], 'log' => [ 'create' => ':user vytvorili :entity', 'create_post' => ':user vytvorili poznámku ":post" v :entity', 'delete' => ':user zmazali :entity', 'delete_post' => ':user zmazali poznámku v :entity', 'reorder_post' => ':user zmenili poradie poznámok v :entity', 'restore' => ':user obnovili :entity', 'update' => ':user aktualizovali :entity', 'update_post' => ':user aktualizovali poznámku ":post" v :entity', ], 'title' => 'História', 'unknown' => [ 'entity' => 'neznámy objekt', ], ]; ================================================ FILE: lang/sk/items.php ================================================ [ 'creators' => [ 'action' => 'Aktivita pre tvorcov', 'remove' => 'Odstrániť všetkých tvorcov', ], ], 'create' => [ 'title' => 'Nový predmet', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'creators' => 'Tvorcovia', 'is_equipped' => 'Vo vybavení', 'price' => 'Cena', 'size' => 'Veľkosť', 'weight' => 'Váha', ], 'helpers' => [], 'hints' => [], 'index' => [], 'inventories' => [], 'lists' => [ 'empty' => 'Pridať zbrane, artefakty alebo dôležité predmety do tvojho sveta.', ], 'placeholders' => [ 'price' => 'Cena predmetu', 'size' => 'veľkosť, váha, rozmery', 'type' => 'zbraň, elixír, artefakt', 'weight'=> 'Váha predmetu', ], 'show' => [ 'tabs' => [ 'inventories' => 'Objekty', ], ], ]; ================================================ FILE: lang/sk/journals.php ================================================ [ 'title' => 'Nový záznam v denníku', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'author' => 'Autor', 'date' => 'Dátum', ], 'helpers' => [], 'index' => [], 'journals' => [], 'placeholders' => [ 'author' => 'Kto napísal tento záznam', 'date' => 'Reálny dátum tohto záznamu', 'type' => 'sedenie, one shot, návrh', ], 'show' => [], ]; ================================================ FILE: lang/sk/languages.php ================================================ [ 'ca' => 'Katalánčina', 'cs' => 'Čeština', 'de' => 'Nemčina', 'el' => 'Gréčtina', 'en' => 'Angličtina', 'en-US' => 'Americká angličtina', 'es' => 'Španielčina', 'fr' => 'Francúzština', 'gl' => 'Galícijčina', 'he' => 'Hebrejčina', 'hr' => 'Chorvátčina', 'hu' => 'Maďarčina', 'it' => 'Taliančina', 'nb' => 'Nórština (Bokmal)', 'nl' => 'Holandština', 'pl' => 'Poľština', 'pt-BR' => 'Brazílska portugalčina', 'ru' => 'Ruština', 'sk' => 'Slovenčina', 'tr' => 'Turečtina', ], 'header'=> 'Jazyky', ]; ================================================ FILE: lang/sk/locations.php ================================================ [], 'create' => [ 'title' => 'Nové miesto', ], 'destroy' => [], 'edit' => [], 'events' => [], 'families' => [], 'fields' => [ 'is_destroyed' => 'Zničené', ], 'helpers' => [ 'characters' => 'Zobraz všetky postavy na tomto mieste a jemu podradených miestach, alebo len tie priamo na tomto mieste.', ], 'hints' => [ 'is_destroyed' => 'Toto miesto je zničené.', ], 'index' => [], 'items' => [], 'journals' => [], 'locations' => [], 'map' => [], 'maps' => [], 'organisations' => [], 'panels' => [], 'placeholders' => [ 'type' => 'mesto, kráľovstvo, ruina', ], 'show' => [], ]; ================================================ FILE: lang/sk/maps/explore.php ================================================ [ 'enter-edit-mode' => 'Zapnúť režim úprav', 'exit-edit-mode' => 'Ukončiť režim úprav', 'finish-drawing' => 'Ukončiť kresbu mnohouholníka', ], 'notifications' => [ 'start-drawing' => 'Klikni na mapu, ak chceš začať kresliť mnohouholník', ], 'toggle' => 'Otvoriť/zatvoriť všetky skupiny', ]; ================================================ FILE: lang/sk/maps/groups.php ================================================ [ 'add' => 'Pridať novú skupinu', ], 'bulks' => [ 'delete' => '{1} Odstránená :count skupina.|[2,4] Odstránené :count skupiny.|[5,*] Odstránených :count skupín.', 'patch' => '{1} Aktualizovaná :count skupina.|[2,4] Aktualizované :count skupiny.|[5,*] Aktualizovaných :count skupín.', ], 'create' => [ 'success' => 'Skupina :name vytvorená.', 'title' => 'Nová skupina', ], 'delete' => [ 'success' => 'Skupina :name odstránená.', ], 'edit' => [ 'success' => 'Skupina :name aktualizovaná.', 'title' => 'Upraviť skupinu :name', ], 'fields' => [ 'is_shown' => 'Zobraziť značky skupiny', 'position' => 'Pozícia', ], 'helper' => [ 'amount_v3' => 'Značky môžu byť zoskupené pomocou mapových skupín. Kliknutím na skupinu je možné pri objavovaní na mape zobraziť alebo skryť všetky jej značky.', ], 'hints' => [ 'is_shown' => 'Aktivovaním sa značky skupiny zobrazia na mape automaticky.', ], 'index' => [ 'title' => 'Skupiny :name', ], 'pitch' => [], 'placeholders' => [ 'name' => 'Obchody, Poklady, NPC', 'position' => 'Nepovinné pole na nastavenie poradia zobrazenia skupín.', 'position_list' => 'Po :name', ], 'reorder' => [ 'save' => 'Uložiť nové poradie', 'success' => '{1} Preuskupená :count skupina.|[2,4] Preskupené :count skupiny.|[5,*] Preskupených :count skupín.', 'title' => 'Preskupiť skupiny', ], ]; ================================================ FILE: lang/sk/maps/layers.php ================================================ [ 'add' => 'Pridať novú vrstvu', ], 'base' => 'Základná vrstva', 'bulks' => [ 'delete' => '{1} Odstránená :count vrstva.|[2,4] Odstránené :count vrstvy.|[5,*] Odstránených :count vrstiev.', 'patch' => '{1} Aktualizovaná :count vrstva.|[2,4] Aktualizované :count vrstvy.|[5,*] Aktualizovaných :count vrstiev.', ], 'create' => [ 'success' => 'Vrstva :name vytvorená.', 'title' => 'Nová vrstva', ], 'delete' => [ 'success' => 'Vrstva :name odstránená.', ], 'edit' => [ 'success' => 'Vrstva :name aktualizovaná.', 'title' => 'Upraviť vrstvu :name', ], 'fields' => [ 'position' => 'Pozícia', 'type' => 'Typ vrstvy', ], 'helper' => [ 'amount_v2' => 'Nahraj vrstvy k mape, ak chceš zmeniť obrázok v pozadí značiek.', 'is_real' => 'Vrstvy nie sú dostupné, ak používaš OpenStreetMaps.', ], 'index' => [ 'title' => 'Vrstvy :name', ], 'pitch' => [], 'placeholders' => [ 'name' => 'Podsvetie, Úroveň 2, Vrak lode', 'position' => 'Nepovinné pole na nastavenie poradia zobrazenia vrstiev.', 'position_list' => 'Po :name', ], 'reorder' => [ 'save' => 'Uložiť nové poradie', 'success' => '{1} Preskupená :count vrstva.|[2,4] Preskupené :count vrstvy.|[5,*] Preskupených :count vrstiev.', 'title' => 'Preskupiť vrstvy', ], 'short_types' => [ 'overlay' => 'Overlay', 'overlay_shown' => 'Overlay (automatické zobrazenie)', 'standard' => 'Štandardné', ], 'types' => [ 'overlay' => 'Overlay (zobrazenie nad aktívnou vrstvou)', 'overlay_shown' => 'Overlay bude zobrazený štandardne', 'standard' => 'Štandardná vrstva (prepnúť medzi vrstvami)', ], ]; ================================================ FILE: lang/sk/maps/markers.php ================================================ [ 'entry' => 'Pre túto značku zapíš vlastné vstupné políčko.', 'remove' => 'Odstrániť značku', 'reset-polygon' => 'Resetovať umiestnenie', 'save_and_explore' => 'Uložiť a otvoriť', 'start-drawing' => 'Zapnúť kreslenie', 'update' => 'Upraviť značku', ], 'bulks' => [ 'delete' => '{1} Odstránená :count značka.|[2,4] Odstránené :count značky.|[5,*] Odstránených :count značiek.', 'patch' => '{1} Aktualizovaná :count značka.|[2,4] Aktualizované :count značky.|[5,*] Aktualizovaných :count značiek.', ], 'circle_sizes' => [ 'custom' => 'Vlastná', 'huge' => 'Obrovská', 'large' => 'Veľká', 'small' => 'Malá', 'standard' => 'Štandardná', 'tiny' => 'Drobná', ], 'create' => [ 'success' => 'Značka :name vytvorená.', 'title' => 'Nová značka', ], 'delete' => [ 'success' => 'Značka :name odstránená.', ], 'details' => [ 'from-entity' => 'Z objektu', ], 'edit' => [ 'success' => 'Značka :name aktualizovaná.', 'title' => 'Upraviť značku :name', ], 'fields' => [ 'bg_colour' => 'Farba pozadia', 'circle_radius' => 'Polomer kruhu', 'copy_elements' => 'Kopírovať prvky', 'custom_icon' => 'Vlastný symbol', 'custom_shape' => 'Vlastný tvar', 'font_colour' => 'Farba symbolu', 'group' => 'Skupina značky', 'icon' => 'Symbol', 'is_draggable' => 'Premiestniteľná', 'latitude' => 'Zemepisná šírka', 'longitude' => 'Zemepisná dĺžka', 'opacity' => 'Nepriehľadnosť', 'pin_size' => 'Veľkosť značky', 'polygon_style' => [ 'stroke' => 'Farba ťahu', 'stroke-opacity' => 'Nepriehľadnosť ťahu', 'stroke-width' => 'Hrúbka ťahu', ], 'popupless' => 'Bublina náhľadu', 'size' => 'Veľkosť', ], 'helpers' => [ 'base' => 'Pridaj značky na mapu kliknutím na hociktorý bod na nej.', 'copy_elements' => 'Kopírovať skupiny, vrstvy a značky.', 'copy_elements_to_campaign' => 'Kopírovať skupiny, vrstvy a značky na mapách. Značky prepojené s objektami budú konverované na štandardné značky.', 'custom_icon_v2' => 'Použi symboly z :fontawesome, :rpgawesome alebo vlastný SVG symbol. Zisti, ako na to v :docs.', 'custom_radius' => 'Vyber si vlastnú veľkosť z možností v menu, ak chceš definovať veľkosť.', 'draggable' => 'Aktivovaním umožníš premiestnenie značky v Prieskumníkovi.', 'is_popupless' => 'Zruší zobrazenie bubliny s náhľadom pri umiestnení kurzoru myši nad značkou.', 'label' => 'Popis sa zobrazuje ako odsek textu na mape. Jeho obsah bude názov značky daného objektu.', 'polygon' => [ 'edit' => 'Klikni na mapu, ak chceš pridať danú pozíciu medzi koordináty viacuholníka.', ], ], 'hints' => [ 'entry' => 'Uprav značku, ak jej chceš pridať vlastný záznam.', ], 'icons' => [ 'custom' => 'Vlastný', 'entity' => 'Objekt', 'exclamation' => 'Výkričník', 'marker' => 'Značka', 'question' => 'Otáznik', ], 'index' => [ 'title' => 'Značky :name', ], 'pitches' => [ 'poly' => 'Nakresli vlastné mnohouholníkové tvary, ktoré stvárňujú hranice alebo nepravidelné objekty.', ], 'placeholders' => [ 'custom_icon' => 'Vyskúšaj :example1 alebo :example2', 'custom_shape' => '100,100 200,240 340,110', 'name' => 'Povinný ak nie je zvolený žiaden objekt', ], 'presets' => [ 'helper' => 'Klikni na prednastavenie, aby sa načítalo, alebo vytvor nové.', ], 'shapes' => [ '0' => 'Kruh', '1' => 'Štvorec', '2' => 'Trojuholník', '3' => 'Vlastný', ], 'sizes' => [ '0' => 'Miniatúrny', '1' => 'Štandardný', '2' => 'Malý', '3' => 'Veľký', '4' => 'Obrovský', ], 'tabs' => [ 'circle' => 'Kruh', 'label' => 'Menovka', 'marker' => 'Značka', 'polygon' => 'Viacuholník', 'preset' => 'Prednastavenie', ], ]; ================================================ FILE: lang/sk/maps.php ================================================ [ 'back' => 'Späť na :name', 'edit' => 'Upraviť mapu', 'explore' => 'Prieskumník', ], 'create' => [ 'title' => 'Nová mapa', ], 'destroy' => [], 'edit' => [], 'errors' => [ 'chunking' => [ 'error' => 'Pri rozdeľovaní mapy na bloky nastala chyba. Prosím, obráť sa ohľadom pomoci na náš tím na :discord.', 'running' => [ 'edit' => 'Mapa počas rozdeľovania na bloky nemôže byť upravovaná.', 'explore' => 'Mapa počas rozdeľovania na bloky nemôže byť zobrazená.', 'time' => 'Môže to teraz trvať niekoľko minút až hodín, v závislosti od veľkosti mapy.', ], ], 'dashboard' => [ 'missing' => 'Táto mapa vyžaduje obrázok, aby mohla byť zobrazená na nástenke.', ], 'explore' => [ 'missing' => 'Na použitie Prieskumníka budeš musieť najprv pridať obrázok mapy.', ], ], 'fields' => [ 'center_marker' => 'Značka', 'center_x' => 'Štandardná zemepisná dĺžka', 'center_y' => 'Štandardná zemepisná šírka', 'centering' => 'Vystredniť', 'distance_measure' => 'Meranie vzdialenosti', 'distance_name' => 'Označenie mierky vzdialenosti', 'grid' => 'Mriežka', 'has_clustering' => 'Značka zhluku', 'initial_zoom' => 'Prvotné priblíženie', 'is_real' => 'Použiť OpenStreetMaps', 'max_zoom' => 'Maximálne priblíženie', 'min_zoom' => 'Minimálne priblíženie', 'tabs' => [ 'coordinates' => 'Koordináty', 'marker' => 'Značka', ], ], 'helpers' => [ 'center' => 'Zmenou týchto hodnôt vieš kontrolovať, na ktorú oblasť mapy bude zameraný náhľad. Ak hodnoty ponecháš prázdne, bude zameranie na stred mapy.', 'centering' => 'Vystrednenie značky bude prioritou pred štandardnými koordinátmi.', 'chunked_zoom' => 'Automaticky zhlukuj značky, keď sa nachádzajú blízko seba.', 'distance_measure' => 'Pridaním merania vzdialenosti sa aktivuje nástroj merania v Prieskumníkovi.', 'distance_measure_2' => 'Aby zodpovedalo 100 pixelov 1 km, zadaj hodnotu 0.0041.', 'grid' => 'Definuj veľkosť mriežky, ktorá sa zobrazí v Prieskumníkovi.', 'has_clustering' => 'Automaticky zhlukuj značky, keď sa nachádzajú blízko seba.', 'initial_zoom' => 'Úroveň prvotného priblíženia mapy, s ktorou sa zobrazí na začiatku. Štandardná hodnota je :default, pričom najvyššia povolená hodnota je :max a najnižšia :min.', 'is_real' => 'Použi toto nastavenie, ak chceš použiť mapu reálneho sveta namiesto nahraného obrázku mapy. Toto nastavenie deaktivuje vrstvy.', 'max_zoom' => 'Mapa môže byť priblížená maximálne na túto hodnotu. Štandardná hodnota je :default, najvyššia povolená hodnota je :max.', 'min_zoom' => 'Mapa môže byť oddialená maximálne na túto hodnotu. Štandardná hodnota je :default, najnižšia povolená hodnota je :max.', 'missing_image' => 'Na použitie vrstiev a značiek budeš musieť najprv pridať obrázok mapy.', ], 'index' => [], 'maps' => [], 'panels' => [ 'groups' => 'Skupiny', 'layers' => 'Vrstvy', 'legend' => 'Legenda', 'markers' => 'Značky', 'settings' => 'Nastavenia', ], 'placeholders' => [ 'center_marker' => 'Ponechaj prázdne, ak sa má mapa zobraziť nastred', 'center_x' => 'Ponechaj prázdne, ak sa má mapa zobraziť nastred', 'center_y' => 'Ponechaj prázdne, ak sa má mapa zobraziť nastred', 'distance_name' => 'km, míle, stopy, hamburgery', 'grid' => 'Vzdialenosť v pixloch medzi prvkami mriežky. Ponechaj prázdne, ak chceš mriežku vypnúť.', 'name' => 'Názov mapy', 'type' => 'Jaskyňa, Mesto, Galaxia', ], 'show' => [ 'tabs' => [ 'maps' => 'Mapy', ], ], 'tooltips' => [ 'chunking' => [ 'running' => 'Mapa sa rozdeľuje na bloky. Tento proces môže trvať niekoľko minút až hodín.', ], ], ]; ================================================ FILE: lang/sk/misc.php ================================================ [ 'member' => 'Staň sa členom.', 'remove_v5' => 'Kanka staviame iba dvaja. Podpor naše úsilie a užívaj si aplikáciu bez reklám za menej ako stojí fajnová kávička.', ], ]; ================================================ FILE: lang/sk/notes.php ================================================ [ 'title' => 'Nová poznámka', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'notes' => 'Podradená poznámka', ], 'helpers' => [], 'hints' => [], 'index' => [], 'lists' => [ 'empty' => 'Uchovávaj nápady, referencie, pravidlá alebo informácie o tom, čo nepasuje inam.', ], 'placeholders' => [ 'note' => 'Vybrať nadradenú poznámku', 'type' => 'náboženstvo, rasa, politický systém', ], 'show' => [], ]; ================================================ FILE: lang/sk/notifications.php ================================================ [ 'application' => [ 'approved' => 'Tvoja prihláška do kampane :campaign bola schválená.', 'approved_message' => 'Tvoja prihláška do kampane :campaign bola schválená. Správa: :reason', 'new' => 'Nová prihláška pre :campaign.', 'rejected' => 'Tvoja prihláška do kampane :campaign bola odmietnutá. Uvedený dôvod: :reason', 'rejected_no_message' => 'Tvoja prihláška do kampane :campaign bola odmietnutá.', ], 'asset_export' => 'Export materiálov kampane je dostupný. Link je dostupný na :time min.', 'boost' => [ 'add' => 'Kampaň :campaign bola boostnutá používateľom :user.', 'remove' => 'Kampaň :campaign už nie je boostovaná používateľom :user.', 'superboost' => ':user superboostol kampaň :campaign.', ], 'deleted' => 'Kampaň :campaign bola zmazaná.', 'export' => 'Export kampane je dostupný. Link je platný po dobu :time minút.', 'export_error' => 'Počas exportu tvojej kampane došlo k chybe. Prosím, kontaktuj nás, ak problém pretrváva.', 'hidden' => 'Kampaň :campaign je teraz skrytá a nezobrazuje sa na stránke verejných kampaní.', 'import' => [ 'failed' => 'Import kampane :campaign zlyhal.', 'success' => 'Import kampane :campaign skončil.', ], 'join' => ':user pristúpil do kampane :campaign.', 'leave' => ':user opustil kampaň :campaign.', 'plugin' => [ 'deleted' => 'Plugin :plugin bol odstránený z trhoviska a tvojej kampane :campaign.', ], 'premium' => [ 'add' => 'Prémiové funkcionality boli odomknuté pre kampaň :campaign užívateľom :user.', 'remove' => ':user už neposkytuje prémiové funkcionality pre kampaň :campaign.', ], 'removed-image' => 'Obrázok alebo hlavička :entity bola odstránená kvôli žiadosti na základe autorských práv.', 'role' => [ 'add' => 'Bola ti pridaná rola :role v kampani :campaign.', 'remove' => 'Bola ti odobraná rola :role v kampani :campaign.', ], 'troubleshooting' => [ 'joined' => 'Člen tímu Kanky :user pristúpil do kampane :campaign.', ], ], 'clear' => [ 'action' => 'Vymazať všetky', 'success' => 'Notifikácie vymazané.', 'title' => 'Vymazať notifikácie', ], 'features' => [ 'approved' => 'Tvoj nápad :feature bol schválený.', 'finished' => 'Tvoj nápad :feature je teraz dostupný v Kanke!', 'rejected' => 'Tvoj nápad :feature bol odmietnutý, dôvod: :reason.', ], 'header' => '{1} Máš :count notifikáciu.|[2,4] Máš :count notifikácie.|[5,*] Máš :count notifikácií.', 'index' => [ 'title' => 'Notifikácie', ], 'map' => [ 'chunked' => 'Mapa :name ukončila rozmieňanie a je teraz použiteľná.', ], 'no_notifications' => 'Aktuálne neexistujú žiadne notifikácie.', 'subscriptions' => [ 'charge_fail' => 'Pri spracovaní tvojej platby došlo k chybe. Prosím, počkaj chvíľu, zatiaľ čo sa o jej spracovanie opäť pokúšame. Ak sa nič nezmení, kontaktuj nás.', 'deleted' => 'Tvoje predplatné Kanky bolo zrušené po viacerých neúspešných pokusoch o žiadosť o platbu prostredníctvom tvojej karty. Prosím, uprav detaily platby v Nastaveniach predplatného.', 'ended' => 'Tvoje predplatné Kanky bolo ukončené. Tvoje boosty kampaní a roly na Discorde boli odstránené. Dúfame, že sa čoskoro zasa uvidíme!', 'failed' => 'Tvoje predplatné Kanky bolo zrušené po prekročení limitu pokusov o spracovanie platby. Prosím, prejdi na Nastavenia predplatného a skús zmeniť tvoje detaily platby.', 'started' => 'Tvoje predplatné Kanky bolo spustené.', 'trial' => 'Tvoja skúšobná doba Kanky vypršala. Dúfame, že sa ti páčila a veríme, že ťa uvidíme čoskoro opäť!', ], 'unread' => 'Nová notifikácia', ]; ================================================ FILE: lang/sk/onboarding/tags.php ================================================ 'NPC-čka', ]; ================================================ FILE: lang/sk/organisations.php ================================================ [ 'title' => 'Nová organizácia', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'is_defunct' => 'Nečinná', 'members' => 'Členovia', ], 'helpers' => [], 'hints' => [ 'is_defunct' => 'Táto organizácia už ukončila činnosť.', ], 'index' => [], 'lists' => [ 'empty' => 'Vytvor cechy, frakcie alebo tajné spolky, ktoré tvoria mocenské štruktúry tvojho sveta.', ], 'members' => [ 'actions' => [ 'add_multiple' => 'Pridať členov', ], 'create' => [ 'helper' => 'Pridať jedného alebo viacerých členov k :name.', 'success_multiple' => '{1} Pridaný :count člen k :name.|[2,4] Pridaní :count členovia k :name.|[5,*] Pridaných 5 členov k :name.', ], 'destroy' => [ 'success' => 'Člen odstránený z organizácie.', ], 'edit' => [ 'helper' => 'Zmeň stav členstva pre :name.', 'title' => 'Upraviť člena organizácie :name', ], 'fields' => [ 'parent' => 'Nadriadená osoba', 'pinned' => 'Pripnuté', 'role' => 'Rola', 'status' => 'Stav členstva', ], 'helpers' => [ 'all_members' => 'Všetky postavy, ktoré sú členmi tejto a podradených organizácií.', 'members' => 'Všetky postavy, ktoré sú členmi tejto organizácie', 'pinned' => 'Zvoľ, či toto členstvo má byť zobrazené v sekcii pripnutých v prehľade priradených objektov.', ], 'pinned' => [ 'both' => 'Oboje', 'none' => 'Žiadne', ], 'placeholders' => [ 'parent' => 'Kto je nadriadená osoba tohto člena?', 'role' => 'veliteľ, členka, veľkňaz, majsterka špiónov', ], 'status' => [ 'active' => 'Aktívne členstvo', 'inactive' => 'Neaktívne členstvo', 'unknown' => 'Neznámy stav', ], ], 'organisations' => [], 'placeholders' => [ 'type' => 'kult, gang, bunka revolúcie, fandom', ], 'show' => [], ]; ================================================ FILE: lang/sk/pagination.php ================================================ '« Predchádzajúca', 'next' => 'Nasledujúca »', ]; ================================================ FILE: lang/sk/partials.php ================================================ [ 'description' => 'Zadaná hodnota nebola správna.', 'title' => 'Ojojoj!', ], ]; ================================================ FILE: lang/sk/passwords.php ================================================ 'Heslo sa musí zhodovať a obsahovať najmenej šesť znakov.', 'reset' => 'Heslo bolo zmenené!', 'sent' => 'Pripomienka k zmene hesla bola odoslaná!', 'token' => 'Klúč pre obnovu hesla je neplatný.', 'user' => 'Nepodarilo sa nájsť používateľa s touto e-mailovou adresou.', ]; ================================================ FILE: lang/sk/patreon.php ================================================ [ 'elemental' => 'Elemental', 'goblin' => 'Goblin', 'kobold' => 'Kobold', 'owlbear' => 'Owlbear', 'wyvern' => 'Wyvern', ], ]; ================================================ FILE: lang/sk/permissions.php ================================================ [ 'delete' => 'Oprávnenie na zmazanie tohto prvku', 'edit' => 'Oprávnenie na úpravu tohto prvku', 'view' => 'Oprávnenie na zobrazenie tohto prvku', ], 'members' => [ 'inherited' => ':member má toto oprávnenie, lebo je súčasťou role :role.', ], 'roles' => [ 'inherited' => 'Role :role má toto oprávnenie na modul :module.', ], ]; ================================================ FILE: lang/sk/pins.php ================================================ 'Zisti viac o pripnutí v našej dokumentácii.', 'options' => [ 'no' => 'Nepripnuté', 'yes' => 'Pripnuté na stránke náhľadu objektu', ], ]; ================================================ FILE: lang/sk/playstyles.php ================================================ 'Voľný / Flexibilný', 'character-focused' => 'Zameraný na postavy', 'combat-focused' => 'Zameraný na boj', 'episodic-one-shot-friendly' => 'Epizodický / One-Shotový', 'exploration-focused' => 'Zameraný na objavovanie', 'linear-gm-led' => 'Lineárny / Vedený GM', 'long-term-campaign' => 'Dlhotrvajúca kampan', 'narrative-first' => 'Príbeh na prvom mieste', 'roleplay-heavy' => 'Zaťažený na Role-Play', 'roleplay-light' => 'Role-Play nie je nutný', 'rules-light' => 'Pravidlovo ľahký', 'sandbox-player-driven' => 'Sandbox / Hnaný hráčstvom', 'serious-immersive' => 'Vážny / Imerzívny', 'story-driven' => 'Hnaný príbehom', 'tactical-crunchy' => 'Taktický / Tabuľkový', ]; ================================================ FILE: lang/sk/post_layouts.php ================================================ 'Organizácie postavy', 'connection_map' => 'Mapa prepojení', 'helper' => 'Táto poznámka je nastavená, aby zobrazila podstránku :subpage objektu.', 'location_characters' => 'Postavy miesta', 'location_events' => 'Udalosti miest', 'location_quests' => 'Úlohy na mieste', 'pitch' => [ 'custom' => 'Zobraziť obsah pre podstránky tohto objektu priamo v prehľade s článkami. Napr. zobrazenie inventára.', 'title' => 'Rozšírené rozmiestnenie článku.', ], 'premium' => 'Niektoré možnosti rozmiestnenia sú neaktívne, pretože požadujú prémiovú kampaň.', 'quest_elements' => 'Časti úlohy', ]; ================================================ FILE: lang/sk/posts.php ================================================ [ 'title' => 'Nový komentár', ], 'fields' => [ 'name' => 'Názov', ], 'helpers' => [ 'new' => 'Pridaj nový komentár k tomuto objektu.', ], 'placeholders' => [ 'name' => 'Názov komentára', ], 'position' => [ 'dont_change' => 'Nezmeniť', 'first' => 'Prvý', 'last' => 'Posledný', ], ]; ================================================ FILE: lang/sk/presets.php ================================================ [ 'create' => 'Vytroviť nové prednastavenie', ], 'create' => [ 'success' => 'Prednastavenie :name vytvorené.', 'title' => 'Nové prednastavenie', ], 'destroy' => [ 'success' => 'Prednastavenie :name zničené.', ], 'edit' => [ 'success' => 'Prednastavenie :name upravené.', 'title' => 'Upraviť prednastavenie :name', ], 'fields' => [ 'name' => 'Názov prednastavenia', ], 'lists' => [ 'empty' => 'V kampani sa aktuálne nenachádzajú žiadne prednastavenia.', ], 'placeholders' => [ 'name' => 'Zvolený názov', ], ]; ================================================ FILE: lang/sk/profiles.php ================================================ [], 'avatar' => [ 'success' => 'Avatar aktualizovaný.', ], 'campaign_switcher_order_by' => [], 'edit' => [ 'success' => 'Profil upravený', ], 'editors' => [], 'fields' => [ 'avatar' => 'Avatar', 'bio' => 'Životopis', 'email' => 'E-mail', 'hide_subscription' => 'Skryť moje meno zo :hall_of_fame.', 'last_login_share' => 'Zdieľaj s ostatnými členmi kampane tvoj posledný čas online.', 'link' => 'Sociálne siete', 'login_sharing' => 'Posledné zdieľanie prihlásenia', 'name' => 'Meno', 'new_password' => 'Nové heslo (voliteľné)', 'new_password_confirmation' => 'Potvrdiť nové heslo', 'newsletter' => 'Prajem si, aby ste ma niekedy kontaktovali mailom.', 'password' => 'Súčasné heslo', 'profile-name' => 'Profilové meno', 'pronouns' => 'Zámená', 'settings' => 'Nastavenia', 'subscription_hiding' => 'Predplatné ukryté', 'theme' => 'Téma', ], 'helpers' => [ 'link' => 'Zmeň spôsob, akým sa sociálne siete zobrazujú na tvojom :profile a na :marketplace. Ak ponecháš prázdne, nebudú sa zobrazovať.', 'profile-name' => 'Zmeň, ako vyzerá tvoje meno na tvojom :profile a :marketplace. Ak ho ponecháš prázdne, bude sa používať meno tvojho konta.', 'pronouns' => 'Zmeň spôsob, akým sa zobrazujú zámená na tvojom :profile a na :marketplace. Ak ponecháš prázdne, nebudú sa zobrazovať.', ], 'link' => [ 'button' => 'Profil :name na sociálnych sieťach', ], 'newsletter' => [ 'helpers' => [ 'header' => 'Prihlás sa na odoberanie e-mailových newsletterov, nech vieš, čo sa s Kankou deje.', ], 'options' => [ 'monthly' => 'Kanka newsletter', ], 'title' => 'Newsletter', 'updated' => 'Nastavenia newsletteru aktualizované.', ], 'password' => [ 'success' => 'Heslo zmenené', ], 'placeholders' => [ 'bio' => 'Krátky životopis alebo informácia o tebe, ktorá sa zobrazí na verejnom profile.', 'email' => 'Tvoja e-mailová adresa', 'name' => 'Tvoje meno, ako sa bude zobrazovať', 'new_password' => 'Tvoje nové heslo', 'new_password_confirmation' => 'Potvrď tvoje nové heslo', 'password' => 'Zadaj tvoje aktuálne heslo', ], 'sections' => [ 'dangerzone' => 'Nebezpečná zóna', 'delete' => [ 'confirm' => 'Áno, odstráň moje konto', 'delete' => 'Odstrániť moje konto', 'goodbye' => 'Ak to chceš, prepíš :code do políčka nižšie.', 'helper' => 'Odstránenie tvojho konta odstráni aj všetky kampane, ktorých si jediným členom. Táto akcia je trvalá a nemôže byť vrátená späť.', 'subscribed' => 'Prosím, ukonči tvoje preplatné, aby bolo možné odstrániť tvoje konto.', 'title' => 'Odstránenie môjho konta', 'warning' => 'Odstránením tvojho konta sa odstránia aj všetky tvoje údaje. Chceš to naozaj urobiť?', ], 'password' => [ 'title' => 'Zmena hesla', ], ], 'settings' => [ 'helpers' => [ 'bio' => 'Životopis je viditeľný na tvojom :link.', 'profile' => 'verejnom profile', ], 'success' => 'Nastavenia zmenené.', ], 'theme' => [ 'success' => 'Téma zmenená.', 'themes' => [ 'dark' => 'Dark', 'default' => 'Štandard', 'future' => 'Future', 'midnight' => 'Midnight Blue', ], ], 'title' => 'Úprava profilu', 'workflows' => [ 'created' => 'Prejsť na vytvorený objekt', 'default' => 'Zoznam objektov', ], ]; ================================================ FILE: lang/sk/quests.php ================================================ [ 'title' => 'Nová úloha', ], 'destroy' => [], 'edit' => [], 'elements' => [ 'create' => [ 'success' => 'Objekt :entity pridaný k úlohe.', 'title' => 'Nový prvok pre :name', ], 'destroy' => [ 'success' => 'Prvok úlohy :entity odstránený.', ], 'edit' => [ 'success' => 'Prvok úlohy :entity aktualizovaný.', 'title' => 'Aktualizovať prvok úlohy pre :name', ], 'fields' => [ 'copy_entity_entry' => 'Použiť popis objektu', 'entity_or_name' => 'Zvoľ buď objekt kampane, alebo pomenuj tento prvok.', ], 'helpers' => [ 'copy_entity_entry' => 'Zobraziť prepojený text objektu namiesto vlastného popisu.', ], 'placeholders' => [ 'name' => 'Názov prvku', ], ], 'fields' => [ 'copy_elements' => 'Kopírovať objekty priradené úlohám', 'date' => 'Dátum', 'element_role' => 'Rola', 'instigator' => 'Podnet od', 'is_completed' => 'Splnená', 'location' => 'Štartovacie miesto', 'role' => 'Rola', 'status' => 'Stav', ], 'helpers' => [ 'is_completed' => 'Daná úloha je považovaná za splnenú.', 'status' => 'Aktuálny stav danej úlohy.', ], 'hints' => [ 'is_abandoned' => 'Úloha bola opustená.', 'is_completed' => 'Úloha je splnená.', 'is_ongoing' => 'Plnenie úlohy prebieha.', ], 'index' => [], 'lists' => [ 'empty' => 'Vytvor úlohy pre zaznamenávanie cieľov, príbehových liniek alebo motivácií postáv.', ], 'placeholders' => [ 'date' => 'Reálny dátum zadania úlohy', 'entity' => 'Názov prvku v úlohe', 'location' => 'Štartovacie miesto úlohy', 'role' => 'Rola objektu v úlohe', 'type' => 'príbeh postavy, bočná úloha, hlavný dej', ], 'show' => [ 'actions' => [ 'add_element' => 'Pridať prvok', ], 'tabs' => [ 'elements' => 'Prvky', ], ], 'status' => [ 'abandoned' => 'Opustená', 'completed' => 'Splnená', 'not_started' => 'Nezačatá', 'ongoing' => 'Prebiehajúca', ], ]; ================================================ FILE: lang/sk/races.php ================================================ [], 'create' => [ 'title' => 'Nový druh', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'members' => 'Príslušníci', ], 'helpers' => [], 'hints' => [ 'is_extinct' => 'Tento druh vyhynul.', ], 'index' => [], 'lists' => [ 'empty' => 'Definuj druh, kultúru alebo ľud, ktorý obýva tvoj svet.', ], 'members' => [ 'create' => [ 'helper' => 'Pridaj jednu alebo viac postáv k :name.', 'submit' => 'Pridaj príslušníkov', 'success' => '{0} Nebol pridaný žiaden príslušník.|{1} Bol pridaný 1 príslušník.|[2,4] :count príslušníci boli pridaní.|[5,*] :count príslušníkov bolo pridaných.', 'title' => 'Nový príslušníci', ], ], 'placeholders' => [ 'type' => 'Človek, Fey, Borg', ], 'races' => [], 'show' => [], ]; ================================================ FILE: lang/sk/redirects.php ================================================ 'Čas vášho pripojenia vypršal. Prosím, opätovne sa prihláste.', 'unknown_entity' => 'Prepáč, nevieme, čo je ":entity".', ]; ================================================ FILE: lang/sk/releases.php ================================================ [ 'event' => 'Udalosť', 'livestream' => 'Livestream', 'other' => 'Iné', 'release' => 'Verzia', 'vote' => 'Komunitné hlasovanie', ], 'index' => [ 'description' => 'Posledné novinky o kanka.io', 'title' => 'Verzie', ], 'post' => [ 'footer' => 'Od :name :date', ], 'show' => [ 'return' => 'Späť na Verzie', 'title' => 'Verzia :name', ], ]; ================================================ FILE: lang/sk/rpg_systems.php ================================================ [ '0' => 'D&D', '1' => 'Pathfinder', '10'=> 'GURPS', '11'=> 'DSA', '12'=> 'Chronicles of Darkness', '13'=> 'Powered by the Apocalypse', '2' => 'Stars Without Numbers', '3' => 'Savage Worlds', '4' => 'Dungeon World', '5' => 'Genesys', '6' => 'Starfinder', '7' => 'Exalted', '8' => 'Shadowrun', '9' => 'Fate', ], 'systems' => [ 'dnd5' => 'D&D 5e', ], ]; ================================================ FILE: lang/sk/search/fulltext.php ================================================ 'Prehľadávajú sa objekty, poznámky, atribúty a ďalšie ohľadom slova :term.', 'title' => 'Fulltextové vyhľadávanie', ]; ================================================ FILE: lang/sk/search.php ================================================ 'Prehľadať všetko', 'lookup' => [ 'empty' => 'Bez výsledkov', 'hint' => 'Napíš min. 3 písmená na hľadanie objektov v kampani.', 'keyboard' => 'stlač :k na hľadanie, :esc na zrušenie', 'lists' => 'Zoznamy', 'recents' => 'Nedávne', 'results' => 'Výsledky', ], 'no_results' => 'Bez výsledkov.', 'placeholder' => 'HĽADAJ', 'placeholders' => [ 'entry' => 'Hľadať záznam', ], 'preview' => [ 'links' => 'Linky', 'no-connections' => 'Žiadne pripnuté vzťahy na zobrazenie', ], 'title' => 'Hľadať', ]; ================================================ FILE: lang/sk/seo.php ================================================ 'Nástenka kampane :campaign', 'entity-list' => 'Preskúmaj :module kampane :campaign', ]; ================================================ FILE: lang/sk/settings/account.php ================================================ 'Kontrola tvojho mailu, hesla, bezpečnosti a ďalších nastavení konta.', 'title' => 'Informácie o konte', ]; ================================================ FILE: lang/sk/settings/appearance.php ================================================ [ 'learn-more' => 'Zisti viac o tomto nastavení v našej dokumentácii.', 'save' => 'Uložiť nastavenia', ], 'campaign-switcher' => [ 'alphabetical' => 'Abecedne (A-Z)', 'date_created' => 'Dátum vytvorenia (od najstaršieho)', 'date_joined' => 'Dátum prístupu (od najstaršieho)', 'r_alphabetical' => 'Abecedne (Z-A)', 'r_date_created' => 'Dátum vytvorenia (od najnovšieho)', 'r_date_joined' => 'Dátum prístupu (od najnovšieho)', ], 'dismissible' => [ 'main' => 'Kontroluj, ako Kanka vyzerá a pôsobí. Vedz ale, že kampane môžu tieto nastavenia prepísať.', ], 'editors' => [ 'default' => 'Štandard (:name)', 'legacy' => 'Odkaz (:name)', ], 'explore' => [ 'grid' => 'Mriežka (štandard)', 'table' => 'Tabuľka', ], 'fields' => [ 'campaign-order' => 'Poradie kampaní', 'date-format' => 'Formát dátumu', 'editor' => 'Textový editor', 'entity-explore' => 'Zoznamy objektov', 'mentions' => 'Referencie pri úpravách', 'new-entity-workflow' => 'Postup pri tvorbe objektu', 'pagination' => 'Výsledky na stránke', 'theme' => 'Preferencia témy', ], 'helpers' => [ 'advanced-mentions' => 'Keď sa edituje text, môžeš zvoliť, či sa referencie budú zobrazovať ako názov alebo rozšírená referencia.', 'campaign-order' => 'Zmeň poradie, v ktorom sa zobrazujú kampane pri ich výbere.', 'date-format' => 'V miestach, kde sa zobrazujú, môžeš zvoliť formát dátumu skutočného sveta.', 'entity-explore' => 'Zvoľ spôsob, akým sú zoznamy objektov zobrazované v kampaniach.', 'new-entity-workflow' => 'Zvoľ, kam sa automaticky dostaneš po vytvorení nového objektu.', 'overridable' => 'Jednotlivé kampane môžu toto nastavenie prepísať.', 'pagination' => 'Pre zoznamy, ktoré presahujú jednu stranu, môžeš definovať počet viditeľných objektov.', 'theme' => 'Zvoľ výzor Kanky.', ], 'mentions' => [ 'advanced' => 'Zobrazovať rozšírené referencie :code', 'default' => 'Referencie v podobe názvu objektu', ], 'nested' => [], 'success' => 'Nastavenia výzoru uložené.', 'values' => [ 'pagination' => ':amount položiek na stránke', 'pagination-sub' => ':amount (dostupné pre predplatiteľov)', ], ]; ================================================ FILE: lang/sk/settings/boosters.php ================================================ [ 'boost_name' => 'Boost :name', ], 'available' => 'Dostupné boosty :amount/:total', 'benefits' => [ 'boosted' => 'Boostnutie kampane s :one booster odomyká prístup k :marketplace, štýlovaniu, nahrávaniu väčších súborov pre všetkých členov, obnovovanie zmazaných objektov a :more.', 'more' => 'ďalšie úžasné funkcionality.', 'superboosted' => 'Superboostnutie kampane s :amount boostami odomyká všetky výhody boostnutej kampane, ako aj galériu kampane, plné reporty zmien, ktoré boli robené na objektoch, a :more.', ], 'boost' => [ 'actions' => [ 'confirm' => 'Boostni to!', 'remove' => 'Ukončiť boostnutie :campaign', 'subscribe' => 'Predplatiť Kanku', 'upgrade' => 'Aktualizovať tvoje predplatné', ], 'confirm' => 'Ako vzrušujúce! Ideš boostnuť :campaign. Toto priradí jeden (:cost) z dostupných boostov kampani.', 'duration' => 'Priradené boosty ostávajú priradené, dokiaľ ich manuálne neodstrániš, alebo dokiaľ predplatné neskončí.', 'errors' => [ 'boosted' => 'Och, zdá sa, že :campaign je už boostnutá!', 'out-of-boosters' => 'Och nie! Nemáš už dostatočný počet boostov. Máš :available a potrebuješ :cost. Buď ukonči boostnutie inej kampane, alebo :upgrade.', ], 'pitch' => 'Zakúp si predplatné a odomkni boostnutie kampaní.', 'success' => 'Kampaň :campaign je teraz boostnutá. Užívaj všetky úžasné funkcionality!', 'title' => 'Boostnuť :campaign', 'upgrade' => 'Aktualizovať tvoje predplatné', ], 'campaign' => [ 'boosted' => 'Boostnuté :user od :time', 'premium' => 'Za Prémium ďakujeme :user od :time', 'standard' => 'Štandard', 'superboosted' => 'Superboostnuté :user od :time', 'unboosted' => 'Neboostnuté', ], 'intro' => [ 'anyone' => 'Nemusíš len boostnuť kampane, ktoré sú vytvorené tebou. Môžeš boostnuť hociktorú kampaň, ktorej si súčasťou alebo ktorá je viditeľná. Toto zahŕňa kampane, v ktorých hrávaš, alebo :public, ktoré sa ti páčia.', 'data' => 'Ak kampaň prestane byť boostnutá, prístup k boostnutým funkciám je odobratý. Ale žiaden obsah sa je odstránený, takže boostnutie kampane v budúcnosti ti ho opäť sprístupní.', 'first' => 'Rozšírené funkcie sa odomykajú priradením boostov na boostnutie alebo superboostnutie kampane. Počet boostov, ktoré máš, je dané tvojím :subscription. Toto číslo je dostupné zakaždým, kým máš predplatné. Boostnutie kampane priradí tejto jeden z tvojich boostov, zatiaľ čo superboostnutie požaduje boosty tri.', ], 'pitch' => [ 'benefits' => [ 'backup' => 'Obnov predtým odstránený objekt spred :amount dní', 'customisable' => 'Úplná možnosť úpravy vizuálu kampane', 'icons' => 'Prístup k tisíckam nádherných ikoniek pre mapy a časové osy', 'title' => 'Boostnuté kampane získajú', 'upload' => 'Väčšie veľkosti nahrávaných súborov pred všetkých členov', ], 'description' => 'Priraď boosty ku kampaniam a pomôž im odomknúť úžasné funkcionality pred všetkých zúčastnených. Nestačí ti boostnutie kampane? Tak potom pre teba máme možnosť kampaň superboostnuť!', 'more' => 'Spoznaj celý zoznam benefitov na stránke :boosters.', 'title' => 'Posuň kampaň na vyššiu úroveň s možnosťou vlastných úprav a výhod pre všetkých jej členov.', ], 'ready' => [ 'available' => 'Tvoje dostupné kampaňové boosty.', 'pricing' => 'Všetky tvoje úrovne predplatného obsahujú aspoň jeden boost kampane a začínajú na :amount mesačne.', 'pricing-amount' => ':amount :currency', 'title' => 'Boostnutie kampane', ], 'superboost'=> [ 'actions' => [ 'confirm' => 'Superboostni to!', 'instead' => 'Superboostni za :count!', 'remove' => 'Ukončiť superboostnutie :campaign', ], 'confirm' => 'Ako vzrušujúce! Ideš superboostnuť :campaign. Toto priradí tri (:cost) z dostupných boostov kampani.', 'errors' => [ 'boosted' => 'Och, zdá sa, že :campaign je už superboostnutá!', ], 'success' => 'Kampaň :campaign je teraz superboostnutá. Užívaj všetky úžasné funkcionality!', 'title' => 'Superboostnuť :campaign', 'upgrade' => 'Priprav sa na ultimátne využitie Kanky! Superboostnutie :campaign jej priradí :cost dodatočné kampaňové boosty.', ], 'title' => 'Kampaňové boosty', 'unboost' => [ 'confirm' => 'Áno, určite', 'status' => [ 'boosting' => 'boostnutá', 'superboosting' => 'superboostnutá', ], 'success' => 'Kampaň :campaign už nie je boostnutá a tvoje boosty sú opäť dostupné.', 'title' => 'Ukončiť boost kampane', 'warning' => 'Naozaj chceš ukončiť :action :campaign? Týmto uvoľníš tvoje priradené boosty a skryje sa všetok obsah a funkcionality, ktoré boli uvoľnené vďaka benefitom boostnutia.', ], ]; ================================================ FILE: lang/sk/settings/premium.php ================================================ [ 'remove' => 'Odstrániť prémium', 'unlock' => 'Získaj prémium', ], 'create' => [ 'actions' => [ 'confirm' => 'Získaj prémium!', ], 'confirm' => 'Vzrušujúce! Ideš odomknúť prémiové funkcionality pre :campaign. Bude na to použité jedno prémium.', 'duration' => 'Prémiové kampane ostávajú dokiaľ ich manuálne neodstrániš alebo kým neskončí tvoje predplatné.', 'success' => 'Kampaň :campaign je teraz prémiová. Užívaj jej nové úžasné funkcionality!', ], 'exceptions' => [ 'already' => 'Prémiové funkcionality boli už pre túto kampaň odomknuté.', 'out-of-stock' => 'Nemáš dostatok prémia, aby bolo možné odomknúť túto kampaň. Odober prémiový status z nejakej kampane alebo :upgrade.', ], 'pitch' => [ 'description' => 'Pridaj prémium kampaniam a pomôž odomknúť úžasné funkcionality pre každého v nich.', 'title' => 'Prémiové kampane získavajú', ], 'ready' => [ 'available' => 'Tvoje dostupné prémiové nastavenia.', 'pricing' => 'Všetky naše predplatné úrovne obsahujú min. 1 prémiovú kampaň a sú dostupné od :amount mesačne.', 'pricing-amount' => ':amount :currency', 'title' => 'Získaj prémium', ], 'remove' => [ 'confirm' => 'Áno, naozaj', 'cooldown' => 'Prémiové funkcie z :campaign môžu byť odstránené po :date.', 'success' => 'Prémiové funkcionality boli odobraté z kampane :campaign. Teraz môžeš odomknúť prémiové funkcionality pre inú kampaň.', 'title' => 'Odobrať prémiové funkcionality', 'warning' => 'Naozaj chceš odobrať prémiové funkcionality z :campaign? Toto ti umožní odomknúť inú kampaň a skryť všetok obsah spojený s prémiovými funkcionalitami dokiaľ sa jej prémiový status neobnoví.', ], ]; ================================================ FILE: lang/sk/settings.php ================================================ [ '2fa' => [ 'actions' => [ 'disable' => 'Deaktivovať dvojstupňové overenie identity', 'disable-confirm' => 'Potvrď ešte jedným klikom', 'finish' => 'Dokončiť nastavenie a prihlásiť sa', ], 'activation_helper' => 'Na dokončenie nastavenia dvojstupňového overenia identity tvojho konta nasleduj tieto inštrukcie.', 'disable' => [ 'helper' => 'Ak chceš deaktivovať dvojstupňové overenie identity, klikni na tlačidlo nižšie. Nezabudni, že toto ponechá tvoje konto zraniteľné v prípade, že niekto pozná tvoje prihlasovacie údaje.', 'title' => 'Deaktivovať dvojstupňové overenie identity', ], 'enable_instructions' => 'Ak chceš spustiť aktivačný proces, vygeneruj tvoj autentifikačný QR kód a zoskenuj ho do aplikácie Google Authenticator (:ios, :android) alebo inej podobnej autentifikačnej aplikácie.', 'enabled' => 'Dvojstupňové overenie identity je aktuálne pre tvoje konto aktivované.', 'error_enable' => 'Nesprávny kód, skús znovu.', 'fields' => [ 'otp' => 'Zadaj jednorazové heslo poskytnuté autentifikačnou aplikáciou.', 'qrcode' => 'Zoskenuj nasledujúci QR kód tvojou autentifikačnou aplikáciou na vygenerovanie jednorazového hesla.', ], 'generate_qr' => 'Generovať QR kód', 'helper' => 'Dvojstupňové overenie identity posilňuje bezpečnosť prístupu požadovaním dvoch metód (stupňov) na overenie identity pri každom prihlásení.', 'learn_more' => 'Dozveď sa viac o dvojstupňovom overení identity.', 'social' => 'Dvojstupňové overenie identity v Kanke je aktívne iba pre osoby, ktoré sa prihlasujú pomocou ich e-mailu a hesla. Zmeň si metódu prihlasovania v tvojom konte predtým, ako aktivuješ toto nastavenie.', 'success_disable' => 'Dvojstupňové overenie identity úspešne deaktivované.', 'success_enable' => 'Dvojstupňové overenie identity úspešne aktivované. Prosím prihlás sa opäť na dokončenie nastavení.', 'success_key' => 'Tvoj QR kód bol úspešne vygenerovaný. Prosím dokonči nastavenie pre aktiváciu dvojstupňového overenia identity.', 'title' => 'Dvojstupňové overenie identity', ], 'actions' => [ 'social' => 'Prepnúť na prihlásenie do Kanky', 'update_email' => 'Aktualizovať e-mail', 'update_password' => 'Aktualizovať heslo', ], 'email' => 'Zmeniť e-mail', 'email_success' => 'E-mail bol aktualizovaný.', 'password' => 'Zmeniť heslo', 'password_success' => 'Heslo bolo aktualizované.', 'social' => [ 'error' => 'Pre toto konto už používaš prihlásenie v Kanke.', 'helper' => 'Tvoje konto je teraz spravované :provider. Môžeš ho prestať používať a prepnúť na štandardné prihlásenie pomocou Kanky nastavením hesla.', 'success' => 'Tvoje konto teraz používa prihlásenie v Kanke.', 'title' => 'Konto cez sociálnu sieť', ], 'title' => 'Konto', ], 'api' => [ 'helper' => 'Vitaj v API Kanky. Vytvor si Osobný prístupový žetón, ktorý budeš používať v tvojich požiadavkách na API s cieľom získať informácie o kampaniach, ku ktorým patríš.', 'link' => 'Čítať API dokumentáciu', 'title' => 'API', ], 'apps' => [ 'actions' => [ 'connect' => 'Pripojiť', 'remove' => 'Odstrániť', ], 'benefits' => 'Kanka poskytuje niekoľko integrácií so službami tretích strán. Široká integrácia s aplikáciami tretích strán je plánovaná v budúcnosti.', 'discord' => [ 'confirm' => 'Naozaj chceš odpojiť svoje konte z Discordu? Toto odstráni aj role, ktoré máš nastavené.', 'errors' => [ 'add' => 'Pri prepojení tvojho Discord účtu s Kankou sa vyskytla chyba. Prosím, skús to ešte raz.', ], 'success' => [ 'add' => 'Tvoje Discord konto bolo prepojené.', 'remove' => 'Tvoje Discord konto bolo odpojené.', ], 'text' => 'Pristupuj automaticky k tvojej roli predplatného.', 'unlock' => 'Odblokovať roly v Discorde', ], 'title' => 'Integrácia aplikácie', ], 'billing' => [ 'placeholder' => 'Ak by bolo potrebné doplniť na potvrdenia dodatočné info alebo daňové informácie (firemná adresa, IČ DPH a pod.), vlož ich nižšie a zobrazia sa na všetkých tvojich potvrdenkách.', 'save' => 'Uložiť platobné informácie', 'title' => 'Platobné informácie', ], 'boost' => [ 'exceptions' => [ 'already_boosted' => 'Kampaň :name už je boostnutá.', 'exhausted_boosts' => 'Nemáš už žiadne boosty na rozdávanie. Odstráň najprv boost od existujúcej kampane pred priradením inej.', 'exhausted_superboosts' => 'Došli ti boosty. Na superboostnutie kampane potrebuješ 3 boosty.', ], ], 'countries' => [ 'austria' => 'Rakúsko', 'belgium' => 'Belgicko', 'france' => 'Francúzsko', 'germany' => 'Nemecko', 'italy' => 'Taliansko', 'netherlands' => 'Holandsko', 'spain' => 'Španielsko', ], 'invoices' => [], 'layout' => [ 'title' => 'Schéma', ], 'marketplace' => [], 'menu' => [ 'account' => 'Konto', 'api' => 'API', 'appearance' => 'Vzhľad', 'apps' => 'Apps', 'boosters' => 'Boosty', 'notifications' => 'Upozornenia', 'other' => 'Iné', 'patreon' => 'Patreon', 'payment_options' => 'Možnosti platby', 'personal_settings' => 'Osobné nastavenia', 'premium' => 'Prémiové kampane', 'profile' => 'Profil', 'settings' => 'Nastavenia', 'subscription' => 'Predplatné', 'subscription_status' => 'Stav predplatného', ], 'patreon' => [ 'deprecated' => 'Zastaralá funkcionalita - Ak chceš podporiť Kanku, urob tak cez :subscription. Prepojenie na Patreon je ešte stále aktívne pre osoby, ktoré nás podporili predtým, než sme z neho odišli.', 'pledge' => 'Úroveň: :name', 'remove' => [ 'button' => 'Zrušiť prepojenie s Patreonom', 'success' => 'Prepojenie s tvojím Patreon kontom bolo zrušené.', 'text' => 'Ak zrušíš prepojenie tvojho Patreon konta s Kankou, stratíš tvoje bonusy, meno v sieni slávy, boosty pre kampane a iné funkcionality získané vďaka podpore Kanky. Nestratíš ale žiaden obsah (napr. záhlavia objektov). Ak si nás neskôr zasa predplatíš, prístup k dátam sa ti obnoví, vrátane možnosti boostnuť predtým boostnuté kampane.', 'title' => 'Zrušiť prepojenie Patreon konta s Kankou', ], 'title' => 'Patreon', ], 'profile' => [ 'actions' => [ 'update_profile' => 'Aktualizovať profil', ], 'avatar' => 'Profilový obrázok', 'success' => 'Profil aktualizovaný.', 'title' => 'Osobný profil', ], 'referrals' => [ 'title' => 'Odporúčania', ], 'subscription' => [ 'actions' => [ 'cancel_sub' => 'Ukončiť predplatné', 'subscribe' => 'Predplatiť', 'update_currency' => 'Uložiť preferovanú menu', ], 'billing' => [ 'helper' => 'Tvoje platobné údaje sú spracované a uložené bezpečne na :stripe. Túto platobnú metódu používame pre všetky platby predplatného.', 'saved' => 'Uložený spôsob platby', ], 'cancel' => [ 'grace' => [ 'text' => 'Tvoje predplatné bude končiť k :date. Po danom dátume sa tvoje prémiové kampane vrátia do štandardnej formy a ostatné výhody spojené s podporou Kanky sa stanú neaktívne.', 'title' => 'Kulantná doba', ], 'options' => [ 'competitor' => 'Prechádzam ku konkurencii', 'financial' => 'Moja finančná situácia sa zmenila', 'missing_features' => 'Chýbajú mi funkcionality', 'not_for' => 'Predplatné nie je pre mňa', 'not_playing' => 'Už sa nehrá alebo je kampaň pozastavená.', 'not_using' => 'Aktuálne Kanku nevyužívam', 'other' => 'Iné', 'testing' => 'Iba testujem Kanku', ], 'text' => 'Ľutujeme, že odchádzaš! Zrušením tvojho predplatného ostáva toto aktívne do ďalšieho platobného obdobia, po ktorom stratíš tvoje boosty kampaní a ostatné výhody vďaka podpore Kanky. Vyplnením následného formulára nám pomôžeš zistiť, čo by sme mali robiť lepšie, alebo čo ťa viedlo k tomuto rozhodnutiu.', 'title' => 'Zrušiť predplatné', ], 'cancelled' => 'Tvoje predplatné bolo zrušené. Môžeš ho obnoviť, keď ti aktívne predplatné skončí.', 'change' => [ 'text' => [ 'downgrade_monthly' => 'Downgraduješ na úroveň :tier za :downgrade, takže mesačne bude splatných :amount.', 'downgrade_yearly' => 'Downgraduješ na úroveň :tier za :downgrade, takže ročne bude splatných :amount.', 'monthly' => 'Máš predplatenú úroveň :tier, splatnú mesačne vo výške :amount.', 'upgrade_monthly' => 'Upgradeuješ na úroveň :tier za :upgrade, takže bude mesačne splatných :amount.', 'upgrade_paypal' => 'Upgradeuješ na úroveň :tier za :upgrade do :date.', 'upgrade_yearly' => 'Upgradeuješ na úroveň :tier za :upgrade, takže bude ročne splatných :amount.', 'yearly' => 'Máš predplatenú úroveň :tier, splatnú ročne vo výške :amount.', ], 'title' => 'Zmeniť úroveň predplatného', ], 'coupon' => [ 'check' => 'Skontrolovať promo kód', 'invalid' => 'Neplatný promo kód.', 'label' => 'Promo kód', 'percent_off' => 'Tvoje prvé ročné predplatné bude zlacnené o :percent%!', ], 'currencies' => [ 'brl' => 'BRL', 'eur' => 'EUR', 'usd' => 'USD', ], 'currency' => [ 'title' => 'Zmeň tebou preferovanú menu', ], 'errors' => [ 'callback' => 'Náš spracovateľ platieb nám nahlásil chybu. Prosím, skús ešte raz alebo nás kontaktuj, ak problém pretrváva.', 'failed' => 'Aktuálne evidujeme problémy s naším platobným systémom. Ak potrebuješ pomôcť, kontaktuj nás na :email.', 'subscribed' => 'Tvoje predplatné sa nám nepodarilo spracovať. Stripe nám poskytlo nasledujúcu informáciu prečo.', ], 'fields' => [ 'active_since' => 'Aktívne od', 'active_until' => 'Aktívne do', 'billing' => 'Zúčtovanie', 'currency' => 'Mena zúčtovania', 'payment_method' => 'Spôsob platby', 'plan' => 'Súčasná úroveň', 'reason' => 'Dôvod', 'reset' => 'Resetovať informáciu platby', 'reset_billing' => 'Chápem, že zmenou meny stratím históriu platieb a budem vyžiadaný zadať môj spôsob platby znovu.', ], 'helpers' => [ 'alternatives' => 'Zaplať za tvoje predplatné pomocou :method. Tento spôsob platby nebude automaticky obnovený na konci tvojho predplatného. :method je iba dostupný v eurách.', 'alternatives-2' => 'Zaplať za tvoje predplatné pomocou :method. Toto je jednorázová platba a nebude automaticky obnovená po skončení tvojho predplatného.', 'alternatives_warning' => 'Aktualizácia predplatného týmto spôsobom nie je možná. Prosím, vytvor nové predplatné, keď tvoje súčasné skončí.', 'alternatives_yearly' => 'Kvôli obmedzeniam ohľadom opakovaných platieb, :method je dostupný len pre ročné zúčtovanie.', 'currency_block' => 'Nie je možné zmeniť menu dokiaľ máš aktívne predplatné Kanky, svoju menu môžeš zmeniť po tom, čo tvoje predplatné skončí.', 'currency_reset' => 'Zmena tvojej meny odstráni históriu tvojich platieb a bude nutné zadať spôsob platby ešte raz.', 'paypal_v3' => 'Zaplať tvoje ročné predplatné bezpečne PayPalom.', 'stripe' => 'Tvoje platobné údaje sú spracované a uložené bezpečne prostredníctvom :stripe.', ], 'manage_subscription' => 'Spravovať predplatné', 'payment_method' => [ 'actions' => [ 'add' => 'Pridať', 'add_new' => 'Pridať nový spôsob platby', 'change' => 'Zmeniť spôsob platby', 'save' => 'Uložiť spôsob platby', 'show_alternatives' => 'Alternatívne možnosti platby', ], 'add_one' => 'Aktuálne nemáš uložený žiadny spôsob platby.', 'alternatives' => 'Predplatné môžeš zaplatiť aj týmito alternatívnymi platobnými možnosťami. Tvoje konto bude jednorázovo zaťažené a predplatné nebude automaticky predĺžené na konci mesiaca.', 'card' => 'Karta', 'card_name' => 'Meno na karte', 'country' => 'Krajina bydliska', 'ending' => 'Platná do', 'helper' => 'Táto karta bude použitá na všetky tvoje predplatné.', 'new_card' => 'Pridať nový spôsob platby', 'saved' => ':brand končiac na :last4', ], 'periods' => [ 'monthly' => 'Mesačne', 'yearly' => 'Ročne', ], 'placeholders' => [ 'downgrade_reason' => 'Alternatívne nám daj vedieť, prečo znižuješ úroveň tvojho predplatného.', 'reason' => 'Alternatívne nám daj vedieť, prečo už nepodporuješ Kanku. Chýbala ti nejaká funkcionalita? Zmenila sa tvoja finančná situácia?', ], 'plans' => [ 'cost_monthly' => ':amount :currency účtovaných mesačne', 'cost_yearly' => ':amount :currency účtovaných ročne', ], 'sub_status' => 'Informácie o predplatnom', 'subscription' => [ 'actions' => [ 'cancel' => 'Zrušiť predplatné', 'downgrading' => 'Prosím, kontaktuj nás ohľadom zníženia úrovne', 'rollback' => 'Zmeniť na Kobolda', 'subscribe' => 'Zmeniť na :tier mesačný', 'subscribe_annual' => 'Zmeniť na :tier ročný', ], ], 'success' => [ 'alternative' => 'Tvoja platba bola zaregistrovaná. Obdržíš oznámenie akonáhle bude spracovaná a tvoje predplatné aktívne.', 'callback' => 'Úspešne predplatené. Tvoje konto bude čoskoro aktualizované akonáhle nás spracovateľ platieb informuje o zmene (môže to pár minút trvať).', 'currency' => 'Nastavenie preferovanej meny bolo aktualizované.', 'subscribed' => 'Úspešne predplatené. Nezabudni sa pridať do newsletteru Komunitných hlasovaní, aby sme ťa mohli informovať, keď bude hlasovanie otvorené. Nastavenie newsletteru si môžeš zmeniť v tvojom profile.', ], 'tiers' => 'Úrovne predplatného', 'trial_period' => 'Ročné predplatné má 14-dňovú skúšobnú lehotu. Kontaktuj nás prostredníctvom :email, ak vypovieš tvoje ročné predplatné a požaduješ vrátenie peňazí.', 'upgrade_downgrade' => [ 'button' => 'Informácie o zmene úrovne predplatného', 'cancel' => [ 'bullets' => [ 'bonuses' => 'Tvoje bonusy ostanú aktívne do konca platobného obdobia.', 'boosts' => 'To isté sa stane aj tvojim boostnutým kampaniam. Výhody boostnutia sa stanú neviditeľnými, ale nebudú odstránené, ak kampaň prestane byť boostnutá.', 'kobold' => 'Ak chceš zrušiť tvoje predplatné, zmeň úroveň na Kobolda.', 'premium' => 'To isté sa stane aj tvojim prémiovým kampaniam. Výhody prémia sa stanú neviditeľnými, ale nebudú odstránené, ak kampaň prestane byť prémiová.', ], 'title' => 'Čo obnáša zrušenie predplatného', ], 'downgrade' => [ 'bullets' => [ 'end' => 'Tvoja aktuálna úroveň ostáva aktívna do konca aktuálneho platobného obdobia. Potom bude znížená na novú úroveň.', ], 'provide_reason' => 'Ak sa dá, daj nám prosím vedieť, prečo znižuješ úroveň tvojho predplatného.', 'title' => 'Pri prechode na nižšiu úroveň', ], 'upgrade' => [ 'bullets' => [ 'immediate' => 'Vybraný spôsob platby bude okamžite zaťažený a hneď budeš mať prístup k novej úrovni.', 'prorate' => 'Ak sa ti úroveň zvýši z Owlbear na Elemental, budeš musieť zaplatiť len rozdiel k vyššej úrovni.', ], 'title' => 'Pri prechode na vyššiu úroveň', ], ], 'warnings' => [ 'incomplete' => 'Nepodarilo sa nám zaťažiť tvoju platobnú kartu. Prosím, aktualizuj tvoje platobné údaje karty a my sa o to pokúsime opäť o pár dní. Ak to nebude možné, tvoje predplatné bude zrušené.', 'patreon' => 'Tvoje konto je aktuálne prepojené s Patreonom. Prosím, odstráňte prepojenie v nastaveniach tvojho :patreon konta predtým, než zmeníš tvoje predplatné v Kanke.', ], ], ]; ================================================ FILE: lang/sk/sidebar.php ================================================ [ 'count' => 'Člen v :member', 'created_campaigns' => 'Tvoje kampane', 'follow_more' => 'Nájsť kampane', 'followed_campaigns'=> 'Sledované kampane', 'new_campaign' => 'Nová kampaň', 'public_campaigns' => 'Verejné kampane', 'reorder' => 'Preusporiadať', 'updated' => 'Upravené', ], 'dashboard' => 'Nástenka', 'entity-creator' => 'Rýchle vytvorenie', 'gallery' => 'Galéria', 'game' => 'Hra', 'other' => 'Ostatné', 'recent' => 'Posledné zmeny', 'relations' => 'Vzťahy', 'settings' => 'Nastavenia', 'time' => 'Čas', 'world' => 'Svet', ]; ================================================ FILE: lang/sk/starter.php ================================================ [ 'name' => ':user - Svet', ], 'character1' => [ 'age' => '[20. / 30. / 40. roky]', 'background' => [ 'cur' => 'Aktuálne [povolanie/rola]', 'loc' => 'Dospievanie v [rodnom meste/regióne]', 'seeking' => 'Hľadá [cieľ/motiváciu]', ], ], 'character2' => [], 'item1' => [], 'kingdom1' => [], 'kingdom2' => [], 'note1' => [], ]; ================================================ FILE: lang/sk/subscription.php ================================================ [ 'main' => 'Predplať si Kanku, aby sa ti odstránili reklamy, odomkli väčšie uploady pre obrázky, :boosters and :more. Na spracovanie platieb používame :stripe, takže na našich serveroch sa neukladá ani nimi neprechádza žiadna informácia o platobných kartách.', 'more' => 'ďalšie úžasné výhody', ], 'errors' => [ 'grace' => 'Tvoje aktuálne predplatné skončí :date, po tomto dátume si ho budeš môcť opäť kúpiť.', 'invalid_card_country' => [ 'brl' => 'Je nám ľúto, ale platby v BRL akceptujeme len pri platbách brazílskymi kreditnými kartami. Ak si myslíš, že sa jedná o chybu, napíš nám na :email.', ], 'invalid_currency' => 'Tvoje predchádzajúce predplatné bolo v :old, čím nebolo možné uzatvoriť predplatné v :new. Prosím, zmeň si menu na :old alebo nás kontaktuj na :email, ak si chceš zmeniť platobnú menu.', ], ]; ================================================ FILE: lang/sk/subscriptions/promos.php ================================================ [ 'inactive' => 'Táto akcia už nie je aktívna.', 'invalid' => 'Neznáma akcia', 'only-new' => 'Táto akcia je dostupná iba pre nových predplatiteľov.', ], ]; ================================================ FILE: lang/sk/subscriptions/renew.php ================================================ [ 'renew' => 'Obnoviť predplatné', ], 'helper' => 'Máš ale možnosť vybrať si nové predplatné, aby výhody plynúce z neho neboli nijak ovplyvnené.', 'title' => 'Obnovenie predplatného', ]; ================================================ FILE: lang/sk/subscriptions.php ================================================ [ 'failed' => 'Aplikácia Stripe nevedela zmeniť tvoj spôsob platby. Tvoje predplatné bolo preto zrušené.', ], ]; ================================================ FILE: lang/sk/tags.php ================================================ [ 'actions' => [ 'add' => 'Pridať novú kategóriu', 'add_entity' => 'Pridať nový objekt', ], 'create' => [ 'attach_success' => '{1} :count objekt pridaný do kategórie :name.|[2,4] :count objekty pridané do kategórie :name.|[5,*] :count objektov pridaných do kategórie :name.', 'attach_success_entity' => 'Kategórie pre :name úspešne pridané.', 'entity' => 'Pridať kategórie k :name', ], ], 'create' => [ 'title' => 'Nová kategória', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'children' => 'Podradené kategórie', 'is_auto_applied' => 'Automaticky nastaviť pre nové objekty', 'is_hidden' => 'Skryté v záhlaví a náhľade', ], 'helpers' => [ 'no_children' => 'Aktuálne nemá túto kategóriu pridelený žiaden objekt.', 'no_posts' => 'Aktuálne nemá túto kategóriu pridelená žiadna poznámka.', ], 'hints' => [ 'children' => 'Tento zoznam obsahuje všetky objekty priamo pod touto kategóriou a jej podriadenými kategóriami.', 'is_auto_applied' => 'Aktivuj toto nastavenie, ak chceš, aby bola táto kategória automaticky pri novo vytvorených objektoch.', 'is_hidden' => 'Ak zaškrtneš túto možnosť, táto kategória sa nezobrazí v záhlaví ani náhľade objektu.', 'tag' => 'Zobrazené sú všetky kategórie, ktoré sú tejto priamo podriadené.', ], 'index' => [], 'placeholders' => [ 'type' => 'mýtus, vojna, historická udalosť, náboženstvo, vexilológia', ], 'show' => [ 'tabs' => [ 'children' => 'Podradené kategórie', ], ], 'tags' => [], 'transfer' => [ 'fail' => 'Nepodarilo sa presunúť objekty z :tag na :newTag', 'fail_post' => 'Nepodarilo sa presunúť ponámky z :tag na :newTag', 'success' => 'Presun objektov z :tag na :newTag úspešný', 'success_post' => 'Presun poznámok z :tag na :newTag úspešný', 'transfer' => 'Presun', ], ]; ================================================ FILE: lang/sk/teams.php ================================================ [ 'lead' => 'Tvorba svetov - zábavná a spoľahlivá', 'translations' => 'Preklady', ], 'leads' => [ 'translators' => 'Kanka je prekladaná do niekoľkých jazykov vďaka týmto úžasným prispievajúcim osobám.', ], 'people'=> [ 'itzamna' => [ 'title' => 'Juniorský vývojár', ], 'jay' => [ 'title' => 'Zakladateľ a hlavný vývojár', ], 'jon' => [ 'title' => 'Spoluzakladateľ a obchodný manažér', ], 'kaz' => [ 'title' => 'Ničiteľ bugov', ], 'laura' => [ 'title' => 'Sociálne médiá', ], ], ]; ================================================ FILE: lang/sk/tiers.php ================================================ [ 'pay' => [ 'monthly' => 'Mesačná platba', 'save' => '2 mesiace zadarmo', 'yearly' => 'Ročná platba', ], 'subscribe' => [ 'choose' => 'Zvoľ :tier', 'monthly' => ':tier mesačne', 'yearly' => ':tier ročne', ], ], 'current' => 'Tvoje aktuálne predplatné', 'features' => [ 'api_requests' => ':amount API požiadavok/min', 'boosters' => 'Boosty pre kampaň', 'discord' => 'Roly v Discorde', 'feature_influence' => 'Vplyv na nové funkcie', 'file_size' => ':size Veľkosť nahrávaných súborov', 'import' => 'Import kampane', 'nice_image' => 'Štandardné obrázky objektov', 'no_ads' => 'Bez reklamy', 'pagination' => ':amount max. objektov v zoznamoch', 'roadmap' => 'Zahlasuj za nápady v pláne', ], 'periods' => [ 'billed_monthly' => 'účtované mesačne', 'billed_yearly' => 'účtované ročne', ], 'pricing' => ':currency :amount / mesiac', 'ribbons' => [ 'best-value' => 'Odporúčané pre GMs', 'current' => 'Aktuálne predplatné', ], 'target' => [ 'elemental' => 'Pre profesionálnu tvorbu svetov, viacero epických prostredí a rozsiahle kampane', 'owlbear' => 'Perfektné pre jednotlivú tvorbu sveta, ktorý sa má stať úžasným miestom pre hlavnú kampaň', 'wyvern' => 'Ideálne pre osoby vedúce hry s viacerými dobrodružstvami alebo spolupracujúce na príbehu', ], 'toggle' => [], ]; ================================================ FILE: lang/sk/timelines/elements.php ================================================ [ 'copy_with_name' => 'Kopírovať rozšírenú referenciu s názvom objektu', 'success' => 'Rozšírená referencia objektu bola skopírovaná do schránky.', ], 'create' => [ 'success' => 'Prvok pridaný na časovú os.', 'title' => 'Nový prvok na časovej osi', ], 'delete' => [ 'success' => 'Prvok :name odstránený.', ], 'edit' => [ 'success' => 'Prvok aktualizovaný.', 'title' => 'Upraviť prvok na časovej osi', ], 'fields' => [ 'date' => 'Dátum', 'era' => 'Obdobie', 'icon' => 'Symbol', 'use_entity_entry' => 'Zobraziť záznam priradeného objektu nižšie. Text tohto prvku bude zobrazený ako prvý, ak nejaký existuje.', 'use_event_date' => 'Použiť dátum prepojenej udalosti', ], 'helpers' => [ 'date' => 'Ak je prvok prepojený s objektom udalosti, zobrazí sa dátum tejto udalosti.', 'entity_is_private' => 'Objekt tohto prvku je súkromný.', 'icon' => 'Skopíruj HTML kód nejakého symbolu z :fontawesome alebo :rpgawesome.', 'is_collapsed' => 'Prvok sa zobrazuje štandardne zbalený.', ], 'placeholders' => [ 'date' => 'napr. 42. marec alebo 1332-1337', 'name' => 'Vyžadované, ak nie je vybraný žiaden objekt', 'position' => 'Pozícia v zozname prvkov pre dané obdobie. Ponechaj prázdnu, ak ju chceš pridať nakoniec.', ], 'warning' => [], ]; ================================================ FILE: lang/sk/timelines/eras.php ================================================ [ 'add' => 'Pridať nové obdobie', ], 'bulks' => [ 'delete' => '{0} Odstránených :count období.|{1} Odstránené :count obdobie.|[2,4] Odstránené :count obdobia.|[5,*] Odstránených :count období.', ], 'create' => [ 'success' => 'Obdobie :name vytvorené.', 'title' => 'Nové obdobie', ], 'delete' => [ 'success' => 'Obdobie :name odstránené.', ], 'edit' => [ 'success' => 'Obdobie :name aktualizované.', 'title' => 'Upraviť obdobie :name', ], 'fields' => [ 'abbreviation' => 'Skratka', 'end_year' => 'Koniec (rok)', 'is_collapsed' => 'Zbalené', 'start_year' => 'Začiatok (rok)', ], 'helpers' => [ 'eras' => 'Pred pridávaním období musíš vytvoriť časovú os.', 'is_collapsed' => 'Obdobia sú štandardne zbalené (minimalizované).', 'primary' => 'Rozdeľ svoju časovú os na obdobia. Časová os vyžaduje min. jedno obdobie, aby správne fungovala.', ], 'index' => [ 'title' => 'Obdobia časovej osi :name', ], 'placeholders' => [ 'abbreviation' => 'pred n.l., po Kr., AD', 'end_year' => 'Rok, kedy končí daný vek. Ponechaj prázdny, ak je to aktuálny vek.', 'name' => 'Novovek, Doba bronzová, Galaktické vojny', 'start_year' => 'Rok, kedy začína daný vek. Ponechaj prázdny, ak je to prvý vek.', ], 'reorder' => [], ]; ================================================ FILE: lang/sk/timelines.php ================================================ [ 'add_element' => 'Pridať prvok k obdobiu :era', 'back' => 'Späť k :name', 'save_order' => 'Uložiť nové poradie', ], 'create' => [ 'title' => 'Nová časová os.', ], 'destroy' => [], 'edit' => [], 'fields' => [ 'copy_elements' => 'Kopírovať prvky', 'copy_eras' => 'Kopírovať obdobie', 'eras' => 'Obdobia', 'reverse_order' => 'Otočiť poradie období', ], 'helpers' => [ 'no_era_v2' => 'Táto časová os aktuálne nemá žiadne obdobia. Pridaj do nej jedno alebo viacero období, potom budeš do nich môcť pridať ďalšie privky.', 'reverse_order' => 'Aktivovaním zobrazíš obdobia v spätnom chronologickom poradí (najstaršie obdobie ako prvé).', ], 'index' => [], 'lists' => [ 'empty' => 'Vytvor vizuálnu časovú os k zaznamenaniu nejdôležitejších udalostí a toho, ako sa mení tvoj svet.', ], 'placeholders' => [ 'type' => 'Primárna, Kronika sveta, Osud kráľovstva', ], 'reorder' => [ 'empty' => 'Pridaj viac období a prvkov do časových osí, aby bolo možné meniť poradie v nich.', 'success' => ':name úspešne preskupená.', 'title' => 'Preskupiť :name', ], 'show' => [ 'tabs' => [ 'reorder-elements' => 'Preskupiť prvky', ], ], 'timelines' => [], ]; ================================================ FILE: lang/sk/tiptap.php ================================================ 'Zdieľaj spätnú väzbu', 'survey'=> 'Skúšaš nový editor? :share (zaberie ti to len 2 minúty)', ]; ================================================ FILE: lang/sk/users/profile.php ================================================ [ 'wordsmith' => 'Skutočný vládca pera! Víťaz worldbuildingovej súťaže.', ], 'fields' => [ 'achievements' => 'Úspechy', 'banned' => 'Tento užívateľ bol zablokovaný', 'entities_created' => 'Objektov vytvorených :help :count', 'member_since' => 'Členstvo od :date', 'public_campaigns' => 'Verejné kampane', 'subscriber_since' => 'Predplatné od :date', ], 'helpers' => [ 'entities_created' => 'Táto hodnota sa každý deň prepočítava.', ], 'title' => 'Profil :name', ]; ================================================ FILE: lang/sk/validation.php ================================================ ':Attribute musí byť akceptovaný.', 'active_url' => ':Attribute má neplatnú URL adresu.', 'after' => ':Attribute musí byť dátum po :date.', 'after_or_equal' => ':Attribute musí byť dátum po alebo presne :date.', 'alpha' => ':Attribute môže obsahovať len písmená.', 'alpha_dash' => ':Attribute môže obsahovať len písmená, čísla a pomlčky.', 'alpha_num' => ':Attribute môže obsahovať len písmená, čísla.', 'array' => ':Attribute musí byť pole.', 'before' => ':Attribute musí byť dátum pred :date.', 'before_or_equal' => ':Attribute musí byť dátum pred alebo presne :date.', 'between' => [ 'numeric' => ':Attribute musí mať rozsah :min - :max.', 'file' => ':Attribute musí mať rozsah :min - :max kilobajtov.', 'string' => ':Attribute musí mať rozsah :min - :max znakov.', 'array' => ':Attribute musí mať rozsah :min - :max prvkov.', ], 'boolean' => ':Attribute musí byť pravda alebo nepravda.', 'confirmed' => ':Attribute konfirmácia sa nezhoduje.', 'date' => ':Attribute má neplatný dátum.', 'date_equals' => ':Attribute musí byť dátum rovnajúci sa :date.', 'date_format' => ':Attribute sa nezhoduje s formátom :format.', 'different' => ':Attribute a :other musia byť odlišné.', 'digits' => ':Attribute musí mať :digits číslic.', 'digits_between' => ':Attribute musí mať rozsah :min až :max číslic.', 'dimensions' => ':Attribute má neplatné rozmery obrázku.', 'distinct' => ':Attribute je duplicitný.', 'email' => ':Attribute má neplatný formát.', 'ends_with' => 'The :attribute must end with one of the following: :values', 'exists' => 'označený :attribute je neplatný.', 'file' => ':Attribute musí byť súbor.', 'filled' => ':Attribute je požadované.', 'gt' => [ 'numeric' => 'Hodnota :attribute musí byť väčšia ako :value.', 'file' => ':Attribute musí mať viac kilobajtov ako :value.', 'string' => ':Attribute musí mať viac znakov ako :value.', 'array' => ':Attribute musí mať viac prvkov ako :value.', ], 'gte' => [ 'numeric' => 'Hodnota :attribute musí byť väčšia alebo rovná ako :value.', 'file' => ':Attribute musí mať rovnaký alebo väčší počet kilobajtov ako :value.', 'string' => ':Attribute musí mať rovnaký alebo väčší počet znakov ako :value.', 'array' => ':Attribute musí mať rovnaký alebo väčší počet prvkov ako :value.', ], 'image' => ':Attribute musí byť obrázok.', 'in' => 'označený :attribute je neplatný.', 'in_array' => ':Attribute sa nenachádza v :other.', 'integer' => ':Attribute musí byť celé číslo.', 'ip' => ':Attribute musí byť platná IP adresa.', 'ipv4' => ':Attribute musí byť platná IPv4 adresa.', 'ipv6' => ':Attribute musí byť platná IPv6 adresa.', 'json' => ':Attribute musí byť platný JSON reťazec.', 'lt' => [ 'numeric' => 'Hodnota :attribute musí byť menšia ako :value.', 'file' => ':Attribute musí mať menej kilobajtov ako :value.', 'string' => ':Attribute musí mať menej znakov ako :value.', 'array' => ':Attribute musí mať menej prvkov ako :value.', ], 'lte' => [ 'numeric' => 'Hodnota :attribute musí byť menšia alebo rovná ako :value.', 'file' => ':Attribute musí mať rovnaký alebo menší počet kilobajtov ako :value.', 'string' => ':Attribute musí mať rovnaký alebo menší počet znakov ako :value.', 'array' => ':Attribute musí mať rovnaký alebo menší počet prvkov ako :value.', ], 'max' => [ 'numeric' => ':Attribute nemôže byť väčší ako :max.', 'file' => ':Attribute nemôže byť väčší ako :max kilobajtov.', 'string' => ':Attribute nemôže byť väčší ako :max znakov.', 'array' => ':Attribute nemôže mať viac ako :max prvkov.', ], 'mimes' => ':Attribute musí byť súbor s koncovkou: :values.', 'mimetypes' => ':Attribute musí byť súbor s koncovkou: :values.', 'min' => [ 'numeric' => ':Attribute musí mať aspoň :min.', 'file' => ':Attribute musí mať aspoň :min kilobajtov.', 'string' => ':Attribute musí mať aspoň :min znakov.', 'array' => ':Attribute musí mať aspoň :min prvkov.', ], 'not_in' => 'označený :attribute je neplatný.', 'not_regex' => ':Attribute má neplatný formát.', 'numeric' => ':Attribute musí byť číslo.', 'present' => ':Attribute musí byť odoslaný.', 'regex' => ':Attribute má neplatný formát.', 'required' => ':Attribute je požadované.', 'required_if' => ':Attribute je požadované keď :other je :value.', 'required_unless' => ':Attribute je požadované, okrem prípadu keď :other je v :values.', 'required_with' => ':Attribute je požadované keď :values je prítomné.', 'required_with_all' => ':Attribute je požadované ak :values je nastavené.', 'required_without' => ':Attribute je požadované keď :values nie je prítomné.', 'required_without_all' => ':Attribute je požadované ak žiadne z :values nie je nastavené.', 'same' => ':Attribute a :other sa musia zhodovať.', 'size' => [ 'numeric' => ':Attribute musí byť :size.', 'file' => ':Attribute musí mať :size kilobajtov.', 'string' => ':Attribute musí mať :size znakov.', 'array' => ':Attribute musí obsahovať :size prvkov.', ], 'starts_with' => ':Attribute musí začínať niektorou z hodnôt: :values', 'string' => ':Attribute musí byť reťazec znakov.', 'timezone' => ':Attribute musí byť platné časové pásmo.', 'unique' => ':Attribute už existuje.', 'uploaded' => 'Nepodarilo sa nahrať :attribute.', 'url' => ':Attribute musí mať formát URL.', 'uuid' => ':Attribute musí byť platné UUID.', /* |-------------------------------------------------------------------------- | Custom Validation Language Lines |-------------------------------------------------------------------------- | | Here you may specify custom validation messages for attributes using the | convention "attribute.rule" to name the lines. This makes it quick to | specify a specific custom language line for a given attribute rule. | */ 'custom' => [ 'attribute-name' => [ 'rule-name' => 'custom-message', ], ], /* |-------------------------------------------------------------------------- | Custom Validation Attributes |-------------------------------------------------------------------------- | | The following language lines are used to swap attribute place-holders | with something more reader friendly such as E-Mail Address instead | of "email". This simply helps us make messages a little cleaner. | */ 'attributes' => [ ], ]; ================================================ FILE: lang/sk/visibilities.php ================================================ [ 'admin' => 'Iba admini kampane môžu vidieť tento prvok.', 'admin-self' => 'Iba ty a admini kampane môžu vidieť tento prvok.', 'all' => 'Každý môže vidieť tento prvok.', 'members' => 'Iba členstvá tejto kampane môžu vidieť tento objekt.', 'self' => 'Iba ty môžeš vidieť tento prvok.', ], 'picker' => [ 'admin' => 'Viditeľné iba pre členstvo role :admin', 'admin-self' => 'Viditeľné iba pre teba a členstvo role :admin.', 'all' => 'Hocikto, kto vidí objekt :entity, vidí aj toto.', 'failed' => 'Nastavenie sa nedalo aktualizovať.', 'member' => 'Viditeľné iba pre členstvo kampane. Vhodné pre verejné kampane.', 'self' => 'Viditeľné iba pre teba.', ], 'title' => 'Aktualizujem viditeľnosť', 'toast' => 'Viditeľnosť úspešne aktualizovaná.', 'tooltip' => 'Klikni sem, ak sa chceš dozvedieť viac o rozličných možnostiach viditeľnosti.', ]; ================================================ FILE: lang/vendor/backup/ar/notifications.php ================================================ 'مهم: حدث خطأ أثناء النسخ الاحتياطي :application_name', 'backup_failed_subject' => 'أخفق النسخ الاحتياطي لل :application_name', 'backup_successful_body' => 'أخبار عظيمة، نسخة احتياطية جديدة ل :application_name تم إنشاؤها بنجاح على القرص المسمى :disk_name.', 'backup_successful_subject' => 'نسخ احتياطي جديد ناجح ل :application_name', 'backup_successful_subject_title' => 'نجاح النسخ الاحتياطي الجديد!', 'cleanup_failed_body' => 'حدث خطأ أثناء تنظيف النسخ الاحتياطية ل :application_name', 'cleanup_failed_subject' => 'فشل تنظيف النسخ الاحتياطي للتطبيق :application_name .', 'cleanup_successful_body' => 'تنظيف النسخ الاحتياطية ل :application_name على القرص المسمى :disk_name تم بنجاح.', 'cleanup_successful_subject' => 'تنظيف النسخ الاحتياطية ل :application_name تمت بنجاح', 'cleanup_successful_subject_title' => 'تنظيف النسخ الاحتياطية تم بنجاح!', 'exception_message' => 'رسالة استثناء: :message', 'exception_message_title' => 'رسالة استثناء', 'exception_trace' => 'تتبع الإستثناء: :trace', 'exception_trace_title' => 'تتبع الإستثناء', 'healthy_backup_found_body' => 'تعتبر النسخ الاحتياطية ل :application_name صحية. عمل جيد!', 'healthy_backup_found_subject' => 'النسخ الاحتياطية ل :application_name على القرص :disk_name صحية', 'healthy_backup_found_subject_title' => 'النسخ الاحتياطية ل :application_name صحية', 'unhealthy_backup_found_body' => 'النسخ الاحتياطية ل :application_name على القرص :disk_name غير صحية.', 'unhealthy_backup_found_empty' => 'لا توجد نسخ احتياطية لهذا التطبيق على الإطلاق.', 'unhealthy_backup_found_full' => 'النسخ الاحتياطية تستخدم الكثير من التخزين. الاستخدام الحالي هو :disk_usage وهو أعلى من الحد المسموح به من :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'لا يمكن الوصول إلى وجهة النسخ الاحتياطي. :error', 'unhealthy_backup_found_old' => 'تم إنشاء أحدث النسخ الاحتياطية في :date وتعتبر قديمة جدا.', 'unhealthy_backup_found_subject' => 'مهم: النسخ الاحتياطية ل :application_name غير صحية', 'unhealthy_backup_found_subject_title' => 'مهم: النسخ الاحتياطية ل :application_name غير صحية. :problem', 'unhealthy_backup_found_unknown' => 'عذرا، لا يمكن تحديد سبب دقيق.', ]; ================================================ FILE: lang/vendor/backup/ca/notifications.php ================================================ 'Important: hi ha hagut un error mente es feia la copia de seguretat de :application_name', 'backup_failed_subject' => 'La còpia de seguretat de :application_name ha fallat', 'backup_successful_body' => 'Bones notícies! Una nova còpia de seguretat de :application_name s\'ha creat exitosament al disc :disk_name.', 'backup_successful_subject' => 'Còpia de seguretat exitosa de :application_name', 'backup_successful_subject_title' => 'Nova còpia de seguretat exitosa!', 'cleanup_failed_body' => 'Hi ha hagut un error al netejar les còpies de seguretat de :application_name', 'cleanup_failed_subject' => 'La neteja de còpies de seguretat de :application_name ha fallat.', 'cleanup_successful_body' => 'La neteja de les còpies de seguretat de :application_name ha finalitzat amb èxit.', 'cleanup_successful_subject' => 'Neteja exitosa de les còpies de seguretat de :application_name', 'cleanup_successful_subject_title' => 'La neteja de còpies de seguretat ha tingut èxit!', 'exception_message' => 'Missatge d\'excepció: :message', 'exception_message_title' => 'Missatge d\'excepció', 'exception_trace' => 'Rastre de l\'excepció: trace', 'exception_trace_title' => 'Rastre de l\'excepció', 'healthy_backup_found_body' => 'Les còpies de seguretat de :application_name es consideren adequades. Ben fet!', 'healthy_backup_found_subject' => 'Les còpies de seguretat de :application_name al disc :disk_name són adequades', 'healthy_backup_found_subject_title' => 'Les còpies de seguretat de :application_name són adequades', 'unhealthy_backup_found_body' => 'Les còpies de seguretat de :application_name al disc :disk_name no són adequades.', 'unhealthy_backup_found_empty' => 'No existeix cap còpia de seguretat.', 'unhealthy_backup_found_full' => 'Les còpies de seguretat ocupen massa espai. El disc té :disk_usage ocupats i supera el límit permès de :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'La destinació de la còpia de seguretat no s\'ha pogut trobar. :error', 'unhealthy_backup_found_old' => 'L\'última còpia de seguretat del :date es considera massa vella.', 'unhealthy_backup_found_subject' => 'Important: les còpies de seguretat de :application_name no són adequades', 'unhealthy_backup_found_subject_title' => 'Important: les còpies de seguretat de :application_name no són adequades. :problem', 'unhealthy_backup_found_unknown' => 'Ho sentim, no es pot determinar una raó concreta.', ]; ================================================ FILE: lang/vendor/backup/cs/notifications.php ================================================ 'Důležité: Při záloze :application_name se vyskytla chyba', 'backup_failed_subject' => 'Záloha :application_name neuspěla', 'backup_successful_body' => 'Dobrá zpráva, na disku jménem :disk_name byla úspěšně vytvořena nová záloha :application_name.', 'backup_successful_subject' => 'Úspěšná nová záloha :application_name', 'backup_successful_subject_title' => 'Úspěšná nová záloha!', 'cleanup_failed_body' => 'Při vyčištění záloh :application_name se vyskytla chyba', 'cleanup_failed_subject' => 'Vyčištění záloh :application_name neuspělo.', 'cleanup_successful_body' => 'Vyčištění záloh :application_name na disku jménem :disk_name bylo úspěšné.', 'cleanup_successful_subject' => 'Vyčištění záloh :application_name úspěšné', 'cleanup_successful_subject_title' => 'Vyčištění záloh bylo úspěšné!', 'exception_message' => 'Zpráva výjimky: :message', 'exception_message_title' => 'Zpráva výjimky', 'exception_trace' => 'Stopa výjimky: :trace', 'exception_trace_title' => 'Stopa výjimky', 'healthy_backup_found_body' => 'Zálohy pro :application_name jsou považovány za zdravé. Dobrá práce!', 'healthy_backup_found_subject' => 'Zálohy pro :application_name na disku :disk_name jsou zdravé', 'healthy_backup_found_subject_title' => 'Zálohy pro :application_name jsou zdravé', 'unhealthy_backup_found_body' => 'Zálohy pro :application_name na disku :disk_name Jsou nezdravé.', 'unhealthy_backup_found_empty' => 'Tato aplikace nemá vůbec žádné zálohy.', 'unhealthy_backup_found_full' => 'Zálohy zabírají příliš mnoho místa na disku. Aktuální využití disku je :disk_usage, což je vyšší než povolený limit :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Nelze se dostat k cíli zálohy. :error', 'unhealthy_backup_found_old' => 'Poslední záloha vytvořená dne :date je považována za příliš starou.', 'unhealthy_backup_found_subject' => 'Důležité: Zálohy pro :application_name jsou nezdravé', 'unhealthy_backup_found_subject_title' => 'Důležité: Zálohy pro :application_name jsou nezdravé. :problem', 'unhealthy_backup_found_unknown' => 'Omlouváme se, nemůžeme určit přesný důvod.', ]; ================================================ FILE: lang/vendor/backup/da/notifications.php ================================================ 'Vigtigt: Der skete en fejl under backup af :application_name', 'backup_failed_subject' => 'Backup af :application_name fejlede', 'backup_successful_body' => 'Gode nyheder - der blev oprettet en ny backup af :application_name på disken :disk_name.', 'backup_successful_subject' => 'Ny backup af :application_name oprettet', 'backup_successful_subject_title' => 'Ny backup!', 'cleanup_failed_body' => 'Der skete en fejl under oprydning af backups for :application_name', 'cleanup_failed_subject' => 'Oprydning af backups for :application_name fejlede.', 'cleanup_successful_body' => 'Oprydningen af backups for :application_name på disken :disk_name er gennemført.', 'cleanup_successful_subject' => 'Oprydning af backups for :application_name gennemført', 'cleanup_successful_subject_title' => 'Backup oprydning gennemført!', 'exception_message' => 'Fejlbesked: :message', 'exception_message_title' => 'Fejlbesked', 'exception_trace' => 'Fejl trace: :trace', 'exception_trace_title' => 'Fejl trace', 'healthy_backup_found_body' => 'Alle backups for :application_name er ok. Godt gået!', 'healthy_backup_found_subject' => 'Alle backups for :application_name på disken :disk_name er OK', 'healthy_backup_found_subject_title' => 'Alle backups for :application_name er OK', 'unhealthy_backup_found_body' => 'Backups for :application_name på disken :disk_name er fejlbehæftede.', 'unhealthy_backup_found_empty' => 'Denne applikation har ingen backups overhovedet.', 'unhealthy_backup_found_full' => 'Backups bruger for meget plads. Nuværende disk forbrug er :disk_usage, hvilket er mere end den tilladte grænse på :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Backup destinationen kunne ikke findes. :error', 'unhealthy_backup_found_old' => 'Den seneste backup fra :date er for gammel.', 'unhealthy_backup_found_subject' => 'Vigtigt: Backups for :application_name fejlbehæftede', 'unhealthy_backup_found_subject_title' => 'Vigtigt: Backups for :application_name er fejlbehæftede. :problem', 'unhealthy_backup_found_unknown' => 'Beklager, en præcis årsag kunne ikke findes.', ]; ================================================ FILE: lang/vendor/backup/de/notifications.php ================================================ 'Wichtig: Beim Backup von :application_name ist ein Fehler aufgetreten', 'backup_failed_subject' => 'Backup von :application_name konnte nicht erstellt werden', 'backup_successful_body' => 'Gute Nachrichten, ein neues Backup von :application_name wurde erfolgreich erstellt und in :disk_name gepeichert.', 'backup_successful_subject' => 'Erfolgreiches neues Backup von :application_name', 'backup_successful_subject_title' => 'Erfolgreiches neues Backup!', 'cleanup_failed_body' => 'Beim aufräumen der Backups von :application_name ist ein Fehler aufgetreten', 'cleanup_failed_subject' => 'Aufräumen der Backups von :application_name schlug fehl.', 'cleanup_successful_body' => 'Aufräumen der Backups von :application_name in :disk_name war erfolgreich.', 'cleanup_successful_subject' => 'Aufräumen der Backups von :application_name backups erfolgreich', 'cleanup_successful_subject_title' => 'Aufräumen der Backups erfolgreich!', 'exception_message' => 'Fehlermeldung: :message', 'exception_message_title' => 'Fehlermeldung', 'exception_trace' => 'Fehlerverfolgung: :trace', 'exception_trace_title' => 'Fehlerverfolgung', 'healthy_backup_found_body' => 'Die Backups von :application_name wurden als gesund eingestuft. Gute Arbeit!', 'healthy_backup_found_subject' => 'Die Backups von :application_name in :disk_name sind gesund', 'healthy_backup_found_subject_title' => 'Die Backups von :application_name sind Gesund', 'unhealthy_backup_found_body' => 'Die Backups für :application_name in :disk_name sind ungesund.', 'unhealthy_backup_found_empty' => 'Es gibt für die Anwendung noch gar keine Backups.', 'unhealthy_backup_found_full' => 'Die Backups verbrauchen zu viel Platz. Aktuell wird :disk_usage belegt, dass ist höher als das erlaubte Limit von :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Das Backup Ziel konnte nicht erreicht werden. :error', 'unhealthy_backup_found_old' => 'Das letzte Backup am :date ist zu lange her.', 'unhealthy_backup_found_subject' => 'Wichtig: Die Backups für :application_name sind nicht gesund', 'unhealthy_backup_found_subject_title' => 'Wichtig: Die Backups für :application_name sind ungesund. :problem', 'unhealthy_backup_found_unknown' => 'Sorry, ein genauer Grund konnte nicht gefunden werden.', ]; ================================================ FILE: lang/vendor/backup/en/notifications.php ================================================ 'Important: An error occurred while backing up :application_name', 'backup_failed_subject' => 'Failed backup of :application_name', 'backup_successful_body' => 'Great news, a new backup of :application_name was successfully created on the disk named :disk_name.', 'backup_successful_subject' => 'Successful new backup of :application_name', 'backup_successful_subject_title' => 'Successful new backup!', 'cleanup_failed_body' => 'An error occurred while cleaning up the backups of :application_name', 'cleanup_failed_subject' => 'Cleaning up the backups of :application_name failed.', 'cleanup_successful_body' => 'The clean up of the :application_name backups on the disk named :disk_name was successful.', 'cleanup_successful_subject' => 'Clean up of :application_name backups successful', 'cleanup_successful_subject_title' => 'Clean up of backups successful!', 'exception_message' => 'Exception message: :message', 'exception_message_title' => 'Exception message', 'exception_trace' => 'Exception trace: :trace', 'exception_trace_title' => 'Exception trace', 'healthy_backup_found_body' => 'The backups for :application_name are considered healthy. Good job!', 'healthy_backup_found_subject' => 'The backups for :application_name on disk :disk_name are healthy', 'healthy_backup_found_subject_title' => 'The backups for :application_name are healthy', 'unhealthy_backup_found_body' => 'The backups for :application_name on disk :disk_name are unhealthy.', 'unhealthy_backup_found_empty' => 'There are no backups of this application at all.', 'unhealthy_backup_found_full' => 'The backups are using too much storage. Current usage is :disk_usage which is higher than the allowed limit of :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'The backup destination cannot be reached. :error', 'unhealthy_backup_found_old' => 'The latest backup made on :date is considered too old.', 'unhealthy_backup_found_subject' => 'Important: The backups for :application_name are unhealthy', 'unhealthy_backup_found_subject_title' => 'Important: The backups for :application_name are unhealthy. :problem', 'unhealthy_backup_found_unknown' => 'Sorry, an exact reason cannot be determined.', ]; ================================================ FILE: lang/vendor/backup/es/notifications.php ================================================ 'Importante: Ocurrió un error al realizar la copia de seguridad de :application_name', 'backup_failed_subject' => 'Copia de seguridad de :application_name fallida', 'backup_successful_body' => 'Buenas noticias, una nueva copia de seguridad de :application_name fue creada con éxito en el disco llamado :disk_name.', 'backup_successful_subject' => 'Se completó con éxito la copia de seguridad de :application_name', 'backup_successful_subject_title' => '¡Nueva copia de seguridad creada con éxito!', 'cleanup_failed_body' => 'Ocurrió un error mientras se realizaba la limpieza de copias de seguridad de :application_name', 'cleanup_failed_subject' => 'La limpieza de copias de seguridad de :application_name falló.', 'cleanup_successful_body' => 'La limpieza de copias de seguridad de :application_name en el disco llamado :disk_name se completo con éxito.', 'cleanup_successful_subject' => 'La limpieza de copias de seguridad de :application_name se completó con éxito', 'cleanup_successful_subject_title' => '!Limpieza de copias de seguridad completada con éxito!', 'exception_message' => 'Mensaje de la excepción: :message', 'exception_message_title' => 'Mensaje de la excepción', 'exception_trace' => 'Traza de la excepción: :trace', 'exception_trace_title' => 'Traza de la excepción', 'healthy_backup_found_body' => 'Las copias de seguridad de :application_name se consideran en buen estado. ¡Buen trabajo!', 'healthy_backup_found_subject' => 'Las copias de seguridad de :application_name en el disco :disk_name están en buen estado', 'healthy_backup_found_subject_title' => 'Las copias de seguridad de :application_name están en buen estado', 'unhealthy_backup_found_body' => 'Las copias de seguridad de :application_name en el disco :disk_name están en mal estado.', 'unhealthy_backup_found_empty' => 'No existe ninguna copia de seguridad de esta aplicación.', 'unhealthy_backup_found_full' => 'Las copias de seguridad están ocupando demasiado espacio. El espacio utilizado actualmente es :disk_usage el cual es mayor que el límite permitido de :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'No se puede acceder al destino de la copia de seguridad. :error', 'unhealthy_backup_found_old' => 'La última copia de seguriad hecha en :date es demasiado antigua.', 'unhealthy_backup_found_subject' => 'Importante: Las copias de seguridad de :application_name están en mal estado', 'unhealthy_backup_found_subject_title' => 'Importante: Las copias de seguridad de :application_name están en mal estado. :problem', 'unhealthy_backup_found_unknown' => 'Lo siento, no es posible determinar la razón exacta.', ]; ================================================ FILE: lang/vendor/backup/fa/notifications.php ================================================ 'پیغام مهم: هنگام پشتیبان‌گیری از :application_name خطایی رخ داده است.', 'backup_failed_subject' => 'پشتیبان‌گیری :application_name با خطا مواجه شد.', 'backup_successful_body' => 'خبر خوب, به تازگی نسخه پشتیبان :application_name بر روی دیسک :disk_name با موفقیت ساخته شد.', 'backup_successful_subject' => 'نسخه پشتیبان جدید :application_name با موفقیت ساخته شد.', 'backup_successful_subject_title' => 'پشتیبان‌گیری موفق!', 'cleanup_failed_body' => 'هنگام پاک‌سازی نسخه پشتیبان :application_name خطایی رخ داده است.', 'cleanup_failed_subject' => 'پاک‌‌سازی نسخه پشتیبان :application_name انجام نشد.', 'cleanup_successful_body' => 'پاک‌سازی نسخه پشتیبان :application_name بر روی دیسک :disk_name با موفقیت انجام شد.', 'cleanup_successful_subject' => 'پاک‌سازی نسخه پشتیبان :application_name با موفقیت انجام شد.', 'cleanup_successful_subject_title' => 'پاک‌سازی نسخه پشتیبان!', 'exception_message' => 'پیغام خطا: :message', 'exception_message_title' => 'پیغام خطا', 'exception_trace' => 'جزییات خطا: :trace', 'exception_trace_title' => 'جزییات خطا', 'healthy_backup_found_body' => 'نسخه پشتیبان :application_name به نظر سالم میاد. دمت گرم!', 'healthy_backup_found_subject' => 'نسخه پشتیبان :application_name بر روی دیسک :disk_name سالم بود.', 'healthy_backup_found_subject_title' => 'نسخه پشتیبان :application_name سالم بود.', 'unhealthy_backup_found_body' => 'نسخه پشتیبان :application_name بر روی دیسک :disk_name سالم نبود.', 'unhealthy_backup_found_empty' => 'برای این برنامه هیچ نسخه پشتیبانی وجود ندارد.', 'unhealthy_backup_found_full' => 'نسخه‌های پشتیبانی که تهیه کرده اید حجم زیادی اشغال کرده اند. میزان دیسک استفاده شده :disk_usage است که از میزان مجاز :disk_limit فراتر رفته است.', 'unhealthy_backup_found_not_reachable' => 'مقصد پشتیبان‌گیری در دسترس نبود. :error', 'unhealthy_backup_found_old' => 'آخرین نسخه پشتیبان برای تاریخ :date است. که به نظر خیلی قدیمی میاد.', 'unhealthy_backup_found_subject' => 'خبر مهم: نسخه پشتیبان :application_name سالم نبود.', 'unhealthy_backup_found_subject_title' => 'خبر مهم: نسخه پشتیبان :application_name سالم نبود. :problem', 'unhealthy_backup_found_unknown' => 'متاسفانه دلیل دقیق مشخص نشده است.', ]; ================================================ FILE: lang/vendor/backup/fi/notifications.php ================================================ 'HUOM!: :application_name varmuuskoipionnissa tapahtui virhe', 'backup_failed_subject' => ':application_name varmuuskopiointi epäonnistui', 'backup_successful_body' => 'Hyviä uutisia! :application_name on varmuuskopioitu levylle :disk_name.', 'backup_successful_subject' => ':application_name varmuuskopioitu onnistuneesti', 'backup_successful_subject_title' => 'Uusi varmuuskopio!', 'cleanup_failed_body' => ':application_name varmuuskopioiden poistamisessa tapahtui virhe.', 'cleanup_failed_subject' => ':application_name varmuuskopioiden poistaminen epäonnistui.', 'cleanup_successful_body' => ':application_name varmuuskopiot poistettu onnistuneesti levyltä :disk_name.', 'cleanup_successful_subject' => ':application_name varmuuskopiot poistettu onnistuneesti', 'cleanup_successful_subject_title' => 'Varmuuskopiot poistettu onnistuneesti!', 'exception_message' => 'Virheilmoitus: :message', 'exception_message_title' => 'Virheilmoitus', 'exception_trace' => 'Virhe, jäljitys: :trace', 'exception_trace_title' => 'Virheen jäljitys', 'healthy_backup_found_body' => ':application_name varmuuskopiot ovat kunnossa. Hieno homma!', 'healthy_backup_found_subject' => ':application_name varmuuskopiot levyllä :disk_name ovat kunnossa', 'healthy_backup_found_subject_title' => ':application_name varmuuskopiot ovat kunnossa', 'unhealthy_backup_found_body' => ':application_name varmuuskopiot levyllä :disk_name ovat vialliset.', 'unhealthy_backup_found_empty' => 'Tästä sovelluksesta ei ole varmuuskopioita.', 'unhealthy_backup_found_full' => 'Varmuuskopiot vievät liikaa levytilaa. Tällä hetkellä käytössä :disk_usage, mikä on suurempi kuin sallittu tilavuus (:disk_limit).', 'unhealthy_backup_found_not_reachable' => 'Varmuuskopioiden kohdekansio ei ole saatavilla. :error', 'unhealthy_backup_found_old' => 'Viimeisin varmuuskopio, luotu :date, on liian vanha.', 'unhealthy_backup_found_subject' => 'HUOM!: :application_name varmuuskopiot ovat vialliset', 'unhealthy_backup_found_subject_title' => 'HUOM!: :application_name varmuuskopiot ovat vialliset. :problem', 'unhealthy_backup_found_unknown' => 'Virhe, tarkempaa tietoa syystä ei valitettavasti ole saatavilla.', ]; ================================================ FILE: lang/vendor/backup/fr/notifications.php ================================================ 'Important : Une erreur est survenue lors de la sauvegarde de :application_name', 'backup_failed_subject' => 'Échec de la sauvegarde de :application_name', 'backup_successful_body' => 'Bonne nouvelle, une nouvelle sauvegarde de :application_name a été créée avec succès sur le disque nommé :disk_name.', 'backup_successful_subject' => 'Succès de la sauvegarde de :application_name', 'backup_successful_subject_title' => 'Sauvegarde créée avec succès !', 'cleanup_failed_body' => 'Une erreur est survenue lors du nettoyage des sauvegardes de :application_name', 'cleanup_failed_subject' => 'Le nettoyage des sauvegardes de :application_name a echoué.', 'cleanup_successful_body' => 'Le nettoyage des sauvegardes de :application_name sur le disque nommé :disk_name a été effectué avec succès.', 'cleanup_successful_subject' => 'Succès du nettoyage des sauvegardes de :application_name', 'cleanup_successful_subject_title' => 'Sauvegardes nettoyées avec succès !', 'exception_message' => 'Message de l\'exception : :message', 'exception_message_title' => 'Message de l\'exception', 'exception_trace' => 'Trace de l\'exception : :trace', 'exception_trace_title' => 'Trace de l\'exception', 'healthy_backup_found_body' => 'Les sauvegardes pour :application_name sont considérées saines. Bon travail !', 'healthy_backup_found_subject' => 'Les sauvegardes pour :application_name sur le disque :disk_name sont saines', 'healthy_backup_found_subject_title' => 'Les sauvegardes pour :application_name sont saines', 'unhealthy_backup_found_body' => 'Les sauvegardes pour :application_name sur le disque :disk_name sont corrompues.', 'unhealthy_backup_found_empty' => 'Il n\'y a aucune sauvegarde pour cette application.', 'unhealthy_backup_found_full' => 'Les sauvegardes utilisent trop d\'espace disque. L\'utilisation actuelle est de :disk_usage alors que la limite autorisée est de :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'La destination de la sauvegarde n\'est pas accessible. :error', 'unhealthy_backup_found_old' => 'La dernière sauvegarde du :date est considérée trop vieille.', 'unhealthy_backup_found_subject' => 'Important : Les sauvegardes pour :application_name sont corrompues', 'unhealthy_backup_found_subject_title' => 'Important : Les sauvegardes pour :application_name sont corrompues. :problem', 'unhealthy_backup_found_unknown' => 'Désolé, une raison exacte ne peut être déterminée.', ]; ================================================ FILE: lang/vendor/backup/gl/notifications.php ================================================ 'Importante: ocorreu un erro ao facer a copia de seguridade de :application_name', 'backup_failed_subject' => 'Copia de seguridade de :application_name errada', 'backup_successful_body' => 'Boas novas, unha nova copia de seguridade de :application_name foi creada exitosamente no disco chamado :disk_name.', 'backup_successful_subject' => 'Nova copia de seguridade exitosa de :application_name', 'backup_successful_subject_title' => 'Nova copia de seguridade completada con éxito!', 'cleanup_failed_body' => 'Ocorreu un erro ao limpar as copias de seguridade de :application_name', 'cleanup_failed_subject' => 'Limpeza das copias de seguridade de :application_name errada.', 'cleanup_successful_body' => 'A limpeza das copias de seguridade de :application_name no disco chamado :disk_name foi completada exitosamente.', 'cleanup_successful_subject' => 'Limpeza das copias de seguridade de :application_name exitosa.', 'cleanup_successful_subject_title' => 'Limpeza das copias de seguridade completada con éxito!', 'exception_message' => 'Mensaxe da excepción: :message', 'exception_message_title' => 'Mensaxe da excepción', 'exception_trace' => 'Traza da excepción: :trace', 'exception_trace_title' => 'Traza da excepción', 'healthy_backup_found_body' => 'As copias de seguridade de :application_name están en bo estado. Bo traballo!', 'healthy_backup_found_subject' => 'As copias de seguridade de :application_name no disco :disk_name están en bo estado.', 'healthy_backup_found_subject_title' => 'As copias de seguridade de :application_name están en bo estado.', 'unhealthy_backup_found_body' => 'As copias de seguridade de :application_name no disco :disk_name están en mal estado.', 'unhealthy_backup_found_empty' => 'Non existen copias de seguridade desta aplicación.', 'unhealthy_backup_found_full' => 'As copias de seguridade están ocupando demasiado espazo. O espazo usado actualmente é :disk_usage, o cal é maior que o límite permitido de :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'O destino da copia de seguridade non pode ser alcanzado. :error', 'unhealthy_backup_found_old' => 'A última copia de seguridade realizada en :date é considerada demasiado antiga.', 'unhealthy_backup_found_subject' => 'Importante: As copias de seguridade de :application_name están en mal estado', 'unhealthy_backup_found_subject_title' => 'Importante: As copias de seguridade de :application_name están en mal estado. :problem', 'unhealthy_backup_found_unknown' => 'Sentímolo, non é posíbel determinar a razón exacta.', ]; ================================================ FILE: lang/vendor/backup/hi/notifications.php ================================================ 'जरूरी सुचना: :application_name का बैकअप लेते समय असफल रहे', 'backup_failed_subject' => ':application_name का बैकअप असफल रहा', 'backup_successful_body' => 'खुशखबरी, :application_name का बैकअप :disk_name पर संग्रहित करने मे सफल रहे.', 'backup_successful_subject' => ':application_name का बैकअप सफल रहा', 'backup_successful_subject_title' => 'बैकअप सफल रहा!', 'cleanup_failed_body' => ':application_name के बैकअप की सफाई करते समय कुछ बाधा आयी है.', 'cleanup_failed_subject' => ':application_name के बैकअप की सफाई असफल रही.', 'cleanup_successful_body' => ':application_name का बैकअप जो :disk_name नाम की डिस्क पर संग्रहित है, उसकी सफाई सफल रही.', 'cleanup_successful_subject' => ':application_name के बैकअप की सफाई सफल रही', 'cleanup_successful_subject_title' => 'बैकअप की सफाई सफल रही!', 'exception_message' => 'गलती संदेश: :message', 'exception_message_title' => 'गलती संदेश', 'exception_trace' => 'गलती निशान: :trace', 'exception_trace_title' => 'गलती निशान', 'healthy_backup_found_body' => 'बहुत बढ़िया! :application_name के सभी बैकअप स्वस्थ है.', 'healthy_backup_found_subject' => ':disk_name नाम की डिस्क पर संग्रहित :application_name के बैकअप स्वस्थ है', 'healthy_backup_found_subject_title' => ':application_name के सभी बैकअप स्वस्थ है', 'unhealthy_backup_found_body' => ':disk_name नाम की डिस्क पर संग्रहित :application_name के बैकअप अस्वस्थ है', 'unhealthy_backup_found_empty' => 'इस एप्लीकेशन का कोई भी बैकअप नहीं है.', 'unhealthy_backup_found_full' => 'सभी बैकअप बहुत ज्यादा जगह का उपयोग कर रहे है. फ़िलहाल सभी बैकअप :disk_usage जगह का उपयोग कर रहे है, जो की :disk_limit अनुमति सीमा से अधिक का है.', 'unhealthy_backup_found_not_reachable' => ':error के बजेसे बैकअप की मंजिल तक पोहोच नहीं सकते.', 'unhealthy_backup_found_old' => 'हालहीमें :date को लिया हुआ बैकअप बहुत पुराना है.', 'unhealthy_backup_found_subject' => 'जरूरी सुचना : :application_name के बैकअप अस्वस्थ है', 'unhealthy_backup_found_subject_title' => 'जरूरी सुचना : :application_name के बैकअप :problem के बजेसे अस्वस्थ है', 'unhealthy_backup_found_unknown' => 'माफ़ कीजिये, सही कारण निर्धारित नहीं कर सकते.', ]; ================================================ FILE: lang/vendor/backup/hr/notifications.php ================================================ 'Važno: Dogodila se pogreška prilikom izrade sigurnosne kopije :application_name', 'backup_failed_subject' => 'Nije uspjelo sigurnosno kopiranje :application_name', 'backup_successful_body' => 'Sjajne vijesti, nova sigurnosna kopija :application_name uspješno je stvorena na disku imena :disk_name.', 'backup_successful_subject' => 'Uspješna nova sigurnosna kopija :application_name', 'backup_successful_subject_title' => 'Uspješna nova sigurnosna kopija!', 'cleanup_failed_body' => 'Došlo je do pogreške prilikom čišćenja sigurnosnih kopija :application_name', 'cleanup_failed_subject' => 'Čišćenje sigurnosnih kopija :application_name nije uspjelo.', 'cleanup_successful_body' => 'Čišćenje sigurnosnih kopija :application_name na disku imena :disk_name je bilo uspješno.', 'cleanup_successful_subject' => 'Čišćenje sigurnosnih kopija :application_name je uspješno', 'cleanup_successful_subject_title' => 'Čišćenje sigurnosnih kopija je uspješno!', 'exception_message' => 'Poruka iznimke: :message', 'exception_message_title' => 'Poruka iznimke', 'exception_trace' => 'Trag iznimke: :trace', 'exception_trace_title' => 'Trag iznimke', 'healthy_backup_found_body' => 'Sigurnosne kopije za :application_name smatraju se zdravim. Dobar posao!', 'healthy_backup_found_subject' => 'Sigurnosne kopije za :application_name na disku :disk_name su zdrave', 'healthy_backup_found_subject_title' => 'Sigurnosne kopije za :application_name su zdrave', 'unhealthy_backup_found_body' => 'Sigurnosne kopije za :application_name na disku :disk_name su nezdrave.', 'unhealthy_backup_found_empty' => 'Nema sigurnosnih kopija ove aplikacije.', 'unhealthy_backup_found_full' => 'Sigurnosne kopije koriste previše prostora za pohranu. Trenutačna upotreba je :disk_usage koja je veća od dopuštene granice :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Nije moguće doći do odredišta za sigurnosne kopije. :error', 'unhealthy_backup_found_old' => 'Posljednja sigurnosna kopija napravljena :date smatra se prestarom.', 'unhealthy_backup_found_subject' => 'Važno: Sigurnosne kopije za :application_name su nezdrave', 'unhealthy_backup_found_subject_title' => 'Važno: Sigurnosne kopije za :application_name su nezdrave. :problem', 'unhealthy_backup_found_unknown' => 'Nažalost, ne može se utvrditi točan razlog.', ]; ================================================ FILE: lang/vendor/backup/id/notifications.php ================================================ 'Penting: Sebuah error terjadi ketika membackup :application_name', 'backup_failed_subject' => 'Gagal backup :application_name', 'backup_successful_body' => 'Kabar baik, sebuah backup baru dari :application_name sukses dibuat pada disk bernama :disk_name.', 'backup_successful_subject' => 'Backup baru sukses dari :application_name', 'backup_successful_subject_title' => 'Backup baru sukses!', 'cleanup_failed_body' => 'Sebuah error teradi ketika membersihkan backup dari :application_name', 'cleanup_failed_subject' => 'Membersihkan backup dari :application_name yang gagal.', 'cleanup_successful_body' => 'Pembersihan backup :application_name pada disk bernama :disk_name telah sukses.', 'cleanup_successful_subject' => 'Sukses membersihkan backup :application_name', 'cleanup_successful_subject_title' => 'Sukses membersihkan backup!', 'exception_message' => 'Pesan pengecualian: :message', 'exception_message_title' => 'Pesan pengecualian', 'exception_trace' => 'Jejak pengecualian: :trace', 'exception_trace_title' => 'Jejak pengecualian', 'healthy_backup_found_body' => 'Backup untuk :application_name dipertimbangkan sehat. Kerja bagus!', 'healthy_backup_found_subject' => 'Backup untuk :application_name pada disk :disk_name sehat', 'healthy_backup_found_subject_title' => 'Backup untuk :application_name sehat', 'unhealthy_backup_found_body' => 'Backup untuk :application_name pada disk :disk_name tidak sehat.', 'unhealthy_backup_found_empty' => 'Tidak ada backup pada aplikasi ini sama sekali.', 'unhealthy_backup_found_full' => 'Backup menggunakan terlalu banyak kapasitas penyimpanan. Penggunaan terkini adalah :disk_usage dimana lebih besar dari batas yang diperbolehkan yaitu :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Tujuan backup tidak dapat terjangkau. :error', 'unhealthy_backup_found_old' => 'Backup terakhir dibuat pada :date dimana dipertimbahkan sudah sangat lama.', 'unhealthy_backup_found_subject' => 'Penting: Backup untuk :application_name tidak sehat', 'unhealthy_backup_found_subject_title' => 'Penting: Backup untuk :application_name tidak sehat. :problem', 'unhealthy_backup_found_unknown' => 'Maaf, sebuah alasan persisnya tidak dapat ditentukan.', ]; ================================================ FILE: lang/vendor/backup/it/notifications.php ================================================ 'Importante: Si è verificato un errore durante il backup di :application_name', 'backup_failed_subject' => 'Fallito il backup di :application_name', 'backup_successful_body' => 'Grande notizia, un nuovo backup di :application_name è stato creato con successo sul disco :disk_name.', 'backup_successful_subject' => 'Creato nuovo backup di :application_name', 'backup_successful_subject_title' => 'Nuovo backup creato!', 'cleanup_failed_body' => 'Si è verificato un errore durante la pulizia dei backup di :application_name', 'cleanup_failed_subject' => 'Pulizia dei backup di :application_name fallita.', 'cleanup_successful_body' => 'La pulizia dei backup di :application_name sul disco :disk_name è avvenuta con successo.', 'cleanup_successful_subject' => 'Pulizia dei backup di :application_name avvenuta con successo', 'cleanup_successful_subject_title' => 'Pulizia dei backup avvenuta con successo!', 'exception_message' => 'Messaggio dell\'eccezione: :message', 'exception_message_title' => 'Messaggio dell\'eccezione', 'exception_trace' => 'Traccia dell\'eccezione: :trace', 'exception_trace_title' => 'Traccia dell\'eccezione', 'healthy_backup_found_body' => 'I backup per :application_name sono considerati sani. Bel Lavoro!', 'healthy_backup_found_subject' => 'I backup per :application_name sul disco :disk_name sono sani', 'healthy_backup_found_subject_title' => 'I backup per :application_name sono sani', 'unhealthy_backup_found_body' => 'I backup per :application_name sul disco :disk_name sono corrotti.', 'unhealthy_backup_found_empty' => 'Non esiste alcun backup di questa applicazione.', 'unhealthy_backup_found_full' => 'I backup utilizzano troppa memoria. L\'utilizzo corrente è :disk_usage che è superiore al limite consentito di :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Impossibile raggiungere la destinazione di backup. :error', 'unhealthy_backup_found_old' => 'L\'ultimo backup fatto il :date è considerato troppo vecchio.', 'unhealthy_backup_found_subject' => 'Importante: i backup per :application_name sono corrotti', 'unhealthy_backup_found_subject_title' => 'Importante: i backup per :application_name sono corrotti. :problem', 'unhealthy_backup_found_unknown' => 'Spiacenti, non è possibile determinare una ragione esatta.', ]; ================================================ FILE: lang/vendor/backup/ja/notifications.php ================================================ '重要: :application_name のバックアップ中にエラーが発生しました。', 'backup_failed_subject' => ':application_name のバックアップに失敗しました。', 'backup_successful_body' => '朗報です。ディスク :disk_name へ :application_name のバックアップが成功しました。', 'backup_successful_subject' => ':application_name のバックアップに成功しました。', 'backup_successful_subject_title' => 'バックアップに成功しました!', 'cleanup_failed_body' => ':application_name のバックアップ削除中にエラーが発生しました。', 'cleanup_failed_subject' => ':application_name のバックアップ削除に失敗しました。', 'cleanup_successful_body' => 'ディスク :disk_name に保存された :application_name のバックアップ削除に成功しました。', 'cleanup_successful_subject' => ':application_name のバックアップ削除に成功しました。', 'cleanup_successful_subject_title' => 'バックアップ削除に成功しました!', 'exception_message' => '例外のメッセージ: :message', 'exception_message_title' => '例外のメッセージ', 'exception_trace' => '例外の追跡: :trace', 'exception_trace_title' => '例外の追跡', 'healthy_backup_found_body' => ':application_name へのバックアップは正常です。いい仕事してますね!', 'healthy_backup_found_subject' => 'ディスク :disk_name への :application_name のバックアップは正常です。', 'healthy_backup_found_subject_title' => ':application_name のバックアップは正常です。', 'unhealthy_backup_found_body' => ':disk_name への :application_name のバックアップに異常があります。', 'unhealthy_backup_found_empty' => 'このアプリケーションのバックアップは見つかりませんでした。', 'unhealthy_backup_found_full' => 'バックアップがディスク容量を圧迫しています。現在の使用量 :disk_usage は、許可された限界値 :disk_limit を超えています。', 'unhealthy_backup_found_not_reachable' => 'バックアップ先にアクセスできませんでした。 :error', 'unhealthy_backup_found_old' => ':date に保存された直近のバックアップが古すぎます。', 'unhealthy_backup_found_subject' => '重要: :application_name のバックアップに異常があります。', 'unhealthy_backup_found_subject_title' => '重要: :application_name のバックアップに異常があります。 :problem', 'unhealthy_backup_found_unknown' => '申し訳ございません。予期せぬエラーです。', ]; ================================================ FILE: lang/vendor/backup/nl/notifications.php ================================================ 'Belangrijk: Er ging iets fout tijdens het maken van een back-up van :application_name', 'backup_failed_subject' => 'Back-up van :application_name mislukt', 'backup_successful_body' => 'Goed nieuws, een nieuwe back-up van :application_name was succesvol aangemaakt op de schijf genaamd :disk_name.', 'backup_successful_subject' => 'Succesvolle nieuwe back-up van :application_name', 'backup_successful_subject_title' => 'Succesvolle nieuwe back-up!', 'cleanup_failed_body' => 'Er ging iets fout tijdens het opschonen van de back-ups van :application_name', 'cleanup_failed_subject' => 'Het opschonen van de back-ups van :application_name is mislukt.', 'cleanup_successful_body' => 'Het opschonen van de :application_name back-ups op de schijf genaamd :disk_name was succesvol.', 'cleanup_successful_subject' => 'Opschonen van :application_name back-ups was succesvol.', 'cleanup_successful_subject_title' => 'Opschonen van back-ups was succesvol!', 'exception_message' => 'Fout bericht: :message', 'exception_message_title' => 'Fout bericht', 'exception_trace' => 'Fout trace: :trace', 'exception_trace_title' => 'Fout trace', 'healthy_backup_found_body' => 'De back-ups voor :application_name worden als gezond beschouwd. Goed gedaan!', 'healthy_backup_found_subject' => 'De back-ups voor :application_name op schijf :disk_name zijn gezond', 'healthy_backup_found_subject_title' => 'De back-ups voor :application_name zijn gezond', 'unhealthy_backup_found_body' => 'De back-ups voor :application_name op schijf :disk_name zijn niet gezond.', 'unhealthy_backup_found_empty' => 'Er zijn geen back-ups van deze applicatie beschikbaar.', 'unhealthy_backup_found_full' => 'De back-ups gebruiken te veel opslagruimte. Momenteel wordt er :disk_usage gebruikt wat hoger is dan de toegestane limiet van :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'De back-upbestemming kon niet worden bereikt. :error', 'unhealthy_backup_found_old' => 'De laatste back-up gemaakt op :date is te oud.', 'unhealthy_backup_found_subject' => 'Belangrijk: De back-ups voor :application_name zijn niet meer gezond', 'unhealthy_backup_found_subject_title' => 'Belangrijk: De back-ups voor :application_name zijn niet gezond. :problem', 'unhealthy_backup_found_unknown' => 'Sorry, een exacte reden kon niet worden bepaald.', ]; ================================================ FILE: lang/vendor/backup/no/notifications.php ================================================ 'Viktg: En feil oppstod under backing av :application_name', 'backup_failed_subject' => 'Backup feilet for :application_name', 'backup_successful_body' => 'Gode nyheter, en ny backup av :application_name ble opprettet på disken :disk_name.', 'backup_successful_subject' => 'Gjennomført backup av :application_name', 'backup_successful_subject_title' => 'Gjennomført backup!', 'cleanup_failed_body' => 'En feil oppstod under opprydding av backups for :application_name', 'cleanup_failed_subject' => 'Opprydding av backup for :application_name feilet.', 'cleanup_successful_body' => 'Oppryddingen av backup for :application_name på disken :disk_name har blitt gjennomført.', 'cleanup_successful_subject' => 'Opprydding av backup for :application_name gjennomført', 'cleanup_successful_subject_title' => 'Opprydding av backup gjennomført!', 'exception_message' => 'Exception: :message', 'exception_message_title' => 'Exception', 'exception_trace' => 'Exception trace: :trace', 'exception_trace_title' => 'Exception trace', 'healthy_backup_found_body' => 'Alle backups for :application_name er ok. Godt jobba!', 'healthy_backup_found_subject' => 'Alle backups for :application_name på disken :disk_name er OK', 'healthy_backup_found_subject_title' => 'Alle backups for :application_name er OK', 'unhealthy_backup_found_body' => 'Backups for :application_name på disken :disk_name er ikke OK.', 'unhealthy_backup_found_empty' => 'Denne applikasjonen mangler backups.', 'unhealthy_backup_found_full' => 'Backups bruker for mye lagringsplass. Nåværende diskbruk er :disk_usage, som er mer enn den tillatte grensen på :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Kunne ikke finne backup-destinasjonen. :error', 'unhealthy_backup_found_old' => 'Den siste backupem fra :date er for gammel.', 'unhealthy_backup_found_subject' => 'Viktig: Backups for :application_name ikke OK', 'unhealthy_backup_found_subject_title' => 'Viktig: Backups for :application_name er ikke OK. :problem', 'unhealthy_backup_found_unknown' => 'Beklager, kunne ikke finne nøyaktig årsak.', ]; ================================================ FILE: lang/vendor/backup/pl/notifications.php ================================================ 'Ważne: Wystąpił błąd podczas tworzenia kopii zapasowej aplikacji :application_name', 'backup_failed_subject' => 'Tworzenie kopii zapasowej aplikacji :application_name nie powiodło się', 'backup_successful_body' => 'Wspaniała wiadomość, nowa kopia zapasowa aplikacji :application_name została pomyślnie utworzona na dysku o nazwie :disk_name.', 'backup_successful_subject' => 'Pomyślnie utworzono kopię zapasową aplikacji :application_name', 'backup_successful_subject_title' => 'Nowa kopia zapasowa!', 'cleanup_failed_body' => 'Wystąpił błąd podczas czyszczenia kopii zapasowej aplikacji :application_name', 'cleanup_failed_subject' => 'Czyszczenie kopii zapasowych aplikacji :application_name nie powiodło się.', 'cleanup_successful_body' => 'Czyszczenie kopii zapasowych aplikacji :application_name na dysku :disk_name zakończone sukcesem.', 'cleanup_successful_subject' => 'Kopie zapasowe aplikacji :application_name zostały pomyślnie wyczyszczone', 'cleanup_successful_subject_title' => 'Kopie zapasowe zostały pomyślnie wyczyszczone!', 'exception_message' => 'Błąd: :message', 'exception_message_title' => 'Błąd', 'exception_trace' => 'Zrzut błędu: :trace', 'exception_trace_title' => 'Zrzut błędu', 'healthy_backup_found_body' => 'Kopie zapasowe aplikacji :application_name są poprawne. Dobra robota!', 'healthy_backup_found_subject' => 'Kopie zapasowe aplikacji :application_name na dysku :disk_name są poprawne', 'healthy_backup_found_subject_title' => 'Kopie zapasowe aplikacji :application_name są poprawne', 'unhealthy_backup_found_body' => 'Kopie zapasowe aplikacji :application_name na dysku :disk_name są niepoprawne.', 'unhealthy_backup_found_empty' => 'W aplikacji nie ma żadnej kopii zapasowych tej aplikacji.', 'unhealthy_backup_found_full' => 'Kopie zapasowe zajmują zbyt dużo miejsca. Obecne użycie dysku :disk_usage jest większe od ustalonego limitu :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Miejsce docelowe kopii zapasowej nie jest osiągalne. :error', 'unhealthy_backup_found_old' => 'Ostatnia kopia zapasowa wykonania dnia :date jest zbyt stara.', 'unhealthy_backup_found_subject' => 'Ważne: Kopie zapasowe aplikacji :application_name są niepoprawne', 'unhealthy_backup_found_subject_title' => 'Ważne: Kopie zapasowe aplikacji :application_name są niepoprawne. :problem', 'unhealthy_backup_found_unknown' => 'Niestety, nie można ustalić dokładnego błędu.', ]; ================================================ FILE: lang/vendor/backup/pt/notifications.php ================================================ 'Importante: Ocorreu um erro ao executar o backup da aplicação :application_name', 'backup_failed_subject' => 'Falha no backup da aplicação :application_name', 'backup_successful_body' => 'Boas notícias, foi criado um novo backup no disco :disk_name referente à aplicação :application_name.', 'backup_successful_subject' => 'Backup realizado com sucesso: :application_name', 'backup_successful_subject_title' => 'Backup Realizado com Sucesso!', 'cleanup_failed_body' => 'Ocorreu um erro ao executar a limpeza dos backups da aplicação :application_name', 'cleanup_failed_subject' => 'Falha na limpeza dos backups da aplicação :application_name.', 'cleanup_successful_body' => 'Concluída a limpeza dos backups da aplicação :application_name no disco :disk_name.', 'cleanup_successful_subject' => 'Limpeza dos backups da aplicação :application_name concluída!', 'cleanup_successful_subject_title' => 'Limpeza dos backups concluída!', 'exception_message' => 'Exception message: :message', 'exception_message_title' => 'Exception message', 'exception_trace' => 'Exception trace: :trace', 'exception_trace_title' => 'Exception trace', 'healthy_backup_found_body' => 'Os backups da aplicação :application_name estão em dia. Bom trabalho!', 'healthy_backup_found_subject' => 'Os backups da aplicação :application_name no disco :disk_name estão em dia', 'healthy_backup_found_subject_title' => 'Os backups da aplicação :application_name estão em dia', 'unhealthy_backup_found_body' => 'Os backups da aplicação :application_name no disco :disk_name não estão em dia.', 'unhealthy_backup_found_empty' => 'Não existem backups para essa aplicação.', 'unhealthy_backup_found_full' => 'Os backups estão a utilizar demasiado espaço de armazenamento. A utilização atual é de :disk_usage, o que é maior que o limite permitido de :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'O destino dos backups não pode ser alcançado. :error', 'unhealthy_backup_found_old' => 'O último backup realizado em :date é demasiado antigo.', 'unhealthy_backup_found_subject' => 'Importante: Os backups da aplicação :application_name não estão em dia', 'unhealthy_backup_found_subject_title' => 'Importante: Os backups da aplicação :application_name não estão em dia. :problem', 'unhealthy_backup_found_unknown' => 'Desculpe, impossível determinar a razão exata.', ]; ================================================ FILE: lang/vendor/backup/pt-BR/notifications.php ================================================ 'Importante: Ocorreu um erro ao fazer o backup da aplicação :application_name', 'backup_failed_subject' => 'Falha no backup da aplicação :application_name', 'backup_successful_body' => 'Boas notícias, um novo backup da aplicação :application_name foi criado no disco :disk_name.', 'backup_successful_subject' => 'Backup realizado com sucesso: :application_name', 'backup_successful_subject_title' => 'Backup Realizado com sucesso!', 'cleanup_failed_body' => 'Um erro ocorreu ao fazer a limpeza dos backups da aplicação :application_name', 'cleanup_failed_subject' => 'Falha na limpeza dos backups da aplicação :application_name.', 'cleanup_successful_body' => 'A limpeza dos backups da aplicação :application_name no disco :disk_name foi concluída.', 'cleanup_successful_subject' => 'Limpeza dos backups da aplicação :application_name concluída!', 'cleanup_successful_subject_title' => 'Limpeza dos backups concluída!', 'exception_message' => 'Exception message: :message', 'exception_message_title' => 'Exception message', 'exception_trace' => 'Exception trace: :trace', 'exception_trace_title' => 'Exception trace', 'healthy_backup_found_body' => 'Os backups da aplicação :application_name estão em dia. Bom trabalho!', 'healthy_backup_found_subject' => 'Os backups da aplicação :application_name no disco :disk_name estão em dia', 'healthy_backup_found_subject_title' => 'Os backups da aplicação :application_name estão em dia', 'unhealthy_backup_found_body' => 'Os backups da aplicação :application_name no disco :disk_name não estão em dia.', 'unhealthy_backup_found_empty' => 'Não existem backups para essa aplicação.', 'unhealthy_backup_found_full' => 'Os backups estão usando muito espaço de armazenamento. A utilização atual é de :disk_usage, o que é maior que o limite permitido de :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'O destino dos backups não pode ser alcançado. :error', 'unhealthy_backup_found_old' => 'O último backup realizado em :date é considerado muito antigo.', 'unhealthy_backup_found_subject' => 'Importante: Os backups da aplicação :application_name não estão em dia', 'unhealthy_backup_found_subject_title' => 'Importante: Os backups da aplicação :application_name não estão em dia. :problem', 'unhealthy_backup_found_unknown' => 'Desculpe, a exata razão não pode ser encontrada.', ]; ================================================ FILE: lang/vendor/backup/ro/notifications.php ================================================ 'Important: A apărut o eroare în timpul generării copiei de rezervă pentru :application_name', 'backup_failed_subject' => 'Nu s-a putut face copie de rezervă pentru :application_name', 'backup_successful_body' => 'Vești bune, o nouă copie de rezervă pentru :application_name a fost creată cu succes pe discul cu numele :disk_name.', 'backup_successful_subject' => 'Copie de rezervă efectuată cu succes pentru :application_name', 'backup_successful_subject_title' => 'O nouă copie de rezervă a fost efectuată cu succes!', 'cleanup_failed_body' => 'A apărut o eroare în timpul curățirii copiilor de rezervă pentru :application_name', 'cleanup_failed_subject' => 'Curățarea copiilor de rezervă pentru :application_name nu a reușit.', 'cleanup_successful_body' => 'Curățarea copiilor de rezervă pentru :application_name de pe discul cu numele :disk_name a fost făcută cu succes.', 'cleanup_successful_subject' => 'Curățarea copiilor de rezervă pentru :application_name a fost făcută cu succes', 'cleanup_successful_subject_title' => 'Curățarea copiilor de rezervă a fost făcută cu succes!', 'exception_message' => 'Cu excepția mesajului: :message', 'exception_message_title' => 'Mesaj de excepție', 'exception_trace' => 'Urmă excepţie: :trace', 'exception_trace_title' => 'Urmă excepţie', 'healthy_backup_found_body' => 'Copiile de rezervă pentru :application_name sunt considerate în regulă. Bună treabă!', 'healthy_backup_found_subject' => 'Copiile de rezervă pentru :application_name de pe discul :disk_name sunt în regulă', 'healthy_backup_found_subject_title' => 'Copiile de rezervă pentru :application_name sunt în regulă', 'unhealthy_backup_found_body' => 'Copiile de rezervă pentru :application_name de pe discul :disk_name nu sunt în regulă.', 'unhealthy_backup_found_empty' => 'Nu există copii de rezervă ale acestei aplicații.', 'unhealthy_backup_found_full' => 'Copiile de rezervă folosesc prea mult spațiu de stocare. Utilizarea curentă este de :disk_usage care este mai mare decât limita permisă de :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Nu se poate ajunge la destinația copiilor de rezervă. :error', 'unhealthy_backup_found_old' => 'Cea mai recentă copie de rezervă făcută la :date este considerată prea veche.', 'unhealthy_backup_found_subject' => 'Important: Copiile de rezervă pentru :application_name nu sunt în regulă', 'unhealthy_backup_found_subject_title' => 'Important: Copiile de rezervă pentru :application_name nu sunt în regulă. :problem', 'unhealthy_backup_found_unknown' => 'Ne pare rău, un motiv exact nu poate fi determinat.', ]; ================================================ FILE: lang/vendor/backup/ru/notifications.php ================================================ 'Внимание: Произошла ошибка во время резервного копирования :application_name', 'backup_failed_subject' => 'Не удалось сделать резервную копию :application_name', 'backup_successful_body' => 'Отличная новость, новая резервная копия :application_name успешно создана и сохранена на диск :disk_name.', 'backup_successful_subject' => 'Успешно создана новая резервная копия :application_name', 'backup_successful_subject_title' => 'Успешно создана новая резервная копия!', 'cleanup_failed_body' => 'Произошла ошибка при очистке резервных копий :application_name', 'cleanup_failed_subject' => 'Не удалось очистить резервные копии :application_name', 'cleanup_successful_body' => 'Очистка от старых резервных копий :application_name на диске :disk_name прошла удачно.', 'cleanup_successful_subject' => 'Очистка от резервных копий :application_name прошла успешно', 'cleanup_successful_subject_title' => 'Очистка резервных копий прошла удачно!', 'exception_message' => 'Сообщение об ошибке: :message', 'exception_message_title' => 'Сообщение об ошибке', 'exception_trace' => 'Сведения об ошибке: :trace', 'exception_trace_title' => 'Сведения об ошибке', 'healthy_backup_found_body' => 'Резервная копия :application_name успешно установлена. Хорошая работа!', 'healthy_backup_found_subject' => 'Резервная копия :application_name с диска :disk_name установлена', 'healthy_backup_found_subject_title' => 'Резервная копия :application_name установлена', 'unhealthy_backup_found_body' => 'Резервная копия для :application_name на диске :disk_name не установилась.', 'unhealthy_backup_found_empty' => 'Резервные копии для этого приложения отсутствуют.', 'unhealthy_backup_found_full' => 'Резервные копии используют слишком много памяти. Используется :disk_usage что выше допустимого предела: :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Резервная копия не смогла установиться. :error', 'unhealthy_backup_found_old' => 'Последнее резервное копирование создано :date является устаревшим.', 'unhealthy_backup_found_subject' => 'Внимание: резервная копия :application_name не установилась', 'unhealthy_backup_found_subject_title' => 'Внимание: резервная копия для :application_name не установилась. :problem', 'unhealthy_backup_found_unknown' => 'Извините, точная причина не может быть определена.', ]; ================================================ FILE: lang/vendor/backup/sk/notifications.php ================================================ 'Dôležité: Pri vytváraní zálohy :application_name sa vyskytla chyba', 'backup_failed_subject' => 'Chyba pri zálohovaní :application_name', 'backup_successful_body' => 'Výborné správy, nová zálohy :application_name bola úspešne vytvorená na disku :disk_name.', 'backup_successful_subject' => 'Záloha :application_name bola úspešná', 'backup_successful_subject_title' => 'Nová záloha úspešná!', 'cleanup_failed_body' => 'Pri čistení záloh :application_name sa vyskytla chyba', 'cleanup_failed_subject' => 'Čistenie záloh :application_name zlyhalo', 'cleanup_successful_body' => 'Čistenie záloh :application_name na disku :disk_name bolo úspešné.', 'cleanup_successful_subject' => 'Čistenie záloh :application_name úspešné', 'cleanup_successful_subject_title' => 'Čistenie záloh skončilo úspešne!', 'exception_message' => 'Hlásenie o chybe: :message', 'exception_message_title' => 'Hlásenie o chybe', 'exception_trace' => 'Stopovanie chyby: :trace', 'exception_trace_title' => 'Stopovanie chyby', 'healthy_backup_found_body' => 'Zálohy :application_name vyzerajú v poriadku. Len tak ďalej!', 'healthy_backup_found_subject' => 'Zálohy :application_name na disku :disk_name sú v poriadku', 'healthy_backup_found_subject_title' => 'Zálohy :application_name sú v poriadku', 'unhealthy_backup_found_body' => 'Zálohy :application name na disku :disk_name sú poškodené.', 'unhealthy_backup_found_empty' => 'Pre túto aplikáciu neexistujú žiadne zálohy.', 'unhealthy_backup_found_full' => 'Zálohy vyžadujú príliš veľa priestoru. Aktuálne zaberajú :disk_usage, čo je viac ako povolený limit :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Priečinok so zálohami nie je dostupný. :error', 'unhealthy_backup_found_old' => 'Posledná záloha z :date je príliš stará.', 'unhealthy_backup_found_subject' => 'Dôležité: Zálohy :application_name sú poškodené', 'unhealthy_backup_found_subject_title' => 'Dôležité: Zálohy :application_name sú poškodené. :problem', 'unhealthy_backup_found_unknown' => 'Prepáč, presný dôvod nebolo možné zistiť.', ]; ================================================ FILE: lang/vendor/backup/tr/notifications.php ================================================ 'Önemli: Yedeklenirken bir hata oluştu :application_name', 'backup_failed_subject' => 'Yedeklenemedi :application_name', 'backup_successful_body' => 'Harika bir haber, :application_name âit yeni bir yedekleme :disk_name adlı diskte başarıyla oluşturuldu.', 'backup_successful_subject' => 'Başarılı :application_name yeni yedeklemesi', 'backup_successful_subject_title' => 'Başarılı bir yeni yedekleme!', 'cleanup_failed_body' => ':application_name yedeklerini temizlerken bir hata oluştu', 'cleanup_failed_subject' => ':application_name yedeklemeleri temizlenmesi başarısız.', 'cleanup_successful_body' => ':application_name yedeklemeleri temizlenmesi ,:disk_name diskinden silindi', 'cleanup_successful_subject' => ':application_name yedeklemeleri temizlenmesi başarılı.', 'cleanup_successful_subject_title' => 'Yedeklerin temizlenmesi başarılı!', 'exception_message' => 'Hata mesajı: :message', 'exception_message_title' => 'Hata mesajı', 'exception_trace' => 'Hata izleri: :trace', 'exception_trace_title' => 'Hata izleri', 'healthy_backup_found_body' => ':application_name için yapılan yedeklemeler sağlıklı sayılır. Aferin!', 'healthy_backup_found_subject' => ':application_name yedeklenmesi ,:disk_name adlı diskte sağlıklı', 'healthy_backup_found_subject_title' => ':application_name yedeklenmesi sağlıklı', 'unhealthy_backup_found_body' => 'Yedeklemeler: :application_name disk: :disk_name sağlıksız.', 'unhealthy_backup_found_empty' => 'Bu uygulamanın yedekleri yok.', 'unhealthy_backup_found_full' => 'Yedeklemeler çok fazla depolama alanı kullanıyor. Şu anki kullanım: :disk_usage, izin verilen sınırdan yüksek: :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Yedekleme hedefine ulaşılamıyor. :error', 'unhealthy_backup_found_old' => ':date tarihinde yapılan en son yedekleme çok eski kabul ediliyor.', 'unhealthy_backup_found_subject' => 'Önemli: :application_name için yedeklemeler sağlıksız', 'unhealthy_backup_found_subject_title' => 'Önemli: :application_name için yedeklemeler sağlıksız. :problem', 'unhealthy_backup_found_unknown' => 'Üzgünüm, kesin bir sebep belirlenemiyor.', ]; ================================================ FILE: lang/vendor/backup/uk/notifications.php ================================================ 'Увага: Трапилась помилка під час резервного копіювання :application_name', 'backup_failed_subject' => 'Не вдалось зробити резервну копію :application_name', 'backup_successful_body' => 'Чудова новина, нова резервна копія :application_name успішно створена і збережена на диск :disk_name.', 'backup_successful_subject' => 'Успішне резервне копіювання :application_name', 'backup_successful_subject_title' => 'Успішно створена резервна копія!', 'cleanup_failed_body' => 'Сталася помилка під час очищення резервних копій :application_name', 'cleanup_failed_subject' => 'Не вдалось очистити резервні копії :application_name', 'cleanup_successful_body' => 'Очищенно від старих резервних копій :application_name на диску :disk_name пойшло успішно.', 'cleanup_successful_subject' => 'Успішне очищення від резервних копій :application_name', 'cleanup_successful_subject_title' => 'Очищення резервних копій пройшло вдало!', 'exception_message' => 'Повідомлення про помилку: :message', 'exception_message_title' => 'Повідомлення помилки', 'exception_trace' => 'Деталі помилки: :trace', 'exception_trace_title' => 'Деталі помилки', 'healthy_backup_found_body' => 'Резервна копія :application_name успішно установлена. Хороша робота!', 'healthy_backup_found_subject' => 'Резервна копія :application_name з диску :disk_name установлена', 'healthy_backup_found_subject_title' => 'Резервна копія :application_name установлена', 'unhealthy_backup_found_body' => 'Резервна копія для :application_name на диску :disk_name не установилась.', 'unhealthy_backup_found_empty' => 'Резервні копії для цього додатку відсутні.', 'unhealthy_backup_found_full' => 'Резервні копії використовують занадто багато пам`яті. Використовується :disk_usage що вище за допустиму межу :disk_limit.', 'unhealthy_backup_found_not_reachable' => 'Резервна копія не змогла установитись. :error', 'unhealthy_backup_found_old' => 'Останнє резервне копіювання створено :date є застарілим.', 'unhealthy_backup_found_subject' => 'Увага: резервна копія :application_name не установилась', 'unhealthy_backup_found_subject_title' => 'Увага: резервна копія для :application_name не установилась. :problem', 'unhealthy_backup_found_unknown' => 'Вибачте, але ми не змогли визначити точну причину.', ]; ================================================ FILE: lang/vendor/backup/zh-CN/notifications.php ================================================ '重要说明:备份 :application_name 时发生错误', 'backup_failed_subject' => ':application_name 备份失败', 'backup_successful_body' => '好消息, :application_name 备份成功,位于磁盘 :disk_name 中。', 'backup_successful_subject' => ':application_name 备份成功', 'backup_successful_subject_title' => '备份成功!', 'cleanup_failed_body' => '清除备份 :application_name 时发生错误', 'cleanup_failed_subject' => '清除 :application_name 的备份失败。', 'cleanup_successful_body' => '成功清除 :disk_name 磁盘上 :application_name 的备份。', 'cleanup_successful_subject' => '成功清除 :application_name 的备份', 'cleanup_successful_subject_title' => '成功清除备份!', 'exception_message' => '异常信息: :message', 'exception_message_title' => '异常信息', 'exception_trace' => '异常跟踪: :trace', 'exception_trace_title' => '异常跟踪', 'healthy_backup_found_body' => ':application_name 的备份是健康的。干的好!', 'healthy_backup_found_subject' => ':disk_name 磁盘上 :application_name 的备份是健康的', 'healthy_backup_found_subject_title' => ':application_name 的备份是健康的', 'unhealthy_backup_found_body' => ':disk_name 磁盘上 :application_name 的备份不健康。', 'unhealthy_backup_found_empty' => '根本没有此应用程序的备份。', 'unhealthy_backup_found_full' => '备份占用了太多存储空间。当前占用了 :disk_usage ,高于允许的限制 :disk_limit。', 'unhealthy_backup_found_not_reachable' => '无法访问备份目标。 :error', 'unhealthy_backup_found_old' => '最近的备份创建于 :date ,太旧了。', 'unhealthy_backup_found_subject' => '重要说明::application_name 的备份不健康', 'unhealthy_backup_found_subject_title' => '重要说明::application_name 备份不健康。 :problem', 'unhealthy_backup_found_unknown' => '对不起,确切原因无法确定。', ]; ================================================ FILE: lang/vendor/backup/zh-TW/notifications.php ================================================ '重要說明:備份 :application_name 時發生錯誤', 'backup_failed_subject' => ':application_name 備份失敗', 'backup_successful_body' => '好消息, :application_name 備份成功,位於磁盤 :disk_name 中。', 'backup_successful_subject' => ':application_name 備份成功', 'backup_successful_subject_title' => '備份成功!', 'cleanup_failed_body' => '清除備份 :application_name 時發生錯誤', 'cleanup_failed_subject' => '清除 :application_name 的備份失敗。', 'cleanup_successful_body' => '成功清除 :disk_name 磁盤上 :application_name 的備份。', 'cleanup_successful_subject' => '成功清除 :application_name 的備份', 'cleanup_successful_subject_title' => '成功清除備份!', 'exception_message' => '異常訊息: :message', 'exception_message_title' => '異常訊息', 'exception_trace' => '異常追蹤: :trace', 'exception_trace_title' => '異常追蹤', 'healthy_backup_found_body' => ':application_name 的備份是健康的。幹的好!', 'healthy_backup_found_subject' => ':disk_name 磁盤上 :application_name 的備份是健康的', 'healthy_backup_found_subject_title' => ':application_name 的備份是健康的', 'unhealthy_backup_found_body' => ':disk_name 磁盤上 :application_name 的備份不健康。', 'unhealthy_backup_found_empty' => '根本沒有此應用程序的備份。', 'unhealthy_backup_found_full' => '備份佔用了太多存儲空間。當前佔用了 :disk_usage ,高於允許的限制 :disk_limit。', 'unhealthy_backup_found_not_reachable' => '無法訪問備份目標。 :error', 'unhealthy_backup_found_old' => '最近的備份創建於 :date ,太舊了。', 'unhealthy_backup_found_subject' => '重要說明::application_name 的備份不健康', 'unhealthy_backup_found_subject_title' => '重要說明::application_name 備份不健康。 :problem', 'unhealthy_backup_found_unknown' => '對不起,確切原因無法確定。', ]; ================================================ FILE: lang/vendor/dnd5emonster/ca/template.php ================================================ 'Acciones', 'armor_class' => 'Clase de Armadura', 'at_will' => 'A voluntad', 'cha' => 'CAR', 'challenge_rating' => 'Desafío', 'con' => 'CON', 'condition_immunities' => 'Inmunidad a las condiciones', 'damage_immunities' => 'Inmunidad al daño', 'damage_resistance' => 'Resistencia al daño', 'dex' => 'DES', 'fields' => [ 'alignment' => 'Alineamiento', 'armor_class' => 'Clase de Armadura', 'cha' => 'CAR', 'challenge_rating' => 'Desafío', 'con' => 'CON', 'condition_immunities' => 'Inmunidad a las condiciones', 'creature_race' => 'Raza', 'damage_immunities' => 'Inmunidad al daño', 'damage_resistances' => 'Resistencia al daño', 'dex' => 'DES', 'hit_points' => 'Puntos de Golpe', 'int' => 'INT', 'languages' => 'Lenguajes', 'multiattack' => 'Ataque Múltiple', 'saving_throws' => 'Tiradas de Salvación', 'senses' => 'Sentidos', 'size' => 'Tamaño', 'skills' => 'Habilidades', 'speed' => 'Velocidad', 'spells_at_will' => 'Hechizos a voluntad', 'str' => 'FUE', 'wis' => 'SAB', ], 'hit_points' => 'Puntos de Golpe', 'innate_spellcasting' => 'Lanzador Innato de Conjuros', 'int' => 'INT', 'languages' => 'Lenguajes', 'legendary_actions' => 'Acciones Legendarias', 'legendary_resistance_count' => 'Resistencia Legendaria|Resistencia Legendaria (:count/Día)', 'magic_resistance' => 'Resistencia Mágica', 'magic_weapons' => 'Armas Mágicas', 'multiattack' => 'Ataque Múltiple', 'name' => 'D&D 5e Monster (ESP)', 'reactions' => 'Reacciones', 'regeneration' => 'Regeneración', 'saving_throws' => 'Tiradas de Salvación', 'senses' => 'Sentidos', 'skills' => 'Habilidades', 'speed' => 'Velocidad', 'spells_1' => '1/día cada uno', 'spells_3' => '3/día cada uno', 'str' => 'FUE', 'values' => [ 'ability_1' => 'Teleportar. {name} se teleporta mágicamente, junto con cualquier equipo que vista o cargue, hasta una distancia de 120 pies a un espacio no ocupado que pueda ver.', 'armor_class' => '19 (armadura natural)', 'attack_1' => 'Garras. Ataque cuerpo a cuerpo: +1 para golpear, alcance de 15 pies, un objetivo. Impacto: 14 (2d6 + 4) daño cortante.', 'attack_2' => 'Aguijón. Ataque cuerpo a cuerpo: +1 para golpear, alcance de 5 pies, un objetivo. Impacto: 5 (1d4 + 2) daño perforante.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'encantado, apresado', 'damage_immunities' => 'frío, fuego', 'damage_resistances' => 'perforante', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => 'La característica para el lanzamiento de conjuros es Carisma. {name} puede lanzar los siguientes conjuros de forma innata, que no requieren componentes materiales:', 'int' => '8 (-1)', 'languages' => 'común, infernal', 'legendary_action_1' => 'Mirada Aterradora (Requiere 2 Acciones). {name} fija su mirada en una criatura que pueda ver a 10 pies. El objetivo debe superar una tirada de salvación de Sabiduría CD 18 contra esta magia o quedará asustado durante un minuto. El objetivo asustado puede repetir la tirada de salvación al final de cada uno de sus turnos. Si tiene éxito en la tirada de salvación o el efecto termina por sí mismo, el objetivo será inmune a la mirada aterradora durante las próximas 24 horas.', 'legendary_actions' => '{name} puede seleccionar 3 acciones legendarias, elegir entre las siguientes opciones. Solo se puede utilizar una acción legendaria a la vez y solo al final del turno de otra criatura. {name} recupera las acciones legendarias gastadas al inicio de su turno.', 'legendary_resistance' => 'Si {name} falla una tirada de salvación, puede elegir tener éxito en su lugar.', 'magic_resistance' => '{name} tiene ventaja en las tiradas de salvación contra hechizos y otros efectos mágicos.', 'magic_weapons' => 'Los ataques de {name} son mágicos.', 'multiattack' => 'dos ataques: uno con sus garras y otro con su aguijón.', 'regeneration' => '{name} recupera 5 puntos de golpe al comienzo de su turno.', 'saving_throws' => 'Con +5', 'senses' => 'visión en la oscuridad 60 pies', 'size' => 'Mediano', 'skills' => 'Atletismo +4', 'speed' => '30 ft.', 'spells_at_will' => 'alterar aspecto, detectar mágia, geas, tormenta de hielo, invisibilidad', 'spells_once' => 'palabra sagrada, símbolo (de dolor solo)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'SAB', ]; ================================================ FILE: lang/vendor/dnd5emonster/cs/template.php ================================================ 'Akce', 'armor_class' => 'Druh brnění', 'at_will' => 'Libovolně', 'cha' => 'CHAR', 'challenge_rating' => 'Úroveň obtížnosti', 'con' => 'ODO', 'condition_immunities' => 'Imunita vůči prostředí', 'damage_immunities' => 'Imunita vůči zranění', 'damage_resistance' => 'Odolnost vůči zranění', 'dex' => 'OBR', 'fields' => [ 'alignment' => 'Přesvědčení', 'armor_class' => 'Druh brnění', 'cha' => 'CHAR', 'challenge_rating' => 'Úroveň obtížnosti', 'con' => 'ODO', 'condition_immunities' => 'Imunita vůči prostředí', 'creature_race' => 'Rasa stvoření', 'damage_immunities' => 'Imunita vůči zranění', 'damage_resistances' => 'Odolnost vůči zranění', 'dex' => 'OBR', 'hit_points' => 'Životy', 'int' => 'INT', 'languages' => 'Jazyky', 'multiattack' => 'Vácenásobný útok', 'saving_throws' => 'Záchranné hody', 'senses' => 'Smysly', 'size' => 'Velikost', 'skills' => 'Dovednosti', 'speed' => 'Rychlost', 'spells_at_will' => 'Libovolná kouzla', 'str' => 'SIL', 'wis' => 'MOU', ], 'hit_points' => 'Životy', 'innate_spellcasting' => 'Vrozené schopnosti kouzlení', 'int' => 'INT', 'languages' => 'Jazyky', 'legendary_actions' => 'Legendární akce', 'legendary_resistance_count' => 'Legendární odolnost|Legendární odolnost (:count/den)', 'magic_resistance' => 'Odolnost proti magii', 'magic_weapons' => 'Magické zbraně', 'multiattack' => 'Vácenásobný útok', 'name' => 'Nestvůra D&D 5. edice', 'reactions' => 'Reakce', 'regeneration' => 'Regenerace', 'saving_throws' => 'Záchranné hody', 'senses' => 'Smysly', 'skills' => 'Dovednosti', 'speed' => 'Rychlost', 'spells_1' => 'Jednou denně', 'spells_3' => 'Třikrát denně', 'str' => 'SIL', 'values' => [ 'ability_1' => 'Teleport. {name} magicky přenese, společně s vybavením, které postava nese nebo má u sebe, na vzdálenost až 40m na volné místo v dohledu.', 'armor_class' => '19 (přirozené brnění)', 'attack_1' => 'Drápy. Útok na blízko. +1 k zásahu, dosah 4,5m, jeden cíl. Zranění 14 (2k6+4) sečné zranění.', 'attack_2' => 'Žihadlo. Útok na blízko. +1 k zásahu, dosah 1,5m, jeden cíl. Zranění 5 (1k4+2) bodné zranění.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 zkušeností)', 'con' => '10 (+0)', 'condition_immunities' => 'omámený, zachycený', 'damage_immunities' => 'mráz, oheň', 'damage_resistances' => 'bodné zranění', 'dex' => '12 (+1)', 'hit_points' => '139 (12k12 + 55)', 'innate_spellcasting' => 'Vrozenou schopnost kouzlení {name} určuje Charisma (záchranný hod proti třídě obtížnosti (DC) 12). Mohou přirozeně sesílat následující kouzla bez nároků na hmotné složky.', 'int' => '8 (-1)', 'languages' => 'běžné, pekelné', 'legendary_action_1' => 'Strašidelná tvář. {name} zacílí na jednu bytost v dohledu do vzdálenosti 20m. Pokud cíl vidí {name}, musí cíl uspět v záchranném hodu na Moudrost proti třídě obtížnosti (DC) 13, jinak bude vyděšen vzhledem {name} do konce svého následujícího tahu.', 'legendary_actions' => '{name} může provést 2 legendární akce. Vybrat si může z následujících akci. Najednou lze provést jen jednu legendární akci a vždy jen na konci tahu jiné bytosti. {name} znovu může akci použít se začátkem svého následujícího kola.', 'legendary_resistance' => 'Pokud {name} není úspěšný v záchranném hodu, může si přesto zvolit úspěch.', 'magic_resistance' => '{name} získá výhodu při záchranných hodech proti kouzlům a dalším magickým efektům.', 'magic_weapons' => 'Zbraň postavy {name} nyní útočí jako kouzelná.', 'multiattack' => 'Dva útoky: jeden drápy a druhý žihadlem.', 'regeneration' => '{name} si na začátku kola vyléčí 5 životů.', 'saving_throws' => 'Odo +5', 'senses' => 'vnímání naslepo na 20m', 'size' => 'Střední', 'skills' => 'Atletika +4', 'speed' => '10 m.', 'spells_at_will' => 'Změnit sebe, najít magii, strach, ledová bouře, neviditelnost', 'spells_once' => 'božské slovo, symbol (pouze bolest)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'MOU', ]; ================================================ FILE: lang/vendor/dnd5emonster/de/template.php ================================================ 'Aktionen', 'armor_class' => 'Rüstungsklasse', 'at_will' => 'Beliebig oft', 'cha' => 'CHA', 'challenge_rating' => 'Herausforderungsgrad', 'con' => 'KON', 'condition_immunities' => 'Zustandsimmunitäten', 'damage_immunities' => 'Schadensimmunitäten', 'damage_resistance' => 'Schadensresistenzen', 'dex' => 'GES', 'fields' => [ 'alignment' => 'Gesinnung', 'armor_class' => 'Rüstungsklasse', 'cha' => 'CHA', 'challenge_rating' => 'Herausforderungsgrad', 'con' => 'Kon', 'condition_immunities' => 'Zustandsimmunitäten', 'creature_race' => 'Kreaturenvolk', 'damage_immunities' => 'Schadensimmunitäten', 'damage_resistances' => 'Schadensresistenzen', 'dex' => 'GES', 'hit_points' => 'Trefferpunkte', 'int' => 'INT', 'languages' => 'Sprachen', 'multiattack' => 'Mehrfachangriff', 'saving_throws' => 'Rettungswürfe', 'senses' => 'Sinne', 'size' => 'Größe', 'skills' => 'Fertigkeiten', 'speed' => 'Bewegungsrate', 'spells_at_will' => 'Zauber beliebig oft', 'str' => 'STR', 'wis' => 'WEI', ], 'hit_points' => 'Trefferpunkte', 'innate_spellcasting' => 'Angeborenes Zauberwirken', 'int' => 'INT', 'languages' => 'Sprachen', 'legendary_actions' => 'Legendäre Aktionen', 'legendary_resistance_count' => 'Legendäre Resistenzen|Legendäre Resistenzen (:count/Day)', 'magic_resistance' => 'Magieresistenz', 'magic_weapons' => 'Magische Waffen', 'multiattack' => 'Mehrfachangriff', 'name' => 'D&D 5e Monster', 'reactions' => 'Reaktionen', 'regeneration' => 'Regeneration', 'saving_throws' => 'Rettungswürfe', 'senses' => 'Sinne', 'skills' => 'Fertigkeiten', 'speed' => 'Bewegungsrate', 'spells_1' => '1/Tag', 'spells_3' => '3/Tag', 'str' => 'STÄ', 'values' => [ 'ability_1' => 'Teleportieren. {name} teleportiert sich magisch mit jeglicher Ausrüstung die die Kreatur trägt oder in der Hand hält bis zu 36 Meter in einen nicht besetzten Bereich, den sie sehen kann.', 'armor_class' => '19 (natürliche Rüstung)', 'attack_1' => 'Klauen. Nahkampf-Waffenangriff: +1 zum Treffen, Reichweite 4,50m, ein Ziel. Treffer: 14 (2W6 + 4) Hiebschaden.', 'attack_2' => 'Stachel. Nahkampf-Waffenangriff: +1 zum Treffen, Reichweite 1,50m, ein Ziel. Treffer: 5 (1W4 + 2) Stichschaden.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'verzaubert, gepackt', 'damage_immunities' => 'Kälte, Feuer', 'damage_resistances' => 'Stich', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => 'Das Attribute zum Wirken angeborener Zauber für {name} ist Charisma (Zauberrettungswurf-SC 12). {name} kann angeboren die folgenden Zauber wirken, wobei er keine Materialkomponenten verbraucht.', 'int' => '8 (-1)', 'languages' => 'Gemeinsprache, Infernalisch', 'legendary_action_1' => 'Furchterregendes Gesicht. {name} wählt eine Kreatur aus, die er sehen kann und die sich innerhalb von 18m befindet. Falls das Ziel {name} sehen kann, muss ihm ein Weisheits-Rettungswurf gegen SG 13 gelingen oder es ist bis zum Ende seines nächsten Zuges verängstigt.', 'legendary_actions' => '{name} kann zwei legendäre Aktionen durchführen, wobei er aus den unten beschrieben auswählt. Er kann nur eine legendäre Option auf einmal verwenden, und nur am Ende eines Zuges eines anderen Charakters. {name} erhält verbrauchte legendäre Aktionen zu Beginn seines Zuges zurück.', 'legendary_resistance' => 'Wenn {name} einen Rettungswurf nicht schafft, kann er sich stattdessen entscheiden, ihn zu schaffen.', 'magic_resistance' => '{name} hat Vorteil bei Rettungswürfen gegen Zauber oder andere magische Effekte.', 'magic_weapons' => '{name}s Waffenangriffe sind magisch.', 'multiattack' => 'zwei Angriffe: einen mit seinen Klauen und einen mit seinem Stachel.', 'regeneration' => '{name} bekommt am Anfang seines Zuges 5 Trefferpunkte zurück.', 'saving_throws' => 'Kon +5', 'senses' => 'Blindsicht 18m', 'size' => 'Mittelgroß', 'skills' => 'Athletik +4', 'speed' => '9m', 'spells_at_will' => 'Gestalt verändern, Magie entdecken, Furcht, Eissturm, Unsichtbarkeit', 'spells_once' => 'Göttliches Wort, Symbol (nur Schmerz)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'WEI', ]; ================================================ FILE: lang/vendor/dnd5emonster/en/template.php ================================================ 'Actions', 'armor_class' => 'Armor Class', 'at_will' => 'At will', 'cha' => 'CHA', 'challenge_rating' => 'Challenge Rating', 'con' => 'CON', 'condition_immunities' => 'Condition Immunities', 'damage_immunities' => 'Damage Immunities', 'damage_resistance' => 'Damage Resistances', 'dex' => 'DEX', 'fields' => [ 'alignment' => 'Alignment', 'armor_class' => 'Armor Class', 'cha' => 'CHA', 'challenge_rating' => 'Challenge Rating', 'con' => 'CON', 'condition_immunities' => 'Condition Immunities', 'creature_race' => 'Creature Race', 'damage_immunities' => 'Damage Immunities', 'damage_resistances' => 'Damage Resistances', 'dex' => 'DEX', 'hit_points' => 'Hit Points', 'int' => 'INT', 'languages' => 'Languages', 'multiattack' => 'Multiattack', 'saving_throws' => 'Saving Throws', 'senses' => 'Senses', 'size' => 'Size', 'skills' => 'Skills', 'speed' => 'Speed', 'spells_at_will' => 'Spells at Will', 'str' => 'STR', 'wis' => 'WIS', ], 'hit_points' => 'Hit Points', 'innate_spellcasting' => 'Innate Spellcasting', 'int' => 'INT', 'languages' => 'Languages', 'legendary_actions' => 'Legendary Actions', 'legendary_resistance_count' => 'Legendary Resistance|Legendary Resistance (:count/Day)', 'magic_resistance' => 'Magic Resistance', 'magic_weapons' => 'Magic Weapons', 'multiattack' => 'Multiattack', 'name' => 'D&D 5e Monster', 'reactions' => 'Reactions', 'regeneration' => 'Regeneration', 'saving_throws' => 'Saving Throws', 'senses' => 'Senses', 'skills' => 'Skills', 'speed' => 'Speed', 'spells_1' => '1/day each', 'spells_3' => '3/day each', 'str' => 'STR', 'values' => [ 'ability_1' => 'Teleport. {name} magically teleports, along with any equipment they are wearing and carrying, up to 120 feet to an unoccupied space they can see.', 'armor_class' => '19 (natural armor)', 'attack_1' => 'Claws. Melee Weapon Attack: +1 to hit, reach 15 ft., one target. Hit: 14 (2d6 + 4) slashing damage.', 'attack_2' => 'Stinger. Melee Weapon Attack: +1 to hit, reach 5 ft., one target. Hit: 5 (1d4 + 2) piercing damage.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'charmed, grappled', 'damage_immunities' => 'cold, fire', 'damage_resistances' => 'piercing', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => '{name}\'s innate spellcasting ability is Charisma (spell save DC12). They can innately cast the following spells, requiring no material components.', 'int' => '8 (-1)', 'languages' => 'common, infernal', 'legendary_action_1' => 'Scary Face. {name} targets one creature they can see within 60 feet of them. If the target can see {name}, the target must succeed on a DC 13 Wisdom saving throw or become frightened of {name} until the end of its next turn.', 'legendary_actions' => '{name} can take 2 legendary actions, choosing from the options below. Only one legendary action option can be used at a time and only at the end of another creature\'s turn. {name} regains spent legendary actions at the start of their turn.', 'legendary_resistance' => 'If {name} fails a saving throw, they can choose to succeed instead.', 'magic_resistance' => '{name} has advantage on saving throws against spells and other magical effects.', 'magic_weapons' => '{name}\'s weapon attacks are magical.', 'multiattack' => 'two attacks: one with its claws and one with its stinger.', 'regeneration' => '{name} regains 5 hit points at the start of its turn.', 'saving_throws' => 'Con +5', 'senses' => 'blindsight 60 ft', 'size' => 'Medium', 'skills' => 'Athletics +4', 'speed' => '30 ft.', 'spells_at_will' => 'alter self, detect magic, fear, ice storm, invisibility', 'spells_once' => 'divine word, symbol (pain only)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'WIS', ]; ================================================ FILE: lang/vendor/dnd5emonster/es/template.php ================================================ 'Acciones', 'armor_class' => 'Clase de Armadura', 'at_will' => 'A voluntad', 'cha' => 'CAR', 'challenge_rating' => 'Desafío', 'con' => 'CON', 'condition_immunities' => 'Inmunidad a las condiciones', 'damage_immunities' => 'Inmunidad al daño', 'damage_resistance' => 'Resistencia al daño', 'dex' => 'DES', 'fields' => [ 'alignment' => 'Alineamiento', 'armor_class' => 'Clase de Armadura', 'cha' => 'CAR', 'challenge_rating' => 'Desafío', 'con' => 'CON', 'condition_immunities' => 'Inmunidad a las condiciones', 'creature_race' => 'Raza', 'damage_immunities' => 'Inmunidad al daño', 'damage_resistances' => 'Resistencia al daño', 'dex' => 'DES', 'hit_points' => 'Puntos de Golpe', 'int' => 'INT', 'languages' => 'Lenguajes', 'multiattack' => 'Ataque Múltiple', 'saving_throws' => 'Tiradas de Salvación', 'senses' => 'Sentidos', 'size' => 'Tamaño', 'skills' => 'Habilidades', 'speed' => 'Velocidad', 'spells_at_will' => 'Hechizos a voluntad', 'str' => 'FUE', 'wis' => 'SAB', ], 'hit_points' => 'Puntos de Golpe', 'innate_spellcasting' => 'Lanzador Innato de Conjuros', 'int' => 'INT', 'languages' => 'Lenguajes', 'legendary_actions' => 'Acciones Legendarias', 'legendary_resistance_count' => 'Resistencia Legendaria|Resistencia Legendaria (:count/Día)', 'magic_resistance' => 'Resistencia Mágica', 'magic_weapons' => 'Armas Mágicas', 'multiattack' => 'Ataque Múltiple', 'name' => 'D&D 5e Monster (ESP)', 'reactions' => 'Reacciones', 'regeneration' => 'Regeneración', 'saving_throws' => 'Tiradas de Salvación', 'senses' => 'Sentidos', 'skills' => 'Habilidades', 'speed' => 'Velocidad', 'spells_1' => '1/día cada uno', 'spells_3' => '3/día cada uno', 'str' => 'FUE', 'values' => [ 'ability_1' => 'Teleportar. {name} se teleporta mágicamente, junto con cualquier equipo que vista o cargue, hasta una distancia de 120 pies a un espacio no ocupado que pueda ver.', 'armor_class' => '19 (armadura natural)', 'attack_1' => 'Garras. Ataque cuerpo a cuerpo: +1 para golpear, alcance de 15 pies, un objetivo. Impacto: 14 (2d6 + 4) daño cortante.', 'attack_2' => 'Aguijón. Ataque cuerpo a cuerpo: +1 para golpear, alcance de 5 pies, un objetivo. Impacto: 5 (1d4 + 2) daño perforante.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'encantado, apresado', 'damage_immunities' => 'frío, fuego', 'damage_resistances' => 'perforante', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => 'La característica para el lanzamiento de conjuros es Carisma. {name} puede lanzar los siguientes conjuros de forma innata, que no requieren componentes materiales:', 'int' => '8 (-1)', 'languages' => 'común, infernal', 'legendary_action_1' => 'Mirada Aterradora (Requiere 2 Acciones). {name} fija su mirada en una criatura que pueda ver a 10 pies. El objetivo debe superar una tirada de salvación de Sabiduría CD 18 contra esta magia o quedará asustado durante un minuto. El objetivo asustado puede repetir la tirada de salvación al final de cada uno de sus turnos. Si tiene éxito en la tirada de salvación o el efecto termina por sí mismo, el objetivo será inmune a la mirada aterradora durante las próximas 24 horas.', 'legendary_actions' => '{name} puede seleccionar 3 acciones legendarias, elegir entre las siguientes opciones. Solo se puede utilizar una acción legendaria a la vez y solo al final del turno de otra criatura. {name} recupera las acciones legendarias gastadas al inicio de su turno.', 'legendary_resistance' => 'Si {name} falla una tirada de salvación, puede elegir tener éxito en su lugar.', 'magic_resistance' => '{name} tiene ventaja en las tiradas de salvación contra hechizos y otros efectos mágicos.', 'magic_weapons' => 'Los ataques de {name} son mágicos.', 'multiattack' => 'dos ataques: uno con sus garras y otro con su aguijón.', 'regeneration' => '{name} recupera 5 puntos de golpe al comienzo de su turno.', 'saving_throws' => 'Con +5', 'senses' => 'visión en la oscuridad 60 pies', 'size' => 'Mediano', 'skills' => 'Atletismo +4', 'speed' => '30 ft.', 'spells_at_will' => 'alterar aspecto, detectar mágia, geas, tormenta de hielo, invisibilidad', 'spells_once' => 'palabra sagrada, símbolo (de dolor solo)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'SAB', ]; ================================================ FILE: lang/vendor/dnd5emonster/fr/template.php ================================================ 'Actions', 'armor_class' => 'Classe d\'Armure', 'at_will' => 'A volonté', 'cha' => 'CHA', 'challenge_rating' => 'Dangerosité', 'con' => 'CON', 'condition_immunities' => 'Immunité contre les états', 'damage_immunities' => 'Immunité contre_les_dégâts', 'damage_resistance' => 'Résistance aux dégâts', 'dex' => 'DEX', 'fields' => [ 'alignment' => 'Alignement', 'armor_class' => 'Classe d\'Armure', 'cha' => 'CHA', 'challenge_rating' => 'Dangerosité', 'con' => 'CON', 'condition_immunities' => 'Immunité contre les états', 'creature_race' => 'Race de la créature', 'damage_immunities' => 'Immunité contre les dégâts', 'damage_resistances' => 'Résistances aux dégâts', 'dex' => 'DEX', 'hit_points' => 'Points de Vie', 'int' => 'INT', 'languages' => 'Langues', 'multiattack' => 'Attaques multiples', 'saving_throws' => 'Jets de sauvegarde', 'senses' => 'Sens', 'size' => 'Taille', 'skills' => 'Compétences', 'speed' => 'Vitesse', 'spells_at_will' => 'Sorts à volonté', 'str' => 'FOR', 'wis' => 'SAG', ], 'hit_points' => 'Points de Vie', 'innate_spellcasting' => 'Incantation innée', 'int' => 'INT', 'languages' => 'Langues', 'legendary_actions' => 'Actions légendaires', 'legendary_resistance_count' => 'Résistance légendaire|Résistance légendaire (:count/jour)', 'magic_resistance' => 'Résistance à la magie', 'magic_weapons' => 'Armes magiques', 'multiattack' => 'Attaques multiples', 'name' => 'D&D 5e Monstre', 'reactions' => 'Réactions', 'regeneration' => 'Régénération', 'saving_throws' => 'Jets de Sauvegarde', 'senses' => 'Sens', 'skills' => 'Compétences', 'speed' => 'Vitesse', 'spells_1' => '1/jour chacun', 'spells_3' => '3/jour chacun', 'str' => 'FOR', 'values' => [ 'ability_1' => 'Téléportation. {name} se téléporte magiquement, avec tout l\'équipement qu\'il porte ou transporte, vers un espace inoccupé qu\'il peut voir et situé dans un rayon de 36 mètres autour de lui.', 'armor_class' => '19 (armure naturelle)', 'attack_1' => 'Griffes. Attaque au corps à corps avec une arme: +1 au toucher, allonge 4.50 mètres, une cible. Touché: 14 (2d6 + 4) dégâts tranchants.', 'attack_2' => 'Dard. Attaque au corps à corps avec une arme: +1 to hit, allonge 1.50 mètre, une cible. Touché: 5 (1d4 + 2) dégâts perforants.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'charmé, agrippé', 'damage_immunities' => 'froid, feu', 'damage_resistances' => 'perforant', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => '{name} a pour caractéristique d\'incantation le Charisme (jet de sauvegarde contre ses sorts DD 12). Il peut naturellement lancer les sorts suivants, sans avoir besoin de composants matériels.', 'int' => '8 (-1)', 'languages' => 'commun, infernal', 'legendary_action_1' => 'Visage effrayant. {name} cible une créature qu\'il peut voir à une distance maximum de 18 mètres. Si la cible peut voir {name}, elle doit réussir un jet de sauvegarde de Sagesse de DD13 ou devenir effrayée par {name} jusqu\'à la fin de son tour suivant.', 'legendary_actions' => '{name} peut effectuer 2 actions légendaires, qu\'il choisit parmi celles décrites ci-dessous. Il ne peut en effectuer qu\'une à la fois et uniquement à la fin du tour d\'une autre créature. {name} récupère au début de son tour l\'utilisation des actions légendaires déjà effectuées.', 'legendary_resistance' => '{name} peut remplacer l_échec d\'un de des jets de sauvegardes par une réussite.', 'magic_resistance' => '{name} a l\'avantage à ses jets de sauvegarde contre les sorts et autres effets magiques.', 'magic_weapons' => 'Les attaques avec une arme de {name} sont magiques.', 'multiattack' => 'deux attaques: une avec ses griffes et une avec son dard.', 'regeneration' => '{name} gagne 5 points de vie au début de son tour.', 'saving_throws' => 'Con +5', 'senses' => 'vision aveugle 18 mètres', 'size' => 'Moyenne', 'skills' => 'Athlétisme +4', 'speed' => '9 mètres', 'spells_at_will' => 'Modification d\'apparence, détection de la magie, équipement, tempête de glace, invisibilité', 'spells_once' => 'Parole divine, symbole (douleur seulement)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'SAG', ]; ================================================ FILE: lang/vendor/dnd5emonster/gl/template.php ================================================ 'Accións', 'armor_class' => 'Clase de Armadura', 'at_will' => 'A vontade', 'cha' => 'CAR', 'challenge_rating' => 'Nivel de Desafío', 'con' => 'CON', 'condition_immunities' => 'Inmunidade a condicións', 'damage_immunities' => 'Inmunidade a tipos de dano', 'damage_resistance' => 'Resistencia a tipos de dano', 'dex' => 'DES', 'fields' => [ 'alignment' => 'Aliñamento', 'armor_class' => 'Clase de Armadura', 'cha' => 'CAR', 'challenge_rating' => 'Nivel de Desafío', 'con' => 'CON', 'condition_immunities' => 'Inmunidade a condicións', 'creature_race' => 'Raza', 'damage_immunities' => 'Inmunidade a tipos de dano', 'damage_resistances' => 'Resistencia a tipos de dano', 'dex' => 'DES', 'hit_points' => 'Puntos de Golpe', 'int' => 'INT', 'languages' => 'Linguas', 'multiattack' => 'Multiataque', 'saving_throws' => 'Tiradas de Salvación', 'senses' => 'Sentidos', 'size' => 'Tamaño', 'skills' => 'Habilidades', 'speed' => 'Velocidade', 'spells_at_will' => 'Feitizos a vontade', 'str' => 'FOR', 'wis' => 'SAB', ], 'hit_points' => 'Puntos de Golpe', 'innate_spellcasting' => 'Feiticería innata', 'int' => 'INT', 'languages' => 'Linguas', 'legendary_actions' => 'Accións lendarias', 'legendary_resistance_count' => 'Resistencia lendaria|Resistencia lendaria (:count/día)', 'magic_resistance' => 'Resistencia máxica', 'magic_weapons' => 'Armas máxicas', 'multiattack' => 'Multiataque', 'name' => 'Monstro D&D 5e', 'reactions' => 'Reaccións', 'regeneration' => 'Rexeneración', 'saving_throws' => 'Tiradas de Salvación', 'senses' => 'Sentidos', 'skills' => 'Habilidades', 'speed' => 'Velocidade', 'spells_1' => '1/día cada un', 'spells_3' => '3/día cada un', 'str' => 'FOR', 'values' => [ 'ability_1' => 'Teleporte. {name} telepórtase máxicamente, xunto con calquer equipamento que leve, ata 120 pés nun espazo non ocupado que poida ver.', 'armor_class' => '19 (armadura natural)', 'attack_1' => 'Garras. Ataque corpo a corpo: +1 para golpear, alcance de 15 pés, un obxetivo. Impacto: 14 (2d6 + 4) dano cortante.', 'attack_2' => 'Aguillón. Ataque corpo a corpo: +1 para golpear, alcance de 5 pés, un obxetivo. Impacto: 5 (1d4 + 2) dano perforante.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'encantado, apresado', 'damage_immunities' => 'frío, lume', 'damage_resistances' => 'perforante', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => 'A característica de lanzamento de feitizos innatos de {name} é Carisma (CD de salvación de maxia 12). Pode lanzar de forma innata os seguintes feitizos, sen precisar compoñentes materiais.', 'int' => '8 (-1)', 'languages' => 'común, infernal', 'legendary_action_1' => 'Mirada aterradora. {name} selecciona unha criatura que poida ver que esté a ata 60 pés de distancia. Se o obxetivo pode ver a {name}, o obxetivo debe ter éxito nunha tirada de salvación de Sabiduría con CD 13 ou ficar asustado de {name} ata o final du seu seguinte turno.', 'legendary_actions' => '{name} pode realizar 2 accións lendarias, escollendo das opcións de abaixo. Só unha opción de acción lendaria pode ser usada cada vez e ten que ser ao final do turno de outra criatura. {name} recupera as accións lendarias que gastou ao inicio do seu turno.', 'legendary_resistance' => 'Se {name} fallar unha tirada de salvación, poderá escoller ter éxito no seu lugar.', 'magic_resistance' => '{name} ten vantaxe en tiradas de salvación contra feitizos e outros efectos máxicos.', 'magic_weapons' => 'Os ataques con armas de {name} son máxicos.', 'multiattack' => 'dous ataques: un coas súas garras e outro co seu aguillón.', 'regeneration' => '{name} recupera 5 puntos de golpe ao inicio do seu turno.', 'saving_throws' => 'Con +5', 'senses' => 'Visión a cegas 60 pés', 'size' => 'Medio', 'skills' => 'Atletismo +4', 'speed' => '30 pés', 'spells_at_will' => 'alterar aspecto, detectar maxia, medo, tormenta de xelo, invisibilidade', 'spells_once' => 'palabra sagrada, símbolo (só de dor)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'SAB', ]; ================================================ FILE: lang/vendor/dnd5emonster/hu/template.php ================================================ 'Akciók', 'armor_class' => 'Védelem', 'at_will' => 'Akaratból', 'cha' => 'KAR', 'challenge_rating' => 'Kihívási ráta', 'con' => 'ÁLL', 'condition_immunities' => 'Kondíció immunitások', 'damage_immunities' => 'Sebzésimmunitások', 'damage_resistance' => 'Sebzésellenállások', 'dex' => 'ÜGY', 'fields' => [ 'alignment' => 'Jellem', 'armor_class' => 'Védelem', 'cha' => 'KAR', 'challenge_rating' => 'Kihívási ráta', 'con' => 'ÁLL', 'condition_immunities' => 'Kondíció immunitások', 'creature_race' => 'Teremtmény faja', 'damage_immunities' => 'Sebzésimmunitások', 'damage_resistances' => 'Sebzésellenállások', 'dex' => 'ÜGY', 'hit_points' => 'Harcpontok', 'int' => 'INT', 'languages' => 'Nyelvek', 'multiattack' => 'Többszörös támadás', 'saving_throws' => 'Mentődobások', 'senses' => 'Érzékek', 'size' => 'Méret', 'skills' => 'Képzettségek', 'speed' => 'Sebesség', 'spells_at_will' => 'Varázslatok akaratból', 'str' => 'ERŐ', 'wis' => 'BÖL', ], 'hit_points' => 'Harcpontok', 'innate_spellcasting' => 'Ösztönös varázslás', 'int' => 'INT', 'languages' => 'Nyelvek', 'legendary_actions' => 'Legendás akciók', 'legendary_resistance_count' => 'Legendás ellenállás|Legendás ellenállás (:cound/Day)', 'magic_resistance' => 'Mágaiellenállás', 'magic_weapons' => 'Mágikus fegyverek', 'multiattack' => 'Többszörös támadás', 'name' => 'D&D 5e Szörny', 'reactions' => 'Reakciók', 'regeneration' => 'Regeneráció', 'saving_throws' => 'Mentődobások', 'senses' => 'Érzékek', 'skills' => 'Képzettségek', 'speed' => 'Sebesség', 'spells_1' => 'Napi 1', 'spells_3' => 'Napi 3', 'str' => 'ERŐ', 'values' => [ 'ability_1' => 'Teleport. {name} mágikusan teleportál minden viselt felszerelésével együtt 120 lábnyira egy olyan helyre, ami üres és amit lát.', 'armor_class' => '19 (természetes páncél)', 'attack_1' => 'Karmok. Közelharci támadás: +1, elérés 15 láb, egy célpont. Találat: 14 (2d6 + 4) vágó sebzés.', 'attack_2' => 'Fullánk. Közelharci támadás: +1, elérés 5 láb, egy célpont. Találat: 5 (1d4 + 2) szúró sebzés.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'elbűvölt, lefogott', 'damage_immunities' => 'hideg, tűz', 'damage_resistances' => 'szúró', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => '{name} ösztönös varázslás képessége karizma alapú (varázslatmentő NH12). Ösztönösen el tudja varázsolni a következő varázslatokat anyagi komponens nélkül.', 'int' => '8 (-1)', 'languages' => 'közös, pokoli', 'legendary_action_1' => 'Ijesztő arc. {name} megcéloz egy teremtmény, amit lát és 60 lábon belül tartózkodik. Ha a célpont látja őt, a célpontnak 13-as nehézségű Bölcsesség mentődobást kell tennie, különben megijed {name} lénytől annak következő köréig.', 'legendary_actions' => '{name} kaphat két legendás akciót az alábbiakból választva. Egyszerre csak egy legendás akciót tehet, és csak egy másik teremtmény körének végén. {name} visszakapja a legendás akciót a köre elején.', 'legendary_resistance' => 'Ha {name} elrontja egy mentődobását, választhatja, hogy inkább mégis sikerült.', 'magic_resistance' => '{name} előnnyel dobja a mentődobásait varázslatok és mágikus hatások ellen.', 'magic_weapons' => '{name} fegyveres támadásai mágikusak.', 'multiattack' => 'Két támadás: egy a karmaival, egy pedig a fullánkjával.', 'regeneration' => '{name} visszakap 4 HP-t a köre kezdetén.', 'saving_throws' => 'Áll +5', 'senses' => 'sötétben látás 60 láb', 'size' => 'Közepes', 'skills' => 'Atlétika +4', 'speed' => '30 láb', 'spells_at_will' => 'önátalakítás, mágia érzékelése, félelem, jégvihar, láthatatlanság', 'spells_once' => 'szent szó, szimbólum (csak fájdalom)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'BÖL', ]; ================================================ FILE: lang/vendor/dnd5emonster/it/template.php ================================================ 'Azioni', 'armor_class' => 'Classe Armatura', 'at_will' => 'A volontà', 'cha' => 'CAR', 'challenge_rating' => 'Sfida', 'con' => 'COS', 'condition_immunities' => 'Immunità alle Condizioni', 'damage_immunities' => 'Immunità ai Danni', 'damage_resistance' => 'Resistenze ai Danni', 'dex' => 'DES', 'fields' => [ 'alignment' => 'Allineamento', 'armor_class' => 'Classe Armatura', 'cha' => 'CAR', 'challenge_rating' => 'Sfida', 'con' => 'COS', 'condition_immunities' => 'Immunità alle Condizioni', 'creature_race' => 'Razza', 'damage_immunities' => 'Immunità ai Danni', 'damage_resistances' => 'Resistenze ai Danni', 'dex' => 'DES', 'hit_points' => 'Punti Ferita', 'int' => 'INT', 'languages' => 'Linguaggi', 'multiattack' => 'Multiattacco', 'saving_throws' => 'Tiri Salvezza', 'senses' => 'Sensi', 'size' => 'Taglia', 'skills' => 'Abilità', 'speed' => 'Velocità', 'spells_at_will' => 'Incantesimi a Volontà', 'str' => 'FOR', 'wis' => 'SAG', ], 'hit_points' => 'Punti Ferita', 'innate_spellcasting' => 'Incantesimi Innati', 'int' => 'INT', 'languages' => 'Linguaggi', 'legendary_actions' => 'Azioni Leggendarie', 'legendary_resistance_count' => 'Resistenza Leggendaria|Resistenza Leggendaria (:count/Giorno)', 'magic_resistance' => 'Resistenza alla Magia', 'magic_weapons' => 'Armi Magiche', 'multiattack' => 'Multiattacco', 'name' => 'Mostro D&D 5e', 'reactions' => 'Reazioni', 'regeneration' => 'Rigenerazione', 'saving_throws' => 'Tiri Salvezza', 'senses' => 'Sensi', 'skills' => 'Abilità', 'speed' => 'Velocità', 'spells_1' => '1/Giorno ciascuno', 'spells_3' => '3/Giorno ciascuno', 'str' => 'FOR', 'values' => [ 'ability_1' => 'Teletrasporto. {name} si teletrasporta magicamente, assieme a ogni oggetto che indossa o trasporta, fino a uno spazio libero situato entro 36 metri e che egli sia in grado di vedere.', 'armor_class' => '19 (armatura naturale)', 'attack_1' => 'Artigli. Attacco con Arma da Mischia +1 al tiro per colpire, portata 4,5 m, una creatura. Colpito: 14 (2d6 + 4) danni taglienti.', 'attack_2' => 'Pungiglione. Attacco con Arma da Mischia: +1 al tiro per colpire, portata 1,5 m, una creatura. Colpito: 5 (1d4 + 2) danni perforanti.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 PE)', 'con' => '10 (+0)', 'condition_immunities' => 'affascinato, afferrato', 'damage_immunities' => 'freddo, fuoco', 'damage_resistances' => 'perforante', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => 'La caratteristica da incantatore di {name} è Carisma (tiro salvezza degli incantesimi DC12). {name} può lanciare i seguenti incantesimi innati, che non richiedono alcuna componente materiale.', 'int' => '8 (-1)', 'languages' => 'comune, infernale', 'legendary_action_1' => 'Volto Terrificante. {name} bersaglia una creatura entro 18 metri e che sia in grado di vedere. Se il bersaglio può vedere {name}, deve superare un tiro salvezza su Saggezza con CD 13, altrimenti è spventato da {name} fino alla fine del suo prossimo turno.', 'legendary_actions' => '{name} può effettuare 2 azioni leggendarie, scelte tra le opzioni sottostanti. Può usare solo un’opzione di azione leggendaria alla volta e solo alla fine del turno di un’altra creatura.{name} recupera le azioni leggendarie spese all’inizio del proprio turno', 'legendary_resistance' => 'Se {name} fallisce un tiro salvezza, può scegliere invece di superarlo.', 'magic_resistance' => '{name} dispone di vantaggio ai tiri salvezza contro incantesimi e altri effetti magici.', 'magic_weapons' => 'Gli attacchi con arma di {name} sono magici.', 'multiattack' => 'due attacchi: uno con i suoi artigli e uno con il suo pungiglione.', 'regeneration' => '{name} recupera 5 punti ferita all\'inizio del suo turno.', 'saving_throws' => 'Cos +5', 'senses' => 'vista cieca 18 m', 'size' => 'Media', 'skills' => 'Atletica +4', 'speed' => '9 m', 'spells_at_will' => 'alterare sé stesso, individuazione del magico, tempesta di ghiaccio, invisibilità', 'spells_once' => 'parola divina, simbolo (solo Dolore)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'SAG', ]; ================================================ FILE: lang/vendor/dnd5emonster/pl/template.php ================================================ 'Akcje', 'armor_class' => 'Klasa Pancerza', 'at_will' => 'Bez ograniczeń', 'cha' => 'CHA', 'challenge_rating' => 'Stopień wyzwania', 'con' => 'KON', 'condition_immunities' => 'Niepodatność na stany', 'damage_immunities' => 'Niepodatność na obrażenia', 'damage_resistance' => 'Odporność na obrażenia', 'dex' => 'ZRC', 'fields' => [ 'alignment' => 'Charakter', 'armor_class' => 'Klasa Pancerza', 'cha' => 'CHA', 'challenge_rating' => 'Stopień wyzwania', 'con' => 'KON', 'condition_immunities' => 'Niepodatność na stany', 'creature_race' => 'Rasa istoty', 'damage_immunities' => 'Niepodatność na obrażenia', 'damage_resistances' => 'Odporność na obrażenia', 'dex' => 'ZRC', 'hit_points' => 'Punkty wytrzymałości', 'int' => 'INT', 'languages' => 'Języki', 'multiattack' => 'Atak wielokrotny', 'saving_throws' => 'Rzuty obronne', 'senses' => 'Zmysły', 'size' => 'Rozmiar', 'skills' => 'Umiejętności', 'speed' => 'Szybkość', 'spells_at_will' => 'Czary bez ograniczeń', 'str' => 'SIŁ', 'wis' => 'MDR', ], 'hit_points' => 'Punkty wytrzymałości', 'innate_spellcasting' => 'Wrodzone czarowanie', 'int' => 'INT', 'languages' => 'Języki', 'legendary_actions' => 'Legendarne akcje', 'legendary_resistance_count' => 'Legendarna odporność|Legendarna odporność (:count/dziennie)', 'magic_resistance' => 'Odporność na magię', 'magic_weapons' => 'Magiczna broń', 'multiattack' => 'Atak wielokrotny', 'name' => 'Powtór do D&D 5e', 'reactions' => 'Reakcje', 'regeneration' => 'Regeneracja', 'saving_throws' => 'Rzuty obronne', 'senses' => 'Zmysły', 'skills' => 'Umiejętności', 'speed' => 'Szybkość', 'spells_1' => '1/dzień każdy', 'spells_3' => '3/dzień każdy', 'str' => 'SIŁ', 'values' => [ 'ability_1' => 'Teleportacja. {name} magicznie się teleportuje wraz z całym noszonym ekwipunkiem, w odległe do 40 m., niezajęte miejsce.', 'armor_class' => '19 (naturalny pancerz)', 'attack_1' => 'Pazury. Atak wręcz bronią: +1 do trafienia, strefa ataku 5 metrów, jeden cel. Trafienie: 17 (2k6 + 4) obrażeń ciętych.', 'attack_2' => 'Użądlenie. Atak wręcz bronią" +1 do trafienia, strefa ataku 1,5 metra, jeden cel. Trafienie: 5 (1k4 + 2) obrażeń kłutych.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 PD)', 'con' => '10 (+0)', 'condition_immunities' => 'zauroczony, pochwycony', 'damage_immunities' => 'zimno, ogień', 'damage_resistances' => 'klute', 'dex' => '12 (+1)', 'hit_points' => '139 (12k12 + 55)', 'innate_spellcasting' => 'Cechą bazową wrodzonego czarowania {name} jest Charyzma (ST rzutu obronnego to 12). Potrafi instynktownie rzucać następujące czary, nie używając komponentów materialnych.', 'int' => '8 (-1)', 'languages' => 'wspólny, piekielny', 'legendary_action_1' => 'Straszne oblicze. {name} wybiera jedną istotę którą widzi odległą o nie więcej niż 20 metrów. Jeżeli cel również ma w zasięgu wzroku {name}, musi wykonać rzut obronny na Mądrość o ST 13 albo {name} przeraża go do początku jego następnej tury.', 'legendary_actions' => '{name} może wykonać 3 legendarne akcje wybierając z poniższych. Tylko jedna akcja legendarna może zostać użyta na raz i tylko pod koniec tury innego stworzenia. {name} odzyskuje wykorzystane akcje legendarne na początku swojej tury.', 'legendary_resistance' => 'Jeżeli {name} nie zdoła wykonać rzutu obronnego, może zamiast tego uznać że się powiódł.', 'magic_resistance' => '{name} na przewagę w rzutach obronnych przeciw czarom i innym efektom magicznym.', 'magic_weapons' => '{name} wykonuje ataki bronią, które są magiczne.', 'multiattack' => 'dwa ataki: jeden pazurami i jeden użądleniem.', 'regeneration' => '{name} odzyskuje 5 punktów wytrzymałości na początku swojej tury.', 'saving_throws' => 'Kon +5', 'senses' => 'ślepowidzenie 30 m.', 'size' => 'Średni', 'skills' => 'Atletyka +4', 'speed' => '10 m.', 'spells_at_will' => 'zmiana siebie, wykrycie magii, strach, burza lodu, niewidzialność', 'spells_once' => 'słowo boże, symbol (tylko ból)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'MDR', ]; ================================================ FILE: lang/vendor/dnd5emonster/pt-BR/template.php ================================================ 'Ações', 'armor_class' => 'Classe de Armadura', 'at_will' => 'Sem limite', 'cha' => 'CAR', 'challenge_rating' => 'Nível de desafio', 'con' => 'CON', 'condition_immunities' => 'Imunidade às condições', 'damage_immunities' => 'Imunidade os danos', 'damage_resistance' => 'Resistência a dano', 'dex' => 'DES', 'fields' => [ 'alignment' => 'Alinhamento', 'armor_class' => 'Classe de Armadura', 'cha' => 'CAR', 'challenge_rating' => 'Nível de Desafio', 'con' => 'CON', 'condition_immunities' => 'Imunidade às condições', 'creature_race' => 'Raça da criatura', 'damage_immunities' => 'Imunidade os danos', 'damage_resistances' => 'Resistência aos danos', 'dex' => 'DES', 'hit_points' => 'Pontos de Vida', 'int' => 'INT', 'languages' => 'Idiomas', 'multiattack' => 'Ataques Múltiplos', 'saving_throws' => 'Testes de Resistência', 'senses' => 'Sentidos', 'size' => 'Tamanho', 'skills' => 'Perícias', 'speed' => 'Velocidade', 'spells_at_will' => 'Magias à vontade', 'str' => 'FOR', 'wis' => 'SAB', ], 'hit_points' => 'Pontos de Vida', 'innate_spellcasting' => 'Conjuração Inata', 'int' => 'INT', 'languages' => 'Idiomas', 'legendary_actions' => 'Ações Lendárias', 'legendary_resistance_count' => 'Resistência Lendária|Resistência Lendária (:count/Dia)', 'magic_resistance' => 'Resistência Mágica', 'magic_weapons' => 'Armas Mágicas', 'multiattack' => 'Ataques Múltiplos', 'name' => 'Monstro D&D 5e', 'reactions' => 'Reações', 'regeneration' => 'Regeneração', 'saving_throws' => 'Testes de Resistência', 'senses' => 'Sentidos', 'skills' => 'Perícias', 'speed' => 'Velocidade', 'spells_1' => '1/dia cada', 'spells_3' => '3/dia cada', 'str' => 'FOR', 'values' => [ 'ability_1' => 'Teleporte. {name} se teletransporta magicamente, junto com qualquer equipamento que esteja usando e carregando, até 36 metros para um espaço desocupado que possa ver.', 'armor_class' => '19 (armadura natural)', 'attack_1' => 'Garras. Ataque corpo a corpo com arma: +1 para atingir, alcance 4,5 m, um alvo. Acerto: 14 (2d6 + 4) de dano cortante', 'attack_2' => 'Ferrão. Ataque corpo a corpo com arma: +1 para atingir, alcance 1,5 m, um alvo. Acerto: 5 (1d4 + 2) de dano perfurante', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'enfeitiçado, garrado', 'damage_immunities' => 'frio, fogo', 'damage_resistances' => 'perfurante', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => 'A habilidade inata de lançar feitiços de {name} é Carisma (CD de resistência de magia 12). Eles podem lançar inatamente os seguintes feitiços, não exigindo componentes materiais.', 'int' => '8(-1)', 'languages' => 'Comum, infernal', 'legendary_action_1' => 'Rosto assustador. {name} tem como alvo uma criatura que ele possa ver a até 18 metros deles. Se o alvo puder ver {nome}, ele deve passar em um teste de resistência de Sabedoria CD 13 ou ficar com medo de {nome} até o final de seu próximo turno.', 'legendary_actions' => '{name} pode realizar 2 ações lendárias, escolhendo uma das opções abaixo. Apenas uma opção de ação lendária pode ser usada por vez e apenas no final do turno de outra criatura. {name} recupera as ações lendárias gastas no início de seu turno.', 'legendary_resistance' => 'Se {nome} falhar em um teste de resistência, ele pode escolher obter sucesso, no lugar', 'magic_resistance' => '{name} tem vantagem em testes de resistência contra magias e outros efeitos mágicos.', 'magic_weapons' => 'Os ataques com armas de {name} são mágicos.', 'multiattack' => 'dois ataques: um com suas garras e outro com seu ferrão.', 'regeneration' => '{name} regenera 5 pontos de vida no início de seu turno', 'saving_throws' => 'Con +5', 'senses' => 'Percepção às cegas 18 m', 'size' => 'Médio', 'skills' => 'Ateltismo +4', 'speed' => '9 m', 'spells_at_will' => 'Alterar, Detectar Magia, Medo, Tempestade de Gelo, Invisibilidade', 'spells_once' => 'Palavra Sagrada, Símbolo (apenas Dor)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'SAB', ]; ================================================ FILE: lang/vendor/dnd5emonster/ru/template.php ================================================ 'Действия', 'armor_class' => 'Класс доспеха', 'at_will' => 'Неограниченно', 'cha' => 'ХАР', 'challenge_rating' => 'Опасность', 'con' => 'ТЕЛ', 'condition_immunities' => 'Иммунитет к состояниям', 'damage_immunities' => 'Иммунитет к урону', 'damage_resistance' => 'Сопротивление к урону', 'dex' => 'ЛОВ', 'fields' => [ 'alignment' => 'Мировоззрение', 'armor_class' => 'Класс доспеха', 'cha' => 'ХАР', 'challenge_rating' => 'Опасность', 'con' => 'ТЕЛ', 'condition_immunities' => 'Иммунитет к состоянию', 'creature_race' => 'Раса существа', 'damage_immunities' => 'Иммунитет к урону', 'damage_resistances' => 'Сопротивление к урону', 'dex' => 'ЛОВ', 'hit_points' => 'Хиты', 'int' => 'ИНТ', 'languages' => 'Языки', 'multiattack' => 'Мультиатака', 'saving_throws' => 'Спасброски', 'senses' => 'Чувства', 'size' => 'Размер', 'skills' => 'Навыки', 'speed' => 'Скорость', 'spells_at_will' => 'Неограниченные заклинания', 'str' => 'СИЛ', 'wis' => 'МДР', ], 'hit_points' => 'Хиты', 'innate_spellcasting' => 'Врожденное колдовство', 'int' => 'ИНТ', 'languages' => 'Языки', 'legendary_actions' => 'Легендарные действия', 'legendary_resistance_count' => 'Легендарное сопротивление|Легендарное сопротивление (:count/день)', 'magic_resistance' => 'Сопротивление магии', 'magic_weapons' => 'Магическое оружие', 'multiattack' => 'Мультиатака', 'name' => 'Монстр D&D 5 изд.', 'reactions' => 'Реакции', 'regeneration' => 'Регенерация', 'saving_throws' => 'Спасброски', 'senses' => 'Чувства', 'skills' => 'Навыки', 'speed' => 'Скорость', 'spells_1' => '1/день каждое', 'spells_3' => '3/день каждое', 'str' => 'СИЛ', 'values' => [ 'ability_1' => 'Телепортация. {name} магически телепортируется в незанятое пространство, которое может видеть в пределах 120 фт. от себя.', 'armor_class' => '19 (природный доспех)', 'attack_1' => 'Когти. Рукопашная атака оружием: +1 к попаданию, досягаемость 15 фт., одна цель. Попадание: 14 (2d6 + 4) рубящего урона.', 'attack_2' => 'Когти. Рукопашная атака оружием: +1 к попаданию, досягаемость 5 фт., одна цель. Попадание: 5 (1d4 + 2) колющего урона.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 опыта)', 'con' => '10 (+0)', 'condition_immunities' => 'очарованный, схваченный', 'damage_immunities' => 'холод, огонь', 'damage_resistances' => 'колющий', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => 'Базовой характеристикой {name} является Харизма (Сл спасброска от заклинания 21). Он может накладывать следующие заклинания, не нуждаясь в материальных компонентах.', 'int' => '8 (-1)', 'languages' => 'общий, инфернальный', 'legendary_action_1' => 'Страшное лицо. {name} атакует одно существо не дальше 60 фт., которое может видеть. Если существо видит {name}, то оно должно преуспеть в спасброске Мудрости Сл 13, иначе оно станет испуганным до конца его следующего хода.', 'legendary_actions' => '{name} может совершить 2 легендарных действия, выбирая из представленных ниже вариантов. За один раз можно использовать только одно легендарное действие, и только в конце хода другого существа. {name} восстанавливает использованные легендарные действия в начале своего хода.', 'legendary_resistance' => 'Если {name} проваливает спасбросок, он может вместо этого сделать спасбросок успешным.', 'magic_resistance' => '{name} совершает с преимуществом спасброски от заклинаний и прочих магических эффектов.', 'magic_weapons' => 'Атаки оружием {name} являются магическими.', 'multiattack' => 'две атаки: одна своими Когтями и одна своим Жалом.', 'regeneration' => '{name} восстанавливает 5 хитов в начале своего хода.', 'saving_throws' => 'Тел +5', 'senses' => 'слепое зрение 60 фт.', 'size' => 'Средний', 'skills' => 'Атлетика +5', 'speed' => '30 фт.', 'spells_at_will' => 'смена обличья, обнаружение магии, ужас, град, невидимость', 'spells_once' => 'божественное слово, знак (только боль)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'МДР', ]; ================================================ FILE: lang/vendor/dnd5emonster/sk/template.php ================================================ 'Akcie', 'armor_class' => 'Trieda brnenia', 'at_will' => 'Hocikedy', 'cha' => 'CHA', 'challenge_rating' => 'Miera náročnosti', 'con' => 'CON', 'condition_immunities' => 'Imunity voči stavom', 'damage_immunities' => 'Imunity voči zraneniam', 'damage_resistance' => 'Odolnosti voči zraneniam', 'dex' => 'DEX', 'fields' => [ 'alignment' => 'Presvedčenie', 'armor_class' => 'Trieda brnenia', 'cha' => 'CHA', 'challenge_rating' => 'Miera náročnosti', 'con' => 'CON', 'condition_immunities' => 'Imunity voči stavom', 'creature_race' => 'Druh bytosti', 'damage_immunities' => 'Imunity voči zraneniam', 'damage_resistances' => 'Odolnosti voči zraneniam', 'dex' => 'DEX', 'hit_points' => 'Body výdrže', 'int' => 'INT', 'languages' => 'Jazyky', 'multiattack' => 'Viacnásobný útok', 'saving_throws' => 'Záchranné hody', 'senses' => 'Zmysly', 'size' => 'Veľkosť', 'skills' => 'Zručnosti', 'speed' => 'Rýchlosť', 'spells_at_will' => 'Kúzla na hocikedy', 'str' => 'STR', 'wis' => 'WIS', ], 'hit_points' => 'Body výdrže', 'innate_spellcasting' => 'Vrodené kúzlenie', 'int' => 'INT', 'languages' => 'Jazyky', 'legendary_actions' => 'Legendárne akcie', 'legendary_resistance_count' => 'Legendárne odolnosti|Legendárne odolnosti (:count/deň)', 'magic_resistance' => 'Magická odolnosť', 'magic_weapons' => 'Magické zbrane', 'multiattack' => 'Viacnásobný útok', 'name' => 'D&D 5e Príšera', 'reactions' => 'Reakcie', 'regeneration' => 'Regenerácia', 'saving_throws' => 'Záchranné hody', 'senses' => 'Zmysly', 'skills' => 'Zručnosti', 'speed' => 'Rýchlosť', 'spells_1' => '1/deň', 'spells_3' => '3/deň', 'str' => 'STR', 'values' => [ 'ability_1' => 'Teleport. {name} magicky sa teleportuje spolu s hocijakým vybavení, ktoré nesie, do 120 stôp na neobsadené miesto, ktoré vidí.', 'armor_class' => '19 (prirodzené brnenie)', 'attack_1' => 'Pazúry. Útok zbraňou zblízka: +1 na zásah, dosah 15 st., jeden cieľ. Zásah: 14 (2d6 + 4) rezných zranení.', 'attack_2' => 'Bodec.Útok zbraňou zblízka: +1 na zásah, dosah 5 st., jeden cieľ. Zásah: 5 (1d4 + 2) bodných zranení.', 'cha' => '14 (+2)', 'challenge_rating' => '5 (300 xp)', 'con' => '10 (+0)', 'condition_immunities' => 'opantaný, spútaný', 'damage_immunities' => 'chlad, oheň', 'damage_resistances' => 'bodné', 'dex' => '12 (+1)', 'hit_points' => '139 (12d12 + 55)', 'innate_spellcasting' => 'Vrodená schopnosť kúzliť pre {name} je Charisma (Spell Save DC 12). Vrodene môžu kúzliť nasledujúce kúzla, bez nutnosti použitia hmotných súčastí.', 'int' => '8 (-1)', 'languages' => 'bežný, pekelný', 'legendary_action_1' => 'Hrozivá tvár. {name} sa zacieli na jednu bytosť, ktorú vidí v dosahu 60 st. Ak cieľ vidí {name}, musí uspieť v záchrannom hode na Wisdom voči 13 alebo sa začne {name} báť do konca svojho ďalšieho kola.', 'legendary_actions' => '{name} môže použiť 2 legendárne akcie, vyberá si z možností nižšie. Iba jednu legendárnu akciu môže použiť v danom čase a iba na konci kola inej bytosti. {name} získa svoje použité legendárne akcie opäť na začiatku ich kola.', 'legendary_resistance' => 'Ak {name} neuspeje v záchrannom hode, môže sa rozhodnúť predsa len uspieť.', 'magic_resistance' => '{name} má výhodu na záchranné hody proti kúzlam a ostatným magickým efektom.', 'magic_weapons' => 'Zbraňové útoky vedené {name} sú magické.', 'multiattack' => 'dva útoky: jeden s pazúrmi a jeden s bodcom.', 'regeneration' => '{name} obdrží 5 bodov výdrže na začiatku ich kola.', 'saving_throws' => 'Con +5', 'senses' => 'Slepocit 60 st.', 'size' => 'Stredná', 'skills' => 'Atletika +4', 'speed' => '30 st.', 'spells_at_will' => 'Zmeň seba, Odhaľ mágiu, Strach, Ľadová búrka, Neviditeľnosť', 'spells_once' => 'božské slovo, symbol (iba bolesť)', 'str' => '11 (+0)', 'wis' => '10 (+0)', ], 'wis' => 'WIS', ]; ================================================ FILE: larastan.php ================================================ withRules([ \RectorLaravel\Rector\ClassMethod\AddGenericReturnTypeToRelationsRector::class, ]); PHP; file_put_contents("$rectorDir/rector.php", $rectorConfigFile); exec("$rectorDir/vendor/bin/rector process app --config=$rectorDir/rector.php --no-progress-bar", $output, $return_var); echo implode("\n", $output); ================================================ FILE: package.json ================================================ { "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build" }, "devDependencies": { "@rollup/plugin-inject": "^5.0.3", "@vitejs/plugin-vue": "^5.0.0", "autoprefixer": "^10.4.20", "axios": "^1.12", "laravel-vite-plugin": "^2.0", "postcss": "^8.5.1", "tailwindcss": "^4.1", "tinymce": "^4.9.11", "vite": "^7.3.2", "vite-plugin-static-copy": "^3.2.0", "vue": "^3.3" }, "dependencies": { "@coddicat/vue-pinch-scroll-zoom": "^4.3.5", "@codemirror/lang-html": "^6.4.11", "@codemirror/theme-one-dark": "^6.1.3", "@floating-ui/dom": "^1.0.0", "@fontsource-variable/roboto": "^5.2.9", "@laravel/echo-vue": "^2.3.0", "@melloware/coloris": "^0.22.0", "@syncfusion/ej2-vue-grids": "^24.2.7", "@tailwindcss/vite": "^4.1.17", "@tiptap/cli": "^3.11.1", "@tiptap/core": "^3.20.0", "@tiptap/extension-color": "^3.20.0", "@tiptap/extension-details": "^3.20.0", "@tiptap/extension-highlight": "^3.20.0", "@tiptap/extension-image": "^3.20.0", "@tiptap/extension-list": "^3.20.0", "@tiptap/extension-mention": "^3.20.0", "@tiptap/extension-table": "^3.20.0", "@tiptap/extension-table-cell": "^3.20.0", "@tiptap/extension-table-header": "^3.20.0", "@tiptap/extension-table-row": "^3.20.0", "@tiptap/extension-text-align": "^3.20.1", "@tiptap/extension-text-style": "^3.20.0", "@tiptap/pm": "^3.20.0", "@tiptap/starter-kit": "^3.20.0", "@tiptap/suggestion": "^3.20.0", "@tiptap/vue-3": "^3.20.0", "click-outside-vue3": "^4.0.1", "codemirror": "^6.0.2", "colord": "^2.9.3", "cookieconsent": "3", "cytoscape": "^3.27.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-dblclick": "^0.3.1", "cytoscape-panzoom": "^2.5.3", "cytoscape-popper": "^4.0.1", "jspdf": "^4.2.1", "konva": "^10.0.2", "laravel-echo": "^2.3.0", "laravel-vue-pagination": "^4.1.3", "leaflet-editable": "^1.2.0", "leaflet.markercluster": "^1.5.3", "leaflet.markercluster.layersupport": "^2.0.1", "leaflet.path.drag": "^0.0.6", "mitt": "^3.0.0", "pusher-js": "^8.4.0", "rpg-awesome": "^0.2.0", "sortablejs": "^1.15.0", "spectrum-colorpicker": "^1.8.1", "tippy.js": "^6.3.7", "tom-select": "^2.5.2", "vue-draggable-next": "^2.2.1", "vue-konva": "^3.2.6", "vue-panzoom": "^1.1.6", "vue-tippy": "v6" } } ================================================ FILE: phpcs.xml ================================================ The PSR12 coding standard for Laravel. app config resources routes tests */.phpstorm* */_ide_helper.php */cache/* */config/* */database/* */docs/* */migrations/* */storage/* */lang/* */vendor/* */tests/* */*.js */*.css */*.xml */*.blade.php */autoload.php */Console/Kernel.php */Exceptions/Handler.php */Http/Kernel.php ================================================ FILE: phpmd_ruleset.xml ================================================ Kanka Mess Detector ================================================ FILE: phpstan.neon ================================================ includes: - ./vendor/larastan/larastan/extension.neon parameters: ignoreErrors: - identifier: unset.possiblyHookedProperty - identifier: function.alreadyNarrowedType - identifier: trait.unused paths: - app/ # The level 9 is the highest level level: 5 excludePaths: - ./*/*/FileToBeExcluded.php - ./app/Models/Concerns/*.php - ./app/Console/Commands/*.php - ./app/Models/Scopes/AclScope.php treatPhpDocTypesAsCertain: false ================================================ FILE: phpunit.xml ================================================ ./tests ./app ================================================ FILE: pint.json ================================================ { "preset": "laravel", "rules": { "concat_space": { "spacing": "one" } }, "exclude": [ "lang/" ] } ================================================ FILE: public/.well-known/security.txt ================================================ Contact: mailto:hello@kanka.io Encryption: Acknowledgements: Policy: Signature: Hiring: ================================================ FILE: public/build/assets/Browser.vue_vue_type_script_setup_true_lang-DjY0tfEc.js ================================================ import{f as j,L as z,E as S,o as a,c as s,a as t,b as g,n as v,F as N,r as $,h as E,i as o}from"./vendor-tiptap-D5xFoo7B.js";const F={class:"flex gap-6 items-center p-4 md:p-6 justify-between"},V=["innerHTML"],O={class:"max-w-4xl p-4"},X={key:0,class:"flex gap-1 w-full"},Y={class:"grow"},A=["placeholder"],G=["title"],J=["title"],R={key:1,class:"md:h-36 md:w-80 text-center flex items-center justify-center w-full"},q=["onClick"],K=["title"],P=["innerHTML"],Q=["innerHTML"],U=300,ee=j({__name:"Browser",props:{api:{},opened:{type:Boolean},i18n:{}},emits:["selected","closed"],setup(C,{emit:L}){const r=C,i=o(null);z(()=>{r.i18n&&typeof r.i18n=="string"?i.value=JSON.parse(r.i18n):i.value=r.i18n});const x=L,u=o(!0),m=o(!1),f=o(),p=o([]),h=o(""),w=o(""),b=o(null),d=o(null),c=o("large"),T=()=>{u.value=!0,f.value.showModal(),f.value.addEventListener("click",function(e){let l=this.getBoundingClientRect();!(l.top<=e.clientY&&e.clientY<=l.top+l.height&&l.left<=e.clientX&&e.clientX<=l.left+l.width)&&e.target.tagName==="DIALOG"&&y()}),axios.get(r.api).then(e=>{p.value=e.data.images,u.value=!1}).catch(e=>{u.value=!1,e.response.status===403&&(d.value=e.response.data.message,d.value+="

    "+i.value.browse.unauthorized+"

    ")})},y=()=>{f.value.close(),x("closed")},k=e=>(e=e??"",c.value==="large"?"w-40 h-28 md:w-48 md:h-36 "+e:"w-20 h-16 "+e),B=e=>(e=e??"",c.value==="large"?"w-40 md:w-48 "+e:"w-20 text-xs "+e),M=()=>c.value==="small"?"flex flex-wrap justify-center gap-2 md:gap-3":"flex flex-wrap justify-center gap-2 md:gap-5",I=e=>{if(e.folder){u.value=!0,axios.get(e.url).then(l=>{p.value=l.data.images,u.value=!1});return}x("selected",e),y()};S(()=>r.opened,(e,l)=>{e&&T()});const D=e=>{h.value=e.target.value,b.value&&clearTimeout(b.value),b.value=setTimeout(()=>{H()},U)},H=()=>{w.value!=h.value&&(w.value=h.value,m.value=!0,axios.get(r.api+"?term="+w.value).then(e=>{p.value=e.data.images,m.value=!1}))},_=e=>{c.value=e};return(e,l)=>(a(),s("dialog",{class:"dialog rounded-2xl text-center bg-base-100 text-base-content",id:"gallery-dialog",ref_key:"galleryDialog",ref:f,"aria-modal":"true","aria-labelledby":"modal-card-label"},[t("header",F,[t("h4",{innerHTML:i.value.browse.title,class:"text-lg font-normal"},null,8,V),t("button",{type:"button",class:"text-base-content",onClick:l[0]||(l[0]=n=>y()),title:"Close"},[...l[3]||(l[3]=[t("i",{class:"fa-regular fa-circle-xmark","aria-hidden":"true"},null,-1),t("span",{class:"sr-only"},"Close",-1)])])]),t("article",O,[!u.value&&!d.value?(a(),s("div",X,[t("div",Y,[t("input",{type:"text",class:"w-full",placeholder:i.value.browse.search.placeholder,onInput:D},null,40,A)]),c.value!=="large"?(a(),s("div",{key:0,class:"flex-none cursor-pointer btn2 btn-ghost btn-sm",onClick:l[1]||(l[1]=n=>_("large")),title:i.value.browse.layouts.large},[...l[4]||(l[4]=[t("i",{class:"fa-regular fa-grid-2","aria-label":"Large previews"},null,-1)])],8,G)):g("",!0),c.value!=="small"?(a(),s("div",{key:1,class:"flex-none cursor-pointer btn2 btn-ghost btn-sm",onClick:l[2]||(l[2]=n=>_("small")),title:i.value.browse.layouts.small},[...l[5]||(l[5]=[t("i",{class:"fa-regular fa-grid-4","aria-label":"Small previews"},null,-1)])],8,J)):g("",!0)])):g("",!0),u.value||m.value?(a(),s("div",R,[...l[6]||(l[6]=[t("i",{class:"fa-solid fa-spinner fa-spin","aria-label":"Loading"},null,-1)])])):(a(),s("div",{key:2,class:v(M())},[(a(!0),s(N,null,$(p.value,n=>(a(),s("div",{class:"cursor-pointer shadow-sm rounded hover:shadow-lg overflow-hidden",onClick:W=>I(n)},[n.folder?(a(),s("div",{key:1,class:v(k("flex items-center align-middle justify-center text-4xl"))},[t("i",{class:v(n.icon),"aria-label":"Folder"},null,2)],2)):(a(),s("div",{key:0,class:v(k("cover-background")),style:E({backgroundImage:"url('"+n.thumbnail+"')"})},null,6)),t("div",{class:v(B("truncate px-2 py-1 ")),title:n.name},[t("span",{innerHTML:n.name},null,8,P)],10,K)],8,q))),256)),d.value?(a(),s("div",{key:0,class:"alert alert-error p-2 rounded",innerHTML:d.value},null,8,Q)):g("",!0)],2))])],512))}});export{ee as _}; ================================================ FILE: public/build/assets/GalleryDialog-B3Id-RLo.js ================================================ import{f as V,E as $,g as z,G as K,o as s,c as n,a as l,s as u,w as q,p as A,b as _,d as y,F as x,r as j,t as C,n as H,i as d}from"./vendor-tiptap-D5xFoo7B.js";import{a as B}from"./index-D5GkNzM3.js";import{_ as J}from"./_plugin-vue_export-helper-DlAUqK2U.js";const O={class:"gallery-container w-[800px] max-w-[90vw] max-h-[80vh] flex flex-col"},Q={class:"gallery-header flex items-center justify-between p-4"},W={class:"p-4 space-y-3"},X={class:"flex gap-2"},Y={class:"relative flex-1"},Z={key:0,class:"flex items-center gap-2 text-sm"},ee={class:"text-base-content/70"},te={class:"gallery-content flex-1 min-h-0 overflow-y-auto p-4"},le={key:0,class:"flex items-center justify-center py-12"},ae={key:1,class:"flex items-center justify-center py-12 text-base-content/50"},se={key:2,class:"grid grid-cols-4 gap-4"},ne=["onClick"],re={key:0,class:"absolute inset-0 flex flex-col items-center justify-center gap-2"},oe={class:"text-sm text-neutral-content truncate max-w-full px-2"},ie=["src","alt"],ue={class:"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/70 to-transparent p-2 opacity-0 group-hover:opacity-100 transition-opacity"},de={class:"text-white text-xs truncate block"},ce={key:0,class:"gallery-footer flex items-center justify-center gap-2 p-4 border-t border-base-300"},ve=["disabled"],fe=["disabled"],pe=V({__name:"GalleryDialog",props:{galleryId:{}},setup(G){const R=G,m=d(null),b=d([]),h=d(!1),c=d(""),o=d(""),k=d(""),f=d({first:null,last:null,prev:null,next:null}),v=d([]),T=d(null);let i=null,r=null;const S=t=>{t.detail.galleryId===R.galleryId&&(T.value=t.detail.editor,k.value=t.detail.galleryUrl,o.value=`${t.detail.galleryUrl}?setup`,v.value=[],c.value="",p(o.value),m.value?.showModal())},w=()=>{r&&(clearTimeout(r),r=null),i&&(i.abort(),i=null),m.value?.close()},p=async t=>{i&&i.abort(),i=new AbortController,h.value=!0;try{const e=await B.get(t,{signal:i.signal});b.value=e.data.data||[],f.value={first:e.data.links?.first||null,last:e.data.links?.last||null,prev:e.data.links?.prev||null,next:e.data.links?.next||null}}catch(e){if(B.isCancel(e))return;console.error("Failed to load gallery images:",e),b.value=[]}finally{h.value=!1}},g=()=>{r&&(clearTimeout(r),r=null),v.value=[];const t=new URL(k.value);t.searchParams.set("setup",""),c.value&&t.searchParams.set("term",c.value),o.value=t.toString(),p(o.value)},D=()=>{r&&clearTimeout(r),r=setTimeout(()=>{g()},500)},E=t=>{t.key==="Enter"&&(t.preventDefault(),g())},L=()=>{c.value="",g()};$(c,(t,e)=>{t!==e&&D()});const N=t=>{!t.folder||!t.url||(v.value.push({url:o.value,name:t.name}),o.value=t.url,p(t.url))},F=()=>{if(v.value.length===0)return;const t=v.value.pop();t&&(o.value=t.url,p(t.url))},M=()=>{v.value=[];const t=new URL(k.value);o.value=t.toString(),p(o.value)},U=t=>{t&&(o.value=t,p(t))},P=t=>{if(t.folder){N(t);return}T.value?.chain().focus().setImage({src:t.src,alt:t.name,"data-gallery-id":t.uuid}).run(),w()};return z(()=>{window.addEventListener("tiptap:open-gallery",S)}),K(()=>{window.removeEventListener("tiptap:open-gallery",S),r&&clearTimeout(r),i&&i.abort()}),(t,e)=>(s(),n("dialog",{ref_key:"dialogRef",ref:m,class:"gallery-dialog rounded-2xl bg-base-100 p-0 backdrop:bg-black/50",onClick:u(w,["self"])},[l("div",O,[l("div",Q,[e[4]||(e[4]=l("h2",{class:""},"Gallery",-1)),l("button",{onClick:u(w,["prevent"]),class:"btn2 btn-ghost"},[...e[3]||(e[3]=[l("i",{class:"fa-regular fa-times","aria-hidden":"true"},null,-1)])])]),l("div",W,[l("div",X,[l("div",Y,[q(l("input",{"onUpdate:modelValue":e[0]||(e[0]=a=>c.value=a),type:"text",placeholder:"Search images...",class:"input input-bordered w-full pr-10",onKeydown:E},null,544),[[A,c.value]]),c.value?(s(),n("button",{key:0,onClick:u(L,["prevent"]),class:"absolute right-3 top-1/2 -translate-y-1/2 text-base-content/50 hover:text-base-content"},[...e[5]||(e[5]=[l("i",{class:"fa-regular fa-times","aria-hidden":"true"},null,-1)])])):_("",!0)]),l("button",{onClick:u(g,["prevent"]),class:"btn2 btn-primary"},[...e[6]||(e[6]=[l("i",{class:"fa-regular fa-search","aria-hidden":"true"},null,-1),y(" Search ",-1)])])]),v.value.length>0?(s(),n("div",Z,[l("button",{onClick:u(M,["prevent"]),class:"btn2 btn-ghost btn-xs"},[...e[7]||(e[7]=[l("i",{class:"fa-regular fa-home","aria-hidden":"true"},null,-1)])]),e[10]||(e[10]=l("span",{class:"text-base-content/50"},"/",-1)),(s(!0),n(x,null,j(v.value,(a,I)=>(s(),n(x,{key:I},[l("span",ee,C(a.name),1),e[8]||(e[8]=l("span",{class:"text-base-content/50"},"/",-1))],64))),128)),l("button",{onClick:u(F,["prevent"]),class:"btn2 btn-ghost btn-xs"},[...e[9]||(e[9]=[l("i",{class:"fa-regular fa-arrow-left","aria-hidden":"true"},null,-1),y(" Back ",-1)])])])):_("",!0)]),l("div",te,[h.value?(s(),n("div",le,[...e[11]||(e[11]=[l("i",{class:"fa-solid fa-spinner fa-spin text-2xl","aria-hidden":"true"},null,-1)])])):b.value.length===0?(s(),n("div",ae," No images found ")):(s(),n("div",se,[(s(!0),n(x,null,j(b.value,a=>(s(),n("button",{key:a.uuid,onClick:u(I=>P(a),["prevent"]),class:"gallery-item group relative aspect-square rounded-lg overflow-hidden border border-base-300 hover:border-primary transition-colors cursor-pointer bg-base-200"},[a.folder?(s(),n("div",re,[l("i",{class:H([a.icon,"text-4xl text-neutral-content"]),"aria-hidden":"true"},null,2),l("span",oe,C(a.name),1)])):(s(),n(x,{key:1},[l("img",{src:a.thumbnail,alt:a.name,class:"absolute inset-0 w-full h-full object-cover",loading:"lazy"},null,8,ie),l("div",ue,[l("span",de,C(a.name),1)])],64))],8,ne))),128))]))]),f.value.prev||f.value.next?(s(),n("div",ce,[l("button",{onClick:e[1]||(e[1]=u(a=>U(f.value.prev),["prevent"])),disabled:!f.value.prev,class:"btn2 btn-ghost btn-xs"},[...e[12]||(e[12]=[l("i",{class:"fa-regular fa-chevron-left","aria-hidden":"true"},null,-1),y(" Previous ",-1)])],8,ve),l("button",{onClick:e[2]||(e[2]=u(a=>U(f.value.next),["prevent"])),disabled:!f.value.next,class:"btn2 btn-ghost btn-xs"},[...e[13]||(e[13]=[y(" Next ",-1),l("i",{class:"fa-regular fa-chevron-right","aria-hidden":"true"},null,-1)])],8,fe)])):_("",!0)])],512))}}),xe=J(pe,[["__scopeId","data-v-1d30234a"]]);export{xe as default}; ================================================ FILE: public/build/assets/GalleryDialog-SGgOgWve.css ================================================ .gallery-dialog[data-v-1d30234a]::backdrop{background:#00000080}.gallery-dialog[open][data-v-1d30234a]{display:flex}.gallery-item[data-v-1d30234a]:focus{outline:2px solid oklch(var(--p));outline-offset:2px} ================================================ FILE: public/build/assets/SourceEditor-BKF0zshA.css ================================================ .source-editor[data-v-d2900713]{border:1px solid hsl(var(--bc)/.1);border-radius:var(--rounded-btn);overflow:hidden}.source-editor-toolbar[data-v-d2900713]{display:flex;justify-content:space-between;align-items:center;padding:.5rem .75rem;background:hsl(var(--b2));border-bottom:1px solid hsl(var(--bc)/.1)}.source-editor-content[data-v-d2900713]{min-height:200px;max-height:70vh;overflow-y:auto}.source-editor-content[data-v-d2900713] .cm-editor{height:100%;min-height:200px;max-height:calc(70vh - 40px);font-size:.875rem}.source-editor-content[data-v-d2900713] .cm-scroller{overflow:auto}.source-editor-content[data-v-d2900713] .cm-content{padding:.5rem}.source-editor-content[data-v-d2900713] .cm-focused{outline:none} ================================================ FILE: public/build/assets/SourceEditor-CaEGRaAo.js ================================================ import{ap as mg,aq as Og,ar as bg,f as hS,g as cS,E as fS,G as uS,o as dS,c as pS,a as zs,d as gS,i as mS}from"./vendor-tiptap-D5xFoo7B.js";import{_ as OS}from"./_plugin-vue_export-helper-DlAUqK2U.js";let Za=[],Sg=[];(()=>{let n="lc,34,7n,7,7b,19,,,,2,,2,,,20,b,1c,l,g,,2t,7,2,6,2,2,,4,z,,u,r,2j,b,1m,9,9,,o,4,,9,,3,,5,17,3,3b,f,,w,1j,,,,4,8,4,,3,7,a,2,t,,1m,,,,2,4,8,,9,,a,2,q,,2,2,1l,,4,2,4,2,2,3,3,,u,2,3,,b,2,1l,,4,5,,2,4,,k,2,m,6,,,1m,,,2,,4,8,,7,3,a,2,u,,1n,,,,c,,9,,14,,3,,1l,3,5,3,,4,7,2,b,2,t,,1m,,2,,2,,3,,5,2,7,2,b,2,s,2,1l,2,,,2,4,8,,9,,a,2,t,,20,,4,,2,3,,,8,,29,,2,7,c,8,2q,,2,9,b,6,22,2,r,,,,,,1j,e,,5,,2,5,b,,10,9,,2u,4,,6,,2,2,2,p,2,4,3,g,4,d,,2,2,6,,f,,jj,3,qa,3,t,3,t,2,u,2,1s,2,,7,8,,2,b,9,,19,3,3b,2,y,,3a,3,4,2,9,,6,3,63,2,2,,1m,,,7,,,,,2,8,6,a,2,,1c,h,1r,4,1c,7,,,5,,14,9,c,2,w,4,2,2,,3,1k,,,2,3,,,3,1m,8,2,2,48,3,,d,,7,4,,6,,3,2,5i,1m,,5,ek,,5f,x,2da,3,3x,,2o,w,fe,6,2x,2,n9w,4,,a,w,2,28,2,7k,,3,,4,,p,2,5,,47,2,q,i,d,,12,8,p,b,1a,3,1c,,2,4,2,2,13,,1v,6,2,2,2,2,c,,8,,1b,,1f,,,3,2,2,5,2,,,16,2,8,,6m,,2,,4,,fn4,,kh,g,g,g,a6,2,gt,,6a,,45,5,1ae,3,,2,5,4,14,3,4,,4l,2,fx,4,ar,2,49,b,4w,,1i,f,1k,3,1d,4,2,2,1x,3,10,5,,8,1q,,c,2,1g,9,a,4,2,,2n,3,2,,,2,6,,4g,,3,8,l,2,1l,2,,,,,m,,e,7,3,5,5f,8,2,3,,,n,,29,,2,6,,,2,,,2,,2,6j,,2,4,6,2,,2,r,2,2d,8,2,,,2,2y,,,,2,6,,,2t,3,2,4,,5,77,9,,2,6t,,a,2,,,4,,40,4,2,2,4,,w,a,14,6,2,4,8,,9,6,2,3,1a,d,,2,ba,7,,6,,,2a,m,2,7,,2,,2,3e,6,3,,,2,,7,,,20,2,3,,,,9n,2,f0b,5,1n,7,t4,,1r,4,29,,f5k,2,43q,,,3,4,5,8,8,2,7,u,4,44,3,1iz,1j,4,1e,8,,e,,m,5,,f,11s,7,,h,2,7,,2,,5,79,7,c5,4,15s,7,31,7,240,5,gx7k,2o,3k,6o".split(",").map(e=>e?parseInt(e,36):1);for(let e=0,t=0;e>1;if(n=Sg[i])e=i+1;else return!0;if(e==t)return!1}}function Xf(n){return n>=127462&&n<=127487}const Lf=8205;function SS(n,e,t=!0,i=!0){return(t?yg:yS)(n,e,i)}function yg(n,e,t){if(e==n.length)return e;e&&xg(n.charCodeAt(e))&&wg(n.charCodeAt(e-1))&&e--;let i=Rl(n,e);for(e+=Ef(i);e=0&&Xf(Rl(n,o));)r++,o-=2;if(r%2==0)break;e+=2}else break}return e}function yS(n,e,t){for(;e>0;){let i=yg(n,e-2,t);if(i=56320&&n<57344}function wg(n){return n>=55296&&n<56320}function Ef(n){return n<65536?1:2}class Z{lineAt(e){if(e<0||e>this.length)throw new RangeError(`Invalid position ${e} in document of length ${this.length}`);return this.lineInner(e,!1,1,0)}line(e){if(e<1||e>this.lines)throw new RangeError(`Invalid line number ${e} in ${this.lines}-line document`);return this.lineInner(e,!0,1,0)}replace(e,t,i){[e,t]=Os(this,e,t);let s=[];return this.decompose(0,e,s,2),i.length&&i.decompose(0,i.length,s,3),this.decompose(t,this.length,s,1),Bt.from(s,this.length-(t-e)+i.length)}append(e){return this.replace(this.length,this.length,e)}slice(e,t=this.length){[e,t]=Os(this,e,t);let i=[];return this.decompose(e,t,i,0),Bt.from(i,t-e)}eq(e){if(e==this)return!0;if(e.length!=this.length||e.lines!=this.lines)return!1;let t=this.scanIdentical(e,1),i=this.length-this.scanIdentical(e,-1),s=new hn(this),r=new hn(e);for(let o=t,l=t;;){if(s.next(o),r.next(o),o=0,s.lineBreak!=r.lineBreak||s.done!=r.done||s.value!=r.value)return!1;if(l+=s.value.length,s.done||l>=i)return!0}}iter(e=1){return new hn(this,e)}iterRange(e,t=this.length){return new kg(this,e,t)}iterLines(e,t){let i;if(e==null)i=this.iter();else{t==null&&(t=this.lines+1);let s=this.line(e).from;i=this.iterRange(s,Math.max(s,t==this.lines+1?this.length:t<=1?0:this.line(t-1).to))}return new Qg(i)}toString(){return this.sliceString(0)}toJSON(){let e=[];return this.flatten(e),e}constructor(){}static of(e){if(e.length==0)throw new RangeError("A document must have at least one line");return e.length==1&&!e[0]?Z.empty:e.length<=32?new ue(e):Bt.from(ue.split(e,[]))}}class ue extends Z{constructor(e,t=xS(e)){super(),this.text=e,this.length=t}get lines(){return this.text.length}get children(){return null}lineInner(e,t,i,s){for(let r=0;;r++){let o=this.text[r],l=s+o.length;if((t?i:l)>=e)return new wS(s,l,i,o);s=l+1,i++}}decompose(e,t,i,s){let r=e<=0&&t>=this.length?this:new ue(Wf(this.text,e,t),Math.min(t,this.length)-Math.max(0,e));if(s&1){let o=i.pop(),l=zr(r.text,o.text.slice(),0,r.length);if(l.length<=32)i.push(new ue(l,o.length+r.length));else{let a=l.length>>1;i.push(new ue(l.slice(0,a)),new ue(l.slice(a)))}}else i.push(r)}replace(e,t,i){if(!(i instanceof ue))return super.replace(e,t,i);[e,t]=Os(this,e,t);let s=zr(this.text,zr(i.text,Wf(this.text,0,e)),t),r=this.length+i.length-(t-e);return s.length<=32?new ue(s,r):Bt.from(ue.split(s,[]),r)}sliceString(e,t=this.length,i=` `){[e,t]=Os(this,e,t);let s="";for(let r=0,o=0;r<=t&&oe&&o&&(s+=i),er&&(s+=l.slice(Math.max(0,e-r),t-r)),r=a+1}return s}flatten(e){for(let t of this.text)e.push(t)}scanIdentical(){return 0}static split(e,t){let i=[],s=-1;for(let r of e)i.push(r),s+=r.length+1,i.length==32&&(t.push(new ue(i,s)),i=[],s=-1);return s>-1&&t.push(new ue(i,s)),t}}class Bt extends Z{constructor(e,t){super(),this.children=e,this.length=t,this.lines=0;for(let i of e)this.lines+=i.lines}lineInner(e,t,i,s){for(let r=0;;r++){let o=this.children[r],l=s+o.length,a=i+o.lines-1;if((t?a:l)>=e)return o.lineInner(e,t,i,s);s=l+1,i=a+1}}decompose(e,t,i,s){for(let r=0,o=0;o<=t&&r=o){let h=s&((o<=e?1:0)|(a>=t?2:0));o>=e&&a<=t&&!h?i.push(l):l.decompose(e-o,t-o,i,h)}o=a+1}}replace(e,t,i){if([e,t]=Os(this,e,t),i.lines=r&&t<=l){let a=o.replace(e-r,t-r,i),h=this.lines-o.lines+a.lines;if(a.lines>4&&a.lines>h>>6){let c=this.children.slice();return c[s]=a,new Bt(c,this.length-(t-e)+i.length)}return super.replace(r,l,a)}r=l+1}return super.replace(e,t,i)}sliceString(e,t=this.length,i=` `){[e,t]=Os(this,e,t);let s="";for(let r=0,o=0;re&&r&&(s+=i),eo&&(s+=l.sliceString(e-o,t-o,i)),o=a+1}return s}flatten(e){for(let t of this.children)t.flatten(e)}scanIdentical(e,t){if(!(e instanceof Bt))return 0;let i=0,[s,r,o,l]=t>0?[0,0,this.children.length,e.children.length]:[this.children.length-1,e.children.length-1,-1,-1];for(;;s+=t,r+=t){if(s==o||r==l)return i;let a=this.children[s],h=e.children[r];if(a!=h)return i+a.scanIdentical(h,t);i+=a.length+1}}static from(e,t=e.reduce((i,s)=>i+s.length+1,-1)){let i=0;for(let d of e)i+=d.lines;if(i<32){let d=[];for(let p of e)p.flatten(d);return new ue(d,t)}let s=Math.max(32,i>>5),r=s<<1,o=s>>1,l=[],a=0,h=-1,c=[];function f(d){let p;if(d.lines>r&&d instanceof Bt)for(let g of d.children)f(g);else d.lines>o&&(a>o||!a)?(u(),l.push(d)):d instanceof ue&&a&&(p=c[c.length-1])instanceof ue&&d.lines+p.lines<=32?(a+=d.lines,h+=d.length+1,c[c.length-1]=new ue(p.text.concat(d.text),p.length+1+d.length)):(a+d.lines>s&&u(),a+=d.lines,h+=d.length+1,c.push(d))}function u(){a!=0&&(l.push(c.length==1?c[0]:Bt.from(c,h)),h=-1,a=c.length=0)}for(let d of e)f(d);return u(),l.length==1?l[0]:new Bt(l,t)}}Z.empty=new ue([""],0);function xS(n){let e=-1;for(let t of n)e+=t.length+1;return e}function zr(n,e,t=0,i=1e9){for(let s=0,r=0,o=!0;r=t&&(a>i&&(l=l.slice(0,i-s)),s0?1:(e instanceof ue?e.text.length:e.children.length)<<1]}nextInner(e,t){for(this.done=this.lineBreak=!1;;){let i=this.nodes.length-1,s=this.nodes[i],r=this.offsets[i],o=r>>1,l=s instanceof ue?s.text.length:s.children.length;if(o==(t>0?l:0)){if(i==0)return this.done=!0,this.value="",this;t>0&&this.offsets[i-1]++,this.nodes.pop(),this.offsets.pop()}else if((r&1)==(t>0?0:1)){if(this.offsets[i]+=t,e==0)return this.lineBreak=!0,this.value=` `,this;e--}else if(s instanceof ue){let a=s.text[o+(t<0?-1:0)];if(this.offsets[i]+=t,a.length>Math.max(0,e))return this.value=e==0?a:t>0?a.slice(e):a.slice(0,a.length-e),this;e-=a.length}else{let a=s.children[o+(t<0?-1:0)];e>a.length?(e-=a.length,this.offsets[i]+=t):(t<0&&this.offsets[i]--,this.nodes.push(a),this.offsets.push(t>0?1:(a instanceof ue?a.text.length:a.children.length)<<1))}}}next(e=0){return e<0&&(this.nextInner(-e,-this.dir),e=this.value.length),this.nextInner(e,this.dir)}}class kg{constructor(e,t,i){this.value="",this.done=!1,this.cursor=new hn(e,t>i?-1:1),this.pos=t>i?e.length:0,this.from=Math.min(t,i),this.to=Math.max(t,i)}nextInner(e,t){if(t<0?this.pos<=this.from:this.pos>=this.to)return this.value="",this.done=!0,this;e+=Math.max(0,t<0?this.pos-this.to:this.from-this.pos);let i=t<0?this.pos-this.from:this.to-this.pos;e>i&&(e=i),i-=e;let{value:s}=this.cursor.next(e);return this.pos+=(s.length+e)*t,this.value=s.length<=i?s:t<0?s.slice(s.length-i):s.slice(0,i),this.done=!this.value,this}next(e=0){return e<0?e=Math.max(e,this.from-this.pos):e>0&&(e=Math.min(e,this.to-this.pos)),this.nextInner(e,this.cursor.dir)}get lineBreak(){return this.cursor.lineBreak&&this.value!=""}}class Qg{constructor(e){this.inner=e,this.afterBreak=!0,this.value="",this.done=!1}next(e=0){let{done:t,lineBreak:i,value:s}=this.inner.next(e);return t&&this.afterBreak?(this.value="",this.afterBreak=!1):t?(this.done=!0,this.value=""):i?this.afterBreak?this.value="":(this.afterBreak=!0,this.next()):(this.value=s,this.afterBreak=!1),this}get lineBreak(){return!1}}typeof Symbol<"u"&&(Z.prototype[Symbol.iterator]=function(){return this.iter()},hn.prototype[Symbol.iterator]=kg.prototype[Symbol.iterator]=Qg.prototype[Symbol.iterator]=function(){return this});class wS{constructor(e,t,i,s){this.from=e,this.to=t,this.number=i,this.text=s}get length(){return this.to-this.from}}function Os(n,e,t){return e=Math.max(0,Math.min(n.length,e)),[e,Math.max(e,Math.min(n.length,t))]}function _(n,e,t=!0,i=!0){return SS(n,e,t,i)}function kS(n){return n>=56320&&n<57344}function QS(n){return n>=55296&&n<56320}function Me(n,e){let t=n.charCodeAt(e);if(!QS(t)||e+1==n.length)return t;let i=n.charCodeAt(e+1);return kS(i)?(t-55296<<10)+(i-56320)+65536:t}function wc(n){return n<=65535?String.fromCharCode(n):(n-=65536,String.fromCharCode((n>>10)+55296,(n&1023)+56320))}function ut(n){return n<65536?1:2}const Ba=/\r\n?|\n/;var te=(function(n){return n[n.Simple=0]="Simple",n[n.TrackDel=1]="TrackDel",n[n.TrackBefore=2]="TrackBefore",n[n.TrackAfter=3]="TrackAfter",n})(te||(te={}));class qt{constructor(e){this.sections=e}get length(){let e=0;for(let t=0;te)return r+(e-s);r+=l}else{if(i!=te.Simple&&h>=e&&(i==te.TrackDel&&se||i==te.TrackBefore&&se))return null;if(h>e||h==e&&t<0&&!l)return e==s||t<0?r:r+a;r+=a}s=h}if(e>s)throw new RangeError(`Position ${e} is out of range for changeset of length ${s}`);return r}touchesRange(e,t=e){for(let i=0,s=0;i=0&&s<=t&&l>=e)return st?"cover":!0;s=l}return!1}toString(){let e="";for(let t=0;t=0?":"+s:"")}return e}toJSON(){return this.sections}static fromJSON(e){if(!Array.isArray(e)||e.length%2||e.some(t=>typeof t!="number"))throw new RangeError("Invalid JSON representation of ChangeDesc");return new qt(e)}static create(e){return new qt(e)}}class he extends qt{constructor(e,t){super(e),this.inserted=t}apply(e){if(this.length!=e.length)throw new RangeError("Applying change set to a document with the wrong length");return Xa(this,(t,i,s,r,o)=>e=e.replace(s,s+(i-t),o),!1),e}mapDesc(e,t=!1){return La(this,e,t,!0)}invert(e){let t=this.sections.slice(),i=[];for(let s=0,r=0;s=0){t[s]=l,t[s+1]=o;let a=s>>1;for(;i.length0&&Oi(i,t,r.text),r.forward(c),l+=c}let h=e[o++];for(;l>1].toJSON()))}return e}static of(e,t,i){let s=[],r=[],o=0,l=null;function a(c=!1){if(!c&&!s.length)return;ou||f<0||u>t)throw new RangeError(`Invalid change range ${f} to ${u} (in doc of length ${t})`);let p=d?typeof d=="string"?Z.of(d.split(i||Ba)):d:Z.empty,g=p.length;if(f==u&&g==0)return;fo&&Qe(s,f-o,-1),Qe(s,u-f,g),Oi(r,s,p),o=u}}return h(e),a(!l),l}static empty(e){return new he(e?[e,-1]:[],[])}static fromJSON(e){if(!Array.isArray(e))throw new RangeError("Invalid JSON representation of ChangeSet");let t=[],i=[];for(let s=0;sl&&typeof o!="string"))throw new RangeError("Invalid JSON representation of ChangeSet");if(r.length==1)t.push(r[0],0);else{for(;i.length=0&&t<=0&&t==n[s+1]?n[s]+=e:s>=0&&e==0&&n[s]==0?n[s+1]+=t:i?(n[s]+=e,n[s+1]+=t):n.push(e,t)}function Oi(n,e,t){if(t.length==0)return;let i=e.length-2>>1;if(i>1])),!(t||o==n.sections.length||n.sections[o+1]<0);)l=n.sections[o++],a=n.sections[o++];e(s,h,r,c,f),s=h,r=c}}}function La(n,e,t,i=!1){let s=[],r=i?[]:null,o=new xn(n),l=new xn(e);for(let a=-1;;){if(o.done&&l.len||l.done&&o.len)throw new Error("Mismatched change set lengths");if(o.ins==-1&&l.ins==-1){let h=Math.min(o.len,l.len);Qe(s,h,-1),o.forward(h),l.forward(h)}else if(l.ins>=0&&(o.ins<0||a==o.i||o.off==0&&(l.len=0&&a=0){let h=0,c=o.len;for(;c;)if(l.ins==-1){let f=Math.min(c,l.len);h+=f,c-=f,l.forward(f)}else if(l.ins==0&&l.lena||o.ins>=0&&o.len>a)&&(l||i.length>h),r.forward2(a),o.forward(a)}}}}class xn{constructor(e){this.set=e,this.i=0,this.next()}next(){let{sections:e}=this.set;this.i>1;return t>=e.length?Z.empty:e[t]}textBit(e){let{inserted:t}=this.set,i=this.i-2>>1;return i>=t.length&&!e?Z.empty:t[i].slice(this.off,e==null?void 0:this.off+e)}forward(e){e==this.len?this.next():(this.len-=e,this.off+=e)}forward2(e){this.ins==-1?this.forward(e):e==this.ins?this.next():(this.ins-=e,this.off+=e)}}class Xi{constructor(e,t,i){this.from=e,this.to=t,this.flags=i}get anchor(){return this.flags&32?this.to:this.from}get head(){return this.flags&32?this.from:this.to}get empty(){return this.from==this.to}get assoc(){return this.flags&8?-1:this.flags&16?1:0}get bidiLevel(){let e=this.flags&7;return e==7?null:e}get goalColumn(){let e=this.flags>>6;return e==16777215?void 0:e}map(e,t=-1){let i,s;return this.empty?i=s=e.mapPos(this.from,t):(i=e.mapPos(this.from,1),s=e.mapPos(this.to,-1)),i==this.from&&s==this.to?this:new Xi(i,s,this.flags)}extend(e,t=e){if(e<=this.anchor&&t>=this.anchor)return S.range(e,t);let i=Math.abs(e-this.anchor)>Math.abs(t-this.anchor)?e:t;return S.range(this.anchor,i)}eq(e,t=!1){return this.anchor==e.anchor&&this.head==e.head&&this.goalColumn==e.goalColumn&&(!t||!this.empty||this.assoc==e.assoc)}toJSON(){return{anchor:this.anchor,head:this.head}}static fromJSON(e){if(!e||typeof e.anchor!="number"||typeof e.head!="number")throw new RangeError("Invalid JSON representation for SelectionRange");return S.range(e.anchor,e.head)}static create(e,t,i){return new Xi(e,t,i)}}class S{constructor(e,t){this.ranges=e,this.mainIndex=t}map(e,t=-1){return e.empty?this:S.create(this.ranges.map(i=>i.map(e,t)),this.mainIndex)}eq(e,t=!1){if(this.ranges.length!=e.ranges.length||this.mainIndex!=e.mainIndex)return!1;for(let i=0;ie.toJSON()),main:this.mainIndex}}static fromJSON(e){if(!e||!Array.isArray(e.ranges)||typeof e.main!="number"||e.main>=e.ranges.length)throw new RangeError("Invalid JSON representation for EditorSelection");return new S(e.ranges.map(t=>Xi.fromJSON(t)),e.main)}static single(e,t=e){return new S([S.range(e,t)],0)}static create(e,t=0){if(e.length==0)throw new RangeError("A selection needs at least one range");for(let i=0,s=0;se?8:0)|r)}static normalized(e,t=0){let i=e[t];e.sort((s,r)=>s.from-r.from),t=e.indexOf(i);for(let s=1;sr.head?S.range(a,l):S.range(l,a))}}return new S(e,t)}}function $g(n,e){for(let t of n.ranges)if(t.to>e)throw new RangeError("Selection points outside of document")}let kc=0;class k{constructor(e,t,i,s,r){this.combine=e,this.compareInput=t,this.compare=i,this.isStatic=s,this.id=kc++,this.default=e([]),this.extensions=typeof r=="function"?r(this):r}get reader(){return this}static define(e={}){return new k(e.combine||(t=>t),e.compareInput||((t,i)=>t===i),e.compare||(e.combine?(t,i)=>t===i:Qc),!!e.static,e.enables)}of(e){return new qr([],this,0,e)}compute(e,t){if(this.isStatic)throw new Error("Can't compute a static facet");return new qr(e,this,1,t)}computeN(e,t){if(this.isStatic)throw new Error("Can't compute a static facet");return new qr(e,this,2,t)}from(e,t){return t||(t=i=>i),this.compute([e],i=>t(i.field(e)))}}function Qc(n,e){return n==e||n.length==e.length&&n.every((t,i)=>t===e[i])}class qr{constructor(e,t,i,s){this.dependencies=e,this.facet=t,this.type=i,this.value=s,this.id=kc++}dynamicSlot(e){var t;let i=this.value,s=this.facet.compareInput,r=this.id,o=e[r]>>1,l=this.type==2,a=!1,h=!1,c=[];for(let f of this.dependencies)f=="doc"?a=!0:f=="selection"?h=!0:(((t=e[f.id])!==null&&t!==void 0?t:1)&1)==0&&c.push(e[f.id]);return{create(f){return f.values[o]=i(f),1},update(f,u){if(a&&u.docChanged||h&&(u.docChanged||u.selection)||Ea(f,c)){let d=i(f);if(l?!Vf(d,f.values[o],s):!s(d,f.values[o]))return f.values[o]=d,1}return 0},reconfigure:(f,u)=>{let d,p=u.config.address[r];if(p!=null){let g=lo(u,p);if(this.dependencies.every(m=>m instanceof k?u.facet(m)===f.facet(m):m instanceof Se?u.field(m,!1)==f.field(m,!1):!0)||(l?Vf(d=i(f),g,s):s(d=i(f),g)))return f.values[o]=g,0}else d=i(f);return f.values[o]=d,1}}}}function Vf(n,e,t){if(n.length!=e.length)return!1;for(let i=0;in[a.id]),s=t.map(a=>a.type),r=i.filter(a=>!(a&1)),o=n[e.id]>>1;function l(a){let h=[];for(let c=0;ci===s),e);return e.provide&&(t.provides=e.provide(t)),t}create(e){let t=e.facet(ir).find(i=>i.field==this);return(t?.create||this.createF)(e)}slot(e){let t=e[this.id]>>1;return{create:i=>(i.values[t]=this.create(i),1),update:(i,s)=>{let r=i.values[t],o=this.updateF(r,s);return this.compareF(r,o)?0:(i.values[t]=o,1)},reconfigure:(i,s)=>{let r=i.facet(ir),o=s.facet(ir),l;return(l=r.find(a=>a.field==this))&&l!=o.find(a=>a.field==this)?(i.values[t]=l.create(i),1):s.config.address[this.id]!=null?(i.values[t]=s.field(this),0):(i.values[t]=this.create(i),1)}}}init(e){return[this,ir.of({field:this,create:e})]}get extension(){return this}}const Zi={lowest:4,low:3,default:2,high:1,highest:0};function qs(n){return e=>new Pg(e,n)}const xt={highest:qs(Zi.highest),high:qs(Zi.high),default:qs(Zi.default),low:qs(Zi.low),lowest:qs(Zi.lowest)};class Pg{constructor(e,t){this.inner=e,this.prec=t}}class il{of(e){return new Wa(this,e)}reconfigure(e){return il.reconfigure.of({compartment:this,extension:e})}get(e){return e.config.compartments.get(this)}}class Wa{constructor(e,t){this.compartment=e,this.inner=t}}class oo{constructor(e,t,i,s,r,o){for(this.base=e,this.compartments=t,this.dynamicSlots=i,this.address=s,this.staticValues=r,this.facets=o,this.statusTemplate=[];this.statusTemplate.length>1]}static resolve(e,t,i){let s=[],r=Object.create(null),o=new Map;for(let u of $S(e,t,o))u instanceof Se?s.push(u):(r[u.facet.id]||(r[u.facet.id]=[])).push(u);let l=Object.create(null),a=[],h=[];for(let u of s)l[u.id]=h.length<<1,h.push(d=>u.slot(d));let c=i?.config.facets;for(let u in r){let d=r[u],p=d[0].facet,g=c&&c[u]||[];if(d.every(m=>m.type==0))if(l[p.id]=a.length<<1|1,Qc(g,d))a.push(i.facet(p));else{let m=p.combine(d.map(O=>O.value));a.push(i&&p.compare(m,i.facet(p))?i.facet(p):m)}else{for(let m of d)m.type==0?(l[m.id]=a.length<<1|1,a.push(m.value)):(l[m.id]=h.length<<1,h.push(O=>m.dynamicSlot(O)));l[p.id]=h.length<<1,h.push(m=>vS(m,p,d))}}let f=h.map(u=>u(l));return new oo(e,o,f,l,a,r)}}function $S(n,e,t){let i=[[],[],[],[],[]],s=new Map;function r(o,l){let a=s.get(o);if(a!=null){if(a<=l)return;let h=i[a].indexOf(o);h>-1&&i[a].splice(h,1),o instanceof Wa&&t.delete(o.compartment)}if(s.set(o,l),Array.isArray(o))for(let h of o)r(h,l);else if(o instanceof Wa){if(t.has(o.compartment))throw new RangeError("Duplicate use of compartment in extensions");let h=e.get(o.compartment)||o.inner;t.set(o.compartment,h),r(h,l)}else if(o instanceof Pg)r(o.inner,o.prec);else if(o instanceof Se)i[l].push(o),o.provides&&r(o.provides,l);else if(o instanceof qr)i[l].push(o),o.facet.extensions&&r(o.facet.extensions,Zi.default);else{let h=o.extension;if(!h)throw new Error(`Unrecognized extension value in extension set (${o}). This sometimes happens because multiple instances of @codemirror/state are loaded, breaking instanceof checks.`);r(h,l)}}return r(n,Zi.default),i.reduce((o,l)=>o.concat(l))}function cn(n,e){if(e&1)return 2;let t=e>>1,i=n.status[t];if(i==4)throw new Error("Cyclic dependency between fields and/or facets");if(i&2)return i;n.status[t]=4;let s=n.computeSlot(n,n.config.dynamicSlots[t]);return n.status[t]=2|s}function lo(n,e){return e&1?n.config.staticValues[e>>1]:n.values[e>>1]}const Cg=k.define(),Va=k.define({combine:n=>n.some(e=>e),static:!0}),Tg=k.define({combine:n=>n.length?n[0]:void 0,static:!0}),Mg=k.define(),Ag=k.define(),Rg=k.define(),Dg=k.define({combine:n=>n.length?n[0]:!1});class wt{constructor(e,t){this.type=e,this.value=t}static define(){return new PS}}class PS{of(e){return new wt(this,e)}}class CS{constructor(e){this.map=e}of(e){return new B(this,e)}}class B{constructor(e,t){this.type=e,this.value=t}map(e){let t=this.type.map(this.value,e);return t===void 0?void 0:t==this.value?this:new B(this.type,t)}is(e){return this.type==e}static define(e={}){return new CS(e.map||(t=>t))}static mapEffects(e,t){if(!e.length)return e;let i=[];for(let s of e){let r=s.map(t);r&&i.push(r)}return i}}B.reconfigure=B.define();B.appendConfig=B.define();class ce{constructor(e,t,i,s,r,o){this.startState=e,this.changes=t,this.selection=i,this.effects=s,this.annotations=r,this.scrollIntoView=o,this._doc=null,this._state=null,i&&$g(i,t.newLength),r.some(l=>l.type==ce.time)||(this.annotations=r.concat(ce.time.of(Date.now())))}static create(e,t,i,s,r,o){return new ce(e,t,i,s,r,o)}get newDoc(){return this._doc||(this._doc=this.changes.apply(this.startState.doc))}get newSelection(){return this.selection||this.startState.selection.map(this.changes)}get state(){return this._state||this.startState.applyTransaction(this),this._state}annotation(e){for(let t of this.annotations)if(t.type==e)return t.value}get docChanged(){return!this.changes.empty}get reconfigured(){return this.startState.config!=this.state.config}isUserEvent(e){let t=this.annotation(ce.userEvent);return!!(t&&(t==e||t.length>e.length&&t.slice(0,e.length)==e&&t[e.length]=="."))}}ce.time=wt.define();ce.userEvent=wt.define();ce.addToHistory=wt.define();ce.remote=wt.define();function TS(n,e){let t=[];for(let i=0,s=0;;){let r,o;if(i=n[i]))r=n[i++],o=n[i++];else if(s=0;s--){let r=i[s](n);r instanceof ce?n=r:Array.isArray(r)&&r.length==1&&r[0]instanceof ce?n=r[0]:n=Bg(e,ts(r),!1)}return n}function AS(n){let e=n.startState,t=e.facet(Rg),i=n;for(let s=t.length-1;s>=0;s--){let r=t[s](n);r&&Object.keys(r).length&&(i=Zg(i,za(e,r,n.changes.newLength),!0))}return i==n?n:ce.create(e,n.changes,n.selection,i.effects,i.annotations,i.scrollIntoView)}const RS=[];function ts(n){return n==null?RS:Array.isArray(n)?n:[n]}var oe=(function(n){return n[n.Word=0]="Word",n[n.Space=1]="Space",n[n.Other=2]="Other",n})(oe||(oe={}));const DS=/[\u00df\u0587\u0590-\u05f4\u0600-\u06ff\u3040-\u309f\u30a0-\u30ff\u3400-\u4db5\u4e00-\u9fcc\uac00-\ud7af]/;let qa;try{qa=new RegExp("[\\p{Alphabetic}\\p{Number}_]","u")}catch{}function ZS(n){if(qa)return qa.test(n);for(let e=0;e"€"&&(t.toUpperCase()!=t.toLowerCase()||DS.test(t)))return!0}return!1}function BS(n){return e=>{if(!/\S/.test(e))return oe.Space;if(ZS(e))return oe.Word;for(let t=0;t-1)return oe.Word;return oe.Other}}class L{constructor(e,t,i,s,r,o){this.config=e,this.doc=t,this.selection=i,this.values=s,this.status=e.statusTemplate.slice(),this.computeSlot=r,o&&(o._state=this);for(let l=0;ls.set(h,a)),t=null),s.set(l.value.compartment,l.value.extension)):l.is(B.reconfigure)?(t=null,i=l.value):l.is(B.appendConfig)&&(t=null,i=ts(i).concat(l.value));let r;t?r=e.startState.values.slice():(t=oo.resolve(i,s,this),r=new L(t,this.doc,this.selection,t.dynamicSlots.map(()=>null),(a,h)=>h.reconfigure(a,this),null).values);let o=e.startState.facet(Va)?e.newSelection:e.newSelection.asSingle();new L(t,e.newDoc,o,r,(l,a)=>a.update(l,e),e)}replaceSelection(e){return typeof e=="string"&&(e=this.toText(e)),this.changeByRange(t=>({changes:{from:t.from,to:t.to,insert:e},range:S.cursor(t.from+e.length)}))}changeByRange(e){let t=this.selection,i=e(t.ranges[0]),s=this.changes(i.changes),r=[i.range],o=ts(i.effects);for(let l=1;lo.spec.fromJSON(l,a)))}}return L.create({doc:e.doc,selection:S.fromJSON(e.selection),extensions:t.extensions?s.concat([t.extensions]):s})}static create(e={}){let t=oo.resolve(e.extensions||[],new Map),i=e.doc instanceof Z?e.doc:Z.of((e.doc||"").split(t.staticFacet(L.lineSeparator)||Ba)),s=e.selection?e.selection instanceof S?e.selection:S.single(e.selection.anchor,e.selection.head):S.single(0);return $g(s,i.length),t.staticFacet(Va)||(s=s.asSingle()),new L(t,i,s,t.dynamicSlots.map(()=>null),(r,o)=>o.create(r),null)}get tabSize(){return this.facet(L.tabSize)}get lineBreak(){return this.facet(L.lineSeparator)||` `}get readOnly(){return this.facet(Dg)}phrase(e,...t){for(let i of this.facet(L.phrases))if(Object.prototype.hasOwnProperty.call(i,e)){e=i[e];break}return t.length&&(e=e.replace(/\$(\$|\d*)/g,(i,s)=>{if(s=="$")return"$";let r=+(s||1);return!r||r>t.length?i:t[r-1]})),e}languageDataAt(e,t,i=-1){let s=[];for(let r of this.facet(Cg))for(let o of r(this,t,i))Object.prototype.hasOwnProperty.call(o,e)&&s.push(o[e]);return s}charCategorizer(e){let t=this.languageDataAt("wordChars",e);return BS(t.length?t[0]:"")}wordAt(e){let{text:t,from:i,length:s}=this.doc.lineAt(e),r=this.charCategorizer(e),o=e-i,l=e-i;for(;o>0;){let a=_(t,o,!1);if(r(t.slice(a,o))!=oe.Word)break;o=a}for(;ln.length?n[0]:4});L.lineSeparator=Tg;L.readOnly=Dg;L.phrases=k.define({compare(n,e){let t=Object.keys(n),i=Object.keys(e);return t.length==i.length&&t.every(s=>n[s]==e[s])}});L.languageData=Cg;L.changeFilter=Mg;L.transactionFilter=Ag;L.transactionExtender=Rg;il.reconfigure=B.define();function _t(n,e,t={}){let i={};for(let s of n)for(let r of Object.keys(s)){let o=s[r],l=i[r];if(l===void 0)i[r]=o;else if(!(l===o||o===void 0))if(Object.hasOwnProperty.call(t,r))i[r]=t[r](l,o);else throw new Error("Config merge conflict for field "+r)}for(let s in e)i[s]===void 0&&(i[s]=e[s]);return i}class Ve{eq(e){return this==e}range(e,t=e){return Ia.create(e,t,this)}}Ve.prototype.startSide=Ve.prototype.endSide=0;Ve.prototype.point=!1;Ve.prototype.mapMode=te.TrackDel;function vc(n,e){return n==e||n.constructor==e.constructor&&n.eq(e)}let Ia=class Xg{constructor(e,t,i){this.from=e,this.to=t,this.value=i}static create(e,t,i){return new Xg(e,t,i)}};function _a(n,e){return n.from-e.from||n.value.startSide-e.value.startSide}class $c{constructor(e,t,i,s){this.from=e,this.to=t,this.value=i,this.maxPoint=s}get length(){return this.to[this.to.length-1]}findIndex(e,t,i,s=0){let r=i?this.to:this.from;for(let o=s,l=r.length;;){if(o==l)return o;let a=o+l>>1,h=r[a]-e||(i?this.value[a].endSide:this.value[a].startSide)-t;if(a==o)return h>=0?o:l;h>=0?l=a:o=a+1}}between(e,t,i,s){for(let r=this.findIndex(t,-1e9,!0),o=this.findIndex(i,1e9,!1,r);rd||u==d&&h.startSide>0&&h.endSide<=0)continue;(d-u||h.endSide-h.startSide)<0||(o<0&&(o=u),h.point&&(l=Math.max(l,d-u)),i.push(h),s.push(u-o),r.push(d-o))}return{mapped:i.length?new $c(s,r,i,l):null,pos:o}}}class M{constructor(e,t,i,s){this.chunkPos=e,this.chunk=t,this.nextLayer=i,this.maxPoint=s}static create(e,t,i,s){return new M(e,t,i,s)}get length(){let e=this.chunk.length-1;return e<0?0:Math.max(this.chunkEnd(e),this.nextLayer.length)}get size(){if(this.isEmpty)return 0;let e=this.nextLayer.size;for(let t of this.chunk)e+=t.value.length;return e}chunkEnd(e){return this.chunkPos[e]+this.chunk[e].length}update(e){let{add:t=[],sort:i=!1,filterFrom:s=0,filterTo:r=this.length}=e,o=e.filter;if(t.length==0&&!o)return this;if(i&&(t=t.slice().sort(_a)),this.isEmpty)return t.length?M.of(t):this;let l=new Lg(this,null,-1).goto(0),a=0,h=[],c=new ai;for(;l.value||a=0){let f=t[a++];c.addInner(f.from,f.to,f.value)||h.push(f)}else l.rangeIndex==1&&l.chunkIndexthis.chunkEnd(l.chunkIndex)||rl.to||r=r&&e<=r+o.length&&o.between(r,e-r,t-r,i)===!1)return}this.nextLayer.between(e,t,i)}}iter(e=0){return wn.from([this]).goto(e)}get isEmpty(){return this.nextLayer==this}static iter(e,t=0){return wn.from(e).goto(t)}static compare(e,t,i,s,r=-1){let o=e.filter(f=>f.maxPoint>0||!f.isEmpty&&f.maxPoint>=r),l=t.filter(f=>f.maxPoint>0||!f.isEmpty&&f.maxPoint>=r),a=zf(o,l,i),h=new Is(o,a,r),c=new Is(l,a,r);i.iterGaps((f,u,d)=>qf(h,f,c,u,d,s)),i.empty&&i.length==0&&qf(h,0,c,0,0,s)}static eq(e,t,i=0,s){s==null&&(s=999999999);let r=e.filter(c=>!c.isEmpty&&t.indexOf(c)<0),o=t.filter(c=>!c.isEmpty&&e.indexOf(c)<0);if(r.length!=o.length)return!1;if(!r.length)return!0;let l=zf(r,o),a=new Is(r,l,0).goto(i),h=new Is(o,l,0).goto(i);for(;;){if(a.to!=h.to||!Ya(a.active,h.active)||a.point&&(!h.point||!vc(a.point,h.point)))return!1;if(a.to>s)return!0;a.next(),h.next()}}static spans(e,t,i,s,r=-1){let o=new Is(e,null,r).goto(t),l=t,a=o.openStart;for(;;){let h=Math.min(o.to,i);if(o.point){let c=o.activeForPoint(o.to),f=o.pointFroml&&(s.span(l,h,o.active,a),a=o.openEnd(h));if(o.to>i)return a+(o.point&&o.to>i?1:0);l=o.to,o.next()}}static of(e,t=!1){let i=new ai;for(let s of e instanceof Ia?[e]:t?XS(e):e)i.add(s.from,s.to,s.value);return i.finish()}static join(e){if(!e.length)return M.empty;let t=e[e.length-1];for(let i=e.length-2;i>=0;i--)for(let s=e[i];s!=M.empty;s=s.nextLayer)t=new M(s.chunkPos,s.chunk,t,Math.max(s.maxPoint,t.maxPoint));return t}}M.empty=new M([],[],null,-1);function XS(n){if(n.length>1)for(let e=n[0],t=1;t0)return n.slice().sort(_a);e=i}return n}M.empty.nextLayer=M.empty;class ai{finishChunk(e){this.chunks.push(new $c(this.from,this.to,this.value,this.maxPoint)),this.chunkPos.push(this.chunkStart),this.chunkStart=-1,this.setMaxPoint=Math.max(this.setMaxPoint,this.maxPoint),this.maxPoint=-1,e&&(this.from=[],this.to=[],this.value=[])}constructor(){this.chunks=[],this.chunkPos=[],this.chunkStart=-1,this.last=null,this.lastFrom=-1e9,this.lastTo=-1e9,this.from=[],this.to=[],this.value=[],this.maxPoint=-1,this.setMaxPoint=-1,this.nextLayer=null}add(e,t,i){this.addInner(e,t,i)||(this.nextLayer||(this.nextLayer=new ai)).add(e,t,i)}addInner(e,t,i){let s=e-this.lastTo||i.startSide-this.last.endSide;if(s<=0&&(e-this.lastFrom||i.startSide-this.last.startSide)<0)throw new Error("Ranges must be added sorted by `from` position and `startSide`");return s<0?!1:(this.from.length==250&&this.finishChunk(!0),this.chunkStart<0&&(this.chunkStart=e),this.from.push(e-this.chunkStart),this.to.push(t-this.chunkStart),this.last=i,this.lastFrom=e,this.lastTo=t,this.value.push(i),i.point&&(this.maxPoint=Math.max(this.maxPoint,t-e)),!0)}addChunk(e,t){if((e-this.lastTo||t.value[0].startSide-this.last.endSide)<0)return!1;this.from.length&&this.finishChunk(!0),this.setMaxPoint=Math.max(this.setMaxPoint,t.maxPoint),this.chunks.push(t),this.chunkPos.push(e);let i=t.value.length-1;return this.last=t.value[i],this.lastFrom=t.from[i]+e,this.lastTo=t.to[i]+e,!0}finish(){return this.finishInner(M.empty)}finishInner(e){if(this.from.length&&this.finishChunk(!1),this.chunks.length==0)return e;let t=M.create(this.chunkPos,this.chunks,this.nextLayer?this.nextLayer.finishInner(e):e,this.setMaxPoint);return this.from=null,t}}function zf(n,e,t){let i=new Map;for(let r of n)for(let o=0;o=this.minPoint)break}}setRangeIndex(e){if(e==this.layer.chunk[this.chunkIndex].value.length){if(this.chunkIndex++,this.skip)for(;this.chunkIndex=i&&s.push(new Lg(o,t,i,r));return s.length==1?s[0]:new wn(s)}get startSide(){return this.value?this.value.startSide:0}goto(e,t=-1e9){for(let i of this.heap)i.goto(e,t);for(let i=this.heap.length>>1;i>=0;i--)Dl(this.heap,i);return this.next(),this}forward(e,t){for(let i of this.heap)i.forward(e,t);for(let i=this.heap.length>>1;i>=0;i--)Dl(this.heap,i);(this.to-e||this.value.endSide-t)<0&&this.next()}next(){if(this.heap.length==0)this.from=this.to=1e9,this.value=null,this.rank=-1;else{let e=this.heap[0];this.from=e.from,this.to=e.to,this.value=e.value,this.rank=e.rank,e.value&&e.next(),Dl(this.heap,0)}}}function Dl(n,e){for(let t=n[e];;){let i=(e<<1)+1;if(i>=n.length)break;let s=n[i];if(i+1=0&&(s=n[i+1],i++),t.compare(s)<0)break;n[i]=t,n[e]=s,e=i}}class Is{constructor(e,t,i){this.minPoint=i,this.active=[],this.activeTo=[],this.activeRank=[],this.minActive=-1,this.point=null,this.pointFrom=0,this.pointRank=0,this.to=-1e9,this.endSide=0,this.openStart=-1,this.cursor=wn.from(e,t,i)}goto(e,t=-1e9){return this.cursor.goto(e,t),this.active.length=this.activeTo.length=this.activeRank.length=0,this.minActive=-1,this.to=e,this.endSide=t,this.openStart=-1,this.next(),this}forward(e,t){for(;this.minActive>-1&&(this.activeTo[this.minActive]-e||this.active[this.minActive].endSide-t)<0;)this.removeActive(this.minActive);this.cursor.forward(e,t)}removeActive(e){sr(this.active,e),sr(this.activeTo,e),sr(this.activeRank,e),this.minActive=If(this.active,this.activeTo)}addActive(e){let t=0,{value:i,to:s,rank:r}=this.cursor;for(;t0;)t++;nr(this.active,t,i),nr(this.activeTo,t,s),nr(this.activeRank,t,r),e&&nr(e,t,this.cursor.from),this.minActive=If(this.active,this.activeTo)}next(){let e=this.to,t=this.point;this.point=null;let i=this.openStart<0?[]:null;for(;;){let s=this.minActive;if(s>-1&&(this.activeTo[s]-this.cursor.from||this.active[s].endSide-this.cursor.startSide)<0){if(this.activeTo[s]>e){this.to=this.activeTo[s],this.endSide=this.active[s].endSide;break}this.removeActive(s),i&&sr(i,s)}else if(this.cursor.value)if(this.cursor.from>e){this.to=this.cursor.from,this.endSide=this.cursor.startSide;break}else{let r=this.cursor.value;if(!r.point)this.addActive(i),this.cursor.next();else if(t&&this.cursor.to==this.to&&this.cursor.from=0&&i[s]=0&&!(this.activeRank[i]e||this.activeTo[i]==e&&this.active[i].endSide>=this.point.endSide)&&t.push(this.active[i]);return t.reverse()}openEnd(e){let t=0;for(let i=this.activeTo.length-1;i>=0&&this.activeTo[i]>e;i--)t++;return t}}function qf(n,e,t,i,s,r){n.goto(e),t.goto(i);let o=i+s,l=i,a=i-e,h=!!r.boundChange;for(let c=!1;;){let f=n.to+a-t.to,u=f||n.endSide-t.endSide,d=u<0?n.to+a:t.to,p=Math.min(d,o);if(n.point||t.point?(n.point&&t.point&&vc(n.point,t.point)&&Ya(n.activeForPoint(n.to),t.activeForPoint(t.to))||r.comparePoint(l,p,n.point,t.point),c=!1):(c&&r.boundChange(l),p>l&&!Ya(n.active,t.active)&&r.compareRange(l,p,n.active,t.active),h&&po)break;l=d,u<=0&&n.next(),u>=0&&t.next()}}function Ya(n,e){if(n.length!=e.length)return!1;for(let t=0;t=e;i--)n[i+1]=n[i];n[e]=t}function If(n,e){let t=-1,i=1e9;for(let s=0;s=e)return s;if(s==n.length)break;r+=n.charCodeAt(s)==9?t-r%t:1,s=_(n,s)}return i===!0?-1:n.length}const Na="ͼ",_f=typeof Symbol>"u"?"__"+Na:Symbol.for(Na),ja=typeof Symbol>"u"?"__styleSet"+Math.floor(Math.random()*1e8):Symbol("styleSet"),Yf=typeof globalThis<"u"?globalThis:typeof window<"u"?window:{};class me{constructor(e,t){this.rules=[];let{finish:i}=t||{};function s(o){return/^@/.test(o)?[o]:o.split(/,\s*/)}function r(o,l,a,h){let c=[],f=/^@(\w+)\b/.exec(o[0]),u=f&&f[1]=="keyframes";if(f&&l==null)return a.push(o[0]+";");for(let d in l){let p=l[d];if(/&/.test(d))r(d.split(/,\s*/).map(g=>o.map(m=>g.replace(/&/,m))).reduce((g,m)=>g.concat(m)),p,a);else if(p&&typeof p=="object"){if(!f)throw new RangeError("The value of a property ("+d+") should be a primitive value.");r(s(d),p,c,u)}else p!=null&&c.push(d.replace(/_.*/,"").replace(/[A-Z]/g,g=>"-"+g.toLowerCase())+": "+p+";")}(c.length||u)&&a.push((i&&!f&&!h?o.map(i):o).join(", ")+" {"+c.join(" ")+"}")}for(let o in e)r(s(o),e[o],this.rules)}getRules(){return this.rules.join(` `)}static newName(){let e=Yf[_f]||1;return Yf[_f]=e+1,Na+e.toString(36)}static mount(e,t,i){let s=e[ja],r=i&&i.nonce;s?r&&s.setNonce(r):s=new LS(e,r),s.mount(Array.isArray(t)?t:[t],e)}}let Nf=new Map;class LS{constructor(e,t){let i=e.ownerDocument||e,s=i.defaultView;if(!e.head&&e.adoptedStyleSheets&&s.CSSStyleSheet){let r=Nf.get(i);if(r)return e[ja]=r;this.sheet=new s.CSSStyleSheet,Nf.set(i,this)}else this.styleTag=i.createElement("style"),t&&this.styleTag.setAttribute("nonce",t);this.modules=[],e[ja]=this}mount(e,t){let i=this.sheet,s=0,r=0;for(let o=0;o-1&&(this.modules.splice(a,1),r--,a=-1),a==-1){if(this.modules.splice(r++,0,l),i)for(let h=0;h2);var C={mac:Gf||/Mac/.test(Ce.platform),windows:/Win/.test(Ce.platform),linux:/Linux|X11/.test(Ce.platform),ie:sl,ie_version:Wg?Ga.documentMode||6:Fa?+Fa[1]:Ha?+Ha[1]:0,gecko:jf,gecko_version:jf?+(/Firefox\/(\d+)/.exec(Ce.userAgent)||[0,0])[1]:0,chrome:!!Zl,chrome_version:Zl?+Zl[1]:0,ios:Gf,android:/Android\b/.test(Ce.userAgent),webkit_version:ES?+(/\bAppleWebKit\/(\d+)/.exec(Ce.userAgent)||[0,0])[1]:0,safari:Ua,safari_version:Ua?+(/\bVersion\/(\d+(\.\d+)?)/.exec(Ce.userAgent)||[0,0])[1]:0,tabSize:Ga.documentElement.style.tabSize!=null?"tab-size":"-moz-tab-size"};function Pc(n,e){for(let t in n)t=="class"&&e.class?e.class+=" "+n.class:t=="style"&&e.style?e.style+=";"+n.style:e[t]=n[t];return e}const ao=Object.create(null);function Cc(n,e,t){if(n==e)return!0;n||(n=ao),e||(e=ao);let i=Object.keys(n),s=Object.keys(e);if(i.length-0!=s.length-0)return!1;for(let r of i)if(r!=t&&(s.indexOf(r)==-1||n[r]!==e[r]))return!1;return!0}function WS(n,e){for(let t=n.attributes.length-1;t>=0;t--){let i=n.attributes[t].name;e[i]==null&&n.removeAttribute(i)}for(let t in e){let i=e[t];t=="style"?n.style.cssText=i:n.getAttribute(t)!=i&&n.setAttribute(t,i)}}function Hf(n,e,t){let i=!1;if(e)for(let s in e)t&&s in t||(i=!0,s=="style"?n.style.cssText="":n.removeAttribute(s));if(t)for(let s in t)e&&e[s]==t[s]||(i=!0,s=="style"?n.style.cssText=t[s]:n.setAttribute(s,t[s]));return i}function VS(n){let e=Object.create(null);for(let t=0;t0?3e8:-4e8:t>0?1e8:-1e8,new Qn(e,t,t,i,e.widget||null,!1)}static replace(e){let t=!!e.block,i,s;if(e.isBlockGap)i=-5e8,s=4e8;else{let{start:r,end:o}=Ig(e,t);i=(r?t?-3e8:-1:5e8)-1,s=(o?t?2e8:1:-6e8)+1}return new Qn(e,i,s,t,e.widget||null,!0)}static line(e){return new Mc(e)}static set(e,t=!1){return M.of(e,t)}hasHeight(){return this.widget?this.widget.estimatedHeight>-1:!1}};E.none=M.empty;let Tc=class Vg extends E{constructor(e){let{start:t,end:i}=Ig(e);super(t?-1:5e8,i?1:-6e8,null,e),this.tagName=e.tagName||"span",this.attrs=e.class&&e.attributes?Pc(e.attributes,{class:e.class}):e.class?{class:e.class}:e.attributes||ao}eq(e){return this==e||e instanceof Vg&&this.tagName==e.tagName&&Cc(this.attrs,e.attrs)}range(e,t=e){if(e>=t)throw new RangeError("Mark decorations may not be empty");return super.range(e,t)}};Tc.prototype.point=!1;let Mc=class zg extends E{constructor(e){super(-2e8,-2e8,null,e)}eq(e){return e instanceof zg&&this.spec.class==e.spec.class&&Cc(this.spec.attributes,e.spec.attributes)}range(e,t=e){if(t!=e)throw new RangeError("Line decoration ranges must be zero-length");return super.range(e,t)}};Mc.prototype.mapMode=te.TrackBefore;Mc.prototype.point=!0;let Qn=class qg extends E{constructor(e,t,i,s,r,o){super(t,i,r,e),this.block=s,this.isReplace=o,this.mapMode=s?t<=0?te.TrackBefore:te.TrackAfter:te.TrackDel}get type(){return this.startSide!=this.endSide?ke.WidgetRange:this.startSide<=0?ke.WidgetBefore:ke.WidgetAfter}get heightRelevant(){return this.block||!!this.widget&&(this.widget.estimatedHeight>=5||this.widget.lineBreaks>0)}eq(e){return e instanceof qg&&zS(this.widget,e.widget)&&this.block==e.block&&this.startSide==e.startSide&&this.endSide==e.endSide}range(e,t=e){if(this.isReplace&&(e>t||e==t&&this.startSide>0&&this.endSide<=0))throw new RangeError("Invalid range for replacement decoration");if(!this.isReplace&&t!=e)throw new RangeError("Widget decorations can only have zero-length ranges");return super.range(e,t)}};Qn.prototype.point=!0;function Ig(n,e=!1){let{inclusiveStart:t,inclusiveEnd:i}=n;return t==null&&(t=n.inclusive),i==null&&(i=n.inclusive),{start:t??e,end:i??e}}function zS(n,e){return n==e||!!(n&&e&&n.compare(e))}function is(n,e,t,i=0){let s=t.length-1;s>=0&&t[s]+i>=n?t[s]=Math.max(t[s],e):t.push(n,e)}let Ff=class Ka extends Ve{constructor(e,t){super(),this.tagName=e,this.attributes=t}eq(e){return e==this||e instanceof Ka&&this.tagName==e.tagName&&Cc(this.attributes,e.attributes)}static create(e){return new Ka(e.tagName,e.attributes||ao)}static set(e,t=!1){return M.of(e,t)}};Ff.prototype.startSide=Ff.prototype.endSide=-1;function bs(n){let e;return n.nodeType==11?e=n.getSelection?n:n.ownerDocument:e=n,e.getSelection()}function Ja(n,e){return e?n==e||n.contains(e.nodeType!=1?e.parentNode:e):!1}function fn(n,e){if(!e.anchorNode)return!1;try{return Ja(n,e.anchorNode)}catch{return!1}}function Ir(n){return n.nodeType==3?vn(n,0,n.nodeValue.length).getClientRects():n.nodeType==1?n.getClientRects():[]}function un(n,e,t,i){return t?Uf(n,e,t,i,-1)||Uf(n,e,t,i,1):!1}function wi(n){for(var e=0;;e++)if(n=n.previousSibling,!n)return e}function ho(n){return n.nodeType==1&&/^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(n.nodeName)}function Uf(n,e,t,i,s){for(;;){if(n==t&&e==i)return!0;if(e==(s<0?0:hi(n))){if(n.nodeName=="DIV")return!1;let r=n.parentNode;if(!r||r.nodeType!=1)return!1;e=wi(n)+(s<0?0:1),n=r}else if(n.nodeType==1){if(n=n.childNodes[e+(s<0?-1:0)],n.nodeType==1&&n.contentEditable=="false")return!1;e=s<0?hi(n):0}else return!1}}function hi(n){return n.nodeType==3?n.nodeValue.length:n.childNodes.length}function co(n,e){let t=e?n.left:n.right;return{left:t,right:t,top:n.top,bottom:n.bottom}}function qS(n){let e=n.visualViewport;return e?{left:0,right:e.width,top:0,bottom:e.height}:{left:0,right:n.innerWidth,top:0,bottom:n.innerHeight}}function _g(n,e){let t=e.width/n.offsetWidth,i=e.height/n.offsetHeight;return(t>.995&&t<1.005||!isFinite(t)||Math.abs(e.width-n.offsetWidth)<1)&&(t=1),(i>.995&&i<1.005||!isFinite(i)||Math.abs(e.height-n.offsetHeight)<1)&&(i=1),{scaleX:t,scaleY:i}}function IS(n,e,t,i,s,r,o,l){let a=n.ownerDocument,h=a.defaultView||window;for(let c=n,f=!1;c&&!f;)if(c.nodeType==1){let u,d=c==a.body,p=1,g=1;if(d)u=qS(h);else{if(/^(fixed|sticky)$/.test(getComputedStyle(c).position)&&(f=!0),c.scrollHeight<=c.clientHeight&&c.scrollWidth<=c.clientWidth){c=c.assignedSlot||c.parentNode;continue}let y=c.getBoundingClientRect();({scaleX:p,scaleY:g}=_g(c,y)),u={left:y.left,right:y.left+c.clientWidth*p,top:y.top,bottom:y.top+c.clientHeight*g}}let m=0,O=0;if(s=="nearest")e.top0&&e.bottom>u.bottom+O&&(O=e.bottom-u.bottom+o)):e.bottom>u.bottom&&(O=e.bottom-u.bottom+o,t<0&&e.top-O0&&e.right>u.right+m&&(m=e.right-u.right+r)):e.right>u.right&&(m=e.right-u.right+r,t<0&&e.leftu.bottom||e.leftu.right)&&(e={left:Math.max(e.left,u.left),right:Math.min(e.right,u.right),top:Math.max(e.top,u.top),bottom:Math.min(e.bottom,u.bottom)}),c=c.assignedSlot||c.parentNode}else if(c.nodeType==11)c=c.host;else break}function _S(n){let e=n.ownerDocument,t,i;for(let s=n.parentNode;s&&!(s==e.body||t&&i);)if(s.nodeType==1)!i&&s.scrollHeight>s.clientHeight&&(i=s),!t&&s.scrollWidth>s.clientWidth&&(t=s),s=s.assignedSlot||s.parentNode;else if(s.nodeType==11)s=s.host;else break;return{x:t,y:i}}let YS=class{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}eq(e){return this.anchorNode==e.anchorNode&&this.anchorOffset==e.anchorOffset&&this.focusNode==e.focusNode&&this.focusOffset==e.focusOffset}setRange(e){let{anchorNode:t,focusNode:i}=e;this.set(t,Math.min(e.anchorOffset,t?hi(t):0),i,Math.min(e.focusOffset,i?hi(i):0))}set(e,t,i,s){this.anchorNode=e,this.anchorOffset=t,this.focusNode=i,this.focusOffset=s}},Ai=null;C.safari&&C.safari_version>=26&&(Ai=!1);function Yg(n){if(n.setActive)return n.setActive();if(Ai)return n.focus(Ai);let e=[];for(let t=n;t&&(e.push(t,t.scrollTop,t.scrollLeft),t!=t.ownerDocument);t=t.parentNode);if(n.focus(Ai==null?{get preventScroll(){return Ai={preventScroll:!0},!0}}:void 0),!Ai){Ai=!1;for(let t=0;tMath.max(1,n.scrollHeight-n.clientHeight-4)}function jg(n,e){for(let t=n,i=e;;){if(t.nodeType==3&&i>0)return{node:t,offset:i};if(t.nodeType==1&&i>0){if(t.contentEditable=="false")return null;t=t.childNodes[i-1],i=hi(t)}else if(t.parentNode&&!ho(t))i=wi(t),t=t.parentNode;else return null}}function Gg(n,e){for(let t=n,i=e;;){if(t.nodeType==3&&i=t){if(l.level==i)return o;(r<0||(s!=0?s<0?l.fromt:e[r].level>l.level))&&(r=o)}}if(r<0)throw new RangeError("Index out of range");return r}};function Ug(n,e){if(n.length!=e.length)return!1;for(let t=0;t=0;g-=3)if(Pt[g+1]==-d){let m=Pt[g+2],O=m&2?s:m&4?m&1?r:s:0;O&&(U[f]=U[Pt[g]]=O),l=g;break}}else{if(Pt.length==189)break;Pt[l++]=f,Pt[l++]=u,Pt[l++]=a}else if((p=U[f])==2||p==1){let g=p==s;a=g?0:1;for(let m=l-3;m>=0;m-=3){let O=Pt[m+2];if(O&2)break;if(g)Pt[m+2]|=2;else{if(O&4)break;Pt[m+2]|=4}}}}}function JS(n,e,t,i){for(let s=0,r=i;s<=t.length;s++){let o=s?t[s-1].to:n,l=sa;)p==m&&(p=t[--g].from,m=g?t[g-1].to:n),U[--p]=d;a=c}else r=h,a++}}}function ih(n,e,t,i,s,r,o){let l=i%2?2:1;if(i%2==s%2)for(let a=e,h=0;aa&&o.push(new ei(a,g.from,d));let m=g.direction==Vi!=!(d%2);sh(n,m?i+1:i,s,g.inner,g.from,g.to,o),a=g.to}p=g.to}else{if(p==t||(c?U[p]!=l:U[p]==l))break;p++}u?ih(n,a,p,i+1,s,u,o):ae;){let c=!0,f=!1;if(!h||a>r[h-1].to){let g=U[a-1];g!=l&&(c=!1,f=g==16)}let u=!c&&l==1?[]:null,d=c?i:i+1,p=a;e:for(;;)if(h&&p==r[h-1].to){if(f)break e;let g=r[--h];if(!c)for(let m=g.from,O=h;;){if(m==e)break e;if(O&&r[O-1].to==m)m=r[--O].from;else{if(U[m-1]==l)break e;break}}if(u)u.push(g);else{g.toU.length;)U[U.length]=256;let i=[],s=e==Vi?0:1;return sh(n,s,s,t,0,n.length,i),i}function Kg(n){return[new ei(0,n,0)]}let Jg="";function ty(n,e,t,i,s){var r;let o=i.head-n.from,l=ei.find(e,o,(r=i.bidiLevel)!==null&&r!==void 0?r:-1,i.assoc),a=e[l],h=a.side(s,t);if(o==h){let u=l+=s?1:-1;if(u<0||u>=e.length)return null;a=e[l=u],o=a.side(!s,t),h=a.side(s,t)}let c=_(n.text,o,a.forward(s,t));(ca.to)&&(c=h),Jg=n.text.slice(Math.min(o,c),Math.max(o,c));let f=l==(s?e.length-1:0)?null:e[l+(s?1:-1)];return f&&c==h&&f.level+(s?0:1)n.some(e=>e)}),lm=k.define({combine:n=>n.some(e=>e)}),am=k.define();let Bl=class rh{constructor(e,t="nearest",i="nearest",s=5,r=5,o=!1){this.range=e,this.y=t,this.x=i,this.yMargin=s,this.xMargin=r,this.isSnapshot=o}map(e){return e.empty?this:new rh(this.range.map(e),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}clip(e){return this.range.to<=e.doc.length?this:new rh(S.cursor(e.doc.length),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}};const rr=B.define({map:(n,e)=>n.map(e)}),hm=B.define();function Le(n,e,t){let i=n.facet(sm);i.length?i[0](e):window.onerror&&window.onerror(String(e),t,void 0,void 0,e)||(t?console.error(t+":",e):console.error(e))}const Ht=k.define({combine:n=>n.length?n[0]:!0});let sy=0;const Hi=k.define({combine(n){return n.filter((e,t)=>{for(let i=0;i{let a=[];return o&&a.push(nl.of(h=>{let c=h.plugin(l);return c?o(c):E.none})),r&&a.push(r(l)),a})}static fromClass(e,t){return oh.define((i,s)=>new e(i,s),t)}},Xl=class{constructor(e){this.spec=e,this.mustUpdate=null,this.value=null}get plugin(){return this.spec&&this.spec.plugin}update(e){if(this.value){if(this.mustUpdate){let t=this.mustUpdate;if(this.mustUpdate=null,this.value.update)try{this.value.update(t)}catch(i){if(Le(t.state,i,"CodeMirror plugin crashed"),this.value.destroy)try{this.value.destroy()}catch{}this.deactivate()}}}else if(this.spec)try{this.value=this.spec.plugin.create(e,this.spec.arg)}catch(t){Le(e.state,t,"CodeMirror plugin crashed"),this.deactivate()}return this}destroy(e){var t;if(!((t=this.value)===null||t===void 0)&&t.destroy)try{this.value.destroy()}catch(i){Le(e.state,i,"CodeMirror plugin crashed")}}deactivate(){this.spec=this.value=null}};const cm=k.define(),Zc=k.define(),nl=k.define(),fm=k.define(),Bc=k.define(),In=k.define(),um=k.define();function Jf(n,e){let t=n.state.facet(um);if(!t.length)return t;let i=t.map(r=>r instanceof Function?r(n):r),s=[];return M.spans(i,e.from,e.to,{point(){},span(r,o,l,a){let h=r-e.from,c=o-e.from,f=s;for(let u=l.length-1;u>=0;u--,a--){let d=l[u].spec.bidiIsolate,p;if(d==null&&(d=iy(e.text,h,c)),a>0&&f.length&&(p=f[f.length-1]).to==h&&p.direction==d)p.to=c,f=p.inner;else{let g={from:h,to:c,direction:d,inner:[]};f.push(g),f=g.inner}}}}),s}const dm=k.define();function Xc(n){let e=0,t=0,i=0,s=0;for(let r of n.state.facet(dm)){let o=r(n);o&&(o.left!=null&&(e=Math.max(e,o.left)),o.right!=null&&(t=Math.max(t,o.right)),o.top!=null&&(i=Math.max(i,o.top)),o.bottom!=null&&(s=Math.max(s,o.bottom)))}return{left:e,right:t,top:i,bottom:s}}const Hs=k.define();let ti=class lh{constructor(e,t,i,s){this.fromA=e,this.toA=t,this.fromB=i,this.toB=s}join(e){return new lh(Math.min(this.fromA,e.fromA),Math.max(this.toA,e.toA),Math.min(this.fromB,e.fromB),Math.max(this.toB,e.toB))}addToSet(e){let t=e.length,i=this;for(;t>0;t--){let s=e[t-1];if(!(s.fromA>i.toA)){if(s.toAs.push(new ti(r,o,l,a))),this.changedRanges=s}static create(e,t,i){return new pm(e,t,i)}get viewportChanged(){return(this.flags&4)>0}get viewportMoved(){return(this.flags&8)>0}get heightChanged(){return(this.flags&2)>0}get geometryChanged(){return this.docChanged||(this.flags&18)>0}get focusChanged(){return(this.flags&1)>0}get docChanged(){return!this.changes.empty}get selectionSet(){return this.transactions.some(e=>e.selection)}get empty(){return this.flags==0&&this.transactions.length==0}};const ny=[];let de=class{constructor(e,t,i=0){this.dom=e,this.length=t,this.flags=i,this.parent=null,e.cmTile=this}get breakAfter(){return this.flags&1}get children(){return ny}isWidget(){return!1}get isHidden(){return!1}isComposite(){return!1}isLine(){return!1}isText(){return!1}isBlock(){return!1}get domAttrs(){return null}sync(e){if(this.flags|=2,this.flags&4){this.flags&=-5;let t=this.domAttrs;t&&WS(this.dom,t)}}toString(){return this.constructor.name+(this.children.length?`(${this.children})`:"")+(this.breakAfter?"#":"")}destroy(){this.parent=null}setDOM(e){this.dom=e,e.cmTile=this}get posAtStart(){return this.parent?this.parent.posBefore(this):0}get posAtEnd(){return this.posAtStart+this.length}posBefore(e,t=this.posAtStart){let i=t;for(let s of this.children){if(s==e)return i;i+=s.length+s.breakAfter}throw new RangeError("Invalid child in posBefore")}posAfter(e){return this.posBefore(e)+e.length}covers(e){return!0}coordsIn(e,t){return null}domPosFor(e,t){let i=wi(this.dom),s=this.length?e>0:t>0;return new bi(this.parent.dom,i+(s?1:0),e==0||e==this.length)}markDirty(e){this.flags&=-3,e&&(this.flags|=4),this.parent&&this.parent.flags&2&&this.parent.markDirty(!1)}get overrideDOMText(){return null}get root(){for(let e=this;e;e=e.parent)if(e instanceof ol)return e;return null}static get(e){return e.cmTile}},rl=class extends de{constructor(e){super(e,0),this._children=[]}isComposite(){return!0}get children(){return this._children}get lastChild(){return this.children.length?this.children[this.children.length-1]:null}append(e){this.children.push(e),e.parent=this}sync(e){if(this.flags&2)return;super.sync(e);let t=this.dom,i=null,s,r=e?.node==t?e:null,o=0;for(let l of this.children){if(l.sync(e),o+=l.length+l.breakAfter,s=i?i.nextSibling:t.firstChild,r&&s!=l.dom&&(r.written=!0),l.dom.parentNode==t)for(;s&&s!=l.dom;)s=tu(s);else t.insertBefore(l.dom,s);i=l.dom}for(s=i?i.nextSibling:t.firstChild,r&&s&&(r.written=!0);s;)s=tu(s);this.length=o}};function tu(n){let e=n.nextSibling;return n.parentNode.removeChild(n),e}let ol=class extends rl{constructor(e,t){super(t),this.view=e}owns(e){for(;e;e=e.parent)if(e==this)return!0;return!1}isBlock(){return!0}nearest(e){for(;;){if(!e)return null;let t=de.get(e);if(t&&this.owns(t))return t;e=e.parentNode}}blockTiles(e){for(let t=[],i=this,s=0,r=0;;)if(s==i.children.length){if(!t.length)return;i=i.parent,i.breakAfter&&r++,s=t.pop()}else{let o=i.children[s++];if(o instanceof ns)t.push(s),i=o,s=0;else{let l=r+o.length,a=e(o,r);if(a!==void 0)return a;r=l+o.breakAfter}}}resolveBlock(e,t){let i,s=-1,r,o=-1;if(this.blockTiles((l,a)=>{let h=a+l.length;if(e>=a&&e<=h){if(l.isWidget()&&t>=-1&&t<=1){if(l.flags&32)return!0;l.flags&16&&(i=void 0)}(ae||e==a&&(t>1?l.length:l.covers(-1)))&&(!r||!l.isWidget()&&r.isWidget())&&(r=l,o=e-a)}}),!i&&!r)throw new Error("No tile at position "+e);return i&&t<0||!r?{tile:i,offset:s}:{tile:r,offset:o}}},ns=class gm extends rl{constructor(e,t){super(e),this.wrapper=t}isBlock(){return!0}covers(e){return this.children.length?e<0?this.children[0].covers(-1):this.lastChild.covers(1):!1}get domAttrs(){return this.wrapper.attributes}static of(e,t){let i=new gm(t||document.createElement(e.tagName),e);return t||(i.flags|=4),i}},fo=class mm extends rl{constructor(e,t){super(e),this.attrs=t}isLine(){return!0}static start(e,t,i){let s=new mm(t||document.createElement("div"),e);return(!t||!i)&&(s.flags|=4),s}get domAttrs(){return this.attrs}resolveInline(e,t,i){let s=null,r=-1,o=null,l=-1;function a(c,f){for(let u=0,d=0;u=f&&(p.isComposite()?a(p,f-d):(!o||o.isHidden&&(t>0||i&&oy(o,p)))&&(g>f||p.flags&32)?(o=p,l=f-d):(di&&(e=i);let s=e,r=e,o=0;e==0&&t<0||e==i&&t>=0?C.chrome||C.gecko||(e?(s--,o=1):r=0)?0:l.length-1];return C.safari&&!o&&a.width==0&&(a=Array.prototype.find.call(l,h=>h.width)||a),o?co(a,o<0):a||null}static of(e,t){let i=new bm(t||document.createTextNode(e),e);return t||(i.flags|=2),i}},$n=class Sm extends de{constructor(e,t,i,s){super(e,t,s),this.widget=i}isWidget(){return!0}get isHidden(){return this.widget.isHidden}covers(e){return this.flags&48?!1:(this.flags&(e<0?64:128))>0}coordsIn(e,t){return this.coordsInWidget(e,t,!1)}coordsInWidget(e,t,i){let s=this.widget.coordsAt(this.dom,e,t);if(s)return s;if(i)return co(this.dom.getBoundingClientRect(),this.length?e==0:t<=0);{let r=this.dom.getClientRects(),o=null;if(!r.length)return null;let l=this.flags&16?!0:this.flags&32?!1:e>0;for(let a=l?r.length-1:0;o=r[a],!(e>0?a==0:a==r.length-1||o.top0;)if(s.isComposite())if(o){if(!e)break;i&&i.break(),e--,o=!1}else if(r==s.children.length){if(!e&&!l.length)break;i&&i.leave(s),o=!!s.breakAfter,{tile:s,index:r}=l.pop(),r++}else{let a=s.children[r],h=a.breakAfter;(t>0?a.length<=e:a.length=0;l--){let a=t.marks[l],h=s.lastChild;if(h instanceof Ke&&h.mark.eq(a.mark))h.dom!=a.dom&&h.setDOM(Ll(a.dom)),s=h;else{if(this.cache.reused.get(a)){let f=de.get(a.dom);f&&f.setDOM(Ll(a.dom))}let c=Ke.of(a.mark,a.dom);s.append(c),s=c}this.cache.reused.set(a,2)}let r=de.get(e.text);r&&this.cache.reused.set(r,2);let o=new Fs(e.text,e.text.nodeValue);o.flags|=8,s.append(o)}addInlineWidget(e,t,i){let s=this.afterWidget&&e.flags&48&&(this.afterWidget.flags&48)==(e.flags&48);s||this.flushBuffer();let r=this.ensureMarks(t,i);!s&&!(e.flags&16)&&r.append(this.getBuffer(1)),r.append(e),this.pos+=e.length,this.afterWidget=e}addMark(e,t,i){this.flushBuffer(),this.ensureMarks(t,i).append(e),this.pos+=e.length,this.afterWidget=null}addBlockWidget(e){this.getBlockPos().append(e),this.pos+=e.length,this.lastBlock=e,this.endLine()}continueWidget(e){let t=this.afterWidget||this.lastBlock;t.length+=e,this.pos+=e}addLineStart(e,t){var i;e||(e=ym);let s=fo.start(e,t||((i=this.cache.find(fo))===null||i===void 0?void 0:i.dom),!!t);this.getBlockPos().append(this.lastBlock=this.curLine=s)}addLine(e){this.getBlockPos().append(e),this.pos+=e.length,this.lastBlock=e,this.endLine()}addBreak(){this.lastBlock.flags|=1,this.endLine(),this.pos++}addLineStartIfNotCovered(e){this.blockPosCovered()||this.addLineStart(e)}ensureLine(e){this.curLine||this.addLineStart(e)}ensureMarks(e,t){var i;let s=this.curLine;for(let r=e.length-1;r>=0;r--){let o=e[r],l;if(t>0&&(l=s.lastChild)&&l instanceof Ke&&l.mark.eq(o))s=l,t--;else{let a=Ke.of(o,(i=this.cache.find(Ke,h=>h.mark.eq(o)))===null||i===void 0?void 0:i.dom);s.append(a),s=a,t=0}}return s}endLine(){if(this.curLine){this.flushBuffer();let e=this.curLine.lastChild;(!e||!iu(this.curLine,!1)||e.dom.nodeName!="BR"&&e.isWidget()&&!(C.ios&&iu(this.curLine,!0)))&&this.curLine.append(this.cache.findWidget(El,0,32)||new $n(El.toDOM(),0,El,32)),this.curLine=this.afterWidget=null}}updateBlockWrappers(){this.wrapperPos>this.pos+1e4&&(this.blockWrappers.goto(this.pos),this.wrappers.length=0);for(let e=this.wrappers.length-1;e>=0;e--)this.wrappers[e].to=this.pos){let t=new ay(e.from,e.to,e.value,e.rank),i=this.wrappers.length;for(;i>0&&(this.wrappers[i-1].rank-t.rank||this.wrappers[i-1].to-t.to)<0;)i--;this.wrappers.splice(i,0,t)}this.wrapperPos=this.pos}getBlockPos(){var e;this.updateBlockWrappers();let t=this.root;for(let i of this.wrappers){let s=t.lastChild;if(i.fromo.wrapper.eq(i.wrapper)))===null||e===void 0?void 0:e.dom);t.append(r),t=r}}return t}blockPosCovered(){let e=this.lastBlock;return e!=null&&!e.breakAfter&&(!e.isWidget()||(e.flags&160)>0)}getBuffer(e){let t=2|(e<0?16:32),i=this.cache.find(uo,void 0,1);return i&&(i.flags=t),i||new uo(t)}flushBuffer(){this.afterWidget&&!(this.afterWidget.flags&32)&&(this.afterWidget.parent.append(this.getBuffer(-1)),this.afterWidget=null)}},cy=class{constructor(e){this.skipCount=0,this.text="",this.textOff=0,this.cursor=e.iter()}skip(e){this.textOff+e<=this.text.length?this.textOff+=e:(this.skipCount+=e-(this.text.length-this.textOff),this.text="",this.textOff=0)}next(e){if(this.textOff==this.text.length){let{value:s,lineBreak:r,done:o}=this.cursor.next(this.skipCount);if(this.skipCount=0,o)throw new Error("Ran out of text content when drawing inline views");this.text=s;let l=this.textOff=Math.min(e,s.length);return r?null:s.slice(0,l)}let t=Math.min(this.text.length,this.textOff+e),i=this.text.slice(this.textOff,t);return this.textOff=t,i}};const po=[$n,fo,Fs,Ke,uo,ns,ol];for(let n=0;n[]),this.index=po.map(()=>0),this.reused=new Map}add(e){let t=e.constructor.bucket,i=this.buckets[t];i.length<6?i.push(e):i[this.index[t]=(this.index[t]+1)%6]=e}find(e,t,i=2){let s=e.bucket,r=this.buckets[s],o=this.index[s];for(let l=r.length-1;l>=0;l--){let a=(l+o)%r.length,h=r[a];if((!t||t(h))&&!this.reused.has(h))return r.splice(a,1),a{if(this.cache.add(o),o.isComposite())return!1},enter:o=>this.cache.add(o),leave:()=>{},break:()=>{}}}run(e,t){let i=t&&this.getCompositionContext(t.text);for(let s=0,r=0,o=0;;){let l=os){let h=a-s;this.preserve(h,!o,!l),s=a,r+=h}if(!l)break;t&&l.fromA<=t.range.fromA&&l.toA>=t.range.toA?(this.forward(l.fromA,t.range.fromA,t.range.fromA{if(o.isWidget())if(this.openWidget)this.builder.continueWidget(a-l);else{let h=a>0||l{o.isLine()?this.builder.addLineStart(o.attrs,this.cache.maybeReuse(o)):(this.cache.add(o),o instanceof Ke&&s.unshift(o.mark)),this.openWidget=!1},leave:o=>{o.isLine()?s.length&&(s.length=r=0):o instanceof Ke&&(s.shift(),r=Math.min(r,s.length))},break:()=>{this.builder.addBreak(),this.openWidget=!1}}),this.text.skip(e)}emit(e,t){let i=null,s=this.builder,r=0,o=M.spans(this.decorations,e,t,{point:(l,a,h,c,f,u)=>{if(h instanceof Qn){if(this.disallowBlockEffectsFor[u]){if(h.block)throw new RangeError("Block decorations may not be specified via plugins");if(a>this.view.state.doc.lineAt(l).to)throw new RangeError("Decorations that replace line breaks may not be specified via plugins")}if(r=c.length,f>c.length)s.continueWidget(a-l);else{let d=h.widget||(h.block?Ss.block:Ss.inline),p=dy(h),g=this.cache.findWidget(d,a-l,p)||$n.of(d,this.view,a-l,p);h.block?(h.startSide>0&&s.addLineStartIfNotCovered(i),s.addBlockWidget(g)):(s.ensureLine(i),s.addInlineWidget(g,c,f))}i=null}else i=py(i,h);a>l&&this.text.skip(a-l)},span:(l,a,h,c)=>{for(let f=l;fr,this.openMarks=o}forward(e,t,i=1){t-e<=10?this.old.advance(t-e,i,this.reuseWalker):(this.old.advance(5,-1,this.reuseWalker),this.old.advance(t-e-10,-1),this.old.advance(5,i,this.reuseWalker))}getCompositionContext(e){let t=[],i=null;for(let s=e.parentNode;;s=s.parentNode){let r=de.get(s);if(s==this.view.contentDOM)break;r instanceof Ke?t.push(r):r?.isLine()?i=r:s.nodeName=="DIV"&&!i&&s!=this.view.contentDOM?i=new fo(s,ym):t.push(Ke.of(new Tc({tagName:s.nodeName.toLowerCase(),attributes:VS(s)}),s))}return{line:i,marks:t}}};function iu(n,e){let t=i=>{for(let s of i.children)if((e?s.isText():s.length)||t(s))return!0;return!1};return t(n)}function dy(n){let e=n.isReplace?(n.startSide<0?64:0)|(n.endSide>0?128:0):n.startSide>0?32:16;return n.block&&(e|=256),e}const ym={class:"cm-line"};function py(n,e){let t=e.spec.attributes,i=e.spec.class;return!t&&!i||(n||(n={class:"cm-line"}),t&&Pc(t,n),i&&(n.class+=" "+i)),n}function gy(n){let e=[];for(let t=n.parents.length;t>1;t--){let i=t==n.parents.length?n.tile:n.parents[t].tile;i instanceof Ke&&e.push(i.mark)}return e}function Ll(n){let e=de.get(n);return e&&e.setDOM(n.cloneNode()),n}let Ss=class extends Yt{constructor(e){super(),this.tag=e}eq(e){return e.tag==this.tag}toDOM(){return document.createElement(this.tag)}updateDOM(e){return e.nodeName.toLowerCase()==this.tag}get isHidden(){return!0}};Ss.inline=new Ss("span");Ss.block=new Ss("div");const El=new class extends Yt{toDOM(){return document.createElement("br")}get isHidden(){return!0}get editable(){return!0}};let su=class{constructor(e){this.view=e,this.decorations=[],this.blockWrappers=[],this.dynamicDecorationMap=[!1],this.domChanged=null,this.hasComposition=null,this.editContextFormatting=E.none,this.lastCompositionAfterCursor=!1,this.minWidth=0,this.minWidthFrom=0,this.minWidthTo=0,this.impreciseAnchor=null,this.impreciseHead=null,this.forceSelection=!1,this.lastUpdate=Date.now(),this.updateDeco(),this.tile=new ol(e,e.contentDOM),this.updateInner([new ti(0,0,0,e.state.doc.length)],null)}update(e){var t;let i=e.changedRanges;this.minWidth>0&&i.length&&(i.every(({fromA:c,toA:f})=>fthis.minWidthTo)?(this.minWidthFrom=e.changes.mapPos(this.minWidthFrom,1),this.minWidthTo=e.changes.mapPos(this.minWidthTo,1)):this.minWidth=this.minWidthFrom=this.minWidthTo=0),this.updateEditContextFormatting(e);let s=-1;this.view.inputState.composing>=0&&!this.view.observer.editContext&&(!((t=this.domChanged)===null||t===void 0)&&t.newSel?s=this.domChanged.newSel.head:!Qy(e.changes,this.hasComposition)&&!e.selectionSet&&(s=e.state.selection.main.head));let r=s>-1?Oy(this.view,e.changes,s):null;if(this.domChanged=null,this.hasComposition){let{from:c,to:f}=this.hasComposition;i=new ti(c,f,e.changes.mapPos(c,-1),e.changes.mapPos(f,1)).addToSet(i.slice())}this.hasComposition=r?{from:r.range.fromB,to:r.range.toB}:null,(C.ie||C.chrome)&&!r&&e&&e.state.doc.lines!=e.startState.doc.lines&&(this.forceSelection=!0);let o=this.decorations,l=this.blockWrappers;this.updateDeco();let a=yy(o,this.decorations,e.changes);a.length&&(i=ti.extendWithRanges(i,a));let h=wy(l,this.blockWrappers,e.changes);return h.length&&(i=ti.extendWithRanges(i,h)),r&&!i.some(c=>c.fromA<=r.range.fromA&&c.toA>=r.range.toA)&&(i=r.range.addToSet(i.slice())),this.tile.flags&2&&i.length==0?!1:(this.updateInner(i,r),e.transactions.length&&(this.lastUpdate=Date.now()),!0)}updateInner(e,t){this.view.viewState.mustMeasureContent=!0;let{observer:i}=this.view;i.ignore(()=>{if(t||e.length){let o=this.tile,l=new uy(this.view,o,this.blockWrappers,this.decorations,this.dynamicDecorationMap);this.tile=l.run(e,t),ah(o,l.cache.reused)}this.tile.dom.style.height=this.view.viewState.contentHeight/this.view.scaleY+"px",this.tile.dom.style.flexBasis=this.minWidth?this.minWidth+"px":"";let r=C.chrome||C.ios?{node:i.selectionRange.focusNode,written:!1}:void 0;this.tile.sync(r),r&&(r.written||i.selectionRange.focusNode!=r.node||!this.tile.dom.contains(r.node))&&(this.forceSelection=!0),this.tile.dom.style.height=""});let s=[];if(this.view.viewport.from||this.view.viewport.to-1)&&fn(i,this.view.observer.selectionRange)&&!(s&&i.contains(s));if(!(r||t||o))return;let l=this.forceSelection;this.forceSelection=!1;let a=this.view.state.selection.main,h,c;if(a.empty?c=h=this.inlineDOMNearPos(a.anchor,a.assoc||1):(c=this.inlineDOMNearPos(a.head,a.head==a.from?1:-1),h=this.inlineDOMNearPos(a.anchor,a.anchor==a.from?1:-1)),C.gecko&&a.empty&&!this.hasComposition&&my(h)){let u=document.createTextNode("");this.view.observer.ignore(()=>h.node.insertBefore(u,h.node.childNodes[h.offset]||null)),h=c=new bi(u,0),l=!0}let f=this.view.observer.selectionRange;(l||!f.focusNode||(!un(h.node,h.offset,f.anchorNode,f.anchorOffset)||!un(c.node,c.offset,f.focusNode,f.focusOffset))&&!this.suppressWidgetCursorChange(f,a))&&(this.view.observer.ignore(()=>{C.android&&C.chrome&&i.contains(f.focusNode)&&ky(f.focusNode,i)&&(i.blur(),i.focus({preventScroll:!0}));let u=bs(this.view.root);if(u)if(a.empty){if(C.gecko){let d=by(h.node,h.offset);if(d&&d!=3){let p=(d==1?jg:Gg)(h.node,h.offset);p&&(h=new bi(p.node,p.offset))}}u.collapse(h.node,h.offset),a.bidiLevel!=null&&u.caretBidiLevel!==void 0&&(u.caretBidiLevel=a.bidiLevel)}else if(u.extend){u.collapse(h.node,h.offset);try{u.extend(c.node,c.offset)}catch{}}else{let d=document.createRange();a.anchor>a.head&&([h,c]=[c,h]),d.setEnd(c.node,c.offset),d.setStart(h.node,h.offset),u.removeAllRanges(),u.addRange(d)}o&&this.view.root.activeElement==i&&(i.blur(),s&&s.focus())}),this.view.observer.setSelectionRange(h,c)),this.impreciseAnchor=h.precise?null:new bi(f.anchorNode,f.anchorOffset),this.impreciseHead=c.precise?null:new bi(f.focusNode,f.focusOffset)}suppressWidgetCursorChange(e,t){return this.hasComposition&&t.empty&&un(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset)&&this.posFromDOM(e.focusNode,e.focusOffset)==t.head}enforceCursorAssoc(){if(this.hasComposition)return;let{view:e}=this,t=e.state.selection.main,i=bs(e.root),{anchorNode:s,anchorOffset:r}=e.observer.selectionRange;if(!i||!t.empty||!t.assoc||!i.modify)return;let o=this.lineAt(t.head,t.assoc);if(!o)return;let l=o.posAtStart;if(t.head==l||t.head==l+o.length)return;let a=this.coordsAt(t.head,-1),h=this.coordsAt(t.head,1);if(!a||!h||a.bottom>h.top)return;let c=this.domAtPos(t.head+t.assoc,t.assoc);i.collapse(c.node,c.offset),i.modify("move",t.assoc<0?"forward":"backward","lineboundary"),e.observer.readSelectionRange();let f=e.observer.selectionRange;e.docView.posFromDOM(f.anchorNode,f.anchorOffset)!=t.from&&i.collapse(s,r)}posFromDOM(e,t){let i=this.tile.nearest(e);if(!i)return this.tile.dom.compareDocumentPosition(e)&2?0:this.view.state.doc.length;let s=i.posAtStart;if(i.isComposite()){let r;if(e==i.dom)r=i.dom.childNodes[t];else{let o=hi(e)==0?0:t==0?-1:1;for(;;){let l=e.parentNode;if(l==i.dom)break;o==0&&l.firstChild!=l.lastChild&&(e==l.firstChild?o=-1:o=1),e=l}o<0?r=e:r=e.nextSibling}if(r==i.dom.firstChild)return s;for(;r&&!de.get(r);)r=r.nextSibling;if(!r)return s+i.length;for(let o=0,l=s;;o++){let a=i.children[o];if(a.dom==r)return l;l+=a.length+a.breakAfter}}else return i.isText()?e==i.dom?s+t:s+(t?i.length:0):s}domAtPos(e,t){let{tile:i,offset:s}=this.tile.resolveBlock(e,t);return i.isWidget()?i.domPosFor(e,t):i.domIn(s,t)}inlineDOMNearPos(e,t){let i,s=-1,r=!1,o,l=-1,a=!1;return this.tile.blockTiles((h,c)=>{if(h.isWidget()){if(h.flags&32&&c>=e)return!0;h.flags&16&&(r=!0)}else{let f=c+h.length;if(c<=e&&(i=h,s=e-c,r=f=e&&!o&&(o=h,l=e-c,a=c>e),c>e&&o)return!0}}),!i&&!o?this.domAtPos(e,t):(r&&o?i=null:a&&i&&(o=null),i&&t<0||!o?i.domIn(s,t):o.domIn(l,t))}coordsAt(e,t){let{tile:i,offset:s}=this.tile.resolveBlock(e,t);return i.isWidget()?i.widget instanceof Wl?null:i.coordsInWidget(s,t,!0):i.coordsIn(s,t)}lineAt(e,t){let{tile:i}=this.tile.resolveBlock(e,t);return i.isLine()?i:null}coordsForChar(e){let{tile:t,offset:i}=this.tile.resolveBlock(e,1);if(!t.isLine())return null;function s(r,o){if(r.isComposite())for(let l of r.children){if(l.length>=o){let a=s(l,o);if(a)return a}if(o-=l.length,o<0)break}else if(r.isText()&&oMath.max(this.view.scrollDOM.clientWidth,this.minWidth)+1,l=-1,a=this.view.textDirection==le.LTR,h=0,c=(f,u,d)=>{for(let p=0;ps);p++){let g=f.children[p],m=u+g.length,O=g.dom.getBoundingClientRect(),{height:y}=O;if(d&&!p&&(h+=O.top-d.top),g instanceof ns)m>i&&c(g,u,O);else if(u>=i&&(h>0&&t.push(-h),t.push(y+h),h=0,o)){let x=g.dom.lastChild,v=x?Ir(x):[];if(v.length){let w=v[v.length-1],Q=a?w.right-O.left:O.right-w.left;Q>l&&(l=Q,this.minWidth=r,this.minWidthFrom=u,this.minWidthTo=m)}}d&&p==f.children.length-1&&(h+=d.bottom-O.bottom),u=m+g.breakAfter}};return c(this.tile,0,null),t}textDirectionAt(e){let{tile:t}=this.tile.resolveBlock(e,1);return getComputedStyle(t.dom).direction=="rtl"?le.RTL:le.LTR}measureTextSize(){let e=this.tile.blockTiles(o=>{if(o.isLine()&&o.children.length&&o.length<=20){let l=0,a;for(let h of o.children){if(!h.isText()||/[^ -~]/.test(h.text))return;let c=Ir(h.dom);if(c.length!=1)return;l+=c[0].width,a=c[0].height}if(l)return{lineHeight:o.dom.getBoundingClientRect().height,charWidth:l/o.length,textHeight:a}}});if(e)return e;let t=document.createElement("div"),i,s,r;return t.className="cm-line",t.style.width="99999px",t.style.position="absolute",t.textContent="abc def ghi jkl mno pqr stu",this.view.observer.ignore(()=>{this.tile.dom.appendChild(t);let o=Ir(t.firstChild)[0];i=t.getBoundingClientRect().height,s=o&&o.width?o.width/27:7,r=o&&o.height?o.height:i,t.remove()}),{lineHeight:i,charWidth:s,textHeight:r}}computeBlockGapDeco(){let e=[],t=this.view.viewState;for(let i=0,s=0;;s++){let r=s==t.viewports.length?null:t.viewports[s],o=r?r.from-1:this.view.state.doc.length;if(o>i){let l=(t.lineBlockAt(o).bottom-t.lineBlockAt(i).top)/this.view.scaleY;e.push(E.replace({widget:new Wl(l),block:!0,inclusive:!0,isBlockGap:!0}).range(i,o))}if(!r)break;i=r.to+1}return E.set(e)}updateDeco(){let e=1,t=this.view.state.facet(nl).map(r=>(this.dynamicDecorationMap[e++]=typeof r=="function")?r(this.view):r),i=!1,s=this.view.state.facet(Bc).map((r,o)=>{let l=typeof r=="function";return l&&(i=!0),l?r(this.view):r});for(s.length&&(this.dynamicDecorationMap[e++]=i,t.push(M.join(s))),this.decorations=[this.editContextFormatting,...t,this.computeBlockGapDeco(),this.view.viewState.lineGapDeco];etypeof r=="function"?r(this.view):r)}scrollIntoView(e){if(e.isSnapshot){let h=this.view.viewState.lineBlockAt(e.range.head);this.view.scrollDOM.scrollTop=h.top-e.yMargin,this.view.scrollDOM.scrollLeft=e.xMargin;return}for(let h of this.view.state.facet(am))try{if(h(this.view,e.range,e))return!0}catch(c){Le(this.view.state,c,"scroll handler")}let{range:t}=e,i=this.coordsAt(t.head,t.empty?t.assoc:t.head>t.anchor?-1:1),s;if(!i)return;!t.empty&&(s=this.coordsAt(t.anchor,t.anchor>t.head?-1:1))&&(i={left:Math.min(i.left,s.left),top:Math.min(i.top,s.top),right:Math.max(i.right,s.right),bottom:Math.max(i.bottom,s.bottom)});let r=Xc(this.view),o={left:i.left-r.left,top:i.top-r.top,right:i.right+r.right,bottom:i.bottom+r.bottom},{offsetWidth:l,offsetHeight:a}=this.view.scrollDOM;IS(this.view.scrollDOM,o,t.headi.isWidget()||i.children.some(t);return t(this.tile.resolveBlock(e,1).tile)}destroy(){ah(this.tile)}};function ah(n,e){let t=e?.get(n);if(t!=1){t==null&&n.destroy();for(let i of n.children)ah(i,e)}}function my(n){return n.node.nodeType==1&&n.node.firstChild&&(n.offset==0||n.node.childNodes[n.offset-1].contentEditable=="false")&&(n.offset==n.node.childNodes.length||n.node.childNodes[n.offset].contentEditable=="false")}function xm(n,e){let t=n.observer.selectionRange;if(!t.focusNode)return null;let i=jg(t.focusNode,t.focusOffset),s=Gg(t.focusNode,t.focusOffset),r=i||s;if(s&&i&&s.node!=i.node){let l=de.get(s.node);if(!l||l.isText()&&l.text!=s.node.nodeValue)r=s;else if(n.docView.lastCompositionAfterCursor){let a=de.get(i.node);!a||a.isText()&&a.text!=i.node.nodeValue||(r=s)}}if(n.docView.lastCompositionAfterCursor=r!=i,!r)return null;let o=e-r.offset;return{from:o,to:o+r.node.nodeValue.length,node:r.node}}function Oy(n,e,t){let i=xm(n,t);if(!i)return null;let{node:s,from:r,to:o}=i,l=s.nodeValue;if(/[\n\r]/.test(l)||n.state.doc.sliceString(i.from,i.to)!=l)return null;let a=e.invertedDesc;return{range:new ti(a.mapPos(r),a.mapPos(o),r,o),text:s}}function by(n,e){return n.nodeType!=1?0:(e&&n.childNodes[e-1].contentEditable=="false"?1:0)|(e{ie.from&&(t=!0)}),t}let Wl=class extends Yt{constructor(e){super(),this.height=e}toDOM(){let e=document.createElement("div");return e.className="cm-gap",this.updateDOM(e),e}eq(e){return e.height==this.height}updateDOM(e){return e.style.height=this.height+"px",!0}get editable(){return!0}get estimatedHeight(){return this.height}ignoreEvent(){return!1}};function vy(n,e,t=1){let i=n.charCategorizer(e),s=n.doc.lineAt(e),r=e-s.from;if(s.length==0)return S.cursor(e);r==0?t=1:r==s.length&&(t=-1);let o=r,l=r;t<0?o=_(s.text,r,!1):l=_(s.text,r);let a=i(s.text.slice(o,l));for(;o>0;){let h=_(s.text,o,!1);if(i(s.text.slice(h,o))!=a)break;o=h}for(;ln.defaultLineHeight*1.5){let l=n.viewState.heightOracle.textHeight,a=Math.floor((s-t.top-(n.defaultLineHeight-l)*.5)/l);r+=a*n.viewState.heightOracle.lineLength}let o=n.state.sliceDoc(t.from,t.to);return t.from+kn(o,r,n.state.tabSize)}function hh(n,e,t){let i=n.lineBlockAt(e);if(Array.isArray(i.type)){let s;for(let r of i.type){if(r.from>e)break;if(!(r.toe)return r;(!s||r.type==ke.Text&&(s.type!=r.type||(t<0?r.frome)))&&(s=r)}}return s||i}return i}function Py(n,e,t,i){let s=hh(n,e.head,e.assoc||-1),r=!i||s.type!=ke.Text||!(n.lineWrapping||s.widgetLineBreaks)?null:n.coordsAtPos(e.assoc<0&&e.head>s.from?e.head-1:e.head);if(r){let o=n.dom.getBoundingClientRect(),l=n.textDirectionAt(s.from),a=n.posAtCoords({x:t==(l==le.LTR)?o.right-1:o.left+1,y:(r.top+r.bottom)/2});if(a!=null)return S.cursor(a,t?-1:1)}return S.cursor(t?s.to:s.from,t?-1:1)}function nu(n,e,t,i){let s=n.state.doc.lineAt(e.head),r=n.bidiSpans(s),o=n.textDirectionAt(s.from);for(let l=e,a=null;;){let h=ty(s,r,o,l,t),c=Jg;if(!h){if(s.number==(t?n.state.doc.lines:1))return l;c=` `,s=n.state.doc.line(s.number+(t?1:-1)),r=n.bidiSpans(s),h=n.visualLineSide(s,!t)}if(a){if(!a(c))return l}else{if(!i)return h;a=i(c)}l=h}}function Cy(n,e,t){let i=n.state.charCategorizer(e),s=i(t);return r=>{let o=i(r);return s==oe.Space&&(s=o),s==o}}function Ty(n,e,t,i){let s=e.head,r=t?1:-1;if(s==(t?n.state.doc.length:0))return S.cursor(s,e.assoc);let o=e.goalColumn,l,a=n.contentDOM.getBoundingClientRect(),h=n.coordsAtPos(s,e.assoc||-1),c=n.documentTop;if(h)o==null&&(o=h.left-a.left),l=r<0?h.top:h.bottom;else{let p=n.viewState.lineBlockAt(s);o==null&&(o=Math.min(a.right-a.left,n.defaultCharacterWidth*(s-p.from))),l=(r<0?p.top:p.bottom)+c}let f=a.left+o,u=i??n.viewState.heightOracle.textHeight>>1,d=ch(n,{x:f,y:l+u*r},!1,r);return S.cursor(d.pos,d.assoc,void 0,o)}function dn(n,e,t){for(;;){let i=0;for(let s of n)s.between(e-1,e+1,(r,o,l)=>{if(e>r&&es(n)),t.from,e.head>t.from?-1:1);return i==t.from?t:S.cursor(i,in.viewState.docHeight)return new Xt(n.state.doc.length,-1);if(h=n.elementAtHeight(a),i==null)break;if(h.type==ke.Text){if(i<0?h.ton.viewport.to)break;let u=n.docView.coordsAt(i<0?h.from:h.to,i);if(u&&(i<0?u.top<=a+r:u.bottom>=a+r))break}let f=n.viewState.heightOracle.textHeight/2;a=i>0?h.bottom+f:h.top-f}if(n.viewport.from>=h.to||n.viewport.to<=h.from){if(t)return null;if(h.type==ke.Text){let f=$y(n,s,h,o,l);return new Xt(f,f==h.from?1:-1)}}if(h.type!=ke.Text)return a<(h.top+h.bottom)/2?new Xt(h.from,1):new Xt(h.to,-1);let c=n.docView.lineAt(h.from,2);return(!c||c.length!=h.length)&&(c=n.docView.lineAt(h.from,-2)),km(n,c,h.from,o,l)}function km(n,e,t,i,s){let r=-1,o=null,l=1e9,a=1e9,h=s,c=s,f=(u,d)=>{for(let p=0;pi?g.left-i:g.rights?g.top-s:g.bottom=h&&(h=Math.min(g.top,h),c=Math.max(g.bottom,c),O=0),(r<0||(O-a||m-l)<0)&&(r>=0&&a&&l=h+2?a=0:(r=d,l=m,a=O,o=g))}};if(e.isText()){for(let d=0;d(o.left+o.right)/2==(ru(n,r+t)==le.LTR)?new Xt(t+_(e.text,r),-1):new Xt(t+r,1)}else{if(!e.length)return new Xt(t,1);for(let g=0;g(o.left+o.right)/2==(ru(n,r+t)==le.LTR)?new Xt(d+u.length,-1):new Xt(d,1)}}function ru(n,e){let t=n.state.doc.lineAt(e);return n.bidiSpans(t)[ei.find(n.bidiSpans(t),e-t.from,-1,1)].dir}const Us="￿";let My=class{constructor(e,t){this.points=e,this.view=t,this.text="",this.lineSeparator=t.state.facet(L.lineSeparator)}append(e){this.text+=e}lineBreak(){this.text+=Us}readRange(e,t){if(!e)return this;let i=e.parentNode;for(let s=e;;){this.findPointBefore(i,s);let r=this.text.length;this.readNode(s);let o=de.get(s),l=s.nextSibling;if(l==t){o?.breakAfter&&!l&&i!=this.view.contentDOM&&this.lineBreak();break}let a=de.get(l);(o&&a?o.breakAfter:(o?o.breakAfter:ho(s))||ho(l)&&(s.nodeName!="BR"||o?.isWidget())&&this.text.length>r)&&!Ry(l,t)&&this.lineBreak(),s=l}return this.findPointBefore(i,t),this}readTextNode(e){let t=e.nodeValue;for(let i of this.points)i.node==e&&(i.pos=this.text.length+Math.min(i.offset,t.length));for(let i=0,s=this.lineSeparator?null:/\r\n?|\n/g;;){let r=-1,o=1,l;if(this.lineSeparator?(r=t.indexOf(this.lineSeparator,i),o=this.lineSeparator.length):(l=s.exec(t))&&(r=l.index,o=l[0].length),this.append(t.slice(i,r<0?t.length:r)),r<0)break;if(this.lineBreak(),o>1)for(let a of this.points)a.node==e&&a.pos>this.text.length&&(a.pos-=o-1);i=r+o}}readNode(e){let t=de.get(e),i=t&&t.overrideDOMText;if(i!=null){this.findPointInside(e,i.length);for(let s=i.iter();!s.next().done;)s.lineBreak?this.lineBreak():this.append(s.value)}else e.nodeType==3?this.readTextNode(e):e.nodeName=="BR"?e.nextSibling&&this.lineBreak():e.nodeType==1&&this.readRange(e.firstChild,null)}findPointBefore(e,t){for(let i of this.points)i.node==e&&e.childNodes[i.offset]==t&&(i.pos=this.text.length)}findPointInside(e,t){for(let i of this.points)(e.nodeType==3?i.node==e:e.contains(i.node))&&(i.pos=this.text.length+(Ay(e,i.node,i.offset)?t:0))}};function Ay(n,e,t){for(;;){if(!e||t-1;let{impreciseHead:r,impreciseAnchor:o}=e.docView;if(e.state.readOnly&&t>-1)this.newSel=null;else if(t>-1&&(this.bounds=Qm(e.docView.tile,t,i,0))){let l=r||o?[]:By(e),a=new My(l,e);a.readRange(this.bounds.startDOM,this.bounds.endDOM),this.text=a.text,this.newSel=Xy(l,this.bounds.from)}else{let l=e.observer.selectionRange,a=r&&r.node==l.focusNode&&r.offset==l.focusOffset||!Ja(e.contentDOM,l.focusNode)?e.state.selection.main.head:e.docView.posFromDOM(l.focusNode,l.focusOffset),h=o&&o.node==l.anchorNode&&o.offset==l.anchorOffset||!Ja(e.contentDOM,l.anchorNode)?e.state.selection.main.anchor:e.docView.posFromDOM(l.anchorNode,l.anchorOffset),c=e.viewport;if((C.ios||C.chrome)&&e.state.selection.main.empty&&a!=h&&(c.from>0||c.to-1&&e.state.selection.ranges.length>1?this.newSel=e.state.selection.replaceRange(S.range(h,a)):this.newSel=S.single(h,a)}}};function Qm(n,e,t,i){if(n.isComposite()){let s=-1,r=-1,o=-1,l=-1;for(let a=0,h=i,c=i;at)return Qm(f,e,t,h);if(u>=e&&s==-1&&(s=a,r=h),h>t&&f.dom.parentNode==n.dom){o=a,l=c;break}c=u,h=u+f.breakAfter}return{from:r,to:l<0?i+n.length:l,startDOM:(s?n.children[s-1].dom.nextSibling:null)||n.dom.firstChild,endDOM:o=0?n.children[o].dom:null}}else return n.isText()?{from:i,to:i+n.length,startDOM:n.dom,endDOM:n.dom.nextSibling}:null}function vm(n,e){let t,{newSel:i}=e,s=n.state.selection.main,r=n.inputState.lastKeyTime>Date.now()-100?n.inputState.lastKeyCode:-1;if(e.bounds){let{from:o,to:l}=e.bounds,a=s.from,h=null;(r===8||C.android&&e.text.length=s.from&&t.to<=s.to&&(t.from!=s.from||t.to!=s.to)&&s.to-s.from-(t.to-t.from)<=4?t={from:s.from,to:s.to,insert:n.state.doc.slice(s.from,t.from).append(t.insert).append(n.state.doc.slice(t.to,s.to))}:n.state.doc.lineAt(s.from).toDate.now()-50?t={from:s.from,to:s.to,insert:n.state.toText(n.inputState.insertingText)}:C.chrome&&t&&t.from==t.to&&t.from==s.head&&t.insert.toString()==` `&&n.lineWrapping&&(i&&(i=S.single(i.main.anchor-1,i.main.head-1)),t={from:s.from,to:s.to,insert:Z.of([" "])}),t)return Lc(n,t,i,r);if(i&&!go(i,s)){let o=!1,l="select";return n.inputState.lastSelectionTime>Date.now()-50&&(n.inputState.lastSelectionOrigin=="select"&&(o=!0),l=n.inputState.lastSelectionOrigin,l=="select.pointer"&&(i=wm(n.state.facet(In).map(a=>a(n)),i))),n.dispatch({selection:i,scrollIntoView:o,userEvent:l}),!0}else return!1}function Lc(n,e,t,i=-1){if(C.ios&&n.inputState.flushIOSKey(e))return!0;let s=n.state.selection.main;if(C.android&&(e.to==s.to&&(e.from==s.from||e.from==s.from-1&&n.state.sliceDoc(e.from,s.from)==" ")&&e.insert.length==1&&e.insert.lines==2&&ss(n.contentDOM,"Enter",13)||(e.from==s.from-1&&e.to==s.to&&e.insert.length==0||i==8&&e.insert.lengths.head)&&ss(n.contentDOM,"Backspace",8)||e.from==s.from&&e.to==s.to+1&&e.insert.length==0&&ss(n.contentDOM,"Delete",46)))return!0;let r=e.insert.toString();n.inputState.composing>=0&&n.inputState.composing++;let o,l=()=>o||(o=Zy(n,e,t));return n.state.facet(nm).some(a=>a(n,e.from,e.to,r,l))||n.dispatch(l()),!0}function Zy(n,e,t){let i,s=n.state,r=s.selection.main,o=-1;if(e.from==e.to&&e.fromr.to){let a=e.fromf(n)),h,a);e.from==c&&(o=c)}if(o>-1)i={changes:e,selection:S.cursor(e.from+e.insert.length,-1)};else if(e.from>=r.from&&e.to<=r.to&&e.to-e.from>=(r.to-r.from)/3&&(!t||t.main.empty&&t.main.from==e.from+e.insert.length)&&n.inputState.composing<0){let a=r.frome.to?s.sliceDoc(e.to,r.to):"";i=s.replaceSelection(n.state.toText(a+e.insert.sliceString(0,void 0,n.state.lineBreak)+h))}else{let a=s.changes(e),h=t&&t.main.to<=a.newLength?t.main:void 0;if(s.selection.ranges.length>1&&(n.inputState.composing>=0||n.inputState.compositionPendingChange)&&e.to<=r.to+10&&e.to>=r.to-10){let c=n.state.sliceDoc(e.from,e.to),f,u=t&&xm(n,t.main.head);if(u){let p=e.insert.length-(e.to-e.from);f={from:u.from,to:u.to-p}}else f=n.state.doc.lineAt(r.head);let d=r.to-e.to;i=s.changeByRange(p=>{if(p.from==r.from&&p.to==r.to)return{changes:a,range:h||p.map(a)};let g=p.to-d,m=g-c.length;if(n.state.sliceDoc(m,g)!=c||g>=f.from&&m<=f.to)return{range:p};let O=s.changes({from:m,to:g,insert:e.insert}),y=p.to-r.to;return{changes:O,range:h?S.range(Math.max(0,h.anchor+y),Math.max(0,h.head+y)):p.map(O)}})}else i={changes:a,selection:h&&s.selection.replaceRange(h)}}let l="input.type";return(n.composing||n.inputState.compositionPendingChange&&n.inputState.compositionEndedAt>Date.now()-50)&&(n.inputState.compositionPendingChange=!1,l+=".compose",n.inputState.compositionFirstChange&&(l+=".start",n.inputState.compositionFirstChange=!1)),s.update(i,{userEvent:l,scrollIntoView:!0})}function $m(n,e,t,i){let s=Math.min(n.length,e.length),r=0;for(;r0&&l>0&&n.charCodeAt(o-1)==e.charCodeAt(l-1);)o--,l--;if(i=="end"){let a=Math.max(0,r-Math.min(o,l));t-=o+a-r}if(o=o?r-t:0;r-=a,l=r+(l-o),o=r}else if(l=l?r-t:0;r-=a,o=r+(o-l),l=r}return{from:r,toA:o,toB:l}}function By(n){let e=[];if(n.root.activeElement!=n.contentDOM)return e;let{anchorNode:t,anchorOffset:i,focusNode:s,focusOffset:r}=n.observer.selectionRange;return t&&(e.push(new ou(t,i)),(s!=t||r!=i)&&e.push(new ou(s,r))),e}function Xy(n,e){if(n.length==0)return null;let t=n[0].pos,i=n.length==2?n[1].pos:t;return t>-1&&i>-1?S.single(t+e,i+e):null}function go(n,e){return e.head==n.main.head&&e.anchor==n.main.anchor}let Ly=class{setSelectionOrigin(e){this.lastSelectionOrigin=e,this.lastSelectionTime=Date.now()}constructor(e){this.view=e,this.lastKeyCode=0,this.lastKeyTime=0,this.lastTouchTime=0,this.lastFocusTime=0,this.lastScrollTop=0,this.lastScrollLeft=0,this.pendingIOSKey=void 0,this.tabFocusMode=-1,this.lastSelectionOrigin=null,this.lastSelectionTime=0,this.lastContextMenu=0,this.scrollHandlers=[],this.handlers=Object.create(null),this.composing=-1,this.compositionFirstChange=null,this.compositionEndedAt=0,this.compositionPendingKey=!1,this.compositionPendingChange=!1,this.insertingText="",this.insertingTextAt=0,this.mouseSelection=null,this.draggedContent=null,this.handleEvent=this.handleEvent.bind(this),this.notifiedFocused=e.hasFocus,C.safari&&e.contentDOM.addEventListener("input",()=>null),C.gecko&&Ky(e.contentDOM.ownerDocument)}handleEvent(e){!Yy(this.view,e)||this.ignoreDuringComposition(e)||e.type=="keydown"&&this.keydown(e)||(this.view.updateState!=0?Promise.resolve().then(()=>this.runHandlers(e.type,e)):this.runHandlers(e.type,e))}runHandlers(e,t){let i=this.handlers[e];if(i){for(let s of i.observers)s(this.view,t);for(let s of i.handlers){if(t.defaultPrevented)break;if(s(this.view,t)){t.preventDefault();break}}}}ensureHandlers(e){let t=Ey(e),i=this.handlers,s=this.view.contentDOM;for(let r in t)if(r!="scroll"){let o=!t[r].handlers.length,l=i[r];l&&o!=!l.handlers.length&&(s.removeEventListener(r,this.handleEvent),l=null),l||s.addEventListener(r,this.handleEvent,{passive:o})}for(let r in i)r!="scroll"&&!t[r]&&s.removeEventListener(r,this.handleEvent);this.handlers=t}keydown(e){if(this.lastKeyCode=e.keyCode,this.lastKeyTime=Date.now(),e.keyCode==9&&this.tabFocusMode>-1&&(!this.tabFocusMode||Date.now()<=this.tabFocusMode))return!0;if(this.tabFocusMode>0&&e.keyCode!=27&&Cm.indexOf(e.keyCode)<0&&(this.tabFocusMode=-1),C.android&&C.chrome&&!e.synthetic&&(e.keyCode==13||e.keyCode==8))return this.view.observer.delayAndroidKey(e.key,e.keyCode),!0;let t;return C.ios&&!e.synthetic&&!e.altKey&&!e.metaKey&&((t=Pm.find(i=>i.keyCode==e.keyCode))&&!e.ctrlKey||Wy.indexOf(e.key)>-1&&e.ctrlKey&&!e.shiftKey)?(this.pendingIOSKey=t||e,setTimeout(()=>this.flushIOSKey(),250),!0):(e.keyCode!=229&&this.view.observer.forceFlush(),!1)}flushIOSKey(e){let t=this.pendingIOSKey;return!t||t.key=="Enter"&&e&&e.from0?!0:C.safari&&!C.ios&&this.compositionPendingKey&&Date.now()-this.compositionEndedAt<100?(this.compositionPendingKey=!1,!0):!1}startMouseSelection(e){this.mouseSelection&&this.mouseSelection.destroy(),this.mouseSelection=e}update(e){this.view.observer.update(e),this.mouseSelection&&this.mouseSelection.update(e),this.draggedContent&&e.docChanged&&(this.draggedContent=this.draggedContent.map(e.changes)),e.transactions.length&&(this.lastKeyCode=this.lastSelectionTime=0)}destroy(){this.mouseSelection&&this.mouseSelection.destroy()}};function lu(n,e){return(t,i)=>{try{return e.call(n,i,t)}catch(s){Le(t.state,s)}}}function Ey(n){let e=Object.create(null);function t(i){return e[i]||(e[i]={observers:[],handlers:[]})}for(let i of n){let s=i.spec,r=s&&s.plugin.domEventHandlers,o=s&&s.plugin.domEventObservers;if(r)for(let l in r){let a=r[l];a&&t(l).handlers.push(lu(i.value,a))}if(o)for(let l in o){let a=o[l];a&&t(l).observers.push(lu(i.value,a))}}for(let i in bt)t(i).handlers.push(bt[i]);for(let i in ot)t(i).observers.push(ot[i]);return e}const Pm=[{key:"Backspace",keyCode:8,inputType:"deleteContentBackward"},{key:"Enter",keyCode:13,inputType:"insertParagraph"},{key:"Enter",keyCode:13,inputType:"insertLineBreak"},{key:"Delete",keyCode:46,inputType:"deleteContentForward"}],Wy="dthko",Cm=[16,17,18,20,91,92,224,225],or=6;function lr(n){return Math.max(0,n)*.7+8}function Vy(n,e){return Math.max(Math.abs(n.clientX-e.clientX),Math.abs(n.clientY-e.clientY))}let zy=class{constructor(e,t,i,s){this.view=e,this.startEvent=t,this.style=i,this.mustSelect=s,this.scrollSpeed={x:0,y:0},this.scrolling=-1,this.lastEvent=t,this.scrollParents=_S(e.contentDOM),this.atoms=e.state.facet(In).map(o=>o(e));let r=e.contentDOM.ownerDocument;r.addEventListener("mousemove",this.move=this.move.bind(this)),r.addEventListener("mouseup",this.up=this.up.bind(this)),this.extend=t.shiftKey,this.multiple=e.state.facet(L.allowMultipleSelections)&&qy(e,t),this.dragging=_y(e,t)&&Am(t)==1?null:!1}start(e){this.dragging===!1&&this.select(e)}move(e){if(e.buttons==0)return this.destroy();if(this.dragging||this.dragging==null&&Vy(this.startEvent,e)<10)return;this.select(this.lastEvent=e);let t=0,i=0,s=0,r=0,o=this.view.win.innerWidth,l=this.view.win.innerHeight;this.scrollParents.x&&({left:s,right:o}=this.scrollParents.x.getBoundingClientRect()),this.scrollParents.y&&({top:r,bottom:l}=this.scrollParents.y.getBoundingClientRect());let a=Xc(this.view);e.clientX-a.left<=s+or?t=-lr(s-e.clientX):e.clientX+a.right>=o-or&&(t=lr(e.clientX-o)),e.clientY-a.top<=r+or?i=-lr(r-e.clientY):e.clientY+a.bottom>=l-or&&(i=lr(e.clientY-l)),this.setScrollSpeed(t,i)}up(e){this.dragging==null&&this.select(this.lastEvent),this.dragging||e.preventDefault(),this.destroy()}destroy(){this.setScrollSpeed(0,0);let e=this.view.contentDOM.ownerDocument;e.removeEventListener("mousemove",this.move),e.removeEventListener("mouseup",this.up),this.view.inputState.mouseSelection=this.view.inputState.draggedContent=null}setScrollSpeed(e,t){this.scrollSpeed={x:e,y:t},e||t?this.scrolling<0&&(this.scrolling=setInterval(()=>this.scroll(),50)):this.scrolling>-1&&(clearInterval(this.scrolling),this.scrolling=-1)}scroll(){let{x:e,y:t}=this.scrollSpeed;e&&this.scrollParents.x&&(this.scrollParents.x.scrollLeft+=e,e=0),t&&this.scrollParents.y&&(this.scrollParents.y.scrollTop+=t,t=0),(e||t)&&this.view.win.scrollBy(e,t),this.dragging===!1&&this.select(this.lastEvent)}select(e){let{view:t}=this,i=wm(this.atoms,this.style.get(e,this.extend,this.multiple));(this.mustSelect||!i.eq(t.state.selection,this.dragging===!1))&&this.view.dispatch({selection:i,userEvent:"select.pointer"}),this.mustSelect=!1}update(e){e.transactions.some(t=>t.isUserEvent("input.type"))?this.destroy():this.style.update(e)&&setTimeout(()=>this.select(this.lastEvent),20)}};function qy(n,e){let t=n.state.facet(em);return t.length?t[0](e):C.mac?e.metaKey:e.ctrlKey}function Iy(n,e){let t=n.state.facet(tm);return t.length?t[0](e):C.mac?!e.altKey:!e.ctrlKey}function _y(n,e){let{main:t}=n.state.selection;if(t.empty)return!1;let i=bs(n.root);if(!i||i.rangeCount==0)return!0;let s=i.getRangeAt(0).getClientRects();for(let r=0;r=e.clientX&&o.top<=e.clientY&&o.bottom>=e.clientY)return!0}return!1}function Yy(n,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;for(let t=e.target,i;t!=n.contentDOM;t=t.parentNode)if(!t||t.nodeType==11||(i=de.get(t))&&i.isWidget()&&!i.isHidden&&i.widget.ignoreEvent(e))return!1;return!0}const bt=Object.create(null),ot=Object.create(null),Tm=C.ie&&C.ie_version<15||C.ios&&C.webkit_version<604;function Ny(n){let e=n.dom.parentNode;if(!e)return;let t=e.appendChild(document.createElement("textarea"));t.style.cssText="position: fixed; left: -10000px; top: 10px",t.focus(),setTimeout(()=>{n.focus(),t.remove(),Mm(n,t.value)},50)}function ll(n,e,t){for(let i of n.facet(e))t=i(t,n);return t}function Mm(n,e){e=ll(n.state,Rc,e);let{state:t}=n,i,s=1,r=t.toText(e),o=r.lines==t.selection.ranges.length;if(fh!=null&&t.selection.ranges.every(a=>a.empty)&&fh==r.toString()){let a=-1;i=t.changeByRange(h=>{let c=t.doc.lineAt(h.from);if(c.from==a)return{range:h};a=c.from;let f=t.toText((o?r.line(s++).text:e)+t.lineBreak);return{changes:{from:c.from,insert:f},range:S.cursor(h.from+f.length)}})}else o?i=t.changeByRange(a=>{let h=r.line(s++);return{changes:{from:a.from,to:a.to,insert:h.text},range:S.cursor(a.from+h.length)}}):i=t.replaceSelection(r);n.dispatch(i,{userEvent:"input.paste",scrollIntoView:!0})}ot.scroll=n=>{n.inputState.lastScrollTop=n.scrollDOM.scrollTop,n.inputState.lastScrollLeft=n.scrollDOM.scrollLeft};bt.keydown=(n,e)=>(n.inputState.setSelectionOrigin("select"),e.keyCode==27&&n.inputState.tabFocusMode!=0&&(n.inputState.tabFocusMode=Date.now()+2e3),!1);ot.touchstart=(n,e)=>{n.inputState.lastTouchTime=Date.now(),n.inputState.setSelectionOrigin("select.pointer")};ot.touchmove=n=>{n.inputState.setSelectionOrigin("select.pointer")};bt.mousedown=(n,e)=>{if(n.observer.flush(),n.inputState.lastTouchTime>Date.now()-2e3)return!1;let t=null;for(let i of n.state.facet(im))if(t=i(n,e),t)break;if(!t&&e.button==0&&(t=Gy(n,e)),t){let i=!n.hasFocus;n.inputState.startMouseSelection(new zy(n,e,t,i)),i&&n.observer.ignore(()=>{Yg(n.contentDOM);let r=n.root.activeElement;r&&!r.contains(n.contentDOM)&&r.blur()});let s=n.inputState.mouseSelection;if(s)return s.start(e),s.dragging===!1}else n.inputState.setSelectionOrigin("select.pointer");return!1};function au(n,e,t,i){if(i==1)return S.cursor(e,t);if(i==2)return vy(n.state,e,t);{let s=n.docView.lineAt(e,t),r=n.state.doc.lineAt(s?s.posAtEnd:e),o=s?s.posAtStart:r.from,l=s?s.posAtEnd:r.to;return lDate.now()-400&&Math.abs(e.clientX-n.clientX)<2&&Math.abs(e.clientY-n.clientY)<2?(cu+1)%3:1}function Gy(n,e){let t=n.posAndSideAtCoords({x:e.clientX,y:e.clientY},!1),i=Am(e),s=n.state.selection;return{update(r){r.docChanged&&(t.pos=r.changes.mapPos(t.pos),s=s.map(r.changes))},get(r,o,l){let a=n.posAndSideAtCoords({x:r.clientX,y:r.clientY},!1),h,c=au(n,a.pos,a.assoc,i);if(t.pos!=a.pos&&!o){let f=au(n,t.pos,t.assoc,i),u=Math.min(f.from,c.from),d=Math.max(f.to,c.to);c=u1&&(h=Hy(s,a.pos))?h:l?s.addRange(c):S.create([c])}}}function Hy(n,e){for(let t=0;t=e)return S.create(n.ranges.slice(0,t).concat(n.ranges.slice(t+1)),n.mainIndex==t?0:n.mainIndex-(n.mainIndex>t?1:0))}return null}bt.dragstart=(n,e)=>{let{selection:{main:t}}=n.state;if(e.target.draggable){let s=n.docView.tile.nearest(e.target);if(s&&s.isWidget()){let r=s.posAtStart,o=r+s.length;(r>=t.to||o<=t.from)&&(t=S.range(r,o))}}let{inputState:i}=n;return i.mouseSelection&&(i.mouseSelection.dragging=!0),i.draggedContent=t,e.dataTransfer&&(e.dataTransfer.setData("Text",ll(n.state,Dc,n.state.sliceDoc(t.from,t.to))),e.dataTransfer.effectAllowed="copyMove"),!1};bt.dragend=n=>(n.inputState.draggedContent=null,!1);function uu(n,e,t,i){if(t=ll(n.state,Rc,t),!t)return;let s=n.posAtCoords({x:e.clientX,y:e.clientY},!1),{draggedContent:r}=n.inputState,o=i&&r&&Iy(n,e)?{from:r.from,to:r.to}:null,l={from:s,insert:t},a=n.state.changes(o?[o,l]:l);n.focus(),n.dispatch({changes:a,selection:{anchor:a.mapPos(s,-1),head:a.mapPos(s,1)},userEvent:o?"move.drop":"input.drop"}),n.inputState.draggedContent=null}bt.drop=(n,e)=>{if(!e.dataTransfer)return!1;if(n.state.readOnly)return!0;let t=e.dataTransfer.files;if(t&&t.length){let i=Array(t.length),s=0,r=()=>{++s==t.length&&uu(n,e,i.filter(o=>o!=null).join(n.state.lineBreak),!1)};for(let o=0;o{/[\x00-\x08\x0e-\x1f]{2}/.test(l.result)||(i[o]=l.result),r()},l.readAsText(t[o])}return!0}else{let i=e.dataTransfer.getData("Text");if(i)return uu(n,e,i,!0),!0}return!1};bt.paste=(n,e)=>{if(n.state.readOnly)return!0;n.observer.flush();let t=Tm?null:e.clipboardData;return t?(Mm(n,t.getData("text/plain")||t.getData("text/uri-list")),!0):(Ny(n),!1)};function Fy(n,e){let t=n.dom.parentNode;if(!t)return;let i=t.appendChild(document.createElement("textarea"));i.style.cssText="position: fixed; left: -10000px; top: 10px",i.value=e,i.focus(),i.selectionEnd=e.length,i.selectionStart=0,setTimeout(()=>{i.remove(),n.focus()},50)}function Uy(n){let e=[],t=[],i=!1;for(let s of n.selection.ranges)s.empty||(e.push(n.sliceDoc(s.from,s.to)),t.push(s));if(!e.length){let s=-1;for(let{from:r}of n.selection.ranges){let o=n.doc.lineAt(r);o.number>s&&(e.push(o.text),t.push({from:o.from,to:Math.min(n.doc.length,o.to+1)})),s=o.number}i=!0}return{text:ll(n,Dc,e.join(n.lineBreak)),ranges:t,linewise:i}}let fh=null;bt.copy=bt.cut=(n,e)=>{let t=bs(n.root);if(t&&!fn(n.contentDOM,t))return!1;let{text:i,ranges:s,linewise:r}=Uy(n.state);if(!i&&!r)return!1;fh=r?i:null,e.type=="cut"&&!n.state.readOnly&&n.dispatch({changes:s,scrollIntoView:!0,userEvent:"delete.cut"});let o=Tm?null:e.clipboardData;return o?(o.clearData(),o.setData("text/plain",i),!0):(Fy(n,i),!1)};const Rm=wt.define();function Dm(n,e){let t=[];for(let i of n.facet(rm)){let s=i(n,e);s&&t.push(s)}return t.length?n.update({effects:t,annotations:Rm.of(!0)}):null}function Zm(n){setTimeout(()=>{let e=n.hasFocus;if(e!=n.inputState.notifiedFocused){let t=Dm(n.state,e);t?n.dispatch(t):n.update([])}},10)}ot.focus=n=>{n.inputState.lastFocusTime=Date.now(),!n.scrollDOM.scrollTop&&(n.inputState.lastScrollTop||n.inputState.lastScrollLeft)&&(n.scrollDOM.scrollTop=n.inputState.lastScrollTop,n.scrollDOM.scrollLeft=n.inputState.lastScrollLeft),Zm(n)};ot.blur=n=>{n.observer.clearSelectionRange(),Zm(n)};ot.compositionstart=ot.compositionupdate=n=>{n.observer.editContext||(n.inputState.compositionFirstChange==null&&(n.inputState.compositionFirstChange=!0),n.inputState.composing<0&&(n.inputState.composing=0))};ot.compositionend=n=>{n.observer.editContext||(n.inputState.composing=-1,n.inputState.compositionEndedAt=Date.now(),n.inputState.compositionPendingKey=!0,n.inputState.compositionPendingChange=n.observer.pendingRecords().length>0,n.inputState.compositionFirstChange=null,C.chrome&&C.android?n.observer.flushSoon():n.inputState.compositionPendingChange?Promise.resolve().then(()=>n.observer.flush()):setTimeout(()=>{n.inputState.composing<0&&n.docView.hasComposition&&n.update([])},50))};ot.contextmenu=n=>{n.inputState.lastContextMenu=Date.now()};bt.beforeinput=(n,e)=>{var t,i;if((e.inputType=="insertText"||e.inputType=="insertCompositionText")&&(n.inputState.insertingText=e.data,n.inputState.insertingTextAt=Date.now()),e.inputType=="insertReplacementText"&&n.observer.editContext){let r=(t=e.dataTransfer)===null||t===void 0?void 0:t.getData("text/plain"),o=e.getTargetRanges();if(r&&o.length){let l=o[0],a=n.posAtDOM(l.startContainer,l.startOffset),h=n.posAtDOM(l.endContainer,l.endOffset);return Lc(n,{from:a,to:h,insert:n.state.toText(r)},null),!0}}let s;if(C.chrome&&C.android&&(s=Pm.find(r=>r.inputType==e.inputType))&&(n.observer.delayAndroidKey(s.key,s.keyCode),s.key=="Backspace"||s.key=="Delete")){let r=((i=window.visualViewport)===null||i===void 0?void 0:i.height)||0;setTimeout(()=>{var o;(((o=window.visualViewport)===null||o===void 0?void 0:o.height)||0)>r+10&&n.hasFocus&&(n.contentDOM.blur(),n.focus())},100)}return C.ios&&e.inputType=="deleteContentForward"&&n.observer.flushSoon(),C.safari&&e.inputType=="insertText"&&n.inputState.composing>=0&&setTimeout(()=>ot.compositionend(n,e),20),!1};const du=new Set;function Ky(n){du.has(n)||(du.add(n),n.addEventListener("copy",()=>{}),n.addEventListener("cut",()=>{}))}const pu=["pre-wrap","normal","pre-line","break-spaces"];let ys=!1;function gu(){ys=!1}let Jy=class{constructor(e){this.lineWrapping=e,this.doc=Z.empty,this.heightSamples={},this.lineHeight=14,this.charWidth=7,this.textHeight=14,this.lineLength=30}heightForGap(e,t){let i=this.doc.lineAt(t).number-this.doc.lineAt(e).number+1;return this.lineWrapping&&(i+=Math.max(0,Math.ceil((t-e-i*this.lineLength*.5)/this.lineLength))),this.lineHeight*i}heightForLine(e){return this.lineWrapping?(1+Math.max(0,Math.ceil((e-this.lineLength)/Math.max(1,this.lineLength-5))))*this.lineHeight:this.lineHeight}setDoc(e){return this.doc=e,this}mustRefreshForWrapping(e){return pu.indexOf(e)>-1!=this.lineWrapping}mustRefreshForHeights(e){let t=!1;for(let i=0;i-1,a=Math.abs(t-this.lineHeight)>.3||this.lineWrapping!=l||Math.abs(i-this.charWidth)>.1;if(this.lineWrapping=l,this.lineHeight=t,this.charWidth=i,this.textHeight=s,this.lineLength=r,a){this.heightSamples={};for(let h=0;h0}set outdated(e){this.flags=(e?2:0)|this.flags&-3}setHeight(e){this.height!=e&&(Math.abs(this.height-e)>_r&&(ys=!0),this.height=e)}replace(e,t,i){return Yr.of(i)}decomposeLeft(e,t){t.push(this)}decomposeRight(e,t){t.push(this)}applyChanges(e,t,i,s){let r=this,o=i.doc;for(let l=s.length-1;l>=0;l--){let{fromA:a,toA:h,fromB:c,toB:f}=s[l],u=r.lineAt(a,se.ByPosNoHeight,i.setDoc(t),0,0),d=u.to>=h?u:r.lineAt(h,se.ByPosNoHeight,i,0,0);for(f+=d.to-h,h=d.to;l>0&&u.from<=s[l-1].toA;)a=s[l-1].fromA,c=s[l-1].fromB,l--,ar*2){let l=e[t-1];l.break?e.splice(--t,1,l.left,null,l.right):e.splice(--t,1,l.left,l.right),i+=1+l.break,s-=l.size}else if(r>s*2){let l=e[i];l.break?e.splice(i,1,l.left,null,l.right):e.splice(i,1,l.left,l.right),i+=2+l.break,r-=l.size}else break;else if(s=r&&o(this.lineAt(0,se.ByPos,i,s,r))}setMeasuredHeight(e){let t=e.heights[e.index++];t<0?(this.spaceAbove=-t,t=e.heights[e.index++]):this.spaceAbove=0,this.setHeight(t)}updateHeight(e,t=0,i=!1,s){return s&&s.from<=t&&s.more&&this.setMeasuredHeight(s),this.outdated=!1,this}toString(){return`block(${this.length})`}},Dt=class uh extends Xm{constructor(e,t,i){super(e,t,null),this.collapsed=0,this.widgetHeight=0,this.breaks=0,this.spaceAbove=i}mainBlock(e,t){return new Ft(t,this.length,e+this.spaceAbove,this.height-this.spaceAbove,this.breaks)}replace(e,t,i){let s=i[0];return i.length==1&&(s instanceof uh||s instanceof rs&&s.flags&4)&&Math.abs(this.length-s.length)<10?(s instanceof rs?s=new uh(s.length,this.height,this.spaceAbove):s.height=this.height,this.outdated||(s.outdated=!1),s):mt.of(i)}updateHeight(e,t=0,i=!1,s){return s&&s.from<=t&&s.more?this.setMeasuredHeight(s):(i||this.outdated)&&(this.spaceAbove=0,this.setHeight(Math.max(this.widgetHeight,e.heightForLine(this.length-this.collapsed))+this.breaks*e.lineHeight)),this.outdated=!1,this}toString(){return`line(${this.length}${this.collapsed?-this.collapsed:""}${this.widgetHeight?":"+this.widgetHeight:""})`}},rs=class ct extends mt{constructor(e){super(e,0)}heightMetrics(e,t){let i=e.doc.lineAt(t).number,s=e.doc.lineAt(t+this.length).number,r=s-i+1,o,l=0;if(e.lineWrapping){let a=Math.min(this.height,e.lineHeight*r);o=a/r,this.length>r+1&&(l=(this.height-a)/(this.length-r-1))}else o=this.height/r;return{firstLine:i,lastLine:s,perLine:o,perChar:l}}blockAt(e,t,i,s){let{firstLine:r,lastLine:o,perLine:l,perChar:a}=this.heightMetrics(t,s);if(t.lineWrapping){let h=s+(e0){let r=i[i.length-1];r instanceof ct?i[i.length-1]=new ct(r.length+s):i.push(null,new ct(s-1))}if(e>0){let r=i[0];r instanceof ct?i[0]=new ct(e+r.length):i.unshift(new ct(e-1),null)}return mt.of(i)}decomposeLeft(e,t){t.push(new ct(e-1),null)}decomposeRight(e,t){t.push(null,new ct(this.length-e-1))}updateHeight(e,t=0,i=!1,s){let r=t+this.length;if(s&&s.from<=t+this.length&&s.more){let o=[],l=Math.max(t,s.from),a=-1;for(s.from>t&&o.push(new ct(s.from-t-1).updateHeight(e,t));l<=r&&s.more;){let c=e.doc.lineAt(l).length;o.length&&o.push(null);let f=s.heights[s.index++],u=0;f<0&&(u=-f,f=s.heights[s.index++]),a==-1?a=f:Math.abs(f-a)>=_r&&(a=-2);let d=new Dt(c,f,u);d.outdated=!1,o.push(d),l+=c+1}l<=r&&o.push(null,new ct(r-l).updateHeight(e,l));let h=mt.of(o);return(a<0||Math.abs(h.height-this.height)>=_r||Math.abs(a-this.heightMetrics(e,t).perLine)>=_r)&&(ys=!0),mo(this,h)}else(i||this.outdated)&&(this.setHeight(e.heightForGap(t,t+this.length)),this.outdated=!1);return this}toString(){return`gap(${this.length})`}},ix=class extends mt{constructor(e,t,i){super(e.length+t+i.length,e.height+i.height,t|(e.outdated||i.outdated?2:0)),this.left=e,this.right=i,this.size=e.size+i.size}get break(){return this.flags&1}blockAt(e,t,i,s){let r=i+this.left.height;return el))return h;let c=t==se.ByPosNoHeight?se.ByPosNoHeight:se.ByPos;return a?h.join(this.right.lineAt(l,c,i,o,l)):this.left.lineAt(l,c,i,s,r).join(h)}forEachLine(e,t,i,s,r,o){let l=s+this.left.height,a=r+this.left.length+this.break;if(this.break)e=a&&this.right.forEachLine(e,t,i,l,a,o);else{let h=this.lineAt(a,se.ByPos,i,s,r);e=e&&h.from<=t&&o(h),t>h.to&&this.right.forEachLine(h.to+1,t,i,l,a,o)}}replace(e,t,i){let s=this.left.length+this.break;if(tthis.left.length)return this.balanced(this.left,this.right.replace(e-s,t-s,i));let r=[];e>0&&this.decomposeLeft(e,r);let o=r.length;for(let l of i)r.push(l);if(e>0&&mu(r,o-1),t=i&&t.push(null)),e>i&&this.right.decomposeLeft(e-i,t)}decomposeRight(e,t){let i=this.left.length,s=i+this.break;if(e>=s)return this.right.decomposeRight(e-s,t);e2*t.size||t.size>2*e.size?mt.of(this.break?[e,null,t]:[e,t]):(this.left=mo(this.left,e),this.right=mo(this.right,t),this.setHeight(e.height+t.height),this.outdated=e.outdated||t.outdated,this.size=e.size+t.size,this.length=e.length+this.break+t.length,this)}updateHeight(e,t=0,i=!1,s){let{left:r,right:o}=this,l=t+r.length+this.break,a=null;return s&&s.from<=t+r.length&&s.more?a=r=r.updateHeight(e,t,i,s):r.updateHeight(e,t,i),s&&s.from<=l+o.length&&s.more?a=o=o.updateHeight(e,l,i,s):o.updateHeight(e,l,i),a?this.balanced(r,o):(this.height=this.left.height+this.right.height,this.outdated=!1,this)}toString(){return this.left+(this.break?" ":"-")+this.right}};function mu(n,e){let t,i;n[e]==null&&(t=n[e-1])instanceof rs&&(i=n[e+1])instanceof rs&&n.splice(e-1,3,new rs(t.length+1+i.length))}const sx=5;let nx=class Lm{constructor(e,t){this.pos=e,this.oracle=t,this.nodes=[],this.lineStart=-1,this.lineEnd=-1,this.covering=null,this.writtenTo=e}get isCovered(){return this.covering&&this.nodes[this.nodes.length-1]==this.covering}span(e,t){if(this.lineStart>-1){let i=Math.min(t,this.lineEnd),s=this.nodes[this.nodes.length-1];s instanceof Dt?s.length+=i-this.pos:(i>this.pos||!this.isCovered)&&this.nodes.push(new Dt(i-this.pos,-1,0)),this.writtenTo=i,t>i&&(this.nodes.push(null),this.writtenTo++,this.lineStart=-1)}this.pos=t}point(e,t,i){if(e=sx)&&this.addLineDeco(s,r,o)}else t>e&&this.span(e,t);this.lineEnd>-1&&this.lineEnd-1)return;let{from:e,to:t}=this.oracle.doc.lineAt(this.pos);this.lineStart=e,this.lineEnd=t,this.writtenToe&&this.nodes.push(new Dt(this.pos-e,-1,0)),this.writtenTo=this.pos}blankContent(e,t){let i=new rs(t-e);return this.oracle.doc.lineAt(e).to==t&&(i.flags|=4),i}ensureLine(){this.enterLine();let e=this.nodes.length?this.nodes[this.nodes.length-1]:null;if(e instanceof Dt)return e;let t=new Dt(0,-1,0);return this.nodes.push(t),t}addBlock(e){this.enterLine();let t=e.deco;t&&t.startSide>0&&!this.isCovered&&this.ensureLine(),this.nodes.push(e),this.writtenTo=this.pos=this.pos+e.length,t&&t.endSide>0&&(this.covering=e)}addLineDeco(e,t,i){let s=this.ensureLine();s.length+=i,s.collapsed+=i,s.widgetHeight=Math.max(s.widgetHeight,e),s.breaks+=t,this.writtenTo=this.pos=this.pos+i}finish(e){let t=this.nodes.length==0?null:this.nodes[this.nodes.length-1];this.lineStart>-1&&!(t instanceof Dt)&&!this.isCovered?this.nodes.push(new Dt(0,-1,0)):(this.writtenToc.clientHeight||c.scrollWidth>c.clientWidth)&&f.overflow!="visible"){let u=c.getBoundingClientRect();r=Math.max(r,u.left),o=Math.min(o,u.right),l=Math.max(l,u.top),a=Math.min(h==n.parentNode?s.innerHeight:a,u.bottom)}h=f.position=="absolute"||f.position=="fixed"?c.offsetParent:c.parentNode}else if(h.nodeType==11)h=h.host;else break;return{left:r-t.left,right:Math.max(r,o)-t.left,top:l-(t.top+e),bottom:Math.max(l,a)-(t.top+e)}}function ax(n){let e=n.getBoundingClientRect(),t=n.ownerDocument.defaultView||window;return e.left0&&e.top0}function hx(n,e){let t=n.getBoundingClientRect();return{left:0,right:t.right-t.left,top:e,bottom:t.bottom-(t.top+e)}}let zl=class{constructor(e,t,i,s){this.from=e,this.to=t,this.size=i,this.displaySize=s}static same(e,t){if(e.length!=t.length)return!1;for(let i=0;itypeof i!="function"&&i.class=="cm-lineWrapping");this.heightOracle=new Jy(t),this.stateDeco=Su(e),this.heightMap=mt.empty().applyChanges(this.stateDeco,Z.empty,this.heightOracle.setDoc(e.doc),[new ti(0,0,0,e.doc.length)]);for(let i=0;i<2&&(this.viewport=this.getViewport(0,null),!!this.updateForViewport());i++);this.updateViewportLines(),this.lineGaps=this.ensureLineGaps([]),this.lineGapDeco=E.set(this.lineGaps.map(i=>i.draw(this,!1))),this.computeVisibleRanges()}updateForViewport(){let e=[this.viewport],{main:t}=this.state.selection;for(let i=0;i<=1;i++){let s=i?t.head:t.anchor;if(!e.some(({from:r,to:o})=>s>=r&&s<=o)){let{from:r,to:o}=this.lineBlockAt(s);e.push(new ar(r,o))}}return this.viewports=e.sort((i,s)=>i.from-s.from),this.updateScaler()}updateScaler(){let e=this.scaler;return this.scaler=this.heightMap.height<=7e6?bu:new dx(this.heightOracle,this.heightMap,this.viewports),e.eq(this.scaler)?0:2}updateViewportLines(){this.viewportLines=[],this.heightMap.forEachLine(this.viewport.from,this.viewport.to,this.heightOracle.setDoc(this.state.doc),0,0,e=>{this.viewportLines.push(Ks(e,this.scaler))})}update(e,t=null){this.state=e.state;let i=this.stateDeco;this.stateDeco=Su(this.state);let s=e.changedRanges,r=ti.extendWithRanges(s,rx(i,this.stateDeco,e?e.changes:he.empty(this.state.doc.length))),o=this.heightMap.height,l=this.scrolledToBottom?null:this.scrollAnchorAt(this.scrollTop);gu(),this.heightMap=this.heightMap.applyChanges(this.stateDeco,e.startState.doc,this.heightOracle.setDoc(this.state.doc),r),(this.heightMap.height!=o||ys)&&(e.flags|=2),l?(this.scrollAnchorPos=e.changes.mapPos(l.from,-1),this.scrollAnchorHeight=l.top):(this.scrollAnchorPos=-1,this.scrollAnchorHeight=o);let a=r.length?this.mapViewport(this.viewport,e.changes):this.viewport;(t&&(t.range.heada.to)||!this.viewportIsAppropriate(a))&&(a=this.getViewport(0,t));let h=a.from!=this.viewport.from||a.to!=this.viewport.to;this.viewport=a,e.flags|=this.updateForViewport(),(h||!e.changes.empty||e.flags&2)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps,e.changes))),e.flags|=this.computeVisibleRanges(e.changes),t&&(this.scrollTarget=t),!this.mustEnforceCursorAssoc&&(e.selectionSet||e.focusChanged)&&e.view.lineWrapping&&e.state.selection.main.empty&&e.state.selection.main.assoc&&!e.state.facet(lm)&&(this.mustEnforceCursorAssoc=!0)}measure(e){let t=e.contentDOM,i=window.getComputedStyle(t),s=this.heightOracle,r=i.whiteSpace;this.defaultTextDirection=i.direction=="rtl"?le.RTL:le.LTR;let o=this.heightOracle.mustRefreshForWrapping(r)||this.mustMeasureContent,l=t.getBoundingClientRect(),a=o||this.mustMeasureContent||this.contentDOMHeight!=l.height;this.contentDOMHeight=l.height,this.mustMeasureContent=!1;let h=0,c=0;if(l.width&&l.height){let{scaleX:v,scaleY:w}=_g(t,l);(v>.005&&Math.abs(this.scaleX-v)>.005||w>.005&&Math.abs(this.scaleY-w)>.005)&&(this.scaleX=v,this.scaleY=w,h|=16,o=a=!0)}let f=(parseInt(i.paddingTop)||0)*this.scaleY,u=(parseInt(i.paddingBottom)||0)*this.scaleY;(this.paddingTop!=f||this.paddingBottom!=u)&&(this.paddingTop=f,this.paddingBottom=u,h|=18),this.editorWidth!=e.scrollDOM.clientWidth&&(s.lineWrapping&&(a=!0),this.editorWidth=e.scrollDOM.clientWidth,h|=16);let d=e.scrollDOM.scrollTop*this.scaleY;this.scrollTop!=d&&(this.scrollAnchorHeight=-1,this.scrollTop=d),this.scrolledToBottom=Ng(e.scrollDOM);let p=(this.printing?hx:lx)(t,this.paddingTop),g=p.top-this.pixelViewport.top,m=p.bottom-this.pixelViewport.bottom;this.pixelViewport=p;let O=this.pixelViewport.bottom>this.pixelViewport.top&&this.pixelViewport.right>this.pixelViewport.left;if(O!=this.inView&&(this.inView=O,O&&(a=!0)),!this.inView&&!this.scrollTarget&&!ax(e.dom))return 0;let y=l.width;if((this.contentDOMWidth!=y||this.editorHeight!=e.scrollDOM.clientHeight)&&(this.contentDOMWidth=l.width,this.editorHeight=e.scrollDOM.clientHeight,h|=16),a){let v=e.docView.measureVisibleLineHeights(this.viewport);if(s.mustRefreshForHeights(v)&&(o=!0),o||s.lineWrapping&&Math.abs(y-this.contentDOMWidth)>s.charWidth){let{lineHeight:w,charWidth:Q,textHeight:$}=e.docView.measureTextSize();o=w>0&&s.refresh(r,w,Q,$,Math.max(5,y/Q),v),o&&(e.docView.minWidth=0,h|=16)}g>0&&m>0?c=Math.max(g,m):g<0&&m<0&&(c=Math.min(g,m)),gu();for(let w of this.viewports){let Q=w.from==this.viewport.from?v:e.docView.measureVisibleLineHeights(w);this.heightMap=(o?mt.empty().applyChanges(this.stateDeco,Z.empty,this.heightOracle,[new ti(0,0,0,e.state.doc.length)]):this.heightMap).updateHeight(s,0,o,new ex(w.from,Q))}ys&&(h|=2)}let x=!this.viewportIsAppropriate(this.viewport,c)||this.scrollTarget&&(this.scrollTarget.range.headthis.viewport.to);return x&&(h&2&&(h|=this.updateScaler()),this.viewport=this.getViewport(c,this.scrollTarget),h|=this.updateForViewport()),(h&2||x)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(o?[]:this.lineGaps,e)),h|=this.computeVisibleRanges(),this.mustEnforceCursorAssoc&&(this.mustEnforceCursorAssoc=!1,e.docView.enforceCursorAssoc()),h}get visibleTop(){return this.scaler.fromDOM(this.pixelViewport.top)}get visibleBottom(){return this.scaler.fromDOM(this.pixelViewport.bottom)}getViewport(e,t){let i=.5-Math.max(-.5,Math.min(.5,e/1e3/2)),s=this.heightMap,r=this.heightOracle,{visibleTop:o,visibleBottom:l}=this,a=new ar(s.lineAt(o-i*1e3,se.ByHeight,r,0,0).from,s.lineAt(l+(1-i)*1e3,se.ByHeight,r,0,0).to);if(t){let{head:h}=t.range;if(ha.to){let c=Math.min(this.editorHeight,this.pixelViewport.bottom-this.pixelViewport.top),f=s.lineAt(h,se.ByPos,r,0,0),u;t.y=="center"?u=(f.top+f.bottom)/2-c/2:t.y=="start"||t.y=="nearest"&&h=l+Math.max(10,Math.min(i,250)))&&s>o-2*1e3&&r>1,o=s<<1;if(this.defaultTextDirection!=le.LTR&&!i)return[];let l=[],a=(c,f,u,d)=>{if(f-cc&&OO.from>=u.from&&O.to<=u.to&&Math.abs(O.from-c)O.fromy));if(!m){if(fx.from<=f&&x.to>=f)){let x=t.moveToLineBoundary(S.cursor(f),!1,!0).head;x>c&&(f=x)}let O=this.gapSize(u,c,f,d),y=i||O<2e6?O:2e6;m=new zl(c,f,O,y)}l.push(m)},h=c=>{if(c.length2e6)for(let Q of e)Q.from>=c.from&&Q.fromc.from&&a(c.from,d,c,f),pt.draw(this,this.heightOracle.lineWrapping))))}computeVisibleRanges(e){let t=this.stateDeco;this.lineGaps.length&&(t=t.concat(this.lineGapDeco));let i=[];M.spans(t,this.viewport.from,this.viewport.to,{span(r,o){i.push({from:r,to:o})},point(){}},20);let s=0;if(i.length!=this.visibleRanges.length)s=12;else for(let r=0;r=this.viewport.from&&e<=this.viewport.to&&this.viewportLines.find(t=>t.from<=e&&t.to>=e)||Ks(this.heightMap.lineAt(e,se.ByPos,this.heightOracle,0,0),this.scaler)}lineBlockAtHeight(e){return e>=this.viewportLines[0].top&&e<=this.viewportLines[this.viewportLines.length-1].bottom&&this.viewportLines.find(t=>t.top<=e&&t.bottom>=e)||Ks(this.heightMap.lineAt(this.scaler.fromDOM(e),se.ByHeight,this.heightOracle,0,0),this.scaler)}scrollAnchorAt(e){let t=this.lineBlockAtHeight(e+8);return t.from>=this.viewport.from||this.viewportLines[0].top-e>200?t:this.viewportLines[0]}elementAtHeight(e){return Ks(this.heightMap.blockAt(this.scaler.fromDOM(e),this.heightOracle,0,0),this.scaler)}get docHeight(){return this.scaler.toDOM(this.heightMap.height)}get contentHeight(){return this.docHeight+this.paddingTop+this.paddingBottom}},ar=class{constructor(e,t){this.from=e,this.to=t}};function fx(n,e,t){let i=[],s=n,r=0;return M.spans(t,n,e,{span(){},point(o,l){o>s&&(i.push({from:s,to:o}),r+=o-s),s=l}},20),s=1)return e[e.length-1].to;let i=Math.floor(n*t);for(let s=0;;s++){let{from:r,to:o}=e[s],l=o-r;if(i<=l)return r+i;i-=l}}function cr(n,e){let t=0;for(let{from:i,to:s}of n.ranges){if(e<=s){t+=e-i;break}t+=s-i}return t/n.total}function ux(n,e){for(let t of n)if(e(t))return t}const bu={toDOM(n){return n},fromDOM(n){return n},scale:1,eq(n){return n==this}};function Su(n){let e=n.facet(nl).filter(i=>typeof i!="function"),t=n.facet(Bc).filter(i=>typeof i!="function");return t.length&&e.push(M.join(t)),e}let dx=class Em{constructor(e,t,i){let s=0,r=0,o=0;this.viewports=i.map(({from:l,to:a})=>{let h=t.lineAt(l,se.ByPos,e,0,0).top,c=t.lineAt(a,se.ByPos,e,0,0).bottom;return s+=c-h,{from:l,to:a,top:h,bottom:c,domTop:0,domBottom:0}}),this.scale=(7e6-s)/(t.height-s);for(let l of this.viewports)l.domTop=o+(l.top-r)*this.scale,o=l.domBottom=l.domTop+(l.bottom-l.top),r=l.bottom}toDOM(e){for(let t=0,i=0,s=0;;t++){let r=tt.from==e.viewports[i].from&&t.to==e.viewports[i].to):!1}};function Ks(n,e){if(e.scale==1)return n;let t=e.toDOM(n.top),i=e.toDOM(n.bottom);return new Ft(n.from,n.length,t,i-t,Array.isArray(n._content)?n._content.map(s=>Ks(s,e)):n._content)}const fr=k.define({combine:n=>n.join(" ")}),dh=k.define({combine:n=>n.indexOf(!0)>-1}),ph=me.newName(),Wm=me.newName(),Vm=me.newName(),zm={"&light":"."+Wm,"&dark":"."+Vm};function gh(n,e,t){return new me(e,{finish(i){return/&/.test(i)?i.replace(/&\w*/,s=>{if(s=="&")return n;if(!t||!t[s])throw new RangeError(`Unsupported selector: ${s}`);return t[s]}):n+" "+i}})}const px=gh("."+ph,{"&":{position:"relative !important",boxSizing:"border-box","&.cm-focused":{outline:"1px dotted #212121"},display:"flex !important",flexDirection:"column"},".cm-scroller":{display:"flex !important",alignItems:"flex-start !important",fontFamily:"monospace",lineHeight:1.4,height:"100%",overflowX:"auto",position:"relative",zIndex:0,overflowAnchor:"none"},".cm-content":{margin:0,flexGrow:2,flexShrink:0,display:"block",whiteSpace:"pre",wordWrap:"normal",boxSizing:"border-box",minHeight:"100%",padding:"4px 0",outline:"none","&[contenteditable=true]":{WebkitUserModify:"read-write-plaintext-only"}},".cm-lineWrapping":{whiteSpace_fallback:"pre-wrap",whiteSpace:"break-spaces",wordBreak:"break-word",overflowWrap:"anywhere",flexShrink:1},"&light .cm-content":{caretColor:"black"},"&dark .cm-content":{caretColor:"white"},".cm-line":{display:"block",padding:"0 2px 0 6px"},".cm-layer":{position:"absolute",left:0,top:0,contain:"size style","& > *":{position:"absolute"}},"&light .cm-selectionBackground":{background:"#d9d9d9"},"&dark .cm-selectionBackground":{background:"#222"},"&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#d7d4f0"},"&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#233"},".cm-cursorLayer":{pointerEvents:"none"},"&.cm-focused > .cm-scroller > .cm-cursorLayer":{animation:"steps(1) cm-blink 1.2s infinite"},"@keyframes cm-blink":{"0%":{},"50%":{opacity:0},"100%":{}},"@keyframes cm-blink2":{"0%":{},"50%":{opacity:0},"100%":{}},".cm-cursor, .cm-dropCursor":{borderLeft:"1.2px solid black",marginLeft:"-0.6px",pointerEvents:"none"},".cm-cursor":{display:"none"},"&dark .cm-cursor":{borderLeftColor:"#ddd"},".cm-dropCursor":{position:"absolute"},"&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor":{display:"block"},".cm-iso":{unicodeBidi:"isolate"},".cm-announced":{position:"fixed",top:"-10000px"},"@media print":{".cm-announced":{display:"none"}},"&light .cm-activeLine":{backgroundColor:"#cceeff44"},"&dark .cm-activeLine":{backgroundColor:"#99eeff33"},"&light .cm-specialChar":{color:"red"},"&dark .cm-specialChar":{color:"#f78"},".cm-gutters":{flexShrink:0,display:"flex",height:"100%",boxSizing:"border-box",zIndex:200},".cm-gutters-before":{insetInlineStart:0},".cm-gutters-after":{insetInlineEnd:0},"&light .cm-gutters":{backgroundColor:"#f5f5f5",color:"#6c6c6c",border:"0px solid #ddd","&.cm-gutters-before":{borderRightWidth:"1px"},"&.cm-gutters-after":{borderLeftWidth:"1px"}},"&dark .cm-gutters":{backgroundColor:"#333338",color:"#ccc"},".cm-gutter":{display:"flex !important",flexDirection:"column",flexShrink:0,boxSizing:"border-box",minHeight:"100%",overflow:"hidden"},".cm-gutterElement":{boxSizing:"border-box"},".cm-lineNumbers .cm-gutterElement":{padding:"0 3px 0 5px",minWidth:"20px",textAlign:"right",whiteSpace:"nowrap"},"&light .cm-activeLineGutter":{backgroundColor:"#e2f2ff"},"&dark .cm-activeLineGutter":{backgroundColor:"#222227"},".cm-panels":{boxSizing:"border-box",position:"sticky",left:0,right:0,zIndex:300},"&light .cm-panels":{backgroundColor:"#f5f5f5",color:"black"},"&light .cm-panels-top":{borderBottom:"1px solid #ddd"},"&light .cm-panels-bottom":{borderTop:"1px solid #ddd"},"&dark .cm-panels":{backgroundColor:"#333338",color:"white"},".cm-dialog":{padding:"2px 19px 4px 6px",position:"relative","& label":{fontSize:"80%"}},".cm-dialog-close":{position:"absolute",top:"3px",right:"4px",backgroundColor:"inherit",border:"none",font:"inherit",fontSize:"14px",padding:"0"},".cm-tab":{display:"inline-block",overflow:"hidden",verticalAlign:"bottom"},".cm-widgetBuffer":{verticalAlign:"text-top",height:"1em",width:0,display:"inline"},".cm-placeholder":{color:"#888",display:"inline-block",verticalAlign:"top",userSelect:"none"},".cm-highlightSpace":{backgroundImage:"radial-gradient(circle at 50% 55%, #aaa 20%, transparent 5%)",backgroundPosition:"center"},".cm-highlightTab":{backgroundImage:`url('data:image/svg+xml,')`,backgroundSize:"auto 100%",backgroundPosition:"right 90%",backgroundRepeat:"no-repeat"},".cm-trailingSpace":{backgroundColor:"#ff332255"},".cm-button":{verticalAlign:"middle",color:"inherit",fontSize:"70%",padding:".2em 1em",borderRadius:"1px"},"&light .cm-button":{backgroundImage:"linear-gradient(#eff1f5, #d9d9df)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#b4b4b4, #d0d3d6)"}},"&dark .cm-button":{backgroundImage:"linear-gradient(#393939, #111)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#111, #333)"}},".cm-textfield":{verticalAlign:"middle",color:"inherit",fontSize:"70%",border:"1px solid silver",padding:".2em .5em"},"&light .cm-textfield":{backgroundColor:"white"},"&dark .cm-textfield":{border:"1px solid #555",backgroundColor:"inherit"}},zm),gx={childList:!0,characterData:!0,subtree:!0,attributes:!0,characterDataOldValue:!0},ql=C.ie&&C.ie_version<=11;let mx=class{constructor(e){this.view=e,this.active=!1,this.editContext=null,this.selectionRange=new YS,this.selectionChanged=!1,this.delayedFlush=-1,this.resizeTimeout=-1,this.queue=[],this.delayedAndroidKey=null,this.flushingAndroidKey=-1,this.lastChange=0,this.scrollTargets=[],this.intersection=null,this.resizeScroll=null,this.intersecting=!1,this.gapIntersection=null,this.gaps=[],this.printQuery=null,this.parentCheck=-1,this.dom=e.contentDOM,this.observer=new MutationObserver(t=>{for(let i of t)this.queue.push(i);(C.ie&&C.ie_version<=11||C.ios&&e.composing)&&t.some(i=>i.type=="childList"&&i.removedNodes.length||i.type=="characterData"&&i.oldValue.length>i.target.nodeValue.length)?this.flushSoon():this.flush()}),window.EditContext&&C.android&&e.constructor.EDIT_CONTEXT!==!1&&!(C.chrome&&C.chrome_version<126)&&(this.editContext=new bx(e),e.state.facet(Ht)&&(e.contentDOM.editContext=this.editContext.editContext)),ql&&(this.onCharData=t=>{this.queue.push({target:t.target,type:"characterData",oldValue:t.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this),this.onResize=this.onResize.bind(this),this.onPrint=this.onPrint.bind(this),this.onScroll=this.onScroll.bind(this),window.matchMedia&&(this.printQuery=window.matchMedia("print")),typeof ResizeObserver=="function"&&(this.resizeScroll=new ResizeObserver(()=>{var t;((t=this.view.docView)===null||t===void 0?void 0:t.lastUpdate){this.parentCheck<0&&(this.parentCheck=setTimeout(this.listenForScroll.bind(this),1e3)),t.length>0&&t[t.length-1].intersectionRatio>0!=this.intersecting&&(this.intersecting=!this.intersecting,this.intersecting!=this.view.inView&&this.onScrollChanged(document.createEvent("Event")))},{threshold:[0,.001]}),this.intersection.observe(this.dom),this.gapIntersection=new IntersectionObserver(t=>{t.length>0&&t[t.length-1].intersectionRatio>0&&this.onScrollChanged(document.createEvent("Event"))},{})),this.listenForScroll(),this.readSelectionRange()}onScrollChanged(e){this.view.inputState.runHandlers("scroll",e),this.intersecting&&this.view.measure()}onScroll(e){this.intersecting&&this.flush(!1),this.editContext&&this.view.requestMeasure(this.editContext.measureReq),this.onScrollChanged(e)}onResize(){this.resizeTimeout<0&&(this.resizeTimeout=setTimeout(()=>{this.resizeTimeout=-1,this.view.requestMeasure()},50))}onPrint(e){(e.type=="change"||!e.type)&&!e.matches||(this.view.viewState.printing=!0,this.view.measure(),setTimeout(()=>{this.view.viewState.printing=!1,this.view.requestMeasure()},500))}updateGaps(e){if(this.gapIntersection&&(e.length!=this.gaps.length||this.gaps.some((t,i)=>t!=e[i]))){this.gapIntersection.disconnect();for(let t of e)this.gapIntersection.observe(t);this.gaps=e}}onSelectionChange(e){let t=this.selectionChanged;if(!this.readSelectionRange()||this.delayedAndroidKey)return;let{view:i}=this,s=this.selectionRange;if(i.state.facet(Ht)?i.root.activeElement!=this.dom:!fn(this.dom,s))return;let r=s.anchorNode&&i.docView.tile.nearest(s.anchorNode);if(r&&r.isWidget()&&r.widget.ignoreEvent(e)){t||(this.selectionChanged=!1);return}(C.ie&&C.ie_version<=11||C.android&&C.chrome)&&!i.state.selection.main.empty&&s.focusNode&&un(s.focusNode,s.focusOffset,s.anchorNode,s.anchorOffset)?this.flushSoon():this.flush(!1)}readSelectionRange(){let{view:e}=this,t=bs(e.root);if(!t)return!1;let i=C.safari&&e.root.nodeType==11&&e.root.activeElement==this.dom&&Ox(this.view,t)||t;if(!i||this.selectionRange.eq(i))return!1;let s=fn(this.dom,i);return s&&!this.selectionChanged&&e.inputState.lastFocusTime>Date.now()-200&&e.inputState.lastTouchTime{let r=this.delayedAndroidKey;r&&(this.clearDelayedAndroidKey(),this.view.inputState.lastKeyCode=r.keyCode,this.view.inputState.lastKeyTime=Date.now(),!this.flush()&&r.force&&ss(this.dom,r.key,r.keyCode))};this.flushingAndroidKey=this.view.win.requestAnimationFrame(s)}(!this.delayedAndroidKey||e=="Enter")&&(this.delayedAndroidKey={key:e,keyCode:t,force:this.lastChange{this.delayedFlush=-1,this.flush()}))}forceFlush(){this.delayedFlush>=0&&(this.view.win.cancelAnimationFrame(this.delayedFlush),this.delayedFlush=-1),this.flush()}pendingRecords(){for(let e of this.observer.takeRecords())this.queue.push(e);return this.queue}processRecords(){let e=this.pendingRecords();e.length&&(this.queue=[]);let t=-1,i=-1,s=!1;for(let r of e){let o=this.readMutation(r);o&&(o.typeOver&&(s=!0),t==-1?{from:t,to:i}=o:(t=Math.min(o.from,t),i=Math.max(o.to,i)))}return{from:t,to:i,typeOver:s}}readChange(){let{from:e,to:t,typeOver:i}=this.processRecords(),s=this.selectionChanged&&fn(this.dom,this.selectionRange);if(e<0&&!s)return null;e>-1&&(this.lastChange=Date.now()),this.view.inputState.lastFocusTime=0,this.selectionChanged=!1;let r=new Dy(this.view,e,t,i);return this.view.docView.domChanged={newSel:r.newSel?r.newSel.main:null},r}flush(e=!0){if(this.delayedFlush>=0||this.delayedAndroidKey)return!1;e&&this.readSelectionRange();let t=this.readChange();if(!t)return this.view.requestMeasure(),!1;let i=this.view.state,s=vm(this.view,t);return this.view.state==i&&(t.domChanged||t.newSel&&!go(this.view.state.selection,t.newSel.main))&&this.view.update([]),s}readMutation(e){let t=this.view.docView.tile.nearest(e.target);if(!t||t.isWidget())return null;if(t.markDirty(e.type=="attributes"),e.type=="childList"){let i=yu(t,e.previousSibling||e.target.previousSibling,-1),s=yu(t,e.nextSibling||e.target.nextSibling,1);return{from:i?t.posAfter(i):t.posAtStart,to:s?t.posBefore(s):t.posAtEnd,typeOver:!1}}else return e.type=="characterData"?{from:t.posAtStart,to:t.posAtEnd,typeOver:e.target.nodeValue==e.oldValue}:null}setWindow(e){e!=this.win&&(this.removeWindowListeners(this.win),this.win=e,this.addWindowListeners(this.win))}addWindowListeners(e){e.addEventListener("resize",this.onResize),this.printQuery?this.printQuery.addEventListener?this.printQuery.addEventListener("change",this.onPrint):this.printQuery.addListener(this.onPrint):e.addEventListener("beforeprint",this.onPrint),e.addEventListener("scroll",this.onScroll),e.document.addEventListener("selectionchange",this.onSelectionChange)}removeWindowListeners(e){e.removeEventListener("scroll",this.onScroll),e.removeEventListener("resize",this.onResize),this.printQuery?this.printQuery.removeEventListener?this.printQuery.removeEventListener("change",this.onPrint):this.printQuery.removeListener(this.onPrint):e.removeEventListener("beforeprint",this.onPrint),e.document.removeEventListener("selectionchange",this.onSelectionChange)}update(e){this.editContext&&(this.editContext.update(e),e.startState.facet(Ht)!=e.state.facet(Ht)&&(e.view.contentDOM.editContext=e.state.facet(Ht)?this.editContext.editContext:null))}destroy(){var e,t,i;this.stop(),(e=this.intersection)===null||e===void 0||e.disconnect(),(t=this.gapIntersection)===null||t===void 0||t.disconnect(),(i=this.resizeScroll)===null||i===void 0||i.disconnect();for(let s of this.scrollTargets)s.removeEventListener("scroll",this.onScroll);this.removeWindowListeners(this.win),clearTimeout(this.parentCheck),clearTimeout(this.resizeTimeout),this.win.cancelAnimationFrame(this.delayedFlush),this.win.cancelAnimationFrame(this.flushingAndroidKey),this.editContext&&(this.view.contentDOM.editContext=null,this.editContext.destroy())}};function yu(n,e,t){for(;e;){let i=de.get(e);if(i&&i.parent==n)return i;let s=e.parentNode;e=s!=n.dom?s:t>0?e.nextSibling:e.previousSibling}return null}function xu(n,e){let t=e.startContainer,i=e.startOffset,s=e.endContainer,r=e.endOffset,o=n.docView.domAtPos(n.state.selection.main.anchor,1);return un(o.node,o.offset,s,r)&&([t,i,s,r]=[s,r,t,i]),{anchorNode:t,anchorOffset:i,focusNode:s,focusOffset:r}}function Ox(n,e){if(e.getComposedRanges){let s=e.getComposedRanges(n.root)[0];if(s)return xu(n,s)}let t=null;function i(s){s.preventDefault(),s.stopImmediatePropagation(),t=s.getTargetRanges()[0]}return n.contentDOM.addEventListener("beforeinput",i,!0),n.dom.ownerDocument.execCommand("indent"),n.contentDOM.removeEventListener("beforeinput",i,!0),t?xu(n,t):null}let bx=class{constructor(e){this.from=0,this.to=0,this.pendingContextChange=null,this.handlers=Object.create(null),this.composing=null,this.resetRange(e.state);let t=this.editContext=new window.EditContext({text:e.state.doc.sliceString(this.from,this.to),selectionStart:this.toContextPos(Math.max(this.from,Math.min(this.to,e.state.selection.main.anchor))),selectionEnd:this.toContextPos(e.state.selection.main.head)});this.handlers.textupdate=i=>{let s=e.state.selection.main,{anchor:r,head:o}=s,l=this.toEditorPos(i.updateRangeStart),a=this.toEditorPos(i.updateRangeEnd);e.inputState.composing>=0&&!this.composing&&(this.composing={contextBase:i.updateRangeStart,editorBase:l,drifted:!1});let h=a-l>i.text.length;l==this.from&&rthis.to&&(a=r);let c=$m(e.state.sliceDoc(l,a),i.text,(h?s.from:s.to)-l,h?"end":null);if(!c){let u=S.single(this.toEditorPos(i.selectionStart),this.toEditorPos(i.selectionEnd));go(u,s)||e.dispatch({selection:u,userEvent:"select"});return}let f={from:c.from+l,to:c.toA+l,insert:Z.of(i.text.slice(c.from,c.toB).split(` `))};if((C.mac||C.android)&&f.from==o-1&&/^\. ?$/.test(i.text)&&e.contentDOM.getAttribute("autocorrect")=="off"&&(f={from:l,to:a,insert:Z.of([i.text.replace("."," ")])}),this.pendingContextChange=f,!e.state.readOnly){let u=this.to-this.from+(f.to-f.from+f.insert.length);Lc(e,f,S.single(this.toEditorPos(i.selectionStart,u),this.toEditorPos(i.selectionEnd,u)))}this.pendingContextChange&&(this.revertPending(e.state),this.setSelection(e.state)),f.from=0&&!/[\\p{Alphabetic}\\p{Number}_]/.test(t.text.slice(Math.max(0,i.updateRangeStart-1),Math.min(t.text.length,i.updateRangeStart+1)))&&this.handlers.compositionend(i)},this.handlers.characterboundsupdate=i=>{let s=[],r=null;for(let o=this.toEditorPos(i.rangeStart),l=this.toEditorPos(i.rangeEnd);o{let s=[];for(let r of i.getTextFormats()){let o=r.underlineStyle,l=r.underlineThickness;if(!/none/i.test(o)&&!/none/i.test(l)){let a=this.toEditorPos(r.rangeStart),h=this.toEditorPos(r.rangeEnd);if(a{e.inputState.composing<0&&(e.inputState.composing=0,e.inputState.compositionFirstChange=!0)},this.handlers.compositionend=()=>{if(e.inputState.composing=-1,e.inputState.compositionFirstChange=null,this.composing){let{drifted:i}=this.composing;this.composing=null,i&&this.reset(e.state)}};for(let i in this.handlers)t.addEventListener(i,this.handlers[i]);this.measureReq={read:i=>{this.editContext.updateControlBounds(i.contentDOM.getBoundingClientRect());let s=bs(i.root);s&&s.rangeCount&&this.editContext.updateSelectionBounds(s.getRangeAt(0).getBoundingClientRect())}}}applyEdits(e){let t=0,i=!1,s=this.pendingContextChange;return e.changes.iterChanges((r,o,l,a,h)=>{if(i)return;let c=h.length-(o-r);if(s&&o>=s.to)if(s.from==r&&s.to==o&&s.insert.eq(h)){s=this.pendingContextChange=null,t+=c,this.to+=c;return}else s=null,this.revertPending(e.state);if(r+=t,o+=t,o<=this.from)this.from+=c,this.to+=c;else if(rthis.to||this.to-this.from+h.length>3e4){i=!0;return}this.editContext.updateText(this.toContextPos(r),this.toContextPos(o),h.toString()),this.to+=c}t+=c}),s&&!i&&this.revertPending(e.state),!i}update(e){let t=this.pendingContextChange,i=e.startState.selection.main;this.composing&&(this.composing.drifted||!e.changes.touchesRange(i.from,i.to)&&e.transactions.some(s=>!s.isUserEvent("input.type")&&s.changes.touchesRange(this.from,this.to)))?(this.composing.drifted=!0,this.composing.editorBase=e.changes.mapPos(this.composing.editorBase)):!this.applyEdits(e)||!this.rangeIsValid(e.state)?(this.pendingContextChange=null,this.reset(e.state)):(e.docChanged||e.selectionSet||t)&&this.setSelection(e.state),(e.geometryChanged||e.docChanged||e.selectionSet)&&e.view.requestMeasure(this.measureReq)}resetRange(e){let{head:t}=e.selection.main;this.from=Math.max(0,t-1e4),this.to=Math.min(e.doc.length,t+1e4)}reset(e){this.resetRange(e),this.editContext.updateText(0,this.editContext.text.length,e.doc.sliceString(this.from,this.to)),this.setSelection(e)}revertPending(e){let t=this.pendingContextChange;this.pendingContextChange=null,this.editContext.updateText(this.toContextPos(t.from),this.toContextPos(t.from+t.insert.length),e.doc.sliceString(t.from,t.to))}setSelection(e){let{main:t}=e.selection,i=this.toContextPos(Math.max(this.from,Math.min(this.to,t.anchor))),s=this.toContextPos(t.head);(this.editContext.selectionStart!=i||this.editContext.selectionEnd!=s)&&this.editContext.updateSelection(i,s)}rangeIsValid(e){let{head:t}=e.selection.main;return!(this.from>0&&t-this.from<500||this.to1e4*3)}toEditorPos(e,t=this.to-this.from){e=Math.min(e,t);let i=this.composing;return i&&i.drifted?i.editorBase+(e-i.contextBase):e+this.from}toContextPos(e){let t=this.composing;return t&&t.drifted?t.contextBase+(e-t.editorBase):e-this.from}destroy(){for(let e in this.handlers)this.editContext.removeEventListener(e,this.handlers[e])}},D=class mh{get state(){return this.viewState.state}get viewport(){return this.viewState.viewport}get visibleRanges(){return this.viewState.visibleRanges}get inView(){return this.viewState.inView}get composing(){return!!this.inputState&&this.inputState.composing>0}get compositionStarted(){return!!this.inputState&&this.inputState.composing>=0}get root(){return this._root}get win(){return this.dom.ownerDocument.defaultView||window}constructor(e={}){var t;this.plugins=[],this.pluginMap=new Map,this.editorAttrs={},this.contentAttrs={},this.bidiCache=[],this.destroyed=!1,this.updateState=2,this.measureScheduled=-1,this.measureRequests=[],this.contentDOM=document.createElement("div"),this.scrollDOM=document.createElement("div"),this.scrollDOM.tabIndex=-1,this.scrollDOM.className="cm-scroller",this.scrollDOM.appendChild(this.contentDOM),this.announceDOM=document.createElement("div"),this.announceDOM.className="cm-announced",this.announceDOM.setAttribute("aria-live","polite"),this.dom=document.createElement("div"),this.dom.appendChild(this.announceDOM),this.dom.appendChild(this.scrollDOM),e.parent&&e.parent.appendChild(this.dom);let{dispatch:i}=e;this.dispatchTransactions=e.dispatchTransactions||i&&(s=>s.forEach(r=>i(r,this)))||(s=>this.update(s)),this.dispatch=this.dispatch.bind(this),this._root=e.root||NS(e.parent)||document,this.viewState=new Ou(e.state||L.create(e)),e.scrollTo&&e.scrollTo.is(rr)&&(this.viewState.scrollTarget=e.scrollTo.value.clip(this.viewState.state)),this.plugins=this.state.facet(Hi).map(s=>new Xl(s));for(let s of this.plugins)s.update(this);this.observer=new mx(this),this.inputState=new Ly(this),this.inputState.ensureHandlers(this.plugins),this.docView=new su(this),this.mountStyles(),this.updateAttrs(),this.updateState=0,this.requestMeasure(),!((t=document.fonts)===null||t===void 0)&&t.ready&&document.fonts.ready.then(()=>{this.viewState.mustMeasureContent=!0,this.requestMeasure()})}dispatch(...e){let t=e.length==1&&e[0]instanceof ce?e:e.length==1&&Array.isArray(e[0])?e[0]:[this.state.update(...e)];this.dispatchTransactions(t,this)}update(e){if(this.updateState!=0)throw new Error("Calls to EditorView.update are not allowed while an update is in progress");let t=!1,i=!1,s,r=this.state;for(let u of e){if(u.startState!=r)throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");r=u.state}if(this.destroyed){this.viewState.state=r;return}let o=this.hasFocus,l=0,a=null;e.some(u=>u.annotation(Rm))?(this.inputState.notifiedFocused=o,l=1):o!=this.inputState.notifiedFocused&&(this.inputState.notifiedFocused=o,a=Dm(r,o),a||(l=1));let h=this.observer.delayedAndroidKey,c=null;if(h?(this.observer.clearDelayedAndroidKey(),c=this.observer.readChange(),(c&&!this.state.doc.eq(r.doc)||!this.state.selection.eq(r.selection))&&(c=null)):this.observer.clear(),r.facet(L.phrases)!=this.state.facet(L.phrases))return this.setState(r);s=eu.create(this,r,e),s.flags|=l;let f=this.viewState.scrollTarget;try{this.updateState=2;for(let u of e){if(f&&(f=f.map(u.changes)),u.scrollIntoView){let{main:d}=u.state.selection;f=new Bl(d.empty?d:S.cursor(d.head,d.head>d.anchor?-1:1))}for(let d of u.effects)d.is(rr)&&(f=d.value.clip(this.state))}this.viewState.update(s,f),this.bidiCache=ku.update(this.bidiCache,s.changes),s.empty||(this.updatePlugins(s),this.inputState.update(s)),t=this.docView.update(s),this.state.facet(Hs)!=this.styleModules&&this.mountStyles(),i=this.updateAttrs(),this.showAnnouncements(e),this.docView.updateSelection(t,e.some(u=>u.isUserEvent("select.pointer")))}finally{this.updateState=0}if(s.startState.facet(fr)!=s.state.facet(fr)&&(this.viewState.mustMeasureContent=!0),(t||i||f||this.viewState.mustEnforceCursorAssoc||this.viewState.mustMeasureContent)&&this.requestMeasure(),t&&this.docViewUpdate(),!s.empty)for(let u of this.state.facet(nh))try{u(s)}catch(d){Le(this.state,d,"update listener")}(a||c)&&Promise.resolve().then(()=>{a&&this.state==a.startState&&this.dispatch(a),c&&!vm(this,c)&&h.force&&ss(this.contentDOM,h.key,h.keyCode)})}setState(e){if(this.updateState!=0)throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");if(this.destroyed){this.viewState.state=e;return}this.updateState=2;let t=this.hasFocus;try{for(let i of this.plugins)i.destroy(this);this.viewState=new Ou(e),this.plugins=e.facet(Hi).map(i=>new Xl(i)),this.pluginMap.clear();for(let i of this.plugins)i.update(this);this.docView.destroy(),this.docView=new su(this),this.inputState.ensureHandlers(this.plugins),this.mountStyles(),this.updateAttrs(),this.bidiCache=[]}finally{this.updateState=0}t&&this.focus(),this.requestMeasure()}updatePlugins(e){let t=e.startState.facet(Hi),i=e.state.facet(Hi);if(t!=i){let s=[];for(let r of i){let o=t.indexOf(r);if(o<0)s.push(new Xl(r));else{let l=this.plugins[o];l.mustUpdate=e,s.push(l)}}for(let r of this.plugins)r.mustUpdate!=e&&r.destroy(this);this.plugins=s,this.pluginMap.clear()}else for(let s of this.plugins)s.mustUpdate=e;for(let s=0;s-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.observer.delayedAndroidKey){this.measureScheduled=-1,this.requestMeasure();return}this.measureScheduled=0,e&&this.observer.forceFlush();let t=null,i=this.scrollDOM,s=i.scrollTop*this.scaleY,{scrollAnchorPos:r,scrollAnchorHeight:o}=this.viewState;Math.abs(s-this.viewState.scrollTop)>1&&(o=-1),this.viewState.scrollAnchorHeight=-1;try{for(let l=0;;l++){if(o<0)if(Ng(i))r=-1,o=this.viewState.heightMap.height;else{let d=this.viewState.scrollAnchorAt(s);r=d.from,o=d.top}this.updateState=1;let a=this.viewState.measure(this);if(!a&&!this.measureRequests.length&&this.viewState.scrollTarget==null)break;if(l>5){console.warn(this.measureRequests.length?"Measure loop restarted more than 5 times":"Viewport failed to stabilize");break}let h=[];a&4||([this.measureRequests,h]=[h,this.measureRequests]);let c=h.map(d=>{try{return d.read(this)}catch(p){return Le(this.state,p),wu}}),f=eu.create(this,this.state,[]),u=!1;f.flags|=a,t?t.flags|=a:t=f,this.updateState=2,f.empty||(this.updatePlugins(f),this.inputState.update(f),this.updateAttrs(),u=this.docView.update(f),u&&this.docViewUpdate());for(let d=0;d1||p<-1){s=s+p,i.scrollTop=s/this.scaleY,o=-1;continue}}break}}}finally{this.updateState=0,this.measureScheduled=-1}if(t&&!t.empty)for(let l of this.state.facet(nh))l(t)}get themeClasses(){return ph+" "+(this.state.facet(dh)?Vm:Wm)+" "+this.state.facet(fr)}updateAttrs(){let e=Qu(this,cm,{class:"cm-editor"+(this.hasFocus?" cm-focused ":" ")+this.themeClasses}),t={spellcheck:"false",autocorrect:"off",autocapitalize:"off",writingsuggestions:"false",translate:"no",contenteditable:this.state.facet(Ht)?"true":"false",class:"cm-content",style:`${C.tabSize}: ${this.state.tabSize}`,role:"textbox","aria-multiline":"true"};this.state.readOnly&&(t["aria-readonly"]="true"),Qu(this,Zc,t);let i=this.observer.ignore(()=>{let s=Hf(this.contentDOM,this.contentAttrs,t),r=Hf(this.dom,this.editorAttrs,e);return s||r});return this.editorAttrs=e,this.contentAttrs=t,i}showAnnouncements(e){let t=!0;for(let i of e)for(let s of i.effects)if(s.is(mh.announce)){t&&(this.announceDOM.textContent=""),t=!1;let r=this.announceDOM.appendChild(document.createElement("div"));r.textContent=s.value}}mountStyles(){this.styleModules=this.state.facet(Hs);let e=this.state.facet(mh.cspNonce);me.mount(this.root,this.styleModules.concat(px).reverse(),e?{nonce:e}:void 0)}readMeasured(){if(this.updateState==2)throw new Error("Reading the editor layout isn't allowed during an update");this.updateState==0&&this.measureScheduled>-1&&this.measure(!1)}requestMeasure(e){if(this.measureScheduled<0&&(this.measureScheduled=this.win.requestAnimationFrame(()=>this.measure())),e){if(this.measureRequests.indexOf(e)>-1)return;if(e.key!=null){for(let t=0;ti.plugin==e)||null),t&&t.update(this).value}get documentTop(){return this.contentDOM.getBoundingClientRect().top+this.viewState.paddingTop}get documentPadding(){return{top:this.viewState.paddingTop,bottom:this.viewState.paddingBottom}}get scaleX(){return this.viewState.scaleX}get scaleY(){return this.viewState.scaleY}elementAtHeight(e){return this.readMeasured(),this.viewState.elementAtHeight(e)}lineBlockAtHeight(e){return this.readMeasured(),this.viewState.lineBlockAtHeight(e)}get viewportLineBlocks(){return this.viewState.viewportLines}lineBlockAt(e){return this.viewState.lineBlockAt(e)}get contentHeight(){return this.viewState.contentHeight}moveByChar(e,t,i){return Vl(this,e,nu(this,e,t,i))}moveByGroup(e,t){return Vl(this,e,nu(this,e,t,i=>Cy(this,e.head,i)))}visualLineSide(e,t){let i=this.bidiSpans(e),s=this.textDirectionAt(e.from),r=i[t?i.length-1:0];return S.cursor(r.side(t,s)+e.from,r.forward(!t,s)?1:-1)}moveToLineBoundary(e,t,i=!0){return Py(this,e,t,i)}moveVertically(e,t,i){return Vl(this,e,Ty(this,e,t,i))}domAtPos(e,t=1){return this.docView.domAtPos(e,t)}posAtDOM(e,t=0){return this.docView.posFromDOM(e,t)}posAtCoords(e,t=!0){this.readMeasured();let i=ch(this,e,t);return i&&i.pos}posAndSideAtCoords(e,t=!0){return this.readMeasured(),ch(this,e,t)}coordsAtPos(e,t=1){this.readMeasured();let i=this.docView.coordsAt(e,t);if(!i||i.left==i.right)return i;let s=this.state.doc.lineAt(e),r=this.bidiSpans(s),o=r[ei.find(r,e-s.from,-1,t)];return co(i,o.dir==le.LTR==t>0)}coordsForChar(e){return this.readMeasured(),this.docView.coordsForChar(e)}get defaultCharacterWidth(){return this.viewState.heightOracle.charWidth}get defaultLineHeight(){return this.viewState.heightOracle.lineHeight}get textDirection(){return this.viewState.defaultTextDirection}textDirectionAt(e){return!this.state.facet(om)||ethis.viewport.to?this.textDirection:(this.readMeasured(),this.docView.textDirectionAt(e))}get lineWrapping(){return this.viewState.heightOracle.lineWrapping}bidiSpans(e){if(e.length>Sx)return Kg(e.length);let t=this.textDirectionAt(e.from),i;for(let r of this.bidiCache)if(r.from==e.from&&r.dir==t&&(r.fresh||Ug(r.isolates,i=Jf(this,e))))return r.order;i||(i=Jf(this,e));let s=ey(e.text,t,i);return this.bidiCache.push(new ku(e.from,e.to,t,i,!0,s)),s}get hasFocus(){var e;return(this.dom.ownerDocument.hasFocus()||C.safari&&((e=this.inputState)===null||e===void 0?void 0:e.lastContextMenu)>Date.now()-3e4)&&this.root.activeElement==this.contentDOM}focus(){this.observer.ignore(()=>{Yg(this.contentDOM),this.docView.updateSelection()})}setRoot(e){this._root!=e&&(this._root=e,this.observer.setWindow((e.nodeType==9?e:e.ownerDocument).defaultView||window),this.mountStyles())}destroy(){this.root.activeElement==this.contentDOM&&this.contentDOM.blur();for(let e of this.plugins)e.destroy(this);this.plugins=[],this.inputState.destroy(),this.docView.destroy(),this.dom.remove(),this.observer.destroy(),this.measureScheduled>-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.destroyed=!0}static scrollIntoView(e,t={}){return rr.of(new Bl(typeof e=="number"?S.cursor(e):e,t.y,t.x,t.yMargin,t.xMargin))}scrollSnapshot(){let{scrollTop:e,scrollLeft:t}=this.scrollDOM,i=this.viewState.scrollAnchorAt(e);return rr.of(new Bl(S.cursor(i.from),"start","start",i.top-e,t,!0))}setTabFocusMode(e){e==null?this.inputState.tabFocusMode=this.inputState.tabFocusMode<0?0:-1:typeof e=="boolean"?this.inputState.tabFocusMode=e?0:-1:this.inputState.tabFocusMode!=0&&(this.inputState.tabFocusMode=Date.now()+e)}static domEventHandlers(e){return Re.define(()=>({}),{eventHandlers:e})}static domEventObservers(e){return Re.define(()=>({}),{eventObservers:e})}static theme(e,t){let i=me.newName(),s=[fr.of(i),Hs.of(gh(`.${i}`,e))];return t&&t.dark&&s.push(dh.of(!0)),s}static baseTheme(e){return xt.lowest(Hs.of(gh("."+ph,e,zm)))}static findFromDOM(e){var t;let i=e.querySelector(".cm-content"),s=i&&de.get(i)||de.get(e);return((t=s?.root)===null||t===void 0?void 0:t.view)||null}};D.styleModule=Hs;D.inputHandler=nm;D.clipboardInputFilter=Rc;D.clipboardOutputFilter=Dc;D.scrollHandler=am;D.focusChangeEffect=rm;D.perLineTextDirection=om;D.exceptionSink=sm;D.updateListener=nh;D.editable=Ht;D.mouseSelectionStyle=im;D.dragMovesSelection=tm;D.clickAddsSelectionRange=em;D.decorations=nl;D.blockWrappers=fm;D.outerDecorations=Bc;D.atomicRanges=In;D.bidiIsolatedRanges=um;D.scrollMargins=dm;D.darkTheme=dh;D.cspNonce=k.define({combine:n=>n.length?n[0]:""});D.contentAttributes=Zc;D.editorAttributes=cm;D.lineWrapping=D.contentAttributes.of({class:"cm-lineWrapping"});D.announce=B.define();const Sx=4096,wu={};let ku=class qm{constructor(e,t,i,s,r,o){this.from=e,this.to=t,this.dir=i,this.isolates=s,this.fresh=r,this.order=o}static update(e,t){if(t.empty&&!e.some(r=>r.fresh))return e;let i=[],s=e.length?e[e.length-1].dir:le.LTR;for(let r=Math.max(0,e.length-10);r=0;s--){let r=i[s],o=typeof r=="function"?r(n):r;o&&Pc(o,t)}return t}const yx=C.mac?"mac":C.windows?"win":C.linux?"linux":"key";function xx(n,e){const t=n.split(/-(?!$)/);let i=t[t.length-1];i=="Space"&&(i=" ");let s,r,o,l;for(let a=0;ai.concat(s),[]))),t}let gi=null;const Qx=4e3;function vx(n,e=yx){let t=Object.create(null),i=Object.create(null),s=(o,l)=>{let a=i[o];if(a==null)i[o]=l;else if(a!=l)throw new Error("Key binding "+o+" is used both as a regular binding and as a multi-stroke prefix")},r=(o,l,a,h,c)=>{var f,u;let d=t[o]||(t[o]=Object.create(null)),p=l.split(/ (?!$)/).map(O=>xx(O,e));for(let O=1;O{let v=gi={view:x,prefix:y,scope:o};return setTimeout(()=>{gi==v&&(gi=null)},Qx),!0}]})}let g=p.join(" ");s(g,!1);let m=d[g]||(d[g]={preventDefault:!1,stopPropagation:!1,run:((u=(f=d._any)===null||f===void 0?void 0:f.run)===null||u===void 0?void 0:u.slice())||[]});a&&m.run.push(a),h&&(m.preventDefault=!0),c&&(m.stopPropagation=!0)};for(let o of n){let l=o.scope?o.scope.split(" "):["editor"];if(o.any)for(let h of l){let c=t[h]||(t[h]=Object.create(null));c._any||(c._any={preventDefault:!1,stopPropagation:!1,run:[]});let{any:f}=o;for(let u in c)c[u].run.push(d=>f(d,Oh))}let a=o[e]||o.key;if(a)for(let h of l)r(h,a,o.run,o.preventDefault,o.stopPropagation),o.shift&&r(h,"Shift-"+a,o.shift,o.preventDefault,o.stopPropagation)}return t}let Oh=null;function $x(n,e,t,i){Oh=e;let s=mg(e),r=Me(s,0),o=ut(r)==s.length&&s!=" ",l="",a=!1,h=!1,c=!1;gi&&gi.view==t&&gi.scope==i&&(l=gi.prefix+" ",Cm.indexOf(e.keyCode)<0&&(h=!0,gi=null));let f=new Set,u=m=>{if(m){for(let O of m.run)if(!f.has(O)&&(f.add(O),O(t)))return m.stopPropagation&&(c=!0),!0;m.preventDefault&&(m.stopPropagation&&(c=!0),h=!0)}return!1},d=n[i],p,g;return d&&(u(d[l+ur(s,e,!o)])?a=!0:o&&(e.altKey||e.metaKey||e.ctrlKey)&&!(C.windows&&e.ctrlKey&&e.altKey)&&!(C.mac&&e.altKey&&!(e.ctrlKey||e.metaKey))&&(p=Og[e.keyCode])&&p!=s?(u(d[l+ur(p,e,!0)])||e.shiftKey&&(g=bg[e.keyCode])!=s&&g!=p&&u(d[l+ur(g,e,!1)]))&&(a=!0):o&&e.shiftKey&&u(d[l+ur(s,e,!0)])&&(a=!0),!a&&u(d._any)&&(a=!0)),h&&(a=!0),a&&c&&e.stopPropagation(),Oh=null,a}class Yn{constructor(e,t,i,s,r){this.className=e,this.left=t,this.top=i,this.width=s,this.height=r}draw(){let e=document.createElement("div");return e.className=this.className,this.adjust(e),e}update(e,t){return t.className!=this.className?!1:(this.adjust(e),!0)}adjust(e){e.style.left=this.left+"px",e.style.top=this.top+"px",this.width!=null&&(e.style.width=this.width+"px"),e.style.height=this.height+"px"}eq(e){return this.left==e.left&&this.top==e.top&&this.width==e.width&&this.height==e.height&&this.className==e.className}static forRange(e,t,i){if(i.empty){let s=e.coordsAtPos(i.head,i.assoc||1);if(!s)return[];let r=Im(e);return[new Yn(t,s.left-r.left,s.top-r.top,null,s.bottom-s.top)]}else return Px(e,t,i)}}function Im(n){let e=n.scrollDOM.getBoundingClientRect();return{left:(n.textDirection==le.LTR?e.left:e.right-n.scrollDOM.clientWidth*n.scaleX)-n.scrollDOM.scrollLeft*n.scaleX,top:e.top-n.scrollDOM.scrollTop*n.scaleY}}function $u(n,e,t,i){let s=n.coordsAtPos(e,t*2);if(!s)return i;let r=n.dom.getBoundingClientRect(),o=(s.top+s.bottom)/2,l=n.posAtCoords({x:r.left+1,y:o}),a=n.posAtCoords({x:r.right-1,y:o});return l==null||a==null?i:{from:Math.max(i.from,Math.min(l,a)),to:Math.min(i.to,Math.max(l,a))}}function Px(n,e,t){if(t.to<=n.viewport.from||t.from>=n.viewport.to)return[];let i=Math.max(t.from,n.viewport.from),s=Math.min(t.to,n.viewport.to),r=n.textDirection==le.LTR,o=n.contentDOM,l=o.getBoundingClientRect(),a=Im(n),h=o.querySelector(".cm-line"),c=h&&window.getComputedStyle(h),f=l.left+(c?parseInt(c.paddingLeft)+Math.min(0,parseInt(c.textIndent)):0),u=l.right-(c?parseInt(c.paddingRight):0),d=hh(n,i,1),p=hh(n,s,-1),g=d.type==ke.Text?d:null,m=p.type==ke.Text?p:null;if(g&&(n.lineWrapping||d.widgetLineBreaks)&&(g=$u(n,i,1,g)),m&&(n.lineWrapping||p.widgetLineBreaks)&&(m=$u(n,s,-1,m)),g&&m&&g.from==m.from&&g.to==m.to)return y(x(t.from,t.to,g));{let w=g?x(t.from,null,g):v(d,!1),Q=m?x(null,t.to,m):v(p,!0),$=[];return(g||d).to<(m||p).from-(g&&m?1:0)||d.widgetLineBreaks>1&&w.bottom+n.defaultLineHeight/2X&&N.from=Pe)break;He>ie&&q(Math.max(ye,ie),w==null&&ye<=X,Math.min(He,Pe),Q==null&&He>=G,vt.dir)}if(ie=ze.to+1,ie>=Pe)break}return F.length==0&&q(X,w==null,G,Q==null,n.textDirection),{top:W,bottom:I,horizontal:F}}function v(w,Q){let $=l.top+(Q?w.top:w.bottom);return{top:$,bottom:$,horizontal:[]}}}function Cx(n,e){return n.constructor==e.constructor&&n.eq(e)}class Tx{constructor(e,t){this.view=e,this.layer=t,this.drawn=[],this.scaleX=1,this.scaleY=1,this.measureReq={read:this.measure.bind(this),write:this.draw.bind(this)},this.dom=e.scrollDOM.appendChild(document.createElement("div")),this.dom.classList.add("cm-layer"),t.above&&this.dom.classList.add("cm-layer-above"),t.class&&this.dom.classList.add(t.class),this.scale(),this.dom.setAttribute("aria-hidden","true"),this.setOrder(e.state),e.requestMeasure(this.measureReq),t.mount&&t.mount(this.dom,e)}update(e){e.startState.facet(Nr)!=e.state.facet(Nr)&&this.setOrder(e.state),(this.layer.update(e,this.dom)||e.geometryChanged)&&(this.scale(),e.view.requestMeasure(this.measureReq))}docViewUpdate(e){this.layer.updateOnDocViewUpdate!==!1&&e.requestMeasure(this.measureReq)}setOrder(e){let t=0,i=e.facet(Nr);for(;t!Cx(t,this.drawn[i]))){let t=this.dom.firstChild,i=0;for(let s of e)s.update&&t&&s.constructor&&this.drawn[i].constructor&&s.update(t,this.drawn[i])?(t=t.nextSibling,i++):this.dom.insertBefore(s.draw(),t);for(;t;){let s=t.nextSibling;t.remove(),t=s}this.drawn=e,C.safari&&C.safari_version>=26&&(this.dom.style.display=this.dom.firstChild?"":"none")}}destroy(){this.layer.destroy&&this.layer.destroy(this.dom,this.view),this.dom.remove()}}const Nr=k.define();function _m(n){return[Re.define(e=>new Tx(e,n)),Nr.of(n)]}const Pn=k.define({combine(n){return _t(n,{cursorBlinkRate:1200,drawRangeCursor:!0},{cursorBlinkRate:(e,t)=>Math.min(e,t),drawRangeCursor:(e,t)=>e||t})}});function Mx(n={}){return[Pn.of(n),Ax,Rx,Dx,lm.of(!0)]}function Ym(n){return n.startState.facet(Pn)!=n.state.facet(Pn)}const Ax=_m({above:!0,markers(n){let{state:e}=n,t=e.facet(Pn),i=[];for(let s of e.selection.ranges){let r=s==e.selection.main;if(s.empty||t.drawRangeCursor){let o=r?"cm-cursor cm-cursor-primary":"cm-cursor cm-cursor-secondary",l=s.empty?s:S.cursor(s.head,s.head>s.anchor?-1:1);for(let a of Yn.forRange(n,o,l))i.push(a)}}return i},update(n,e){n.transactions.some(i=>i.selection)&&(e.style.animationName=e.style.animationName=="cm-blink"?"cm-blink2":"cm-blink");let t=Ym(n);return t&&Pu(n.state,e),n.docChanged||n.selectionSet||t},mount(n,e){Pu(e.state,n)},class:"cm-cursorLayer"});function Pu(n,e){e.style.animationDuration=n.facet(Pn).cursorBlinkRate+"ms"}const Rx=_m({above:!1,markers(n){return n.state.selection.ranges.map(e=>e.empty?[]:Yn.forRange(n,"cm-selectionBackground",e)).reduce((e,t)=>e.concat(t))},update(n,e){return n.docChanged||n.selectionSet||n.viewportChanged||Ym(n)},class:"cm-selectionLayer"}),Dx=xt.highest(D.theme({".cm-line":{"& ::selection, &::selection":{backgroundColor:"transparent !important"},caretColor:"transparent !important"},".cm-content":{caretColor:"transparent !important","& :focus":{caretColor:"initial !important","&::selection, & ::selection":{backgroundColor:"Highlight !important"}}}})),Nm=B.define({map(n,e){return n==null?null:e.mapPos(n)}}),Js=Se.define({create(){return null},update(n,e){return n!=null&&(n=e.changes.mapPos(n)),e.effects.reduce((t,i)=>i.is(Nm)?i.value:t,n)}}),Zx=Re.fromClass(class{constructor(n){this.view=n,this.cursor=null,this.measureReq={read:this.readPos.bind(this),write:this.drawCursor.bind(this)}}update(n){var e;let t=n.state.field(Js);t==null?this.cursor!=null&&((e=this.cursor)===null||e===void 0||e.remove(),this.cursor=null):(this.cursor||(this.cursor=this.view.scrollDOM.appendChild(document.createElement("div")),this.cursor.className="cm-dropCursor"),(n.startState.field(Js)!=t||n.docChanged||n.geometryChanged)&&this.view.requestMeasure(this.measureReq))}readPos(){let{view:n}=this,e=n.state.field(Js),t=e!=null&&n.coordsAtPos(e);if(!t)return null;let i=n.scrollDOM.getBoundingClientRect();return{left:t.left-i.left+n.scrollDOM.scrollLeft*n.scaleX,top:t.top-i.top+n.scrollDOM.scrollTop*n.scaleY,height:t.bottom-t.top}}drawCursor(n){if(this.cursor){let{scaleX:e,scaleY:t}=this.view;n?(this.cursor.style.left=n.left/e+"px",this.cursor.style.top=n.top/t+"px",this.cursor.style.height=n.height/t+"px"):this.cursor.style.left="-100000px"}}destroy(){this.cursor&&this.cursor.remove()}setDropPos(n){this.view.state.field(Js)!=n&&this.view.dispatch({effects:Nm.of(n)})}},{eventObservers:{dragover(n){this.setDropPos(this.view.posAtCoords({x:n.clientX,y:n.clientY}))},dragleave(n){(n.target==this.view.contentDOM||!this.view.contentDOM.contains(n.relatedTarget))&&this.setDropPos(null)},dragend(){this.setDropPos(null)},drop(){this.setDropPos(null)}}});function Bx(){return[Js,Zx]}function Cu(n,e,t,i,s){e.lastIndex=0;for(let r=n.iterRange(t,i),o=t,l;!r.next().done;o+=r.value.length)if(!r.lineBreak)for(;l=e.exec(r.value);)s(o+l.index,l)}function Xx(n,e){let t=n.visibleRanges;if(t.length==1&&t[0].from==n.viewport.from&&t[0].to==n.viewport.to)return t;let i=[];for(let{from:s,to:r}of t)s=Math.max(n.state.doc.lineAt(s).from,s-e),r=Math.min(n.state.doc.lineAt(r).to,r+e),i.length&&i[i.length-1].to>=s?i[i.length-1].to=r:i.push({from:s,to:r});return i}class Lx{constructor(e){const{regexp:t,decoration:i,decorate:s,boundary:r,maxLength:o=1e3}=e;if(!t.global)throw new RangeError("The regular expression given to MatchDecorator should have its 'g' flag set");if(this.regexp=t,s)this.addMatch=(l,a,h,c)=>s(c,h,h+l[0].length,l,a);else if(typeof i=="function")this.addMatch=(l,a,h,c)=>{let f=i(l,a,h);f&&c(h,h+l[0].length,f)};else if(i)this.addMatch=(l,a,h,c)=>c(h,h+l[0].length,i);else throw new RangeError("Either 'decorate' or 'decoration' should be provided to MatchDecorator");this.boundary=r,this.maxLength=o}createDeco(e){let t=new ai,i=t.add.bind(t);for(let{from:s,to:r}of Xx(e,this.maxLength))Cu(e.state.doc,this.regexp,s,r,(o,l)=>this.addMatch(l,e,o,i));return t.finish()}updateDeco(e,t){let i=1e9,s=-1;return e.docChanged&&e.changes.iterChanges((r,o,l,a)=>{a>=e.view.viewport.from&&l<=e.view.viewport.to&&(i=Math.min(l,i),s=Math.max(a,s))}),e.viewportMoved||s-i>1e3?this.createDeco(e.view):s>-1?this.updateRange(e.view,t.map(e.changes),i,s):t}updateRange(e,t,i,s){for(let r of e.visibleRanges){let o=Math.max(r.from,i),l=Math.min(r.to,s);if(l>=o){let a=e.state.doc.lineAt(o),h=a.toa.from;o--)if(this.boundary.test(a.text[o-1-a.from])){c=o;break}for(;lu.push(O.range(g,m));if(a==h)for(this.regexp.lastIndex=c-a.from;(d=this.regexp.exec(a.text))&&d.indexthis.addMatch(m,e,g,p));t=t.update({filterFrom:c,filterTo:f,filter:(g,m)=>gf,add:u})}}return t}}const bh=/x/.unicode!=null?"gu":"g",Ex=new RegExp(`[\0-\b --Ÿ­؜​‎‏\u2028\u2029‭‮⁦⁧⁩\uFEFF-]`,bh),Wx={0:"null",7:"bell",8:"backspace",10:"newline",11:"vertical tab",13:"carriage return",27:"escape",8203:"zero width space",8204:"zero width non-joiner",8205:"zero width joiner",8206:"left-to-right mark",8207:"right-to-left mark",8232:"line separator",8237:"left-to-right override",8238:"right-to-left override",8294:"left-to-right isolate",8295:"right-to-left isolate",8297:"pop directional isolate",8233:"paragraph separator",65279:"zero width no-break space",65532:"object replacement"};let Il=null;function Vx(){var n;if(Il==null&&typeof document<"u"&&document.body){let e=document.body.style;Il=((n=e.tabSize)!==null&&n!==void 0?n:e.MozTabSize)!=null}return Il||!1}const jr=k.define({combine(n){let e=_t(n,{render:null,specialChars:Ex,addSpecialChars:null});return(e.replaceTabs=!Vx())&&(e.specialChars=new RegExp(" |"+e.specialChars.source,bh)),e.addSpecialChars&&(e.specialChars=new RegExp(e.specialChars.source+"|"+e.addSpecialChars.source,bh)),e}});function zx(n={}){return[jr.of(n),qx()]}let Tu=null;function qx(){return Tu||(Tu=Re.fromClass(class{constructor(n){this.view=n,this.decorations=E.none,this.decorationCache=Object.create(null),this.decorator=this.makeDecorator(n.state.facet(jr)),this.decorations=this.decorator.createDeco(n)}makeDecorator(n){return new Lx({regexp:n.specialChars,decoration:(e,t,i)=>{let{doc:s}=t.state,r=Me(e[0],0);if(r==9){let o=s.lineAt(i),l=t.state.tabSize,a=Ls(o.text,l,i-o.from);return E.replace({widget:new Nx((l-a%l)*this.view.defaultCharacterWidth/this.view.scaleX)})}return this.decorationCache[r]||(this.decorationCache[r]=E.replace({widget:new Yx(n,r)}))},boundary:n.replaceTabs?void 0:/[^]/})}update(n){let e=n.state.facet(jr);n.startState.facet(jr)!=e?(this.decorator=this.makeDecorator(e),this.decorations=this.decorator.createDeco(n.view)):this.decorations=this.decorator.updateDeco(n,this.decorations)}},{decorations:n=>n.decorations}))}const Ix="•";function _x(n){return n>=32?Ix:n==10?"␤":String.fromCharCode(9216+n)}class Yx extends Yt{constructor(e,t){super(),this.options=e,this.code=t}eq(e){return e.code==this.code}toDOM(e){let t=_x(this.code),i=e.state.phrase("Control character")+" "+(Wx[this.code]||"0x"+this.code.toString(16)),s=this.options.render&&this.options.render(this.code,i,t);if(s)return s;let r=document.createElement("span");return r.textContent=t,r.title=i,r.setAttribute("aria-label",i),r.className="cm-specialChar",r}ignoreEvent(){return!1}}class Nx extends Yt{constructor(e){super(),this.width=e}eq(e){return e.width==this.width}toDOM(){let e=document.createElement("span");return e.textContent=" ",e.className="cm-tab",e.style.width=this.width+"px",e}ignoreEvent(){return!1}}function jx(){return Hx}const Gx=E.line({class:"cm-activeLine"}),Hx=Re.fromClass(class{constructor(n){this.decorations=this.getDeco(n)}update(n){(n.docChanged||n.selectionSet)&&(this.decorations=this.getDeco(n.view))}getDeco(n){let e=-1,t=[];for(let i of n.state.selection.ranges){let s=n.lineBlockAt(i.head);s.from>e&&(t.push(Gx.range(s.from)),e=s.from)}return E.set(t)}},{decorations:n=>n.decorations}),Sh=2e3;function Fx(n,e,t){let i=Math.min(e.line,t.line),s=Math.max(e.line,t.line),r=[];if(e.off>Sh||t.off>Sh||e.col<0||t.col<0){let o=Math.min(e.off,t.off),l=Math.max(e.off,t.off);for(let a=i;a<=s;a++){let h=n.doc.line(a);h.length<=l&&r.push(S.range(h.from+o,h.to+l))}}else{let o=Math.min(e.col,t.col),l=Math.max(e.col,t.col);for(let a=i;a<=s;a++){let h=n.doc.line(a),c=kn(h.text,o,n.tabSize,!0);if(c<0)r.push(S.cursor(h.to));else{let f=kn(h.text,l,n.tabSize);r.push(S.range(h.from+c,h.from+f))}}}return r}function Ux(n,e){let t=n.coordsAtPos(n.viewport.from);return t?Math.round(Math.abs((t.left-e)/n.defaultCharacterWidth)):-1}function Mu(n,e){let t=n.posAtCoords({x:e.clientX,y:e.clientY},!1),i=n.state.doc.lineAt(t),s=t-i.from,r=s>Sh?-1:s==i.length?Ux(n,e.clientX):Ls(i.text,n.state.tabSize,t-i.from);return{line:i.number,col:r,off:s}}function Kx(n,e){let t=Mu(n,e),i=n.state.selection;return t?{update(s){if(s.docChanged){let r=s.changes.mapPos(s.startState.doc.line(t.line).from),o=s.state.doc.lineAt(r);t={line:o.number,col:t.col,off:Math.min(t.off,o.length)},i=i.map(s.changes)}},get(s,r,o){let l=Mu(n,s);if(!l)return i;let a=Fx(n.state,t,l);return a.length?o?S.create(a.concat(i.ranges)):S.create(a):i}}:null}function Jx(n){let e=(t=>t.altKey&&t.button==0);return D.mouseSelectionStyle.of((t,i)=>e(i)?Kx(t,i):null)}const ew={Alt:[18,n=>!!n.altKey],Control:[17,n=>!!n.ctrlKey],Shift:[16,n=>!!n.shiftKey],Meta:[91,n=>!!n.metaKey]},tw={style:"cursor: crosshair"};function iw(n={}){let[e,t]=ew[n.key||"Alt"],i=Re.fromClass(class{constructor(s){this.view=s,this.isDown=!1}set(s){this.isDown!=s&&(this.isDown=s,this.view.update([]))}},{eventObservers:{keydown(s){this.set(s.keyCode==e||t(s))},keyup(s){(s.keyCode==e||!t(s))&&this.set(!1)},mousemove(s){this.set(t(s))}}});return[i,D.contentAttributes.of(s=>{var r;return!((r=s.plugin(i))===null||r===void 0)&&r.isDown?tw:null})]}const dr="-10000px";class jm{constructor(e,t,i,s){this.facet=t,this.createTooltipView=i,this.removeTooltipView=s,this.input=e.state.facet(t),this.tooltips=this.input.filter(o=>o);let r=null;this.tooltipViews=this.tooltips.map(o=>r=i(o,r))}update(e,t){var i;let s=e.state.facet(this.facet),r=s.filter(a=>a);if(s===this.input){for(let a of this.tooltipViews)a.update&&a.update(e);return!1}let o=[],l=t?[]:null;for(let a=0;at[h]=a),t.length=l.length),this.input=s,this.tooltips=r,this.tooltipViews=o,!0}}function sw(n){let e=n.dom.ownerDocument.documentElement;return{top:0,left:0,bottom:e.clientHeight,right:e.clientWidth}}const _l=k.define({combine:n=>{var e,t,i;return{position:C.ios?"absolute":((e=n.find(s=>s.position))===null||e===void 0?void 0:e.position)||"fixed",parent:((t=n.find(s=>s.parent))===null||t===void 0?void 0:t.parent)||null,tooltipSpace:((i=n.find(s=>s.tooltipSpace))===null||i===void 0?void 0:i.tooltipSpace)||sw}}}),Au=new WeakMap,Ec=Re.fromClass(class{constructor(n){this.view=n,this.above=[],this.inView=!0,this.madeAbsolute=!1,this.lastTransaction=0,this.measureTimeout=-1;let e=n.state.facet(_l);this.position=e.position,this.parent=e.parent,this.classes=n.themeClasses,this.createContainer(),this.measureReq={read:this.readMeasure.bind(this),write:this.writeMeasure.bind(this),key:this},this.resizeObserver=typeof ResizeObserver=="function"?new ResizeObserver(()=>this.measureSoon()):null,this.manager=new jm(n,Wc,(t,i)=>this.createTooltip(t,i),t=>{this.resizeObserver&&this.resizeObserver.unobserve(t.dom),t.dom.remove()}),this.above=this.manager.tooltips.map(t=>!!t.above),this.intersectionObserver=typeof IntersectionObserver=="function"?new IntersectionObserver(t=>{Date.now()>this.lastTransaction-50&&t.length>0&&t[t.length-1].intersectionRatio<1&&this.measureSoon()},{threshold:[1]}):null,this.observeIntersection(),n.win.addEventListener("resize",this.measureSoon=this.measureSoon.bind(this)),this.maybeMeasure()}createContainer(){this.parent?(this.container=document.createElement("div"),this.container.style.position="relative",this.container.className=this.view.themeClasses,this.parent.appendChild(this.container)):this.container=this.view.dom}observeIntersection(){if(this.intersectionObserver){this.intersectionObserver.disconnect();for(let n of this.manager.tooltipViews)this.intersectionObserver.observe(n.dom)}}measureSoon(){this.measureTimeout<0&&(this.measureTimeout=setTimeout(()=>{this.measureTimeout=-1,this.maybeMeasure()},50))}update(n){n.transactions.length&&(this.lastTransaction=Date.now());let e=this.manager.update(n,this.above);e&&this.observeIntersection();let t=e||n.geometryChanged,i=n.state.facet(_l);if(i.position!=this.position&&!this.madeAbsolute){this.position=i.position;for(let s of this.manager.tooltipViews)s.dom.style.position=this.position;t=!0}if(i.parent!=this.parent){this.parent&&this.container.remove(),this.parent=i.parent,this.createContainer();for(let s of this.manager.tooltipViews)this.container.appendChild(s.dom);t=!0}else this.parent&&this.view.themeClasses!=this.classes&&(this.classes=this.container.className=this.view.themeClasses);t&&this.maybeMeasure()}createTooltip(n,e){let t=n.create(this.view),i=e?e.dom:null;if(t.dom.classList.add("cm-tooltip"),n.arrow&&!t.dom.querySelector(".cm-tooltip > .cm-tooltip-arrow")){let s=document.createElement("div");s.className="cm-tooltip-arrow",t.dom.appendChild(s)}return t.dom.style.position=this.position,t.dom.style.top=dr,t.dom.style.left="0px",this.container.insertBefore(t.dom,i),t.mount&&t.mount(this.view),this.resizeObserver&&this.resizeObserver.observe(t.dom),t}destroy(){var n,e,t;this.view.win.removeEventListener("resize",this.measureSoon);for(let i of this.manager.tooltipViews)i.dom.remove(),(n=i.destroy)===null||n===void 0||n.call(i);this.parent&&this.container.remove(),(e=this.resizeObserver)===null||e===void 0||e.disconnect(),(t=this.intersectionObserver)===null||t===void 0||t.disconnect(),clearTimeout(this.measureTimeout)}readMeasure(){let n=1,e=1,t=!1;if(this.position=="fixed"&&this.manager.tooltipViews.length){let{dom:r}=this.manager.tooltipViews[0];if(C.safari){let o=r.getBoundingClientRect();t=Math.abs(o.top+1e4)>1||Math.abs(o.left)>1}else t=!!r.offsetParent&&r.offsetParent!=this.container.ownerDocument.body}if(t||this.position=="absolute")if(this.parent){let r=this.parent.getBoundingClientRect();r.width&&r.height&&(n=r.width/this.parent.offsetWidth,e=r.height/this.parent.offsetHeight)}else({scaleX:n,scaleY:e}=this.view.viewState);let i=this.view.scrollDOM.getBoundingClientRect(),s=Xc(this.view);return{visible:{left:i.left+s.left,top:i.top+s.top,right:i.right-s.right,bottom:i.bottom-s.bottom},parent:this.parent?this.container.getBoundingClientRect():this.view.dom.getBoundingClientRect(),pos:this.manager.tooltips.map((r,o)=>{let l=this.manager.tooltipViews[o];return l.getCoords?l.getCoords(r.pos):this.view.coordsAtPos(r.pos)}),size:this.manager.tooltipViews.map(({dom:r})=>r.getBoundingClientRect()),space:this.view.state.facet(_l).tooltipSpace(this.view),scaleX:n,scaleY:e,makeAbsolute:t}}writeMeasure(n){var e;if(n.makeAbsolute){this.madeAbsolute=!0,this.position="absolute";for(let l of this.manager.tooltipViews)l.dom.style.position="absolute"}let{visible:t,space:i,scaleX:s,scaleY:r}=n,o=[];for(let l=0;l=Math.min(t.bottom,i.bottom)||f.rightMath.min(t.right,i.right)+.1)){c.style.top=dr;continue}let d=a.arrow?h.dom.querySelector(".cm-tooltip-arrow"):null,p=d?7:0,g=u.right-u.left,m=(e=Au.get(h))!==null&&e!==void 0?e:u.bottom-u.top,O=h.offset||rw,y=this.view.textDirection==le.LTR,x=u.width>i.right-i.left?y?i.left:i.right-u.width:y?Math.max(i.left,Math.min(f.left-(d?14:0)+O.x,i.right-g)):Math.min(Math.max(i.left,f.left-g+(d?14:0)-O.x),i.right-g),v=this.above[l];!a.strictSide&&(v?f.top-m-p-O.yi.bottom)&&v==i.bottom-f.bottom>f.top-i.top&&(v=this.above[l]=!v);let w=(v?f.top-i.top:i.bottom-f.bottom)-p;if(wx&&W.topQ&&(Q=v?W.top-m-2-p:W.bottom+p+2);if(this.position=="absolute"?(c.style.top=(Q-n.parent.top)/r+"px",Ru(c,(x-n.parent.left)/s)):(c.style.top=Q/r+"px",Ru(c,x/s)),d){let W=f.left+(y?O.x:-O.x)-(x+14-7);d.style.left=W/s+"px"}h.overlap!==!0&&o.push({left:x,top:Q,right:$,bottom:Q+m}),c.classList.toggle("cm-tooltip-above",v),c.classList.toggle("cm-tooltip-below",!v),h.positioned&&h.positioned(n.space)}}maybeMeasure(){if(this.manager.tooltips.length&&(this.view.inView&&this.view.requestMeasure(this.measureReq),this.inView!=this.view.inView&&(this.inView=this.view.inView,!this.inView)))for(let n of this.manager.tooltipViews)n.dom.style.top=dr}},{eventObservers:{scroll(){this.maybeMeasure()}}});function Ru(n,e){let t=parseInt(n.style.left,10);(isNaN(t)||Math.abs(e-t)>1)&&(n.style.left=e+"px")}const nw=D.baseTheme({".cm-tooltip":{zIndex:500,boxSizing:"border-box"},"&light .cm-tooltip":{border:"1px solid #bbb",backgroundColor:"#f5f5f5"},"&light .cm-tooltip-section:not(:first-child)":{borderTop:"1px solid #bbb"},"&dark .cm-tooltip":{backgroundColor:"#333338",color:"white"},".cm-tooltip-arrow":{height:"7px",width:"14px",position:"absolute",zIndex:-1,overflow:"hidden","&:before, &:after":{content:"''",position:"absolute",width:0,height:0,borderLeft:"7px solid transparent",borderRight:"7px solid transparent"},".cm-tooltip-above &":{bottom:"-7px","&:before":{borderTop:"7px solid #bbb"},"&:after":{borderTop:"7px solid #f5f5f5",bottom:"1px"}},".cm-tooltip-below &":{top:"-7px","&:before":{borderBottom:"7px solid #bbb"},"&:after":{borderBottom:"7px solid #f5f5f5",top:"1px"}}},"&dark .cm-tooltip .cm-tooltip-arrow":{"&:before":{borderTopColor:"#333338",borderBottomColor:"#333338"},"&:after":{borderTopColor:"transparent",borderBottomColor:"transparent"}}}),rw={x:0,y:0},Wc=k.define({enables:[Ec,nw]}),Oo=k.define({combine:n=>n.reduce((e,t)=>e.concat(t),[])});class al{static create(e){return new al(e)}constructor(e){this.view=e,this.mounted=!1,this.dom=document.createElement("div"),this.dom.classList.add("cm-tooltip-hover"),this.manager=new jm(e,Oo,(t,i)=>this.createHostedView(t,i),t=>t.dom.remove())}createHostedView(e,t){let i=e.create(this.view);return i.dom.classList.add("cm-tooltip-section"),this.dom.insertBefore(i.dom,t?t.dom.nextSibling:this.dom.firstChild),this.mounted&&i.mount&&i.mount(this.view),i}mount(e){for(let t of this.manager.tooltipViews)t.mount&&t.mount(e);this.mounted=!0}positioned(e){for(let t of this.manager.tooltipViews)t.positioned&&t.positioned(e)}update(e){this.manager.update(e)}destroy(){var e;for(let t of this.manager.tooltipViews)(e=t.destroy)===null||e===void 0||e.call(t)}passProp(e){let t;for(let i of this.manager.tooltipViews){let s=i[e];if(s!==void 0){if(t===void 0)t=s;else if(t!==s)return}}return t}get offset(){return this.passProp("offset")}get getCoords(){return this.passProp("getCoords")}get overlap(){return this.passProp("overlap")}get resize(){return this.passProp("resize")}}const ow=Wc.compute([Oo],n=>{let e=n.facet(Oo);return e.length===0?null:{pos:Math.min(...e.map(t=>t.pos)),end:Math.max(...e.map(t=>{var i;return(i=t.end)!==null&&i!==void 0?i:t.pos})),create:al.create,above:e[0].above,arrow:e.some(t=>t.arrow)}});class lw{constructor(e,t,i,s,r){this.view=e,this.source=t,this.field=i,this.setHover=s,this.hoverTime=r,this.hoverTimeout=-1,this.restartTimeout=-1,this.pending=null,this.lastMove={x:0,y:0,target:e.dom,time:0},this.checkHover=this.checkHover.bind(this),e.dom.addEventListener("mouseleave",this.mouseleave=this.mouseleave.bind(this)),e.dom.addEventListener("mousemove",this.mousemove=this.mousemove.bind(this))}update(){this.pending&&(this.pending=null,clearTimeout(this.restartTimeout),this.restartTimeout=setTimeout(()=>this.startHover(),20))}get active(){return this.view.state.field(this.field)}checkHover(){if(this.hoverTimeout=-1,this.active.length)return;let e=Date.now()-this.lastMove.time;el.bottom||t.xl.right+e.defaultCharacterWidth)return;let a=e.bidiSpans(e.state.doc.lineAt(s)).find(c=>c.from<=s&&c.to>=s),h=a&&a.dir==le.RTL?-1:1;r=t.x{this.pending==l&&(this.pending=null,a&&!(Array.isArray(a)&&!a.length)&&e.dispatch({effects:this.setHover.of(Array.isArray(a)?a:[a])}))},a=>Le(e.state,a,"hover tooltip"))}else o&&!(Array.isArray(o)&&!o.length)&&e.dispatch({effects:this.setHover.of(Array.isArray(o)?o:[o])})}get tooltip(){let e=this.view.plugin(Ec),t=e?e.manager.tooltips.findIndex(i=>i.create==al.create):-1;return t>-1?e.manager.tooltipViews[t]:null}mousemove(e){var t,i;this.lastMove={x:e.clientX,y:e.clientY,target:e.target,time:Date.now()},this.hoverTimeout<0&&(this.hoverTimeout=setTimeout(this.checkHover,this.hoverTime));let{active:s,tooltip:r}=this;if(s.length&&r&&!aw(r.dom,e)||this.pending){let{pos:o}=s[0]||this.pending,l=(i=(t=s[0])===null||t===void 0?void 0:t.end)!==null&&i!==void 0?i:o;(o==l?this.view.posAtCoords(this.lastMove)!=o:!hw(this.view,o,l,e.clientX,e.clientY))&&(this.view.dispatch({effects:this.setHover.of([])}),this.pending=null)}}mouseleave(e){clearTimeout(this.hoverTimeout),this.hoverTimeout=-1;let{active:t}=this;if(t.length){let{tooltip:i}=this;i&&i.dom.contains(e.relatedTarget)?this.watchTooltipLeave(i.dom):this.view.dispatch({effects:this.setHover.of([])})}}watchTooltipLeave(e){let t=i=>{e.removeEventListener("mouseleave",t),this.active.length&&!this.view.dom.contains(i.relatedTarget)&&this.view.dispatch({effects:this.setHover.of([])})};e.addEventListener("mouseleave",t)}destroy(){clearTimeout(this.hoverTimeout),clearTimeout(this.restartTimeout),this.view.dom.removeEventListener("mouseleave",this.mouseleave),this.view.dom.removeEventListener("mousemove",this.mousemove)}}const pr=4;function aw(n,e){let{left:t,right:i,top:s,bottom:r}=n.getBoundingClientRect(),o;if(o=n.querySelector(".cm-tooltip-arrow")){let l=o.getBoundingClientRect();s=Math.min(l.top,s),r=Math.max(l.bottom,r)}return e.clientX>=t-pr&&e.clientX<=i+pr&&e.clientY>=s-pr&&e.clientY<=r+pr}function hw(n,e,t,i,s,r){let o=n.scrollDOM.getBoundingClientRect(),l=n.documentTop+n.documentPadding.top+n.contentHeight;if(o.left>i||o.rights||Math.min(o.bottom,l)=e&&a<=t}function cw(n,e={}){let t=B.define(),i=Se.define({create(){return[]},update(s,r){if(s.length&&(e.hideOnChange&&(r.docChanged||r.selection)?s=[]:e.hideOn&&(s=s.filter(o=>!e.hideOn(r,o))),r.docChanged)){let o=[];for(let l of s){let a=r.changes.mapPos(l.pos,-1,te.TrackDel);if(a!=null){let h=Object.assign(Object.create(null),l);h.pos=a,h.end!=null&&(h.end=r.changes.mapPos(h.end)),o.push(h)}}s=o}for(let o of r.effects)o.is(t)&&(s=o.value),o.is(fw)&&(s=[]);return s},provide:s=>Oo.from(s)});return{active:i,extension:[i,Re.define(s=>new lw(s,n,i,t,e.hoverTime||300)),ow]}}function Gm(n,e){let t=n.plugin(Ec);if(!t)return null;let i=t.manager.tooltips.indexOf(e);return i<0?null:t.manager.tooltipViews[i]}const fw=B.define(),Du=k.define({combine(n){let e,t;for(let i of n)e=e||i.topContainer,t=t||i.bottomContainer;return{topContainer:e,bottomContainer:t}}});function uw(n,e){let t=n.plugin(Hm),i=t?t.specs.indexOf(e):-1;return i>-1?t.panels[i]:null}const Hm=Re.fromClass(class{constructor(n){this.input=n.state.facet(yh),this.specs=this.input.filter(t=>t),this.panels=this.specs.map(t=>t(n));let e=n.state.facet(Du);this.top=new gr(n,!0,e.topContainer),this.bottom=new gr(n,!1,e.bottomContainer),this.top.sync(this.panels.filter(t=>t.top)),this.bottom.sync(this.panels.filter(t=>!t.top));for(let t of this.panels)t.dom.classList.add("cm-panel"),t.mount&&t.mount()}update(n){let e=n.state.facet(Du);this.top.container!=e.topContainer&&(this.top.sync([]),this.top=new gr(n.view,!0,e.topContainer)),this.bottom.container!=e.bottomContainer&&(this.bottom.sync([]),this.bottom=new gr(n.view,!1,e.bottomContainer)),this.top.syncClasses(),this.bottom.syncClasses();let t=n.state.facet(yh);if(t!=this.input){let i=t.filter(a=>a),s=[],r=[],o=[],l=[];for(let a of i){let h=this.specs.indexOf(a),c;h<0?(c=a(n.view),l.push(c)):(c=this.panels[h],c.update&&c.update(n)),s.push(c),(c.top?r:o).push(c)}this.specs=i,this.panels=s,this.top.sync(r),this.bottom.sync(o);for(let a of l)a.dom.classList.add("cm-panel"),a.mount&&a.mount()}else for(let i of this.panels)i.update&&i.update(n)}destroy(){this.top.sync([]),this.bottom.sync([])}},{provide:n=>D.scrollMargins.of(e=>{let t=e.plugin(n);return t&&{top:t.top.scrollMargin(),bottom:t.bottom.scrollMargin()}})});let gr=class{constructor(e,t,i){this.view=e,this.top=t,this.container=i,this.dom=void 0,this.classes="",this.panels=[],this.syncClasses()}sync(e){for(let t of this.panels)t.destroy&&e.indexOf(t)<0&&t.destroy();this.panels=e,this.syncDOM()}syncDOM(){if(this.panels.length==0){this.dom&&(this.dom.remove(),this.dom=void 0);return}if(!this.dom){this.dom=document.createElement("div"),this.dom.className=this.top?"cm-panels cm-panels-top":"cm-panels cm-panels-bottom",this.dom.style[this.top?"top":"bottom"]="0";let t=this.container||this.view.dom;t.insertBefore(this.dom,this.top?t.firstChild:null)}let e=this.dom.firstChild;for(let t of this.panels)if(t.dom.parentNode==this.dom){for(;e!=t.dom;)e=Zu(e);e=e.nextSibling}else this.dom.insertBefore(t.dom,e);for(;e;)e=Zu(e)}scrollMargin(){return!this.dom||this.container?0:Math.max(0,this.top?this.dom.getBoundingClientRect().bottom-Math.max(0,this.view.scrollDOM.getBoundingClientRect().top):Math.min(innerHeight,this.view.scrollDOM.getBoundingClientRect().bottom)-this.dom.getBoundingClientRect().top)}syncClasses(){if(!(!this.container||this.classes==this.view.themeClasses)){for(let e of this.classes.split(" "))e&&this.container.classList.remove(e);for(let e of(this.classes=this.view.themeClasses).split(" "))e&&this.container.classList.add(e)}}};function Zu(n){let e=n.nextSibling;return n.remove(),e}const yh=k.define({enables:Hm});let ci=class extends Ve{compare(e){return this==e||this.constructor==e.constructor&&this.eq(e)}eq(e){return!1}destroy(e){}};ci.prototype.elementClass="";ci.prototype.toDOM=void 0;ci.prototype.mapMode=te.TrackBefore;ci.prototype.startSide=ci.prototype.endSide=-1;ci.prototype.point=!0;const Gr=k.define(),dw=k.define(),pw={class:"",renderEmptyElements:!1,elementStyle:"",markers:()=>M.empty,lineMarker:()=>null,widgetMarker:()=>null,lineMarkerChange:null,initialSpacer:null,updateSpacer:null,domEventHandlers:{},side:"before"},pn=k.define();function gw(n){return[Fm(),pn.of({...pw,...n})]}const Bu=k.define({combine:n=>n.some(e=>e)});function Fm(n){return[mw]}const mw=Re.fromClass(class{constructor(n){this.view=n,this.domAfter=null,this.prevViewport=n.viewport,this.dom=document.createElement("div"),this.dom.className="cm-gutters cm-gutters-before",this.dom.setAttribute("aria-hidden","true"),this.dom.style.minHeight=this.view.contentHeight/this.view.scaleY+"px",this.gutters=n.state.facet(pn).map(e=>new Lu(n,e)),this.fixed=!n.state.facet(Bu);for(let e of this.gutters)e.config.side=="after"?this.getDOMAfter().appendChild(e.dom):this.dom.appendChild(e.dom);this.fixed&&(this.dom.style.position="sticky"),this.syncGutters(!1),n.scrollDOM.insertBefore(this.dom,n.contentDOM)}getDOMAfter(){return this.domAfter||(this.domAfter=document.createElement("div"),this.domAfter.className="cm-gutters cm-gutters-after",this.domAfter.setAttribute("aria-hidden","true"),this.domAfter.style.minHeight=this.view.contentHeight/this.view.scaleY+"px",this.domAfter.style.position=this.fixed?"sticky":"",this.view.scrollDOM.appendChild(this.domAfter)),this.domAfter}update(n){if(this.updateGutters(n)){let e=this.prevViewport,t=n.view.viewport,i=Math.min(e.to,t.to)-Math.max(e.from,t.from);this.syncGutters(i<(t.to-t.from)*.8)}if(n.geometryChanged){let e=this.view.contentHeight/this.view.scaleY+"px";this.dom.style.minHeight=e,this.domAfter&&(this.domAfter.style.minHeight=e)}this.view.state.facet(Bu)!=!this.fixed&&(this.fixed=!this.fixed,this.dom.style.position=this.fixed?"sticky":"",this.domAfter&&(this.domAfter.style.position=this.fixed?"sticky":"")),this.prevViewport=n.view.viewport}syncGutters(n){let e=this.dom.nextSibling;n&&(this.dom.remove(),this.domAfter&&this.domAfter.remove());let t=M.iter(this.view.state.facet(Gr),this.view.viewport.from),i=[],s=this.gutters.map(r=>new Ow(r,this.view.viewport,-this.view.documentPadding.top));for(let r of this.view.viewportLineBlocks)if(i.length&&(i=[]),Array.isArray(r.type)){let o=!0;for(let l of r.type)if(l.type==ke.Text&&o){xh(t,i,l.from);for(let a of s)a.line(this.view,l,i);o=!1}else if(l.widget)for(let a of s)a.widget(this.view,l)}else if(r.type==ke.Text){xh(t,i,r.from);for(let o of s)o.line(this.view,r,i)}else if(r.widget)for(let o of s)o.widget(this.view,r);for(let r of s)r.finish();n&&(this.view.scrollDOM.insertBefore(this.dom,e),this.domAfter&&this.view.scrollDOM.appendChild(this.domAfter))}updateGutters(n){let e=n.startState.facet(pn),t=n.state.facet(pn),i=n.docChanged||n.heightChanged||n.viewportChanged||!M.eq(n.startState.facet(Gr),n.state.facet(Gr),n.view.viewport.from,n.view.viewport.to);if(e==t)for(let s of this.gutters)s.update(n)&&(i=!0);else{i=!0;let s=[];for(let r of t){let o=e.indexOf(r);o<0?s.push(new Lu(this.view,r)):(this.gutters[o].update(n),s.push(this.gutters[o]))}for(let r of this.gutters)r.dom.remove(),s.indexOf(r)<0&&r.destroy();for(let r of s)r.config.side=="after"?this.getDOMAfter().appendChild(r.dom):this.dom.appendChild(r.dom);this.gutters=s}return i}destroy(){for(let n of this.gutters)n.destroy();this.dom.remove(),this.domAfter&&this.domAfter.remove()}},{provide:n=>D.scrollMargins.of(e=>{let t=e.plugin(n);if(!t||t.gutters.length==0||!t.fixed)return null;let i=t.dom.offsetWidth*e.scaleX,s=t.domAfter?t.domAfter.offsetWidth*e.scaleX:0;return e.textDirection==le.LTR?{left:i,right:s}:{right:i,left:s}})});function Xu(n){return Array.isArray(n)?n:[n]}function xh(n,e,t){for(;n.value&&n.from<=t;)n.from==t&&e.push(n.value),n.next()}class Ow{constructor(e,t,i){this.gutter=e,this.height=i,this.i=0,this.cursor=M.iter(e.markers,t.from)}addElement(e,t,i){let{gutter:s}=this,r=(t.top-this.height)/e.scaleY,o=t.height/e.scaleY;if(this.i==s.elements.length){let l=new Um(e,o,r,i);s.elements.push(l),s.dom.appendChild(l.dom)}else s.elements[this.i].update(e,o,r,i);this.height=t.bottom,this.i++}line(e,t,i){let s=[];xh(this.cursor,s,t.from),i.length&&(s=s.concat(i));let r=this.gutter.config.lineMarker(e,t,s);r&&s.unshift(r);let o=this.gutter;s.length==0&&!o.config.renderEmptyElements||this.addElement(e,t,s)}widget(e,t){let i=this.gutter.config.widgetMarker(e,t.widget,t),s=i?[i]:null;for(let r of e.state.facet(dw)){let o=r(e,t.widget,t);o&&(s||(s=[])).push(o)}s&&this.addElement(e,t,s)}finish(){let e=this.gutter;for(;e.elements.length>this.i;){let t=e.elements.pop();e.dom.removeChild(t.dom),t.destroy()}}}class Lu{constructor(e,t){this.view=e,this.config=t,this.elements=[],this.spacer=null,this.dom=document.createElement("div"),this.dom.className="cm-gutter"+(this.config.class?" "+this.config.class:"");for(let i in t.domEventHandlers)this.dom.addEventListener(i,s=>{let r=s.target,o;if(r!=this.dom&&this.dom.contains(r)){for(;r.parentNode!=this.dom;)r=r.parentNode;let a=r.getBoundingClientRect();o=(a.top+a.bottom)/2}else o=s.clientY;let l=e.lineBlockAtHeight(o-e.documentTop);t.domEventHandlers[i](e,l,s)&&s.preventDefault()});this.markers=Xu(t.markers(e)),t.initialSpacer&&(this.spacer=new Um(e,0,0,[t.initialSpacer(e)]),this.dom.appendChild(this.spacer.dom),this.spacer.dom.style.cssText+="visibility: hidden; pointer-events: none")}update(e){let t=this.markers;if(this.markers=Xu(this.config.markers(e.view)),this.spacer&&this.config.updateSpacer){let s=this.config.updateSpacer(this.spacer.markers[0],e);s!=this.spacer.markers[0]&&this.spacer.update(e.view,0,0,[s])}let i=e.view.viewport;return!M.eq(this.markers,t,i.from,i.to)||(this.config.lineMarkerChange?this.config.lineMarkerChange(e):!1)}destroy(){for(let e of this.elements)e.destroy()}}class Um{constructor(e,t,i,s){this.height=-1,this.above=0,this.markers=[],this.dom=document.createElement("div"),this.dom.className="cm-gutterElement",this.update(e,t,i,s)}update(e,t,i,s){this.height!=t&&(this.height=t,this.dom.style.height=t+"px"),this.above!=i&&(this.dom.style.marginTop=(this.above=i)?i+"px":""),bw(this.markers,s)||this.setMarkers(e,s)}setMarkers(e,t){let i="cm-gutterElement",s=this.dom.firstChild;for(let r=0,o=0;;){let l=o,a=rr(l,a,h)||o(l,a,h):o}return i}})}});class Yl extends ci{constructor(e){super(),this.number=e}eq(e){return this.number==e.number}toDOM(){return document.createTextNode(this.number)}}function Nl(n,e){return n.state.facet(Fi).formatNumber(e,n.state)}const xw=pn.compute([Fi],n=>({class:"cm-lineNumbers",renderEmptyElements:!1,markers(e){return e.state.facet(Sw)},lineMarker(e,t,i){return i.some(s=>s.toDOM)?null:new Yl(Nl(e,e.state.doc.lineAt(t.from).number))},widgetMarker:(e,t,i)=>{for(let s of e.state.facet(yw)){let r=s(e,t,i);if(r)return r}return null},lineMarkerChange:e=>e.startState.facet(Fi)!=e.state.facet(Fi),initialSpacer(e){return new Yl(Nl(e,Eu(e.state.doc.lines)))},updateSpacer(e,t){let i=Nl(t.view,Eu(t.view.state.doc.lines));return i==e.number?e:new Yl(i)},domEventHandlers:n.facet(Fi).domEventHandlers,side:"before"}));function ww(n={}){return[Fi.of(n),Fm(),xw]}function Eu(n){let e=9;for(;e{let e=[],t=-1;for(let i of n.selection.ranges){let s=n.doc.lineAt(i.head).from;s>t&&(t=s,e.push(kw.range(s)))}return M.of(e)});function vw(){return Qw}const Km=1024;let $w=0;class et{constructor(e,t){this.from=e,this.to=t}}class V{constructor(e={}){this.id=$w++,this.perNode=!!e.perNode,this.deserialize=e.deserialize||(()=>{throw new Error("This node type doesn't define a deserialize function")}),this.combine=e.combine||null}add(e){if(this.perNode)throw new RangeError("Can't add per-node props to node types");return typeof e!="function"&&(e=De.match(e)),t=>{let i=e(t);return i===void 0?null:[this,i]}}}V.closedBy=new V({deserialize:n=>n.split(" ")});V.openedBy=new V({deserialize:n=>n.split(" ")});V.group=new V({deserialize:n=>n.split(" ")});V.isolate=new V({deserialize:n=>{if(n&&n!="rtl"&&n!="ltr"&&n!="auto")throw new RangeError("Invalid value for isolate: "+n);return n||"auto"}});V.contextHash=new V({perNode:!0});V.lookAhead=new V({perNode:!0});V.mounted=new V({perNode:!0});class os{constructor(e,t,i,s=!1){this.tree=e,this.overlay=t,this.parser=i,this.bracketed=s}static get(e){return e&&e.props&&e.props[V.mounted.id]}}const Pw=Object.create(null);class De{constructor(e,t,i,s=0){this.name=e,this.props=t,this.id=i,this.flags=s}static define(e){let t=e.props&&e.props.length?Object.create(null):Pw,i=(e.top?1:0)|(e.skipped?2:0)|(e.error?4:0)|(e.name==null?8:0),s=new De(e.name||"",t,e.id,i);if(e.props){for(let r of e.props)if(Array.isArray(r)||(r=r(s)),r){if(r[0].perNode)throw new RangeError("Can't store a per-node prop on a node type");t[r[0].id]=r[1]}}return s}prop(e){return this.props[e.id]}get isTop(){return(this.flags&1)>0}get isSkipped(){return(this.flags&2)>0}get isError(){return(this.flags&4)>0}get isAnonymous(){return(this.flags&8)>0}is(e){if(typeof e=="string"){if(this.name==e)return!0;let t=this.prop(V.group);return t?t.indexOf(e)>-1:!1}return this.id==e}static match(e){let t=Object.create(null);for(let i in e)for(let s of i.split(" "))t[s]=e[i];return i=>{for(let s=i.prop(V.group),r=-1;r<(s?s.length:0);r++){let o=t[r<0?i.name:s[r]];if(o)return o}}}}De.none=new De("",Object.create(null),0,8);class Vc{constructor(e){this.types=e;for(let t=0;t0;for(let a=this.cursor(o|j.IncludeAnonymous);;){let h=!1;if(a.from<=r&&a.to>=s&&(!l&&a.type.isAnonymous||t(a)!==!1)){if(a.firstChild())continue;h=!0}for(;h&&i&&(l||!a.type.isAnonymous)&&i(a),!a.nextSibling();){if(!a.parent())return;h=!0}}}prop(e){return e.perNode?this.props?this.props[e.id]:void 0:this.type.prop(e)}get propValues(){let e=[];if(this.props)for(let t in this.props)e.push([+t,this.props[t]]);return e}balance(e={}){return this.children.length<=8?this:Ic(De.none,this.children,this.positions,0,this.children.length,0,this.length,(t,i,s)=>new ae(this.type,t,i,s,this.propValues),e.makeTree||((t,i,s)=>new ae(De.none,t,i,s)))}static build(e){return Aw(e)}}ae.empty=new ae(De.none,[],[],0);class zc{constructor(e,t){this.buffer=e,this.index=t}get id(){return this.buffer[this.index-4]}get start(){return this.buffer[this.index-3]}get end(){return this.buffer[this.index-2]}get size(){return this.buffer[this.index-1]}get pos(){return this.index}next(){this.index-=4}fork(){return new zc(this.buffer,this.index)}}class ki{constructor(e,t,i){this.buffer=e,this.length=t,this.set=i}get type(){return De.none}toString(){let e=[];for(let t=0;t0));a=o[a+3]);return l}slice(e,t,i){let s=this.buffer,r=new Uint16Array(t-e),o=0;for(let l=e,a=0;l=e&&te;case 1:return t<=e&&i>e;case 2:return i>e;case 4:return!0}}function Cn(n,e,t,i){for(var s;n.from==n.to||(t<1?n.from>=e:n.from>e)||(t>-1?n.to<=e:n.to0?l.length:-1;e!=h;e+=t){let c=l[e],f=a[e]+o.from,u;if(!(!(r&j.EnterBracketed&&c instanceof ae&&(u=os.get(c))&&!u.overlay&&u.bracketed&&i>=f&&i<=f+c.length)&&!Jm(s,i,f,f+c.length))){if(c instanceof ki){if(r&j.ExcludeBuffers)continue;let d=c.findChild(0,c.buffer.length,t,i-f,s);if(d>-1)return new Wt(new Cw(o,c,e,f),null,d)}else if(r&j.IncludeAnonymous||!c.type.isAnonymous||qc(c)){let d;if(!(r&j.IgnoreMounts)&&(d=os.get(c))&&!d.overlay)return new ve(d.tree,f,e,o);let p=new ve(c,f,e,o);return r&j.IncludeAnonymous||!p.type.isAnonymous?p:p.nextChild(t<0?c.children.length-1:0,t,i,s,r)}}}if(r&j.IncludeAnonymous||!o.type.isAnonymous||(o.index>=0?e=o.index+t:e=t<0?-1:o._parent._tree.children.length,o=o._parent,!o))return null}}get firstChild(){return this.nextChild(0,1,0,4)}get lastChild(){return this.nextChild(this._tree.children.length-1,-1,0,4)}childAfter(e){return this.nextChild(0,1,e,2)}childBefore(e){return this.nextChild(this._tree.children.length-1,-1,e,-2)}prop(e){return this._tree.prop(e)}enter(e,t,i=0){let s;if(!(i&j.IgnoreOverlays)&&(s=os.get(this._tree))&&s.overlay){let r=e-this.from,o=i&j.EnterBracketed&&s.bracketed;for(let{from:l,to:a}of s.overlay)if((t>0||o?l<=r:l=r:a>r))return new ve(s.tree,s.overlay[0].from+this.from,-1,this)}return this.nextChild(0,1,e,t,i)}nextSignificantParent(){let e=this;for(;e.type.isAnonymous&&e._parent;)e=e._parent;return e}get parent(){return this._parent?this._parent.nextSignificantParent():null}get nextSibling(){return this._parent&&this.index>=0?this._parent.nextChild(this.index+1,1,0,4):null}get prevSibling(){return this._parent&&this.index>=0?this._parent.nextChild(this.index-1,-1,0,4):null}get tree(){return this._tree}toTree(){return this._tree}toString(){return this._tree.toString()}}function Vu(n,e,t,i){let s=n.cursor(),r=[];if(!s.firstChild())return r;if(t!=null){for(let o=!1;!o;)if(o=s.type.is(t),!s.nextSibling())return r}for(;;){if(i!=null&&s.type.is(i))return r;if(s.type.is(e)&&r.push(s.node),!s.nextSibling())return i==null?r:[]}}function wh(n,e,t=e.length-1){for(let i=n;t>=0;i=i.parent){if(!i)return!1;if(!i.type.isAnonymous){if(e[t]&&e[t]!=i.name)return!1;t--}}return!0}class Cw{constructor(e,t,i,s){this.parent=e,this.buffer=t,this.index=i,this.start=s}}class Wt extends eO{get name(){return this.type.name}get from(){return this.context.start+this.context.buffer.buffer[this.index+1]}get to(){return this.context.start+this.context.buffer.buffer[this.index+2]}constructor(e,t,i){super(),this.context=e,this._parent=t,this.index=i,this.type=e.buffer.set.types[e.buffer.buffer[i]]}child(e,t,i){let{buffer:s}=this.context,r=s.findChild(this.index+4,s.buffer[this.index+3],e,t-this.context.start,i);return r<0?null:new Wt(this.context,this,r)}get firstChild(){return this.child(1,0,4)}get lastChild(){return this.child(-1,0,4)}childAfter(e){return this.child(1,e,2)}childBefore(e){return this.child(-1,e,-2)}prop(e){return this.type.prop(e)}enter(e,t,i=0){if(i&j.ExcludeBuffers)return null;let{buffer:s}=this.context,r=s.findChild(this.index+4,s.buffer[this.index+3],t>0?1:-1,e-this.context.start,t);return r<0?null:new Wt(this.context,this,r)}get parent(){return this._parent||this.context.parent.nextSignificantParent()}externalSibling(e){return this._parent?null:this.context.parent.nextChild(this.context.index+e,e,0,4)}get nextSibling(){let{buffer:e}=this.context,t=e.buffer[this.index+3];return t<(this._parent?e.buffer[this._parent.index+3]:e.buffer.length)?new Wt(this.context,this._parent,t):this.externalSibling(1)}get prevSibling(){let{buffer:e}=this.context,t=this._parent?this._parent.index+4:0;return this.index==t?this.externalSibling(-1):new Wt(this.context,this._parent,e.findChild(t,this.index,-1,0,4))}get tree(){return null}toTree(){let e=[],t=[],{buffer:i}=this.context,s=this.index+4,r=i.buffer[this.index+3];if(r>s){let o=i.buffer[this.index+1];e.push(i.slice(s,r,o)),t.push(0)}return new ae(this.type,e,t,this.to-this.from)}toString(){return this.context.buffer.childString(this.index)}}function tO(n){if(!n.length)return null;let e=0,t=n[0];for(let r=1;rt.from||o.to=e){let l=new ve(o.tree,o.overlay[0].from+r.from,-1,r);(s||(s=[i])).push(Cn(l,e,t,!1))}}return s?tO(s):i}class bo{get name(){return this.type.name}constructor(e,t=0){if(this.buffer=null,this.stack=[],this.index=0,this.bufferNode=null,this.mode=t&~j.EnterBracketed,e instanceof ve)this.yieldNode(e);else{this._tree=e.context.parent,this.buffer=e.context;for(let i=e._parent;i;i=i._parent)this.stack.unshift(i.index);this.bufferNode=e,this.yieldBuf(e.index)}}yieldNode(e){return e?(this._tree=e,this.type=e.type,this.from=e.from,this.to=e.to,!0):!1}yieldBuf(e,t){this.index=e;let{start:i,buffer:s}=this.buffer;return this.type=t||s.set.types[s.buffer[e]],this.from=i+s.buffer[e+1],this.to=i+s.buffer[e+2],!0}yield(e){return e?e instanceof ve?(this.buffer=null,this.yieldNode(e)):(this.buffer=e.context,this.yieldBuf(e.index,e.type)):!1}toString(){return this.buffer?this.buffer.buffer.childString(this.index):this._tree.toString()}enterChild(e,t,i){if(!this.buffer)return this.yield(this._tree.nextChild(e<0?this._tree._tree.children.length-1:0,e,t,i,this.mode));let{buffer:s}=this.buffer,r=s.findChild(this.index+4,s.buffer[this.index+3],e,t-this.buffer.start,i);return r<0?!1:(this.stack.push(this.index),this.yieldBuf(r))}firstChild(){return this.enterChild(1,0,4)}lastChild(){return this.enterChild(-1,0,4)}childAfter(e){return this.enterChild(1,e,2)}childBefore(e){return this.enterChild(-1,e,-2)}enter(e,t,i=this.mode){return this.buffer?i&j.ExcludeBuffers?!1:this.enterChild(1,e,t):this.yield(this._tree.enter(e,t,i))}parent(){if(!this.buffer)return this.yieldNode(this.mode&j.IncludeAnonymous?this._tree._parent:this._tree.parent);if(this.stack.length)return this.yieldBuf(this.stack.pop());let e=this.mode&j.IncludeAnonymous?this.buffer.parent:this.buffer.parent.nextSignificantParent();return this.buffer=null,this.yieldNode(e)}sibling(e){if(!this.buffer)return this._tree._parent?this.yield(this._tree.index<0?null:this._tree._parent.nextChild(this._tree.index+e,e,0,4,this.mode)):!1;let{buffer:t}=this.buffer,i=this.stack.length-1;if(e<0){let s=i<0?0:this.stack[i]+4;if(this.index!=s)return this.yieldBuf(t.findChild(s,this.index,-1,0,4))}else{let s=t.buffer[this.index+3];if(s<(i<0?t.buffer.length:t.buffer[this.stack[i]+3]))return this.yieldBuf(s)}return i<0?this.yield(this.buffer.parent.nextChild(this.buffer.index+e,e,0,4,this.mode)):!1}nextSibling(){return this.sibling(1)}prevSibling(){return this.sibling(-1)}atLastNode(e){let t,i,{buffer:s}=this;if(s){if(e>0){if(this.index-1)for(let r=t+e,o=e<0?-1:i._tree.children.length;r!=o;r+=e){let l=i._tree.children[r];if(this.mode&j.IncludeAnonymous||l instanceof ki||!l.type.isAnonymous||qc(l))return!1}return!0}move(e,t){if(t&&this.enterChild(e,0,4))return!0;for(;;){if(this.sibling(e))return!0;if(this.atLastNode(e)||!this.parent())return!1}}next(e=!0){return this.move(1,e)}prev(e=!0){return this.move(-1,e)}moveTo(e,t=0){for(;(this.from==this.to||(t<1?this.from>=e:this.from>e)||(t>-1?this.to<=e:this.to=0;){for(let o=e;o;o=o._parent)if(o.index==s){if(s==this.index)return o;t=o,i=r+1;break e}s=this.stack[--r]}for(let s=i;s=0;r--){if(r<0)return wh(this._tree,e,s);let o=i[t.buffer[this.stack[r]]];if(!o.isAnonymous){if(e[s]&&e[s]!=o.name)return!1;s--}}return!0}}function qc(n){return n.children.some(e=>e instanceof ki||!e.type.isAnonymous||qc(e))}function Aw(n){var e;let{buffer:t,nodeSet:i,maxBufferLength:s=Km,reused:r=[],minRepeatType:o=i.types.length}=n,l=Array.isArray(t)?new zc(t,t.length):t,a=i.types,h=0,c=0;function f(w,Q,$,W,I,F){let{id:q,start:X,end:G,size:N}=l,ie=c,Pe=h;if(N<0)if(l.next(),N==-1){let Nt=r[q];$.push(Nt),W.push(X-w);return}else if(N==-3){h=q;return}else if(N==-4){c=q;return}else throw new RangeError(`Unrecognized record size: ${N}`);let ze=a[q],vt,ye,He=X-w;if(G-X<=s&&(ye=m(l.pos-Q,I))){let Nt=new Uint16Array(ye.size-ye.skip),Fe=l.pos-ye.size,$t=Nt.length;for(;l.pos>Fe;)$t=O(ye.start,Nt,$t);vt=new ki(Nt,G-ye.start,i),He=ye.start-w}else{let Nt=l.pos-N;l.next();let Fe=[],$t=[],Ti=q>=o?q:-1,Ni=0,tr=G;for(;l.pos>Nt;)Ti>=0&&l.id==Ti&&l.size>=0?(l.end<=tr-s&&(p(Fe,$t,X,Ni,l.end,tr,Ti,ie,Pe),Ni=Fe.length,tr=l.end),l.next()):F>2500?u(X,Nt,Fe,$t):f(X,Nt,Fe,$t,Ti,F+1);if(Ti>=0&&Ni>0&&Ni-1&&Ni>0){let Bf=d(ze,Pe);vt=Ic(ze,Fe,$t,0,Fe.length,0,G-X,Bf,Bf)}else vt=g(ze,Fe,$t,G-X,ie-G,Pe)}$.push(vt),W.push(He)}function u(w,Q,$,W){let I=[],F=0,q=-1;for(;l.pos>Q;){let{id:X,start:G,end:N,size:ie}=l;if(ie>4)l.next();else{if(q>-1&&G=0;N-=3)X[ie++]=I[N],X[ie++]=I[N+1]-G,X[ie++]=I[N+2]-G,X[ie++]=ie;$.push(new ki(X,I[2]-G,i)),W.push(G-w)}}function d(w,Q){return($,W,I)=>{let F=0,q=$.length-1,X,G;if(q>=0&&(X=$[q])instanceof ae){if(!q&&X.type==w&&X.length==I)return X;(G=X.prop(V.lookAhead))&&(F=W[q]+X.length+G)}return g(w,$,W,I,F,Q)}}function p(w,Q,$,W,I,F,q,X,G){let N=[],ie=[];for(;w.length>W;)N.push(w.pop()),ie.push(Q.pop()+$-I);w.push(g(i.types[q],N,ie,F-I,X-F,G)),Q.push(I-$)}function g(w,Q,$,W,I,F,q){if(F){let X=[V.contextHash,F];q=q?[X].concat(q):[X]}if(I>25){let X=[V.lookAhead,I];q=q?[X].concat(q):[X]}return new ae(w,Q,$,W,q)}function m(w,Q){let $=l.fork(),W=0,I=0,F=0,q=$.end-s,X={size:0,start:0,skip:0};e:for(let G=$.pos-w;$.pos>G;){let N=$.size;if($.id==Q&&N>=0){X.size=W,X.start=I,X.skip=F,F+=4,W+=4,$.next();continue}let ie=$.pos-N;if(N<0||ie=o?4:0,ze=$.start;for($.next();$.pos>ie;){if($.size<0)if($.size==-3||$.size==-4)Pe+=4;else break e;else $.id>=o&&(Pe+=4);$.next()}I=ze,W+=N,F+=Pe}return(Q<0||W==w)&&(X.size=W,X.start=I,X.skip=F),X.size>4?X:void 0}function O(w,Q,$){let{id:W,start:I,end:F,size:q}=l;if(l.next(),q>=0&&W4){let G=l.pos-(q-4);for(;l.pos>G;)$=O(w,Q,$)}Q[--$]=X,Q[--$]=F-w,Q[--$]=I-w,Q[--$]=W}else q==-3?h=W:q==-4&&(c=W);return $}let y=[],x=[];for(;l.pos>0;)f(n.start||0,n.bufferStart||0,y,x,-1,0);let v=(e=n.length)!==null&&e!==void 0?e:y.length?x[0]+y[0].length:0;return new ae(a[n.topID],y.reverse(),x.reverse(),v)}const zu=new WeakMap;function Hr(n,e){if(!n.isAnonymous||e instanceof ki||e.type!=n)return 1;let t=zu.get(e);if(t==null){t=1;for(let i of e.children){if(i.type!=n||!(i instanceof ae)){t=1;break}t+=Hr(n,i)}zu.set(e,t)}return t}function Ic(n,e,t,i,s,r,o,l,a){let h=0;for(let p=i;p=c)break;Q+=$}if(x==v+1){if(Q>c){let $=p[v];d($.children,$.positions,0,$.children.length,g[v]+y);continue}f.push(p[v])}else{let $=g[x-1]+p[x-1].length-w;f.push(Ic(n,p,g,v,x,w,$,null,a))}u.push(w+y-r)}}return d(e,t,i,s,0),(l||a)(f,u,o)}class iO{constructor(){this.map=new WeakMap}setBuffer(e,t,i){let s=this.map.get(e);s||this.map.set(e,s=new Map),s.set(t,i)}getBuffer(e,t){let i=this.map.get(e);return i&&i.get(t)}set(e,t){e instanceof Wt?this.setBuffer(e.context.buffer,e.index,t):e instanceof ve&&this.map.set(e.tree,t)}get(e){return e instanceof Wt?this.getBuffer(e.context.buffer,e.index):e instanceof ve?this.map.get(e.tree):void 0}cursorSet(e,t){e.buffer?this.setBuffer(e.buffer.buffer,e.index,t):this.map.set(e.tree,t)}cursorGet(e){return e.buffer?this.getBuffer(e.buffer.buffer,e.index):this.map.get(e.tree)}}class li{constructor(e,t,i,s,r=!1,o=!1){this.from=e,this.to=t,this.tree=i,this.offset=s,this.open=(r?1:0)|(o?2:0)}get openStart(){return(this.open&1)>0}get openEnd(){return(this.open&2)>0}static addTree(e,t=[],i=!1){let s=[new li(0,e.length,e,0,!1,i)];for(let r of t)r.to>e.length&&s.push(r);return s}static applyChanges(e,t,i=128){if(!t.length)return e;let s=[],r=1,o=e.length?e[0]:null;for(let l=0,a=0,h=0;;l++){let c=l=i)for(;o&&o.from=u.from||f<=u.to||h){let d=Math.max(u.from,a)-h,p=Math.min(u.to,f)-h;u=d>=p?null:new li(d,p,u.tree,u.offset+h,l>0,!!c)}if(u&&s.push(u),o.to>f)break;o=rnew et(s.from,s.to)):[new et(0,0)]:[new et(0,e.length)],this.createParse(e,t||[],i)}parse(e,t,i){let s=this.startParse(e,t,i);for(;;){let r=s.advance();if(r)return r}}}class Rw{constructor(e){this.string=e}get length(){return this.string.length}chunk(e){return this.string.slice(e)}get lineChunks(){return!1}read(e,t){return this.string.slice(e,t)}}function Dw(n){return(e,t,i,s)=>new Bw(e,n,t,i,s)}class qu{constructor(e,t,i,s,r,o){this.parser=e,this.parse=t,this.overlay=i,this.bracketed=s,this.target=r,this.from=o}}function Iu(n){if(!n.length||n.some(e=>e.from>=e.to))throw new RangeError("Invalid inner parse ranges given: "+JSON.stringify(n))}class Zw{constructor(e,t,i,s,r,o,l,a){this.parser=e,this.predicate=t,this.mounts=i,this.index=s,this.start=r,this.bracketed=o,this.target=l,this.prev=a,this.depth=0,this.ranges=[]}}const kh=new V({perNode:!0});class Bw{constructor(e,t,i,s,r){this.nest=t,this.input=i,this.fragments=s,this.ranges=r,this.inner=[],this.innerDone=0,this.baseTree=null,this.stoppedAt=null,this.baseParse=e}advance(){if(this.baseParse){let i=this.baseParse.advance();if(!i)return null;if(this.baseParse=null,this.baseTree=i,this.startInner(),this.stoppedAt!=null)for(let s of this.inner)s.parse.stopAt(this.stoppedAt)}if(this.innerDone==this.inner.length){let i=this.baseTree;return this.stoppedAt!=null&&(i=new ae(i.type,i.children,i.positions,i.length,i.propValues.concat([[kh,this.stoppedAt]]))),i}let e=this.inner[this.innerDone],t=e.parse.advance();if(t){this.innerDone++;let i=Object.assign(Object.create(null),e.target.props);i[V.mounted.id]=new os(t,e.overlay,e.parser,e.bracketed),e.target.props=i}return null}get parsedPos(){if(this.baseParse)return 0;let e=this.input.length;for(let t=this.innerDone;t=this.stoppedAt)l=!1;else if(e.hasNode(s)){if(t){let h=t.mounts.find(c=>c.frag.from<=s.from&&c.frag.to>=s.to&&c.mount.overlay);if(h)for(let c of h.mount.overlay){let f=c.from+h.pos,u=c.to+h.pos;f>=s.from&&u<=s.to&&!t.ranges.some(d=>d.fromf)&&t.ranges.push({from:f,to:u})}}l=!1}else if(i&&(o=Xw(i.ranges,s.from,s.to)))l=o!=2;else if(!s.type.isAnonymous&&(r=this.nest(s,this.input))&&(s.fromnew et(f.from-s.from,f.to-s.from)):null,!!r.bracketed,s.tree,c.length?c[0].from:s.from)),r.overlay?c.length&&(i={ranges:c,depth:0,prev:i}):l=!1}}else if(t&&(a=t.predicate(s))&&(a===!0&&(a=new et(s.from,s.to)),a.from=0&&t.ranges[h].to==a.from?t.ranges[h]={from:t.ranges[h].from,to:a.to}:t.ranges.push(a)}if(l&&s.firstChild())t&&t.depth++,i&&i.depth++;else for(;!s.nextSibling();){if(!s.parent())break e;if(t&&!--t.depth){let h=Nu(this.ranges,t.ranges);h.length&&(Iu(h),this.inner.splice(t.index,0,new qu(t.parser,t.parser.startParse(this.input,ju(t.mounts,h),h),t.ranges.map(c=>new et(c.from-t.start,c.to-t.start)),t.bracketed,t.target,h[0].from))),t=t.prev}i&&!--i.depth&&(i=i.prev)}}}}function Xw(n,e,t){for(let i of n){if(i.from>=t)break;if(i.to>e)return i.from<=e&&i.to>=t?2:1}return 0}function _u(n,e,t,i,s,r){if(e=e&&t.enter(i,1,j.IgnoreOverlays|j.ExcludeBuffers)||t.next(!1)||(this.done=!0)}hasNode(e){if(this.moveTo(e.from),!this.done&&this.cursor.from+this.offset==e.from&&this.cursor.tree)for(let t=this.cursor.tree;;){if(t==e.tree)return!0;if(t.children.length&&t.positions[0]==0&&t.children[0]instanceof ae)t=t.children[0];else break}return!1}}let Ew=class{constructor(e){var t;if(this.fragments=e,this.curTo=0,this.fragI=0,e.length){let i=this.curFrag=e[0];this.curTo=(t=i.tree.prop(kh))!==null&&t!==void 0?t:i.to,this.inner=new Yu(i.tree,-i.offset)}else this.curFrag=this.inner=null}hasNode(e){for(;this.curFrag&&e.from>=this.curTo;)this.nextFrag();return this.curFrag&&this.curFrag.from<=e.from&&this.curTo>=e.to&&this.inner.hasNode(e)}nextFrag(){var e;if(this.fragI++,this.fragI==this.fragments.length)this.curFrag=this.inner=null;else{let t=this.curFrag=this.fragments[this.fragI];this.curTo=(e=t.tree.prop(kh))!==null&&e!==void 0?e:t.to,this.inner=new Yu(t.tree,-t.offset)}}findMounts(e,t){var i;let s=[];if(this.inner){this.inner.cursor.moveTo(e,1);for(let r=this.inner.cursor.node;r;r=r.parent){let o=(i=r.tree)===null||i===void 0?void 0:i.prop(V.mounted);if(o&&o.parser==t)for(let l=this.fragI;l=r.to)break;a.tree==this.curFrag.tree&&s.push({frag:a,pos:r.from-a.offset,mount:o})}}}return s}};function Nu(n,e){let t=null,i=e;for(let s=1,r=0;s=l)break;a.to<=o||(t||(i=t=e.slice()),a.froml&&t.splice(r+1,0,new et(l,a.to))):a.to>l?t[r--]=new et(l,a.to):t.splice(r--,1))}}return i}function Ww(n,e,t,i){let s=0,r=0,o=!1,l=!1,a=-1e9,h=[];for(;;){let c=s==n.length?1e9:o?n[s].to:n[s].from,f=r==e.length?1e9:l?e[r].to:e[r].from;if(o!=l){let u=Math.max(a,t),d=Math.min(c,f,i);unew et(u.from+i,u.to+i)),f=Ww(e,c,a,h);for(let u=0,d=a;;u++){let p=u==f.length,g=p?h:f[u].from;if(g>d&&t.push(new li(d,g,s.tree,-o,r.from>=d||r.openStart,r.to<=g||r.openEnd)),p)break;d=f[u].to}}else t.push(new li(a,h,s.tree,-o,r.from>=o||r.openStart,r.to<=l||r.openEnd))}return t}let Vw=0;class Ue{constructor(e,t,i,s){this.name=e,this.set=t,this.base=i,this.modified=s,this.id=Vw++}toString(){let{name:e}=this;for(let t of this.modified)t.name&&(e=`${t.name}(${e})`);return e}static define(e,t){let i=typeof e=="string"?e:"?";if(e instanceof Ue&&(t=e),t?.base)throw new Error("Can not derive from a modified tag");let s=new Ue(i,[],null,[]);if(s.set.push(s),t)for(let r of t.set)s.set.push(r);return s}static defineModifier(e){let t=new So(e);return i=>i.modified.indexOf(t)>-1?i:So.get(i.base||i,i.modified.concat(t).sort((s,r)=>s.id-r.id))}}let zw=0;class So{constructor(e){this.name=e,this.instances=[],this.id=zw++}static get(e,t){if(!t.length)return e;let i=t[0].instances.find(l=>l.base==e&&qw(t,l.modified));if(i)return i;let s=[],r=new Ue(e.name,s,e,t);for(let l of t)l.instances.push(r);let o=Iw(t);for(let l of e.set)if(!l.modified.length)for(let a of o)s.push(So.get(l,a));return r}}function qw(n,e){return n.length==e.length&&n.every((t,i)=>t==e[i])}function Iw(n){let e=[[]];for(let t=0;ti.length-t.length)}function hl(n){let e=Object.create(null);for(let t in n){let i=n[t];Array.isArray(i)||(i=[i]);for(let s of t.split(" "))if(s){let r=[],o=2,l=s;for(let f=0;;){if(l=="..."&&f>0&&f+3==s.length){o=1;break}let u=/^"(?:[^"\\]|\\.)*?"|[^\/!]+/.exec(l);if(!u)throw new RangeError("Invalid path: "+s);if(r.push(u[0]=="*"?"":u[0][0]=='"'?JSON.parse(u[0]):u[0]),f+=u[0].length,f==s.length)break;let d=s[f++];if(f==s.length&&d=="!"){o=0;break}if(d!="/")throw new RangeError("Invalid path: "+s);l=s.slice(f)}let a=r.length-1,h=r[a];if(!h)throw new RangeError("Invalid path: "+s);let c=new Tn(i,o,a>0?r.slice(0,a):null);e[h]=c.sort(e[h])}}return nO.add(e)}const nO=new V({combine(n,e){let t,i,s;for(;n||e;){if(!n||e&&n.depth>=e.depth?(s=e,e=e.next):(s=n,n=n.next),t&&t.mode==s.mode&&!s.context&&!t.context)continue;let r=new Tn(s.tags,s.mode,s.context);t?t.next=r:i=r,t=r}return i}});class Tn{constructor(e,t,i,s){this.tags=e,this.mode=t,this.context=i,this.next=s}get opaque(){return this.mode==0}get inherit(){return this.mode==1}sort(e){return!e||e.depth{let o=s;for(let l of r)for(let a of l.set){let h=t[a.id];if(h){o=o?o+" "+h:h;break}}return o},scope:i}}function _w(n,e){let t=null;for(let i of n){let s=i.style(e);s&&(t=t?t+" "+s:s)}return t}function Yw(n,e,t,i=0,s=n.length){let r=new Nw(i,Array.isArray(e)?e:[e],t);r.highlightRange(n.cursor(),i,s,"",r.highlighters),r.flush(s)}class Nw{constructor(e,t,i){this.at=e,this.highlighters=t,this.span=i,this.class=""}startSpan(e,t){t!=this.class&&(this.flush(e),e>this.at&&(this.at=e),this.class=t)}flush(e){e>this.at&&this.class&&this.span(this.at,e,this.class)}highlightRange(e,t,i,s,r){let{type:o,from:l,to:a}=e;if(l>=i||a<=t)return;o.isTop&&(r=this.highlighters.filter(d=>!d.scope||d.scope(o)));let h=s,c=jw(e)||Tn.empty,f=_w(r,c.tags);if(f&&(h&&(h+=" "),h+=f,c.mode==1&&(s+=(s?" ":"")+f)),this.startSpan(Math.max(t,l),h),c.opaque)return;let u=e.tree&&e.tree.prop(V.mounted);if(u&&u.overlay){let d=e.node.enter(u.overlay[0].from+l,1),p=this.highlighters.filter(m=>!m.scope||m.scope(u.tree.type)),g=e.firstChild();for(let m=0,O=l;;m++){let y=m=x||!e.nextSibling())););if(!y||x>i)break;O=y.to+l,O>t&&(this.highlightRange(d.cursor(),Math.max(t,y.from+l),Math.min(i,O),"",p),this.startSpan(Math.min(i,O),h))}g&&e.parent()}else if(e.firstChild()){u&&(s="");do if(!(e.to<=t)){if(e.from>=i)break;this.highlightRange(e,t,i,s,r),this.startSpan(Math.min(i,e.to),h)}while(e.nextSibling());e.parent()}}}function jw(n){let e=n.type.prop(nO);for(;e&&e.context&&!n.matchContext(e.context);)e=e.next;return e||null}const P=Ue.define,Or=P(),di=P(),Gu=P(di),Hu=P(di),pi=P(),br=P(pi),jl=P(pi),Rt=P(),Mi=P(Rt),Ct=P(),Tt=P(),Qh=P(),_s=P(Qh),Sr=P(),b={comment:Or,lineComment:P(Or),blockComment:P(Or),docComment:P(Or),name:di,variableName:P(di),typeName:Gu,tagName:P(Gu),propertyName:Hu,attributeName:P(Hu),className:P(di),labelName:P(di),namespace:P(di),macroName:P(di),literal:pi,string:br,docString:P(br),character:P(br),attributeValue:P(br),number:jl,integer:P(jl),float:P(jl),bool:P(pi),regexp:P(pi),escape:P(pi),color:P(pi),url:P(pi),keyword:Ct,self:P(Ct),null:P(Ct),atom:P(Ct),unit:P(Ct),modifier:P(Ct),operatorKeyword:P(Ct),controlKeyword:P(Ct),definitionKeyword:P(Ct),moduleKeyword:P(Ct),operator:Tt,derefOperator:P(Tt),arithmeticOperator:P(Tt),logicOperator:P(Tt),bitwiseOperator:P(Tt),compareOperator:P(Tt),updateOperator:P(Tt),definitionOperator:P(Tt),typeOperator:P(Tt),controlOperator:P(Tt),punctuation:Qh,separator:P(Qh),bracket:_s,angleBracket:P(_s),squareBracket:P(_s),paren:P(_s),brace:P(_s),content:Rt,heading:Mi,heading1:P(Mi),heading2:P(Mi),heading3:P(Mi),heading4:P(Mi),heading5:P(Mi),heading6:P(Mi),contentSeparator:P(Rt),list:P(Rt),quote:P(Rt),emphasis:P(Rt),strong:P(Rt),link:P(Rt),monospace:P(Rt),strikethrough:P(Rt),inserted:P(),deleted:P(),changed:P(),invalid:P(),meta:Sr,documentMeta:P(Sr),annotation:P(Sr),processingInstruction:P(Sr),definition:Ue.defineModifier("definition"),constant:Ue.defineModifier("constant"),function:Ue.defineModifier("function"),standard:Ue.defineModifier("standard"),local:Ue.defineModifier("local"),special:Ue.defineModifier("special")};for(let n in b){let e=b[n];e instanceof Ue&&(e.name=n)}rO([{tag:b.link,class:"tok-link"},{tag:b.heading,class:"tok-heading"},{tag:b.emphasis,class:"tok-emphasis"},{tag:b.strong,class:"tok-strong"},{tag:b.keyword,class:"tok-keyword"},{tag:b.atom,class:"tok-atom"},{tag:b.bool,class:"tok-bool"},{tag:b.url,class:"tok-url"},{tag:b.labelName,class:"tok-labelName"},{tag:b.inserted,class:"tok-inserted"},{tag:b.deleted,class:"tok-deleted"},{tag:b.literal,class:"tok-literal"},{tag:b.string,class:"tok-string"},{tag:b.number,class:"tok-number"},{tag:[b.regexp,b.escape,b.special(b.string)],class:"tok-string2"},{tag:b.variableName,class:"tok-variableName"},{tag:b.local(b.variableName),class:"tok-variableName tok-local"},{tag:b.definition(b.variableName),class:"tok-variableName tok-definition"},{tag:b.special(b.variableName),class:"tok-variableName2"},{tag:b.definition(b.propertyName),class:"tok-propertyName tok-definition"},{tag:b.typeName,class:"tok-typeName"},{tag:b.namespace,class:"tok-namespace"},{tag:b.className,class:"tok-className"},{tag:b.macroName,class:"tok-macroName"},{tag:b.propertyName,class:"tok-propertyName"},{tag:b.operator,class:"tok-operator"},{tag:b.comment,class:"tok-comment"},{tag:b.meta,class:"tok-meta"},{tag:b.invalid,class:"tok-invalid"},{tag:b.punctuation,class:"tok-punctuation"}]);var Gl;const Ui=new V;function oO(n){return k.define({combine:n?e=>e.concat(n):void 0})}const _c=new V;class pt{constructor(e,t,i=[],s=""){this.data=e,this.name=s,L.prototype.hasOwnProperty("tree")||Object.defineProperty(L.prototype,"tree",{get(){return fe(this)}}),this.parser=t,this.extension=[Qi.of(this),L.languageData.of((r,o,l)=>{let a=Fu(r,o,l),h=a.type.prop(Ui);if(!h)return[];let c=r.facet(h),f=a.type.prop(_c);if(f){let u=a.resolve(o-a.from,l);for(let d of f)if(d.test(u,r)){let p=r.facet(d.facet);return d.type=="replace"?p:p.concat(c)}}return c})].concat(i)}isActiveAt(e,t,i=-1){return Fu(e,t,i).type.prop(Ui)==this.data}findRegions(e){let t=e.facet(Qi);if(t?.data==this.data)return[{from:0,to:e.doc.length}];if(!t||!t.allowsNesting)return[];let i=[],s=(r,o)=>{if(r.prop(Ui)==this.data){i.push({from:o,to:o+r.length});return}let l=r.prop(V.mounted);if(l){if(l.tree.prop(Ui)==this.data){if(l.overlay)for(let a of l.overlay)i.push({from:a.from+o,to:a.to+o});else i.push({from:o,to:o+r.length});return}else if(l.overlay){let a=i.length;if(s(l.tree,l.overlay[0].from+o),i.length>a)return}}for(let a=0;ai.isTop?t:void 0)]}),e.name)}configure(e,t){return new xs(this.data,this.parser.configure(e),t||this.name)}get allowsNesting(){return this.parser.hasWrappers()}}function fe(n){let e=n.field(pt.state,!1);return e?e.tree:ae.empty}class Gw{constructor(e){this.doc=e,this.cursorPos=0,this.string="",this.cursor=e.iter()}get length(){return this.doc.length}syncTo(e){return this.string=this.cursor.next(e-this.cursorPos).value,this.cursorPos=e+this.string.length,this.cursorPos-this.string.length}chunk(e){return this.syncTo(e),this.string}get lineChunks(){return!0}read(e,t){let i=this.cursorPos-this.string.length;return e=this.cursorPos?this.doc.sliceString(e,t):this.string.slice(e-i,t-i)}}let Ys=null;class yo{constructor(e,t,i=[],s,r,o,l,a){this.parser=e,this.state=t,this.fragments=i,this.tree=s,this.treeLen=r,this.viewport=o,this.skipped=l,this.scheduleOn=a,this.parse=null,this.tempSkipped=[]}static create(e,t,i){return new yo(e,t,[],ae.empty,0,i,[],null)}startParse(){return this.parser.startParse(new Gw(this.state.doc),this.fragments)}work(e,t){return t!=null&&t>=this.state.doc.length&&(t=void 0),this.tree!=ae.empty&&this.isDone(t??this.state.doc.length)?(this.takeTree(),!0):this.withContext(()=>{var i;if(typeof e=="number"){let s=Date.now()+e;e=()=>Date.now()>s}for(this.parse||(this.parse=this.startParse()),t!=null&&(this.parse.stoppedAt==null||this.parse.stoppedAt>t)&&t=this.treeLen&&((this.parse.stoppedAt==null||this.parse.stoppedAt>e)&&this.parse.stopAt(e),this.withContext(()=>{for(;!(t=this.parse.advance()););}),this.treeLen=e,this.tree=t,this.fragments=this.withoutTempSkipped(li.addTree(this.tree,this.fragments,!0)),this.parse=null)}withContext(e){let t=Ys;Ys=this;try{return e()}finally{Ys=t}}withoutTempSkipped(e){for(let t;t=this.tempSkipped.pop();)e=Uu(e,t.from,t.to);return e}changes(e,t){let{fragments:i,tree:s,treeLen:r,viewport:o,skipped:l}=this;if(this.takeTree(),!e.empty){let a=[];if(e.iterChangedRanges((h,c,f,u)=>a.push({fromA:h,toA:c,fromB:f,toB:u})),i=li.applyChanges(i,a),s=ae.empty,r=0,o={from:e.mapPos(o.from,-1),to:e.mapPos(o.to,1)},this.skipped.length){l=[];for(let h of this.skipped){let c=e.mapPos(h.from,1),f=e.mapPos(h.to,-1);ce.from&&(this.fragments=Uu(this.fragments,s,r),this.skipped.splice(i--,1))}return this.skipped.length>=t?!1:(this.reset(),!0)}reset(){this.parse&&(this.takeTree(),this.parse=null)}skipUntilInView(e,t){this.skipped.push({from:e,to:t})}static getSkippingParser(e){return new class extends sO{createParse(t,i,s){let r=s[0].from,o=s[s.length-1].to;return{parsedPos:r,advance(){let a=Ys;if(a){for(let h of s)a.tempSkipped.push(h);e&&(a.scheduleOn=a.scheduleOn?Promise.all([a.scheduleOn,e]):e)}return this.parsedPos=o,new ae(De.none,[],[],o-r)},stoppedAt:null,stopAt(){}}}}}isDone(e){e=Math.min(e,this.state.doc.length);let t=this.fragments;return this.treeLen>=e&&t.length&&t[0].from==0&&t[0].to>=e}static get(){return Ys}}function Uu(n,e,t){return li.applyChanges(n,[{fromA:e,toA:t,fromB:e,toB:t}])}class ws{constructor(e){this.context=e,this.tree=e.tree}apply(e){if(!e.docChanged&&this.tree==this.context.tree)return this;let t=this.context.changes(e.changes,e.state),i=this.context.treeLen==e.startState.doc.length?void 0:Math.max(e.changes.mapPos(this.context.treeLen),t.viewport.to);return t.work(20,i)||t.takeTree(),new ws(t)}static init(e){let t=Math.min(3e3,e.doc.length),i=yo.create(e.facet(Qi).parser,e,{from:0,to:t});return i.work(20,t)||i.takeTree(),new ws(i)}}pt.state=Se.define({create:ws.init,update(n,e){for(let t of e.effects)if(t.is(pt.setState))return t.value;return e.startState.facet(Qi)!=e.state.facet(Qi)?ws.init(e.state):n.apply(e)}});let lO=n=>{let e=setTimeout(()=>n(),500);return()=>clearTimeout(e)};typeof requestIdleCallback<"u"&&(lO=n=>{let e=-1,t=setTimeout(()=>{e=requestIdleCallback(n,{timeout:400})},100);return()=>e<0?clearTimeout(t):cancelIdleCallback(e)});const Hl=typeof navigator<"u"&&(!((Gl=navigator.scheduling)===null||Gl===void 0)&&Gl.isInputPending)?()=>navigator.scheduling.isInputPending():null,Hw=Re.fromClass(class{constructor(e){this.view=e,this.working=null,this.workScheduled=0,this.chunkEnd=-1,this.chunkBudget=-1,this.work=this.work.bind(this),this.scheduleWork()}update(e){let t=this.view.state.field(pt.state).context;(t.updateViewport(e.view.viewport)||this.view.viewport.to>t.treeLen)&&this.scheduleWork(),(e.docChanged||e.selectionSet)&&(this.view.hasFocus&&(this.chunkBudget+=50),this.scheduleWork()),this.checkAsyncSchedule(t)}scheduleWork(){if(this.working)return;let{state:e}=this.view,t=e.field(pt.state);(t.tree!=t.context.tree||!t.context.isDone(e.doc.length))&&(this.working=lO(this.work))}work(e){this.working=null;let t=Date.now();if(this.chunkEnds+1e3,a=r.context.work(()=>Hl&&Hl()||Date.now()>o,s+(l?0:1e5));this.chunkBudget-=Date.now()-t,(a||this.chunkBudget<=0)&&(r.context.takeTree(),this.view.dispatch({effects:pt.setState.of(new ws(r.context))})),this.chunkBudget>0&&!(a&&!l)&&this.scheduleWork(),this.checkAsyncSchedule(r.context)}checkAsyncSchedule(e){e.scheduleOn&&(this.workScheduled++,e.scheduleOn.then(()=>this.scheduleWork()).catch(t=>Le(this.view.state,t)).then(()=>this.workScheduled--),e.scheduleOn=null)}destroy(){this.working&&this.working()}isWorking(){return!!(this.working||this.workScheduled>0)}},{eventHandlers:{focus(){this.scheduleWork()}}}),Qi=k.define({combine(n){return n.length?n[0]:null},enables:n=>[pt.state,Hw,D.contentAttributes.compute([n],e=>{let t=e.facet(n);return t&&t.name?{"data-language":t.name}:{}})]});class Yc{constructor(e,t=[]){this.language=e,this.support=t,this.extension=[e,t]}}const Fw=k.define(),cl=k.define({combine:n=>{if(!n.length)return" ";let e=n[0];if(!e||/\S/.test(e)||Array.from(e).some(t=>t!=e[0]))throw new Error("Invalid indent unit: "+JSON.stringify(n[0]));return e}});function xo(n){let e=n.facet(cl);return e.charCodeAt(0)==9?n.tabSize*e.length:e.length}function Mn(n,e){let t="",i=n.tabSize,s=n.facet(cl)[0];if(s==" "){for(;e>=i;)t+=" ",e-=i;s=" "}for(let r=0;r=e?Uw(n,t,e):null}class fl{constructor(e,t={}){this.state=e,this.options=t,this.unit=xo(e)}lineAt(e,t=1){let i=this.state.doc.lineAt(e),{simulateBreak:s,simulateDoubleBreak:r}=this.options;return s!=null&&s>=i.from&&s<=i.to?r&&s==e?{text:"",from:e}:(t<0?s-1&&(r+=o-this.countColumn(i,i.search(/\S|$/))),r}countColumn(e,t=e.length){return Ls(e,this.state.tabSize,t)}lineIndent(e,t=1){let{text:i,from:s}=this.lineAt(e,t),r=this.options.overrideIndentation;if(r){let o=r(s);if(o>-1)return o}return this.countColumn(i,i.search(/\S|$/))}get simulatedBreak(){return this.options.simulateBreak||null}}const ul=new V;function Uw(n,e,t){let i=e.resolveStack(t),s=e.resolveInner(t,-1).resolve(t,0).enterUnfinishedNodesBefore(t);if(s!=i.node){let r=[];for(let o=s;o&&!(o.fromi.node.to||o.from==i.node.from&&o.type==i.node.type);o=o.parent)r.push(o);for(let o=r.length-1;o>=0;o--)i={node:r[o],next:i}}return aO(i,n,t)}function aO(n,e,t){for(let i=n;i;i=i.next){let s=Jw(i.node);if(s)return s(jc.create(e,t,i))}return 0}function Kw(n){return n.pos==n.options.simulateBreak&&n.options.simulateDoubleBreak}function Jw(n){let e=n.type.prop(ul);if(e)return e;let t=n.firstChild,i;if(t&&(i=t.type.prop(V.closedBy))){let s=n.lastChild,r=s&&i.indexOf(s.name)>-1;return o=>hO(o,!0,1,void 0,r&&!Kw(o)?s.from:void 0)}return n.parent==null?ek:null}function ek(){return 0}class jc extends fl{constructor(e,t,i){super(e.state,e.options),this.base=e,this.pos=t,this.context=i}get node(){return this.context.node}static create(e,t,i){return new jc(e,t,i)}get textAfter(){return this.textAfterPos(this.pos)}get baseIndent(){return this.baseIndentFor(this.node)}baseIndentFor(e){let t=this.state.doc.lineAt(e.from);for(;;){let i=e.resolve(t.from);for(;i.parent&&i.parent.from==i.from;)i=i.parent;if(tk(i,e))break;t=this.state.doc.lineAt(i.from)}return this.lineIndent(t.from)}continue(){return aO(this.context.next,this.base,this.pos)}}function tk(n,e){for(let t=e;t;t=t.parent)if(n==t)return!0;return!1}function ik(n){let e=n.node,t=e.childAfter(e.from),i=e.lastChild;if(!t)return null;let s=n.options.simulateBreak,r=n.state.doc.lineAt(t.from),o=s==null||s<=r.from?r.to:Math.min(r.to,s);for(let l=t.to;;){let a=e.childAfter(l);if(!a||a==i)return null;if(!a.type.isSkipped){if(a.from>=o)return null;let h=/^ */.exec(r.text.slice(t.to-r.from))[0].length;return{from:t.from,to:t.to+h}}l=a.to}}function sk({closing:n,align:e=!0,units:t=1}){return i=>hO(i,e,t,n)}function hO(n,e,t,i,s){let r=n.textAfter,o=r.match(/^\s*/)[0].length,l=i&&r.slice(o,o+i.length)==i||s==n.pos+o,a=e?ik(n):null;return a?l?n.column(a.from):n.column(a.to):n.baseIndent+(l?0:n.unit*t)}const nk=n=>n.baseIndent;function Fr({except:n,units:e=1}={}){return t=>{let i=n&&n.test(t.textAfter);return t.baseIndent+(i?0:e*t.unit)}}const rk=200;function ok(){return L.transactionFilter.of(n=>{if(!n.docChanged||!n.isUserEvent("input.type")&&!n.isUserEvent("input.complete"))return n;let e=n.startState.languageDataAt("indentOnInput",n.startState.selection.main.head);if(!e.length)return n;let t=n.newDoc,{head:i}=n.newSelection.main,s=t.lineAt(i);if(i>s.from+rk)return n;let r=t.sliceString(s.from,i);if(!e.some(h=>h.test(r)))return n;let{state:o}=n,l=-1,a=[];for(let{head:h}of o.selection.ranges){let c=o.doc.lineAt(h);if(c.from==l)continue;l=c.from;let f=Nc(o,c.from);if(f==null)continue;let u=/^\s*/.exec(c.text)[0],d=Mn(o,f);u!=d&&a.push({from:c.from,to:c.from+u.length,insert:d})}return a.length?[n,{changes:a,sequential:!0}]:n})}const lk=k.define(),dl=new V;function cO(n){let e=n.firstChild,t=n.lastChild;return e&&e.tot)continue;if(r&&l.from=e&&h.to>t&&(r=h)}}return r}function hk(n){let e=n.lastChild;return e&&e.to==n.to&&e.type.isError}function wo(n,e,t){for(let i of n.facet(lk)){let s=i(n,e,t);if(s)return s}return ak(n,e,t)}function fO(n,e){let t=e.mapPos(n.from,1),i=e.mapPos(n.to,-1);return t>=i?void 0:{from:t,to:i}}const pl=B.define({map:fO}),Nn=B.define({map:fO});function uO(n){let e=[];for(let{head:t}of n.state.selection.ranges)e.some(i=>i.from<=t&&i.to>=t)||e.push(n.lineBlockAt(t));return e}const zi=Se.define({create(){return E.none},update(n,e){e.isUserEvent("delete")&&e.changes.iterChangedRanges((t,i)=>n=Ku(n,t,i)),n=n.map(e.changes);for(let t of e.effects)if(t.is(pl)&&!ck(n,t.value.from,t.value.to)){let{preparePlaceholder:i}=e.state.facet(gO),s=i?E.replace({widget:new Ok(i(e.state,t.value))}):Ju;n=n.update({add:[s.range(t.value.from,t.value.to)]})}else t.is(Nn)&&(n=n.update({filter:(i,s)=>t.value.from!=i||t.value.to!=s,filterFrom:t.value.from,filterTo:t.value.to}));return e.selection&&(n=Ku(n,e.selection.main.head)),n},provide:n=>D.decorations.from(n),toJSON(n,e){let t=[];return n.between(0,e.doc.length,(i,s)=>{t.push(i,s)}),t},fromJSON(n){if(!Array.isArray(n)||n.length%2)throw new RangeError("Invalid JSON for fold state");let e=[];for(let t=0;t{se&&(i=!0)}),i?n.update({filterFrom:e,filterTo:t,filter:(s,r)=>s>=t||r<=e}):n}function ko(n,e,t){var i;let s=null;return(i=n.field(zi,!1))===null||i===void 0||i.between(e,t,(r,o)=>{(!s||s.from>r)&&(s={from:r,to:o})}),s}function ck(n,e,t){let i=!1;return n.between(e,e,(s,r)=>{s==e&&r==t&&(i=!0)}),i}function dO(n,e){return n.field(zi,!1)?e:e.concat(B.appendConfig.of(mO()))}const fk=n=>{for(let e of uO(n)){let t=wo(n.state,e.from,e.to);if(t)return n.dispatch({effects:dO(n.state,[pl.of(t),pO(n,t)])}),!0}return!1},uk=n=>{if(!n.state.field(zi,!1))return!1;let e=[];for(let t of uO(n)){let i=ko(n.state,t.from,t.to);i&&e.push(Nn.of(i),pO(n,i,!1))}return e.length&&n.dispatch({effects:e}),e.length>0};function pO(n,e,t=!0){let i=n.state.doc.lineAt(e.from).number,s=n.state.doc.lineAt(e.to).number;return D.announce.of(`${n.state.phrase(t?"Folded lines":"Unfolded lines")} ${i} ${n.state.phrase("to")} ${s}.`)}const dk=n=>{let{state:e}=n,t=[];for(let i=0;i{let e=n.state.field(zi,!1);if(!e||!e.size)return!1;let t=[];return e.between(0,n.state.doc.length,(i,s)=>{t.push(Nn.of({from:i,to:s}))}),n.dispatch({effects:t}),!0},gk=[{key:"Ctrl-Shift-[",mac:"Cmd-Alt-[",run:fk},{key:"Ctrl-Shift-]",mac:"Cmd-Alt-]",run:uk},{key:"Ctrl-Alt-[",run:dk},{key:"Ctrl-Alt-]",run:pk}],mk={placeholderDOM:null,preparePlaceholder:null,placeholderText:"…"},gO=k.define({combine(n){return _t(n,mk)}});function mO(n){return[zi,yk]}function OO(n,e){let{state:t}=n,i=t.facet(gO),s=o=>{let l=n.lineBlockAt(n.posAtDOM(o.target)),a=ko(n.state,l.from,l.to);a&&n.dispatch({effects:Nn.of(a)}),o.preventDefault()};if(i.placeholderDOM)return i.placeholderDOM(n,s,e);let r=document.createElement("span");return r.textContent=i.placeholderText,r.setAttribute("aria-label",t.phrase("folded code")),r.title=t.phrase("unfold"),r.className="cm-foldPlaceholder",r.onclick=s,r}const Ju=E.replace({widget:new class extends Yt{toDOM(n){return OO(n,null)}}});class Ok extends Yt{constructor(e){super(),this.value=e}eq(e){return this.value==e.value}toDOM(e){return OO(e,this.value)}}const bk={openText:"⌄",closedText:"›",markerDOM:null,domEventHandlers:{},foldingChanged:()=>!1};class Fl extends ci{constructor(e,t){super(),this.config=e,this.open=t}eq(e){return this.config==e.config&&this.open==e.open}toDOM(e){if(this.config.markerDOM)return this.config.markerDOM(this.open);let t=document.createElement("span");return t.textContent=this.open?this.config.openText:this.config.closedText,t.title=e.state.phrase(this.open?"Fold line":"Unfold line"),t}}function Sk(n={}){let e={...bk,...n},t=new Fl(e,!0),i=new Fl(e,!1),s=Re.fromClass(class{constructor(o){this.from=o.viewport.from,this.markers=this.buildMarkers(o)}update(o){(o.docChanged||o.viewportChanged||o.startState.facet(Qi)!=o.state.facet(Qi)||o.startState.field(zi,!1)!=o.state.field(zi,!1)||fe(o.startState)!=fe(o.state)||e.foldingChanged(o))&&(this.markers=this.buildMarkers(o.view))}buildMarkers(o){let l=new ai;for(let a of o.viewportLineBlocks){let h=ko(o.state,a.from,a.to)?i:wo(o.state,a.from,a.to)?t:null;h&&l.add(a.from,a.from,h)}return l.finish()}}),{domEventHandlers:r}=e;return[s,gw({class:"cm-foldGutter",markers(o){var l;return((l=o.plugin(s))===null||l===void 0?void 0:l.markers)||M.empty},initialSpacer(){return new Fl(e,!1)},domEventHandlers:{...r,click:(o,l,a)=>{if(r.click&&r.click(o,l,a))return!0;let h=ko(o.state,l.from,l.to);if(h)return o.dispatch({effects:Nn.of(h)}),!0;let c=wo(o.state,l.from,l.to);return c?(o.dispatch({effects:pl.of(c)}),!0):!1}}}),mO()]}const yk=D.baseTheme({".cm-foldPlaceholder":{backgroundColor:"#eee",border:"1px solid #ddd",color:"#888",borderRadius:".2em",margin:"0 1px",padding:"0 1px",cursor:"pointer"},".cm-foldGutter span":{padding:"0 1px",cursor:"pointer"}});class jn{constructor(e,t){this.specs=e;let i;function s(l){let a=me.newName();return(i||(i=Object.create(null)))["."+a]=l,a}const r=typeof t.all=="string"?t.all:t.all?s(t.all):void 0,o=t.scope;this.scope=o instanceof pt?l=>l.prop(Ui)==o.data:o?l=>l==o:void 0,this.style=rO(e.map(l=>({tag:l.tag,class:l.class||s(Object.assign({},l,{tag:null}))})),{all:r}).style,this.module=i?new me(i):null,this.themeType=t.themeType}static define(e,t){return new jn(e,t||{})}}const vh=k.define(),bO=k.define({combine(n){return n.length?[n[0]]:null}});function Ul(n){let e=n.facet(vh);return e.length?e:n.facet(bO)}function SO(n,e){let t=[wk],i;return n instanceof jn&&(n.module&&t.push(D.styleModule.of(n.module)),i=n.themeType),e?.fallback?t.push(bO.of(n)):i?t.push(vh.computeN([D.darkTheme],s=>s.facet(D.darkTheme)==(i=="dark")?[n]:[])):t.push(vh.of(n)),t}class xk{constructor(e){this.markCache=Object.create(null),this.tree=fe(e.state),this.decorations=this.buildDeco(e,Ul(e.state)),this.decoratedTo=e.viewport.to}update(e){let t=fe(e.state),i=Ul(e.state),s=i!=Ul(e.startState),{viewport:r}=e.view,o=e.changes.mapPos(this.decoratedTo,1);t.length=r.to?(this.decorations=this.decorations.map(e.changes),this.decoratedTo=o):(t!=this.tree||e.viewportChanged||s)&&(this.tree=t,this.decorations=this.buildDeco(e.view,i),this.decoratedTo=r.to)}buildDeco(e,t){if(!t||!this.tree.length)return E.none;let i=new ai;for(let{from:s,to:r}of e.visibleRanges)Yw(this.tree,t,(o,l,a)=>{i.add(o,l,this.markCache[a]||(this.markCache[a]=E.mark({class:a})))},s,r);return i.finish()}}const wk=xt.high(Re.fromClass(xk,{decorations:n=>n.decorations})),kk=jn.define([{tag:b.meta,color:"#404740"},{tag:b.link,textDecoration:"underline"},{tag:b.heading,textDecoration:"underline",fontWeight:"bold"},{tag:b.emphasis,fontStyle:"italic"},{tag:b.strong,fontWeight:"bold"},{tag:b.strikethrough,textDecoration:"line-through"},{tag:b.keyword,color:"#708"},{tag:[b.atom,b.bool,b.url,b.contentSeparator,b.labelName],color:"#219"},{tag:[b.literal,b.inserted],color:"#164"},{tag:[b.string,b.deleted],color:"#a11"},{tag:[b.regexp,b.escape,b.special(b.string)],color:"#e40"},{tag:b.definition(b.variableName),color:"#00f"},{tag:b.local(b.variableName),color:"#30a"},{tag:[b.typeName,b.namespace],color:"#085"},{tag:b.className,color:"#167"},{tag:[b.special(b.variableName),b.macroName],color:"#256"},{tag:b.definition(b.propertyName),color:"#00c"},{tag:b.comment,color:"#940"},{tag:b.invalid,color:"#f00"}]),Qk=D.baseTheme({"&.cm-focused .cm-matchingBracket":{backgroundColor:"#328c8252"},"&.cm-focused .cm-nonmatchingBracket":{backgroundColor:"#bb555544"}}),yO=1e4,xO="()[]{}",wO=k.define({combine(n){return _t(n,{afterCursor:!0,brackets:xO,maxScanDistance:yO,renderMatch:Pk})}}),vk=E.mark({class:"cm-matchingBracket"}),$k=E.mark({class:"cm-nonmatchingBracket"});function Pk(n){let e=[],t=n.matched?vk:$k;return e.push(t.range(n.start.from,n.start.to)),n.end&&e.push(t.range(n.end.from,n.end.to)),e}const Ck=Se.define({create(){return E.none},update(n,e){if(!e.docChanged&&!e.selection)return n;let t=[],i=e.state.facet(wO);for(let s of e.state.selection.ranges){if(!s.empty)continue;let r=Vt(e.state,s.head,-1,i)||s.head>0&&Vt(e.state,s.head-1,1,i)||i.afterCursor&&(Vt(e.state,s.head,1,i)||s.headD.decorations.from(n)}),Tk=[Ck,Qk];function Mk(n={}){return[wO.of(n),Tk]}const kO=new V;function $h(n,e,t){let i=n.prop(e<0?V.openedBy:V.closedBy);if(i)return i;if(n.name.length==1){let s=t.indexOf(n.name);if(s>-1&&s%2==(e<0?1:0))return[t[s+e]]}return null}function Ph(n){let e=n.type.prop(kO);return e?e(n.node):n}function Vt(n,e,t,i={}){let s=i.maxScanDistance||yO,r=i.brackets||xO,o=fe(n),l=o.resolveInner(e,t);for(let a=l;a;a=a.parent){let h=$h(a.type,t,r);if(h&&a.from0?e>=c.from&&ec.from&&e<=c.to))return Ak(n,e,t,a,c,h,r)}}return Rk(n,e,t,o,l.type,s,r)}function Ak(n,e,t,i,s,r,o){let l=i.parent,a={from:s.from,to:s.to},h=0,c=l?.cursor();if(c&&(t<0?c.childBefore(i.from):c.childAfter(i.to)))do if(t<0?c.to<=i.from:c.from>=i.to){if(h==0&&r.indexOf(c.type.name)>-1&&c.from0)return null;let h={from:t<0?e-1:e,to:t>0?e+1:e},c=n.doc.iterRange(e,t>0?n.doc.length:0),f=0;for(let u=0;!c.next().done&&u<=r;){let d=c.value;t<0&&(u+=d.length);let p=e+u*t;for(let g=t>0?0:d.length-1,m=t>0?d.length:-1;g!=m;g+=t){let O=o.indexOf(d[g]);if(!(O<0||i.resolveInner(p+g,1).type!=s))if(O%2==0==t>0)f++;else{if(f==1)return{start:h,end:{from:p+g,to:p+g+1},matched:O>>1==a>>1};f--}}t>0&&(u+=d.length)}return c.done?{start:h,matched:!1}:null}const Dk=Object.create(null),ed=[De.none],td=[],id=Object.create(null),Zk=Object.create(null);for(let[n,e]of[["variable","variableName"],["variable-2","variableName.special"],["string-2","string.special"],["def","variableName.definition"],["tag","tagName"],["attribute","attributeName"],["type","typeName"],["builtin","variableName.standard"],["qualifier","modifier"],["error","invalid"],["header","heading"],["property","propertyName"]])Zk[n]=Bk(Dk,e);function Kl(n,e){td.indexOf(n)>-1||(td.push(n),console.warn(e))}function Bk(n,e){let t=[];for(let l of e.split(" ")){let a=[];for(let h of l.split(".")){let c=n[h]||b[h];c?typeof c=="function"?a.length?a=a.map(c):Kl(h,`Modifier ${h} used at start of tag`):a.length?Kl(h,`Tag ${h} used as modifier`):a=Array.isArray(c)?c:[c]:Kl(h,`Unknown highlighting tag ${h}`)}for(let h of a)t.push(h)}if(!t.length)return 0;let i=e.replace(/ /g,"_"),s=i+" "+t.map(l=>l.id),r=id[s];if(r)return r.id;let o=id[s]=De.define({id:ed.length,name:i,props:[hl({[i]:t})]});return ed.push(o),o.id}le.RTL,le.LTR;let Ne=typeof navigator<"u"?navigator:{userAgent:"",vendor:"",platform:""},Ch=typeof document<"u"?document:{documentElement:{style:{}}};const Th=/Edge\/(\d+)/.exec(Ne.userAgent),QO=/MSIE \d/.test(Ne.userAgent),Mh=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(Ne.userAgent),gl=!!(QO||Mh||Th),sd=!gl&&/gecko\/(\d+)/i.test(Ne.userAgent),Jl=!gl&&/Chrome\/(\d+)/.exec(Ne.userAgent),Xk="webkitFontSmoothing"in Ch.documentElement.style,Ah=!gl&&/Apple Computer/.test(Ne.vendor),nd=Ah&&(/Mobile\/\w+/.test(Ne.userAgent)||Ne.maxTouchPoints>2);var A={mac:nd||/Mac/.test(Ne.platform),ie:gl,ie_version:QO?Ch.documentMode||6:Mh?+Mh[1]:Th?+Th[1]:0,gecko:sd,gecko_version:sd?+(/Firefox\/(\d+)/.exec(Ne.userAgent)||[0,0])[1]:0,chrome:!!Jl,chrome_version:Jl?+Jl[1]:0,ios:nd,android:/Android\b/.test(Ne.userAgent),webkit_version:Xk?+(/\bAppleWebKit\/(\d+)/.exec(Ne.userAgent)||[0,0])[1]:0,safari:Ah,safari_version:Ah?+(/\bVersion\/(\d+(\.\d+)?)/.exec(Ne.userAgent)||[0,0])[1]:0,tabSize:Ch.documentElement.style.tabSize!=null?"tab-size":"-moz-tab-size"};function Gc(n,e){for(let t in n)t=="class"&&e.class?e.class+=" "+n.class:t=="style"&&e.style?e.style+=";"+n.style:e[t]=n[t];return e}const Qo=Object.create(null);function Hc(n,e,t){if(n==e)return!0;n||(n=Qo),e||(e=Qo);let i=Object.keys(n),s=Object.keys(e);if(i.length-0!=s.length-0)return!1;for(let r of i)if(r!=t&&(s.indexOf(r)==-1||n[r]!==e[r]))return!1;return!0}function Lk(n,e){for(let t=n.attributes.length-1;t>=0;t--){let i=n.attributes[t].name;e[i]==null&&n.removeAttribute(i)}for(let t in e){let i=e[t];t=="style"?n.style.cssText=i:n.getAttribute(t)!=i&&n.setAttribute(t,i)}}function rd(n,e,t){let i=!1;if(e)for(let s in e)t&&s in t||(i=!0,s=="style"?n.style.cssText="":n.removeAttribute(s));if(t)for(let s in t)e&&e[s]==t[s]||(i=!0,s=="style"?n.style.cssText=t[s]:n.setAttribute(s,t[s]));return i}function Ek(n){let e=Object.create(null);for(let t=0;t0?3e8:-4e8:t>0?1e8:-1e8,new An(e,t,t,i,e.widget||null,!1)}static replace(e){let t=!!e.block,i,s;if(e.isBlockGap)i=-5e8,s=4e8;else{let{start:r,end:o}=CO(e,t);i=(r?t?-3e8:-1:5e8)-1,s=(o?t?2e8:1:-6e8)+1}return new An(e,i,s,t,e.widget||null,!0)}static line(e){return new Uc(e)}static set(e,t=!1){return M.of(e,t)}hasHeight(){return this.widget?this.widget.estimatedHeight>-1:!1}};We.none=M.empty;let Fc=class vO extends We{constructor(e){let{start:t,end:i}=CO(e);super(t?-1:5e8,i?1:-6e8,null,e),this.tagName=e.tagName||"span",this.attrs=e.class&&e.attributes?Gc(e.attributes,{class:e.class}):e.class?{class:e.class}:e.attributes||Qo}eq(e){return this==e||e instanceof vO&&this.tagName==e.tagName&&Hc(this.attrs,e.attrs)}range(e,t=e){if(e>=t)throw new RangeError("Mark decorations may not be empty");return super.range(e,t)}};Fc.prototype.point=!1;let Uc=class $O extends We{constructor(e){super(-2e8,-2e8,null,e)}eq(e){return e instanceof $O&&this.spec.class==e.spec.class&&Hc(this.spec.attributes,e.spec.attributes)}range(e,t=e){if(t!=e)throw new RangeError("Line decoration ranges must be zero-length");return super.range(e,t)}};Uc.prototype.mapMode=te.TrackBefore;Uc.prototype.point=!0;let An=class PO extends We{constructor(e,t,i,s,r,o){super(t,i,r,e),this.block=s,this.isReplace=o,this.mapMode=s?t<=0?te.TrackBefore:te.TrackAfter:te.TrackDel}get type(){return this.startSide!=this.endSide?nt.WidgetRange:this.startSide<=0?nt.WidgetBefore:nt.WidgetAfter}get heightRelevant(){return this.block||!!this.widget&&(this.widget.estimatedHeight>=5||this.widget.lineBreaks>0)}eq(e){return e instanceof PO&&Wk(this.widget,e.widget)&&this.block==e.block&&this.startSide==e.startSide&&this.endSide==e.endSide}range(e,t=e){if(this.isReplace&&(e>t||e==t&&this.startSide>0&&this.endSide<=0))throw new RangeError("Invalid range for replacement decoration");if(!this.isReplace&&t!=e)throw new RangeError("Widget decorations can only have zero-length ranges");return super.range(e,t)}};An.prototype.point=!0;function CO(n,e=!1){let{inclusiveStart:t,inclusiveEnd:i}=n;return t==null&&(t=n.inclusive),i==null&&(i=n.inclusive),{start:t??e,end:i??e}}function Wk(n,e){return n==e||!!(n&&e&&n.compare(e))}function ls(n,e,t,i=0){let s=t.length-1;s>=0&&t[s]+i>=n?t[s]=Math.max(t[s],e):t.push(n,e)}let od=class Rh extends Ve{constructor(e,t){super(),this.tagName=e,this.attributes=t}eq(e){return e==this||e instanceof Rh&&this.tagName==e.tagName&&Hc(this.attributes,e.attributes)}static create(e){return new Rh(e.tagName,e.attributes||Qo)}static set(e,t=!1){return M.of(e,t)}};od.prototype.startSide=od.prototype.endSide=-1;function ks(n){let e;return n.nodeType==11?e=n.getSelection?n:n.ownerDocument:e=n,e.getSelection()}function Dh(n,e){return e?n==e||n.contains(e.nodeType!=1?e.parentNode:e):!1}function gn(n,e){if(!e.anchorNode)return!1;try{return Dh(n,e.anchorNode)}catch{return!1}}function Ur(n){return n.nodeType==3?Rn(n,0,n.nodeValue.length).getClientRects():n.nodeType==1?n.getClientRects():[]}function mn(n,e,t,i){return t?ld(n,e,t,i,-1)||ld(n,e,t,i,1):!1}function vi(n){for(var e=0;;e++)if(n=n.previousSibling,!n)return e}function vo(n){return n.nodeType==1&&/^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(n.nodeName)}function ld(n,e,t,i,s){for(;;){if(n==t&&e==i)return!0;if(e==(s<0?0:fi(n))){if(n.nodeName=="DIV")return!1;let r=n.parentNode;if(!r||r.nodeType!=1)return!1;e=vi(n)+(s<0?0:1),n=r}else if(n.nodeType==1){if(n=n.childNodes[e+(s<0?-1:0)],n.nodeType==1&&n.contentEditable=="false")return!1;e=s<0?fi(n):0}else return!1}}function fi(n){return n.nodeType==3?n.nodeValue.length:n.childNodes.length}function $o(n,e){let t=e?n.left:n.right;return{left:t,right:t,top:n.top,bottom:n.bottom}}function Vk(n){let e=n.visualViewport;return e?{left:0,right:e.width,top:0,bottom:e.height}:{left:0,right:n.innerWidth,top:0,bottom:n.innerHeight}}function TO(n,e){let t=e.width/n.offsetWidth,i=e.height/n.offsetHeight;return(t>.995&&t<1.005||!isFinite(t)||Math.abs(e.width-n.offsetWidth)<1)&&(t=1),(i>.995&&i<1.005||!isFinite(i)||Math.abs(e.height-n.offsetHeight)<1)&&(i=1),{scaleX:t,scaleY:i}}function zk(n,e,t,i,s,r,o,l){let a=n.ownerDocument,h=a.defaultView||window;for(let c=n,f=!1;c&&!f;)if(c.nodeType==1){let u,d=c==a.body,p=1,g=1;if(d)u=Vk(h);else{if(/^(fixed|sticky)$/.test(getComputedStyle(c).position)&&(f=!0),c.scrollHeight<=c.clientHeight&&c.scrollWidth<=c.clientWidth){c=c.assignedSlot||c.parentNode;continue}let y=c.getBoundingClientRect();({scaleX:p,scaleY:g}=TO(c,y)),u={left:y.left,right:y.left+c.clientWidth*p,top:y.top,bottom:y.top+c.clientHeight*g}}let m=0,O=0;if(s=="nearest")e.top0&&e.bottom>u.bottom+O&&(O=e.bottom-u.bottom+o)):e.bottom>u.bottom&&(O=e.bottom-u.bottom+o,t<0&&e.top-O0&&e.right>u.right+m&&(m=e.right-u.right+r)):e.right>u.right&&(m=e.right-u.right+r,t<0&&e.leftu.bottom||e.leftu.right)&&(e={left:Math.max(e.left,u.left),right:Math.min(e.right,u.right),top:Math.max(e.top,u.top),bottom:Math.min(e.bottom,u.bottom)}),c=c.assignedSlot||c.parentNode}else if(c.nodeType==11)c=c.host;else break}function qk(n){let e=n.ownerDocument,t,i;for(let s=n.parentNode;s&&!(s==e.body||t&&i);)if(s.nodeType==1)!i&&s.scrollHeight>s.clientHeight&&(i=s),!t&&s.scrollWidth>s.clientWidth&&(t=s),s=s.assignedSlot||s.parentNode;else if(s.nodeType==11)s=s.host;else break;return{x:t,y:i}}let Ik=class{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}eq(e){return this.anchorNode==e.anchorNode&&this.anchorOffset==e.anchorOffset&&this.focusNode==e.focusNode&&this.focusOffset==e.focusOffset}setRange(e){let{anchorNode:t,focusNode:i}=e;this.set(t,Math.min(e.anchorOffset,t?fi(t):0),i,Math.min(e.focusOffset,i?fi(i):0))}set(e,t,i,s){this.anchorNode=e,this.anchorOffset=t,this.focusNode=i,this.focusOffset=s}},Ri=null;A.safari&&A.safari_version>=26&&(Ri=!1);function MO(n){if(n.setActive)return n.setActive();if(Ri)return n.focus(Ri);let e=[];for(let t=n;t&&(e.push(t,t.scrollTop,t.scrollLeft),t!=t.ownerDocument);t=t.parentNode);if(n.focus(Ri==null?{get preventScroll(){return Ri={preventScroll:!0},!0}}:void 0),!Ri){Ri=!1;for(let t=0;tMath.max(1,n.scrollHeight-n.clientHeight-4)}function RO(n,e){for(let t=n,i=e;;){if(t.nodeType==3&&i>0)return{node:t,offset:i};if(t.nodeType==1&&i>0){if(t.contentEditable=="false")return null;t=t.childNodes[i-1],i=fi(t)}else if(t.parentNode&&!vo(t))i=vi(t),t=t.parentNode;else return null}}function DO(n,e){for(let t=n,i=e;;){if(t.nodeType==3&&i=t){if(l.level==i)return o;(r<0||(s!=0?s<0?l.fromt:e[r].level>l.level))&&(r=o)}}if(r<0)throw new RangeError("Index out of range");return r}};function XO(n,e){if(n.length!=e.length)return!1;for(let t=0;t=0;g-=3)if(Mt[g+1]==-d){let m=Mt[g+2],O=m&2?s:m&4?m&1?r:s:0;O&&(K[f]=K[Mt[g]]=O),l=g;break}}else{if(Mt.length==189)break;Mt[l++]=f,Mt[l++]=u,Mt[l++]=a}else if((p=K[f])==2||p==1){let g=p==s;a=g?0:1;for(let m=l-3;m>=0;m-=3){let O=Mt[m+2];if(O&2)break;if(g)Mt[m+2]|=2;else{if(O&4)break;Mt[m+2]|=4}}}}}function Uk(n,e,t,i){for(let s=0,r=i;s<=t.length;s++){let o=s?t[s-1].to:n,l=sa;)p==m&&(p=t[--g].from,m=g?t[g-1].to:n),K[--p]=d;a=c}else r=h,a++}}}function Xh(n,e,t,i,s,r,o){let l=i%2?2:1;if(i%2==s%2)for(let a=e,h=0;aa&&o.push(new ii(a,g.from,d));let m=g.direction==qi!=!(d%2);Lh(n,m?i+1:i,s,g.inner,g.from,g.to,o),a=g.to}p=g.to}else{if(p==t||(c?K[p]!=l:K[p]==l))break;p++}u?Xh(n,a,p,i+1,s,u,o):ae;){let c=!0,f=!1;if(!h||a>r[h-1].to){let g=K[a-1];g!=l&&(c=!1,f=g==16)}let u=!c&&l==1?[]:null,d=c?i:i+1,p=a;e:for(;;)if(h&&p==r[h-1].to){if(f)break e;let g=r[--h];if(!c)for(let m=g.from,O=h;;){if(m==e)break e;if(O&&r[O-1].to==m)m=r[--O].from;else{if(K[m-1]==l)break e;break}}if(u)u.push(g);else{g.toK.length;)K[K.length]=256;let i=[],s=e==qi?0:1;return Lh(n,s,s,t,0,n.length,i),i}function LO(n){return[new ii(0,n,0)]}let EO="";function Jk(n,e,t,i,s){var r;let o=i.head-n.from,l=ii.find(e,o,(r=i.bidiLevel)!==null&&r!==void 0?r:-1,i.assoc),a=e[l],h=a.side(s,t);if(o==h){let u=l+=s?1:-1;if(u<0||u>=e.length)return null;a=e[l=u],o=a.side(!s,t),h=a.side(s,t)}let c=_(n.text,o,a.forward(s,t));(ca.to)&&(c=h),EO=n.text.slice(Math.min(o,c),Math.max(o,c));let f=l==(s?e.length-1:0)?null:e[l+(s?1:-1)];return f&&c==h&&f.level+(s?0:1)n.some(e=>e)}),tQ=k.define({combine:n=>n.some(e=>e)}),NO=k.define();let ea=class Wh{constructor(e,t="nearest",i="nearest",s=5,r=5,o=!1){this.range=e,this.y=t,this.x=i,this.yMargin=s,this.xMargin=r,this.isSnapshot=o}map(e){return e.empty?this:new Wh(this.range.map(e),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}clip(e){return this.range.to<=e.doc.length?this:new Wh(S.cursor(e.doc.length),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}};const yr=B.define({map:(n,e)=>n.map(e)}),jO=B.define();function si(n,e,t){let i=n.facet(qO);i.length?i[0](e):window.onerror&&window.onerror(String(e),t,void 0,void 0,e)||(t?console.error(t+":",e):console.error(e))}const Ut=k.define({combine:n=>n.length?n[0]:!0});let iQ=0;const Ki=k.define({combine(n){return n.filter((e,t)=>{for(let i=0;i{let a=[];return o&&a.push(Ol.of(h=>{let c=h.plugin(l);return c?o(c):We.none})),r&&a.push(r(l)),a})}static fromClass(e,t){return Vh.define((i,s)=>new e(i,s),t)}},ta=class{constructor(e){this.spec=e,this.mustUpdate=null,this.value=null}get plugin(){return this.spec&&this.spec.plugin}update(e){if(this.value){if(this.mustUpdate){let t=this.mustUpdate;if(this.mustUpdate=null,this.value.update)try{this.value.update(t)}catch(i){if(si(t.state,i,"CodeMirror plugin crashed"),this.value.destroy)try{this.value.destroy()}catch{}this.deactivate()}}}else if(this.spec)try{this.value=this.spec.plugin.create(e,this.spec.arg)}catch(t){si(e.state,t,"CodeMirror plugin crashed"),this.deactivate()}return this}destroy(e){var t;if(!((t=this.value)===null||t===void 0)&&t.destroy)try{this.value.destroy()}catch(i){si(e.state,i,"CodeMirror plugin crashed")}}deactivate(){this.spec=this.value=null}};const GO=k.define(),tf=k.define(),Ol=k.define(),HO=k.define(),sf=k.define(),Gn=k.define(),FO=k.define();function cd(n,e){let t=n.state.facet(FO);if(!t.length)return t;let i=t.map(r=>r instanceof Function?r(n):r),s=[];return M.spans(i,e.from,e.to,{point(){},span(r,o,l,a){let h=r-e.from,c=o-e.from,f=s;for(let u=l.length-1;u>=0;u--,a--){let d=l[u].spec.bidiIsolate,p;if(d==null&&(d=eQ(e.text,h,c)),a>0&&f.length&&(p=f[f.length-1]).to==h&&p.direction==d)p.to=c,f=p.inner;else{let g={from:h,to:c,direction:d,inner:[]};f.push(g),f=g.inner}}}}),s}const UO=k.define();function KO(n){let e=0,t=0,i=0,s=0;for(let r of n.state.facet(UO)){let o=r(n);o&&(o.left!=null&&(e=Math.max(e,o.left)),o.right!=null&&(t=Math.max(t,o.right)),o.top!=null&&(i=Math.max(i,o.top)),o.bottom!=null&&(s=Math.max(s,o.bottom)))}return{left:e,right:t,top:i,bottom:s}}const en=k.define();let ni=class zh{constructor(e,t,i,s){this.fromA=e,this.toA=t,this.fromB=i,this.toB=s}join(e){return new zh(Math.min(this.fromA,e.fromA),Math.max(this.toA,e.toA),Math.min(this.fromB,e.fromB),Math.max(this.toB,e.toB))}addToSet(e){let t=e.length,i=this;for(;t>0;t--){let s=e[t-1];if(!(s.fromA>i.toA)){if(s.toAs.push(new ni(r,o,l,a))),this.changedRanges=s}static create(e,t,i){return new JO(e,t,i)}get viewportChanged(){return(this.flags&4)>0}get viewportMoved(){return(this.flags&8)>0}get heightChanged(){return(this.flags&2)>0}get geometryChanged(){return this.docChanged||(this.flags&18)>0}get focusChanged(){return(this.flags&1)>0}get docChanged(){return!this.changes.empty}get selectionSet(){return this.transactions.some(e=>e.selection)}get empty(){return this.flags==0&&this.transactions.length==0}};const sQ=[];let pe=class{constructor(e,t,i=0){this.dom=e,this.length=t,this.flags=i,this.parent=null,e.cmTile=this}get breakAfter(){return this.flags&1}get children(){return sQ}isWidget(){return!1}get isHidden(){return!1}isComposite(){return!1}isLine(){return!1}isText(){return!1}isBlock(){return!1}get domAttrs(){return null}sync(e){if(this.flags|=2,this.flags&4){this.flags&=-5;let t=this.domAttrs;t&&Lk(this.dom,t)}}toString(){return this.constructor.name+(this.children.length?`(${this.children})`:"")+(this.breakAfter?"#":"")}destroy(){this.parent=null}setDOM(e){this.dom=e,e.cmTile=this}get posAtStart(){return this.parent?this.parent.posBefore(this):0}get posAtEnd(){return this.posAtStart+this.length}posBefore(e,t=this.posAtStart){let i=t;for(let s of this.children){if(s==e)return i;i+=s.length+s.breakAfter}throw new RangeError("Invalid child in posBefore")}posAfter(e){return this.posBefore(e)+e.length}covers(e){return!0}coordsIn(e,t){return null}domPosFor(e,t){let i=vi(this.dom),s=this.length?e>0:t>0;return new Si(this.parent.dom,i+(s?1:0),e==0||e==this.length)}markDirty(e){this.flags&=-3,e&&(this.flags|=4),this.parent&&this.parent.flags&2&&this.parent.markDirty(!1)}get overrideDOMText(){return null}get root(){for(let e=this;e;e=e.parent)if(e instanceof Sl)return e;return null}static get(e){return e.cmTile}},bl=class extends pe{constructor(e){super(e,0),this._children=[]}isComposite(){return!0}get children(){return this._children}get lastChild(){return this.children.length?this.children[this.children.length-1]:null}append(e){this.children.push(e),e.parent=this}sync(e){if(this.flags&2)return;super.sync(e);let t=this.dom,i=null,s,r=e?.node==t?e:null,o=0;for(let l of this.children){if(l.sync(e),o+=l.length+l.breakAfter,s=i?i.nextSibling:t.firstChild,r&&s!=l.dom&&(r.written=!0),l.dom.parentNode==t)for(;s&&s!=l.dom;)s=ud(s);else t.insertBefore(l.dom,s);i=l.dom}for(s=i?i.nextSibling:t.firstChild,r&&s&&(r.written=!0);s;)s=ud(s);this.length=o}};function ud(n){let e=n.nextSibling;return n.parentNode.removeChild(n),e}let Sl=class extends bl{constructor(e,t){super(t),this.view=e}owns(e){for(;e;e=e.parent)if(e==this)return!0;return!1}isBlock(){return!0}nearest(e){for(;;){if(!e)return null;let t=pe.get(e);if(t&&this.owns(t))return t;e=e.parentNode}}blockTiles(e){for(let t=[],i=this,s=0,r=0;;)if(s==i.children.length){if(!t.length)return;i=i.parent,i.breakAfter&&r++,s=t.pop()}else{let o=i.children[s++];if(o instanceof hs)t.push(s),i=o,s=0;else{let l=r+o.length,a=e(o,r);if(a!==void 0)return a;r=l+o.breakAfter}}}resolveBlock(e,t){let i,s=-1,r,o=-1;if(this.blockTiles((l,a)=>{let h=a+l.length;if(e>=a&&e<=h){if(l.isWidget()&&t>=-1&&t<=1){if(l.flags&32)return!0;l.flags&16&&(i=void 0)}(ae||e==a&&(t>1?l.length:l.covers(-1)))&&(!r||!l.isWidget()&&r.isWidget())&&(r=l,o=e-a)}}),!i&&!r)throw new Error("No tile at position "+e);return i&&t<0||!r?{tile:i,offset:s}:{tile:r,offset:o}}},hs=class e0 extends bl{constructor(e,t){super(e),this.wrapper=t}isBlock(){return!0}covers(e){return this.children.length?e<0?this.children[0].covers(-1):this.lastChild.covers(1):!1}get domAttrs(){return this.wrapper.attributes}static of(e,t){let i=new e0(t||document.createElement(e.tagName),e);return t||(i.flags|=4),i}},Po=class t0 extends bl{constructor(e,t){super(e),this.attrs=t}isLine(){return!0}static start(e,t,i){let s=new t0(t||document.createElement("div"),e);return(!t||!i)&&(s.flags|=4),s}get domAttrs(){return this.attrs}resolveInline(e,t,i){let s=null,r=-1,o=null,l=-1;function a(c,f){for(let u=0,d=0;u=f&&(p.isComposite()?a(p,f-d):(!o||o.isHidden&&(t>0||i&&rQ(o,p)))&&(g>f||p.flags&32)?(o=p,l=f-d):(di&&(e=i);let s=e,r=e,o=0;e==0&&t<0||e==i&&t>=0?A.chrome||A.gecko||(e?(s--,o=1):r=0)?0:l.length-1];return A.safari&&!o&&a.width==0&&(a=Array.prototype.find.call(l,h=>h.width)||a),o?$o(a,o<0):a||null}static of(e,t){let i=new s0(t||document.createTextNode(e),e);return t||(i.flags|=2),i}},Dn=class n0 extends pe{constructor(e,t,i,s){super(e,t,s),this.widget=i}isWidget(){return!0}get isHidden(){return this.widget.isHidden}covers(e){return this.flags&48?!1:(this.flags&(e<0?64:128))>0}coordsIn(e,t){return this.coordsInWidget(e,t,!1)}coordsInWidget(e,t,i){let s=this.widget.coordsAt(this.dom,e,t);if(s)return s;if(i)return $o(this.dom.getBoundingClientRect(),this.length?e==0:t<=0);{let r=this.dom.getClientRects(),o=null;if(!r.length)return null;let l=this.flags&16?!0:this.flags&32?!1:e>0;for(let a=l?r.length-1:0;o=r[a],!(e>0?a==0:a==r.length-1||o.top0;)if(s.isComposite())if(o){if(!e)break;i&&i.break(),e--,o=!1}else if(r==s.children.length){if(!e&&!l.length)break;i&&i.leave(s),o=!!s.breakAfter,{tile:s,index:r}=l.pop(),r++}else{let a=s.children[r],h=a.breakAfter;(t>0?a.length<=e:a.length=0;l--){let a=t.marks[l],h=s.lastChild;if(h instanceof Je&&h.mark.eq(a.mark))h.dom!=a.dom&&h.setDOM(ia(a.dom)),s=h;else{if(this.cache.reused.get(a)){let f=pe.get(a.dom);f&&f.setDOM(ia(a.dom))}let c=Je.of(a.mark,a.dom);s.append(c),s=c}this.cache.reused.set(a,2)}let r=pe.get(e.text);r&&this.cache.reused.set(r,2);let o=new tn(e.text,e.text.nodeValue);o.flags|=8,s.append(o)}addInlineWidget(e,t,i){let s=this.afterWidget&&e.flags&48&&(this.afterWidget.flags&48)==(e.flags&48);s||this.flushBuffer();let r=this.ensureMarks(t,i);!s&&!(e.flags&16)&&r.append(this.getBuffer(1)),r.append(e),this.pos+=e.length,this.afterWidget=e}addMark(e,t,i){this.flushBuffer(),this.ensureMarks(t,i).append(e),this.pos+=e.length,this.afterWidget=null}addBlockWidget(e){this.getBlockPos().append(e),this.pos+=e.length,this.lastBlock=e,this.endLine()}continueWidget(e){let t=this.afterWidget||this.lastBlock;t.length+=e,this.pos+=e}addLineStart(e,t){var i;e||(e=r0);let s=Po.start(e,t||((i=this.cache.find(Po))===null||i===void 0?void 0:i.dom),!!t);this.getBlockPos().append(this.lastBlock=this.curLine=s)}addLine(e){this.getBlockPos().append(e),this.pos+=e.length,this.lastBlock=e,this.endLine()}addBreak(){this.lastBlock.flags|=1,this.endLine(),this.pos++}addLineStartIfNotCovered(e){this.blockPosCovered()||this.addLineStart(e)}ensureLine(e){this.curLine||this.addLineStart(e)}ensureMarks(e,t){var i;let s=this.curLine;for(let r=e.length-1;r>=0;r--){let o=e[r],l;if(t>0&&(l=s.lastChild)&&l instanceof Je&&l.mark.eq(o))s=l,t--;else{let a=Je.of(o,(i=this.cache.find(Je,h=>h.mark.eq(o)))===null||i===void 0?void 0:i.dom);s.append(a),s=a,t=0}}return s}endLine(){if(this.curLine){this.flushBuffer();let e=this.curLine.lastChild;(!e||!dd(this.curLine,!1)||e.dom.nodeName!="BR"&&e.isWidget()&&!(A.ios&&dd(this.curLine,!0)))&&this.curLine.append(this.cache.findWidget(sa,0,32)||new Dn(sa.toDOM(),0,sa,32)),this.curLine=this.afterWidget=null}}updateBlockWrappers(){this.wrapperPos>this.pos+1e4&&(this.blockWrappers.goto(this.pos),this.wrappers.length=0);for(let e=this.wrappers.length-1;e>=0;e--)this.wrappers[e].to=this.pos){let t=new lQ(e.from,e.to,e.value,e.rank),i=this.wrappers.length;for(;i>0&&(this.wrappers[i-1].rank-t.rank||this.wrappers[i-1].to-t.to)<0;)i--;this.wrappers.splice(i,0,t)}this.wrapperPos=this.pos}getBlockPos(){var e;this.updateBlockWrappers();let t=this.root;for(let i of this.wrappers){let s=t.lastChild;if(i.fromo.wrapper.eq(i.wrapper)))===null||e===void 0?void 0:e.dom);t.append(r),t=r}}return t}blockPosCovered(){let e=this.lastBlock;return e!=null&&!e.breakAfter&&(!e.isWidget()||(e.flags&160)>0)}getBuffer(e){let t=2|(e<0?16:32),i=this.cache.find(Co,void 0,1);return i&&(i.flags=t),i||new Co(t)}flushBuffer(){this.afterWidget&&!(this.afterWidget.flags&32)&&(this.afterWidget.parent.append(this.getBuffer(-1)),this.afterWidget=null)}},hQ=class{constructor(e){this.skipCount=0,this.text="",this.textOff=0,this.cursor=e.iter()}skip(e){this.textOff+e<=this.text.length?this.textOff+=e:(this.skipCount+=e-(this.text.length-this.textOff),this.text="",this.textOff=0)}next(e){if(this.textOff==this.text.length){let{value:s,lineBreak:r,done:o}=this.cursor.next(this.skipCount);if(this.skipCount=0,o)throw new Error("Ran out of text content when drawing inline views");this.text=s;let l=this.textOff=Math.min(e,s.length);return r?null:s.slice(0,l)}let t=Math.min(this.text.length,this.textOff+e),i=this.text.slice(this.textOff,t);return this.textOff=t,i}};const To=[Dn,Po,tn,Je,Co,hs,Sl];for(let n=0;n[]),this.index=To.map(()=>0),this.reused=new Map}add(e){let t=e.constructor.bucket,i=this.buckets[t];i.length<6?i.push(e):i[this.index[t]=(this.index[t]+1)%6]=e}find(e,t,i=2){let s=e.bucket,r=this.buckets[s],o=this.index[s];for(let l=r.length-1;l>=0;l--){let a=(l+o)%r.length,h=r[a];if((!t||t(h))&&!this.reused.has(h))return r.splice(a,1),a{if(this.cache.add(o),o.isComposite())return!1},enter:o=>this.cache.add(o),leave:()=>{},break:()=>{}}}run(e,t){let i=t&&this.getCompositionContext(t.text);for(let s=0,r=0,o=0;;){let l=os){let h=a-s;this.preserve(h,!o,!l),s=a,r+=h}if(!l)break;t&&l.fromA<=t.range.fromA&&l.toA>=t.range.toA?(this.forward(l.fromA,t.range.fromA,t.range.fromA{if(o.isWidget())if(this.openWidget)this.builder.continueWidget(a-l);else{let h=a>0||l{o.isLine()?this.builder.addLineStart(o.attrs,this.cache.maybeReuse(o)):(this.cache.add(o),o instanceof Je&&s.unshift(o.mark)),this.openWidget=!1},leave:o=>{o.isLine()?s.length&&(s.length=r=0):o instanceof Je&&(s.shift(),r=Math.min(r,s.length))},break:()=>{this.builder.addBreak(),this.openWidget=!1}}),this.text.skip(e)}emit(e,t){let i=null,s=this.builder,r=0,o=M.spans(this.decorations,e,t,{point:(l,a,h,c,f,u)=>{if(h instanceof An){if(this.disallowBlockEffectsFor[u]){if(h.block)throw new RangeError("Block decorations may not be specified via plugins");if(a>this.view.state.doc.lineAt(l).to)throw new RangeError("Decorations that replace line breaks may not be specified via plugins")}if(r=c.length,f>c.length)s.continueWidget(a-l);else{let d=h.widget||(h.block?Qs.block:Qs.inline),p=uQ(h),g=this.cache.findWidget(d,a-l,p)||Dn.of(d,this.view,a-l,p);h.block?(h.startSide>0&&s.addLineStartIfNotCovered(i),s.addBlockWidget(g)):(s.ensureLine(i),s.addInlineWidget(g,c,f))}i=null}else i=dQ(i,h);a>l&&this.text.skip(a-l)},span:(l,a,h,c)=>{for(let f=l;fr,this.openMarks=o}forward(e,t,i=1){t-e<=10?this.old.advance(t-e,i,this.reuseWalker):(this.old.advance(5,-1,this.reuseWalker),this.old.advance(t-e-10,-1),this.old.advance(5,i,this.reuseWalker))}getCompositionContext(e){let t=[],i=null;for(let s=e.parentNode;;s=s.parentNode){let r=pe.get(s);if(s==this.view.contentDOM)break;r instanceof Je?t.push(r):r?.isLine()?i=r:s.nodeName=="DIV"&&!i&&s!=this.view.contentDOM?i=new Po(s,r0):t.push(Je.of(new Fc({tagName:s.nodeName.toLowerCase(),attributes:Ek(s)}),s))}return{line:i,marks:t}}};function dd(n,e){let t=i=>{for(let s of i.children)if((e?s.isText():s.length)||t(s))return!0;return!1};return t(n)}function uQ(n){let e=n.isReplace?(n.startSide<0?64:0)|(n.endSide>0?128:0):n.startSide>0?32:16;return n.block&&(e|=256),e}const r0={class:"cm-line"};function dQ(n,e){let t=e.spec.attributes,i=e.spec.class;return!t&&!i||(n||(n={class:"cm-line"}),t&&Gc(t,n),i&&(n.class+=" "+i)),n}function pQ(n){let e=[];for(let t=n.parents.length;t>1;t--){let i=t==n.parents.length?n.tile:n.parents[t].tile;i instanceof Je&&e.push(i.mark)}return e}function ia(n){let e=pe.get(n);return e&&e.setDOM(n.cloneNode()),n}let Qs=class extends ml{constructor(e){super(),this.tag=e}eq(e){return e.tag==this.tag}toDOM(){return document.createElement(this.tag)}updateDOM(e){return e.nodeName.toLowerCase()==this.tag}get isHidden(){return!0}};Qs.inline=new Qs("span");Qs.block=new Qs("div");const sa=new class extends ml{toDOM(){return document.createElement("br")}get isHidden(){return!0}get editable(){return!0}};let pd=class{constructor(e){this.view=e,this.decorations=[],this.blockWrappers=[],this.dynamicDecorationMap=[!1],this.domChanged=null,this.hasComposition=null,this.editContextFormatting=We.none,this.lastCompositionAfterCursor=!1,this.minWidth=0,this.minWidthFrom=0,this.minWidthTo=0,this.impreciseAnchor=null,this.impreciseHead=null,this.forceSelection=!1,this.lastUpdate=Date.now(),this.updateDeco(),this.tile=new Sl(e,e.contentDOM),this.updateInner([new ni(0,0,0,e.state.doc.length)],null)}update(e){var t;let i=e.changedRanges;this.minWidth>0&&i.length&&(i.every(({fromA:c,toA:f})=>fthis.minWidthTo)?(this.minWidthFrom=e.changes.mapPos(this.minWidthFrom,1),this.minWidthTo=e.changes.mapPos(this.minWidthTo,1)):this.minWidth=this.minWidthFrom=this.minWidthTo=0),this.updateEditContextFormatting(e);let s=-1;this.view.inputState.composing>=0&&!this.view.observer.editContext&&(!((t=this.domChanged)===null||t===void 0)&&t.newSel?s=this.domChanged.newSel.head:!kQ(e.changes,this.hasComposition)&&!e.selectionSet&&(s=e.state.selection.main.head));let r=s>-1?mQ(this.view,e.changes,s):null;if(this.domChanged=null,this.hasComposition){let{from:c,to:f}=this.hasComposition;i=new ni(c,f,e.changes.mapPos(c,-1),e.changes.mapPos(f,1)).addToSet(i.slice())}this.hasComposition=r?{from:r.range.fromB,to:r.range.toB}:null,(A.ie||A.chrome)&&!r&&e&&e.state.doc.lines!=e.startState.doc.lines&&(this.forceSelection=!0);let o=this.decorations,l=this.blockWrappers;this.updateDeco();let a=SQ(o,this.decorations,e.changes);a.length&&(i=ni.extendWithRanges(i,a));let h=xQ(l,this.blockWrappers,e.changes);return h.length&&(i=ni.extendWithRanges(i,h)),r&&!i.some(c=>c.fromA<=r.range.fromA&&c.toA>=r.range.toA)&&(i=r.range.addToSet(i.slice())),this.tile.flags&2&&i.length==0?!1:(this.updateInner(i,r),e.transactions.length&&(this.lastUpdate=Date.now()),!0)}updateInner(e,t){this.view.viewState.mustMeasureContent=!0;let{observer:i}=this.view;i.ignore(()=>{if(t||e.length){let o=this.tile,l=new fQ(this.view,o,this.blockWrappers,this.decorations,this.dynamicDecorationMap);this.tile=l.run(e,t),qh(o,l.cache.reused)}this.tile.dom.style.height=this.view.viewState.contentHeight/this.view.scaleY+"px",this.tile.dom.style.flexBasis=this.minWidth?this.minWidth+"px":"";let r=A.chrome||A.ios?{node:i.selectionRange.focusNode,written:!1}:void 0;this.tile.sync(r),r&&(r.written||i.selectionRange.focusNode!=r.node||!this.tile.dom.contains(r.node))&&(this.forceSelection=!0),this.tile.dom.style.height=""});let s=[];if(this.view.viewport.from||this.view.viewport.to-1)&&gn(i,this.view.observer.selectionRange)&&!(s&&i.contains(s));if(!(r||t||o))return;let l=this.forceSelection;this.forceSelection=!1;let a=this.view.state.selection.main,h,c;if(a.empty?c=h=this.inlineDOMNearPos(a.anchor,a.assoc||1):(c=this.inlineDOMNearPos(a.head,a.head==a.from?1:-1),h=this.inlineDOMNearPos(a.anchor,a.anchor==a.from?1:-1)),A.gecko&&a.empty&&!this.hasComposition&&gQ(h)){let u=document.createTextNode("");this.view.observer.ignore(()=>h.node.insertBefore(u,h.node.childNodes[h.offset]||null)),h=c=new Si(u,0),l=!0}let f=this.view.observer.selectionRange;(l||!f.focusNode||(!mn(h.node,h.offset,f.anchorNode,f.anchorOffset)||!mn(c.node,c.offset,f.focusNode,f.focusOffset))&&!this.suppressWidgetCursorChange(f,a))&&(this.view.observer.ignore(()=>{A.android&&A.chrome&&i.contains(f.focusNode)&&wQ(f.focusNode,i)&&(i.blur(),i.focus({preventScroll:!0}));let u=ks(this.view.root);if(u)if(a.empty){if(A.gecko){let d=OQ(h.node,h.offset);if(d&&d!=3){let p=(d==1?RO:DO)(h.node,h.offset);p&&(h=new Si(p.node,p.offset))}}u.collapse(h.node,h.offset),a.bidiLevel!=null&&u.caretBidiLevel!==void 0&&(u.caretBidiLevel=a.bidiLevel)}else if(u.extend){u.collapse(h.node,h.offset);try{u.extend(c.node,c.offset)}catch{}}else{let d=document.createRange();a.anchor>a.head&&([h,c]=[c,h]),d.setEnd(c.node,c.offset),d.setStart(h.node,h.offset),u.removeAllRanges(),u.addRange(d)}o&&this.view.root.activeElement==i&&(i.blur(),s&&s.focus())}),this.view.observer.setSelectionRange(h,c)),this.impreciseAnchor=h.precise?null:new Si(f.anchorNode,f.anchorOffset),this.impreciseHead=c.precise?null:new Si(f.focusNode,f.focusOffset)}suppressWidgetCursorChange(e,t){return this.hasComposition&&t.empty&&mn(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset)&&this.posFromDOM(e.focusNode,e.focusOffset)==t.head}enforceCursorAssoc(){if(this.hasComposition)return;let{view:e}=this,t=e.state.selection.main,i=ks(e.root),{anchorNode:s,anchorOffset:r}=e.observer.selectionRange;if(!i||!t.empty||!t.assoc||!i.modify)return;let o=this.lineAt(t.head,t.assoc);if(!o)return;let l=o.posAtStart;if(t.head==l||t.head==l+o.length)return;let a=this.coordsAt(t.head,-1),h=this.coordsAt(t.head,1);if(!a||!h||a.bottom>h.top)return;let c=this.domAtPos(t.head+t.assoc,t.assoc);i.collapse(c.node,c.offset),i.modify("move",t.assoc<0?"forward":"backward","lineboundary"),e.observer.readSelectionRange();let f=e.observer.selectionRange;e.docView.posFromDOM(f.anchorNode,f.anchorOffset)!=t.from&&i.collapse(s,r)}posFromDOM(e,t){let i=this.tile.nearest(e);if(!i)return this.tile.dom.compareDocumentPosition(e)&2?0:this.view.state.doc.length;let s=i.posAtStart;if(i.isComposite()){let r;if(e==i.dom)r=i.dom.childNodes[t];else{let o=fi(e)==0?0:t==0?-1:1;for(;;){let l=e.parentNode;if(l==i.dom)break;o==0&&l.firstChild!=l.lastChild&&(e==l.firstChild?o=-1:o=1),e=l}o<0?r=e:r=e.nextSibling}if(r==i.dom.firstChild)return s;for(;r&&!pe.get(r);)r=r.nextSibling;if(!r)return s+i.length;for(let o=0,l=s;;o++){let a=i.children[o];if(a.dom==r)return l;l+=a.length+a.breakAfter}}else return i.isText()?e==i.dom?s+t:s+(t?i.length:0):s}domAtPos(e,t){let{tile:i,offset:s}=this.tile.resolveBlock(e,t);return i.isWidget()?i.domPosFor(e,t):i.domIn(s,t)}inlineDOMNearPos(e,t){let i,s=-1,r=!1,o,l=-1,a=!1;return this.tile.blockTiles((h,c)=>{if(h.isWidget()){if(h.flags&32&&c>=e)return!0;h.flags&16&&(r=!0)}else{let f=c+h.length;if(c<=e&&(i=h,s=e-c,r=f=e&&!o&&(o=h,l=e-c,a=c>e),c>e&&o)return!0}}),!i&&!o?this.domAtPos(e,t):(r&&o?i=null:a&&i&&(o=null),i&&t<0||!o?i.domIn(s,t):o.domIn(l,t))}coordsAt(e,t){let{tile:i,offset:s}=this.tile.resolveBlock(e,t);return i.isWidget()?i.widget instanceof na?null:i.coordsInWidget(s,t,!0):i.coordsIn(s,t)}lineAt(e,t){let{tile:i}=this.tile.resolveBlock(e,t);return i.isLine()?i:null}coordsForChar(e){let{tile:t,offset:i}=this.tile.resolveBlock(e,1);if(!t.isLine())return null;function s(r,o){if(r.isComposite())for(let l of r.children){if(l.length>=o){let a=s(l,o);if(a)return a}if(o-=l.length,o<0)break}else if(r.isText()&&oMath.max(this.view.scrollDOM.clientWidth,this.minWidth)+1,l=-1,a=this.view.textDirection==be.LTR,h=0,c=(f,u,d)=>{for(let p=0;ps);p++){let g=f.children[p],m=u+g.length,O=g.dom.getBoundingClientRect(),{height:y}=O;if(d&&!p&&(h+=O.top-d.top),g instanceof hs)m>i&&c(g,u,O);else if(u>=i&&(h>0&&t.push(-h),t.push(y+h),h=0,o)){let x=g.dom.lastChild,v=x?Ur(x):[];if(v.length){let w=v[v.length-1],Q=a?w.right-O.left:O.right-w.left;Q>l&&(l=Q,this.minWidth=r,this.minWidthFrom=u,this.minWidthTo=m)}}d&&p==f.children.length-1&&(h+=d.bottom-O.bottom),u=m+g.breakAfter}};return c(this.tile,0,null),t}textDirectionAt(e){let{tile:t}=this.tile.resolveBlock(e,1);return getComputedStyle(t.dom).direction=="rtl"?be.RTL:be.LTR}measureTextSize(){let e=this.tile.blockTiles(o=>{if(o.isLine()&&o.children.length&&o.length<=20){let l=0,a;for(let h of o.children){if(!h.isText()||/[^ -~]/.test(h.text))return;let c=Ur(h.dom);if(c.length!=1)return;l+=c[0].width,a=c[0].height}if(l)return{lineHeight:o.dom.getBoundingClientRect().height,charWidth:l/o.length,textHeight:a}}});if(e)return e;let t=document.createElement("div"),i,s,r;return t.className="cm-line",t.style.width="99999px",t.style.position="absolute",t.textContent="abc def ghi jkl mno pqr stu",this.view.observer.ignore(()=>{this.tile.dom.appendChild(t);let o=Ur(t.firstChild)[0];i=t.getBoundingClientRect().height,s=o&&o.width?o.width/27:7,r=o&&o.height?o.height:i,t.remove()}),{lineHeight:i,charWidth:s,textHeight:r}}computeBlockGapDeco(){let e=[],t=this.view.viewState;for(let i=0,s=0;;s++){let r=s==t.viewports.length?null:t.viewports[s],o=r?r.from-1:this.view.state.doc.length;if(o>i){let l=(t.lineBlockAt(o).bottom-t.lineBlockAt(i).top)/this.view.scaleY;e.push(We.replace({widget:new na(l),block:!0,inclusive:!0,isBlockGap:!0}).range(i,o))}if(!r)break;i=r.to+1}return We.set(e)}updateDeco(){let e=1,t=this.view.state.facet(Ol).map(r=>(this.dynamicDecorationMap[e++]=typeof r=="function")?r(this.view):r),i=!1,s=this.view.state.facet(sf).map((r,o)=>{let l=typeof r=="function";return l&&(i=!0),l?r(this.view):r});for(s.length&&(this.dynamicDecorationMap[e++]=i,t.push(M.join(s))),this.decorations=[this.editContextFormatting,...t,this.computeBlockGapDeco(),this.view.viewState.lineGapDeco];etypeof r=="function"?r(this.view):r)}scrollIntoView(e){if(e.isSnapshot){let h=this.view.viewState.lineBlockAt(e.range.head);this.view.scrollDOM.scrollTop=h.top-e.yMargin,this.view.scrollDOM.scrollLeft=e.xMargin;return}for(let h of this.view.state.facet(NO))try{if(h(this.view,e.range,e))return!0}catch(c){si(this.view.state,c,"scroll handler")}let{range:t}=e,i=this.coordsAt(t.head,t.empty?t.assoc:t.head>t.anchor?-1:1),s;if(!i)return;!t.empty&&(s=this.coordsAt(t.anchor,t.anchor>t.head?-1:1))&&(i={left:Math.min(i.left,s.left),top:Math.min(i.top,s.top),right:Math.max(i.right,s.right),bottom:Math.max(i.bottom,s.bottom)});let r=KO(this.view),o={left:i.left-r.left,top:i.top-r.top,right:i.right+r.right,bottom:i.bottom+r.bottom},{offsetWidth:l,offsetHeight:a}=this.view.scrollDOM;zk(this.view.scrollDOM,o,t.headi.isWidget()||i.children.some(t);return t(this.tile.resolveBlock(e,1).tile)}destroy(){qh(this.tile)}};function qh(n,e){let t=e?.get(n);if(t!=1){t==null&&n.destroy();for(let i of n.children)qh(i,e)}}function gQ(n){return n.node.nodeType==1&&n.node.firstChild&&(n.offset==0||n.node.childNodes[n.offset-1].contentEditable=="false")&&(n.offset==n.node.childNodes.length||n.node.childNodes[n.offset].contentEditable=="false")}function o0(n,e){let t=n.observer.selectionRange;if(!t.focusNode)return null;let i=RO(t.focusNode,t.focusOffset),s=DO(t.focusNode,t.focusOffset),r=i||s;if(s&&i&&s.node!=i.node){let l=pe.get(s.node);if(!l||l.isText()&&l.text!=s.node.nodeValue)r=s;else if(n.docView.lastCompositionAfterCursor){let a=pe.get(i.node);!a||a.isText()&&a.text!=i.node.nodeValue||(r=s)}}if(n.docView.lastCompositionAfterCursor=r!=i,!r)return null;let o=e-r.offset;return{from:o,to:o+r.node.nodeValue.length,node:r.node}}function mQ(n,e,t){let i=o0(n,t);if(!i)return null;let{node:s,from:r,to:o}=i,l=s.nodeValue;if(/[\n\r]/.test(l)||n.state.doc.sliceString(i.from,i.to)!=l)return null;let a=e.invertedDesc;return{range:new ni(a.mapPos(r),a.mapPos(o),r,o),text:s}}function OQ(n,e){return n.nodeType!=1?0:(e&&n.childNodes[e-1].contentEditable=="false"?1:0)|(e{ie.from&&(t=!0)}),t}let na=class extends ml{constructor(e){super(),this.height=e}toDOM(){let e=document.createElement("div");return e.className="cm-gap",this.updateDOM(e),e}eq(e){return e.height==this.height}updateDOM(e){return e.style.height=this.height+"px",!0}get editable(){return!0}get estimatedHeight(){return this.height}ignoreEvent(){return!1}};function QQ(n,e,t=1){let i=n.charCategorizer(e),s=n.doc.lineAt(e),r=e-s.from;if(s.length==0)return S.cursor(e);r==0?t=1:r==s.length&&(t=-1);let o=r,l=r;t<0?o=_(s.text,r,!1):l=_(s.text,r);let a=i(s.text.slice(o,l));for(;o>0;){let h=_(s.text,o,!1);if(i(s.text.slice(h,o))!=a)break;o=h}for(;ln.defaultLineHeight*1.5){let l=n.viewState.heightOracle.textHeight,a=Math.floor((s-t.top-(n.defaultLineHeight-l)*.5)/l);r+=a*n.viewState.heightOracle.lineLength}let o=n.state.sliceDoc(t.from,t.to);return t.from+kn(o,r,n.state.tabSize)}function $Q(n,e,t){let i=n.lineBlockAt(e);if(Array.isArray(i.type)){let s;for(let r of i.type){if(r.from>e)break;if(!(r.toe)return r;(!s||r.type==nt.Text&&(s.type!=r.type||(t<0?r.frome)))&&(s=r)}}return s||i}return i}function PQ(n,e,t,i){let s=$Q(n,e.head,e.assoc||-1),r=!i||s.type!=nt.Text||!(n.lineWrapping||s.widgetLineBreaks)?null:n.coordsAtPos(e.assoc<0&&e.head>s.from?e.head-1:e.head);if(r){let o=n.dom.getBoundingClientRect(),l=n.textDirectionAt(s.from),a=n.posAtCoords({x:t==(l==be.LTR)?o.right-1:o.left+1,y:(r.top+r.bottom)/2});if(a!=null)return S.cursor(a,t?-1:1)}return S.cursor(t?s.to:s.from,t?-1:1)}function gd(n,e,t,i){let s=n.state.doc.lineAt(e.head),r=n.bidiSpans(s),o=n.textDirectionAt(s.from);for(let l=e,a=null;;){let h=Jk(s,r,o,l,t),c=EO;if(!h){if(s.number==(t?n.state.doc.lines:1))return l;c=` `,s=n.state.doc.line(s.number+(t?1:-1)),r=n.bidiSpans(s),h=n.visualLineSide(s,!t)}if(a){if(!a(c))return l}else{if(!i)return h;a=i(c)}l=h}}function CQ(n,e,t){let i=n.state.charCategorizer(e),s=i(t);return r=>{let o=i(r);return s==oe.Space&&(s=o),s==o}}function TQ(n,e,t,i){let s=e.head,r=t?1:-1;if(s==(t?n.state.doc.length:0))return S.cursor(s,e.assoc);let o=e.goalColumn,l,a=n.contentDOM.getBoundingClientRect(),h=n.coordsAtPos(s,(e.empty?e.assoc:0)||(t?1:-1)),c=n.documentTop;if(h)o==null&&(o=h.left-a.left),l=r<0?h.top:h.bottom;else{let p=n.viewState.lineBlockAt(s);o==null&&(o=Math.min(a.right-a.left,n.defaultCharacterWidth*(s-p.from))),l=(r<0?p.top:p.bottom)+c}let f=a.left+o,u=i??n.viewState.heightOracle.textHeight>>1,d=Ih(n,{x:f,y:l+u*r},!1,r);return S.cursor(d.pos,d.assoc,void 0,o)}function On(n,e,t){for(;;){let i=0;for(let s of n)s.between(e-1,e+1,(r,o,l)=>{if(e>r&&es(n)),t.from,e.head>t.from?-1:1);return i==t.from?t:S.cursor(i,in.viewState.docHeight)return new Lt(n.state.doc.length,-1);if(h=n.elementAtHeight(a),i==null)break;if(h.type==nt.Text){if(i<0?h.ton.viewport.to)break;let u=n.docView.coordsAt(i<0?h.from:h.to,i>0?-1:1);if(u&&(i<0?u.top<=a+r:u.bottom>=a+r))break}let f=n.viewState.heightOracle.textHeight/2;a=i>0?h.bottom+f:h.top-f}if(n.viewport.from>=h.to||n.viewport.to<=h.from){if(t)return null;if(h.type==nt.Text){let f=vQ(n,s,h,o,l);return new Lt(f,f==h.from?1:-1)}}if(h.type!=nt.Text)return a<(h.top+h.bottom)/2?new Lt(h.from,1):new Lt(h.to,-1);let c=n.docView.lineAt(h.from,2);return(!c||c.length!=h.length)&&(c=n.docView.lineAt(h.from,-2)),a0(n,c,h.from,o,l)}function a0(n,e,t,i,s){let r=-1,o=null,l=1e9,a=1e9,h=s,c=s,f=(u,d)=>{for(let p=0;pi?g.left-i:g.rights?g.top-s:g.bottom=h&&(h=Math.min(g.top,h),c=Math.max(g.bottom,c),O=0),(r<0||(O-a||m-l)<0)&&(r>=0&&a&&l=h+2?a=0:(r=d,l=m,a=O,o=g))}};if(e.isText()){for(let d=0;d(o.left+o.right)/2==(md(n,r+t)==be.LTR)?new Lt(t+_(e.text,r),-1):new Lt(t+r,1)}else{if(!e.length)return new Lt(t,1);for(let g=0;g(o.left+o.right)/2==(md(n,r+t)==be.LTR)?new Lt(d+u.length,-1):new Lt(d,1)}}function md(n,e){let t=n.state.doc.lineAt(e);return n.bidiSpans(t)[ii.find(n.bidiSpans(t),e-t.from,-1,1)].dir}const sn="￿";let MQ=class{constructor(e,t){this.points=e,this.view=t,this.text="",this.lineSeparator=t.state.facet(L.lineSeparator)}append(e){this.text+=e}lineBreak(){this.text+=sn}readRange(e,t){if(!e)return this;let i=e.parentNode;for(let s=e;;){this.findPointBefore(i,s);let r=this.text.length;this.readNode(s);let o=pe.get(s),l=s.nextSibling;if(l==t){o?.breakAfter&&!l&&i!=this.view.contentDOM&&this.lineBreak();break}let a=pe.get(l);(o&&a?o.breakAfter:(o?o.breakAfter:vo(s))||vo(l)&&(s.nodeName!="BR"||o?.isWidget())&&this.text.length>r)&&!RQ(l,t)&&this.lineBreak(),s=l}return this.findPointBefore(i,t),this}readTextNode(e){let t=e.nodeValue;for(let i of this.points)i.node==e&&(i.pos=this.text.length+Math.min(i.offset,t.length));for(let i=0,s=this.lineSeparator?null:/\r\n?|\n/g;;){let r=-1,o=1,l;if(this.lineSeparator?(r=t.indexOf(this.lineSeparator,i),o=this.lineSeparator.length):(l=s.exec(t))&&(r=l.index,o=l[0].length),this.append(t.slice(i,r<0?t.length:r)),r<0)break;if(this.lineBreak(),o>1)for(let a of this.points)a.node==e&&a.pos>this.text.length&&(a.pos-=o-1);i=r+o}}readNode(e){let t=pe.get(e),i=t&&t.overrideDOMText;if(i!=null){this.findPointInside(e,i.length);for(let s=i.iter();!s.next().done;)s.lineBreak?this.lineBreak():this.append(s.value)}else e.nodeType==3?this.readTextNode(e):e.nodeName=="BR"?e.nextSibling&&this.lineBreak():e.nodeType==1&&this.readRange(e.firstChild,null)}findPointBefore(e,t){for(let i of this.points)i.node==e&&e.childNodes[i.offset]==t&&(i.pos=this.text.length)}findPointInside(e,t){for(let i of this.points)(e.nodeType==3?i.node==e:e.contains(i.node))&&(i.pos=this.text.length+(AQ(e,i.node,i.offset)?t:0))}};function AQ(n,e,t){for(;;){if(!e||t-1;let{impreciseHead:r,impreciseAnchor:o}=e.docView;if(e.state.readOnly&&t>-1)this.newSel=null;else if(t>-1&&(this.bounds=h0(e.docView.tile,t,i,0))){let l=r||o?[]:BQ(e),a=new MQ(l,e);a.readRange(this.bounds.startDOM,this.bounds.endDOM),this.text=a.text,this.newSel=XQ(l,this.bounds.from)}else{let l=e.observer.selectionRange,a=r&&r.node==l.focusNode&&r.offset==l.focusOffset||!Dh(e.contentDOM,l.focusNode)?e.state.selection.main.head:e.docView.posFromDOM(l.focusNode,l.focusOffset),h=o&&o.node==l.anchorNode&&o.offset==l.anchorOffset||!Dh(e.contentDOM,l.anchorNode)?e.state.selection.main.anchor:e.docView.posFromDOM(l.anchorNode,l.anchorOffset),c=e.viewport;if((A.ios||A.chrome)&&e.state.selection.main.empty&&a!=h&&(c.from>0||c.to-1&&e.state.selection.ranges.length>1?this.newSel=e.state.selection.replaceRange(S.range(h,a)):this.newSel=S.single(h,a)}}};function h0(n,e,t,i){if(n.isComposite()){let s=-1,r=-1,o=-1,l=-1;for(let a=0,h=i,c=i;at)return h0(f,e,t,h);if(u>=e&&s==-1&&(s=a,r=h),h>t&&f.dom.parentNode==n.dom){o=a,l=c;break}c=u,h=u+f.breakAfter}return{from:r,to:l<0?i+n.length:l,startDOM:(s?n.children[s-1].dom.nextSibling:null)||n.dom.firstChild,endDOM:o=0?n.children[o].dom:null}}else return n.isText()?{from:i,to:i+n.length,startDOM:n.dom,endDOM:n.dom.nextSibling}:null}function c0(n,e){let t,{newSel:i}=e,s=n.state.selection.main,r=n.inputState.lastKeyTime>Date.now()-100?n.inputState.lastKeyCode:-1;if(e.bounds){let{from:o,to:l}=e.bounds,a=s.from,h=null;(r===8||A.android&&e.text.length=s.from&&t.to<=s.to&&(t.from!=s.from||t.to!=s.to)&&s.to-s.from-(t.to-t.from)<=4?t={from:s.from,to:s.to,insert:n.state.doc.slice(s.from,t.from).append(t.insert).append(n.state.doc.slice(t.to,s.to))}:n.state.doc.lineAt(s.from).toDate.now()-50?t={from:s.from,to:s.to,insert:n.state.toText(n.inputState.insertingText)}:A.chrome&&t&&t.from==t.to&&t.from==s.head&&t.insert.toString()==` `&&n.lineWrapping&&(i&&(i=S.single(i.main.anchor-1,i.main.head-1)),t={from:s.from,to:s.to,insert:Z.of([" "])}),t)return nf(n,t,i,r);if(i&&!Mo(i,s)){let o=!1,l="select";return n.inputState.lastSelectionTime>Date.now()-50&&(n.inputState.lastSelectionOrigin=="select"&&(o=!0),l=n.inputState.lastSelectionOrigin,l=="select.pointer"&&(i=l0(n.state.facet(Gn).map(a=>a(n)),i))),n.dispatch({selection:i,scrollIntoView:o,userEvent:l}),!0}else return!1}function nf(n,e,t,i=-1){if(A.ios&&n.inputState.flushIOSKey(e))return!0;let s=n.state.selection.main;if(A.android&&(e.to==s.to&&(e.from==s.from||e.from==s.from-1&&n.state.sliceDoc(e.from,s.from)==" ")&&e.insert.length==1&&e.insert.lines==2&&as(n.contentDOM,"Enter",13)||(e.from==s.from-1&&e.to==s.to&&e.insert.length==0||i==8&&e.insert.lengths.head)&&as(n.contentDOM,"Backspace",8)||e.from==s.from&&e.to==s.to+1&&e.insert.length==0&&as(n.contentDOM,"Delete",46)))return!0;let r=e.insert.toString();n.inputState.composing>=0&&n.inputState.composing++;let o,l=()=>o||(o=ZQ(n,e,t));return n.state.facet(IO).some(a=>a(n,e.from,e.to,r,l))||n.dispatch(l()),!0}function ZQ(n,e,t){let i,s=n.state,r=s.selection.main,o=-1;if(e.from==e.to&&e.fromr.to){let a=e.fromf(n)),h,a);e.from==c&&(o=c)}if(o>-1)i={changes:e,selection:S.cursor(e.from+e.insert.length,-1)};else if(e.from>=r.from&&e.to<=r.to&&e.to-e.from>=(r.to-r.from)/3&&(!t||t.main.empty&&t.main.from==e.from+e.insert.length)&&n.inputState.composing<0){let a=r.frome.to?s.sliceDoc(e.to,r.to):"";i=s.replaceSelection(n.state.toText(a+e.insert.sliceString(0,void 0,n.state.lineBreak)+h))}else{let a=s.changes(e),h=t&&t.main.to<=a.newLength?t.main:void 0;if(s.selection.ranges.length>1&&(n.inputState.composing>=0||n.inputState.compositionPendingChange)&&e.to<=r.to+10&&e.to>=r.to-10){let c=n.state.sliceDoc(e.from,e.to),f,u=t&&o0(n,t.main.head);if(u){let p=e.insert.length-(e.to-e.from);f={from:u.from,to:u.to-p}}else f=n.state.doc.lineAt(r.head);let d=r.to-e.to;i=s.changeByRange(p=>{if(p.from==r.from&&p.to==r.to)return{changes:a,range:h||p.map(a)};let g=p.to-d,m=g-c.length;if(n.state.sliceDoc(m,g)!=c||g>=f.from&&m<=f.to)return{range:p};let O=s.changes({from:m,to:g,insert:e.insert}),y=p.to-r.to;return{changes:O,range:h?S.range(Math.max(0,h.anchor+y),Math.max(0,h.head+y)):p.map(O)}})}else i={changes:a,selection:h&&s.selection.replaceRange(h)}}let l="input.type";return(n.composing||n.inputState.compositionPendingChange&&n.inputState.compositionEndedAt>Date.now()-50)&&(n.inputState.compositionPendingChange=!1,l+=".compose",n.inputState.compositionFirstChange&&(l+=".start",n.inputState.compositionFirstChange=!1)),s.update(i,{userEvent:l,scrollIntoView:!0})}function f0(n,e,t,i){let s=Math.min(n.length,e.length),r=0;for(;r0&&l>0&&n.charCodeAt(o-1)==e.charCodeAt(l-1);)o--,l--;if(i=="end"){let a=Math.max(0,r-Math.min(o,l));t-=o+a-r}if(o=o?r-t:0;r-=a,l=r+(l-o),o=r}else if(l=l?r-t:0;r-=a,o=r+(o-l),l=r}return{from:r,toA:o,toB:l}}function BQ(n){let e=[];if(n.root.activeElement!=n.contentDOM)return e;let{anchorNode:t,anchorOffset:i,focusNode:s,focusOffset:r}=n.observer.selectionRange;return t&&(e.push(new Od(t,i)),(s!=t||r!=i)&&e.push(new Od(s,r))),e}function XQ(n,e){if(n.length==0)return null;let t=n[0].pos,i=n.length==2?n[1].pos:t;return t>-1&&i>-1?S.single(t+e,i+e):null}function Mo(n,e){return e.head==n.main.head&&e.anchor==n.main.anchor}let LQ=class{setSelectionOrigin(e){this.lastSelectionOrigin=e,this.lastSelectionTime=Date.now()}constructor(e){this.view=e,this.lastKeyCode=0,this.lastKeyTime=0,this.lastTouchTime=0,this.lastFocusTime=0,this.lastScrollTop=0,this.lastScrollLeft=0,this.pendingIOSKey=void 0,this.tabFocusMode=-1,this.lastSelectionOrigin=null,this.lastSelectionTime=0,this.lastContextMenu=0,this.scrollHandlers=[],this.handlers=Object.create(null),this.composing=-1,this.compositionFirstChange=null,this.compositionEndedAt=0,this.compositionPendingKey=!1,this.compositionPendingChange=!1,this.insertingText="",this.insertingTextAt=0,this.mouseSelection=null,this.draggedContent=null,this.handleEvent=this.handleEvent.bind(this),this.notifiedFocused=e.hasFocus,A.safari&&e.contentDOM.addEventListener("input",()=>null),A.gecko&&JQ(e.contentDOM.ownerDocument)}handleEvent(e){!NQ(this.view,e)||this.ignoreDuringComposition(e)||e.type=="keydown"&&this.keydown(e)||(this.view.updateState!=0?Promise.resolve().then(()=>this.runHandlers(e.type,e)):this.runHandlers(e.type,e))}runHandlers(e,t){let i=this.handlers[e];if(i){for(let s of i.observers)s(this.view,t);for(let s of i.handlers){if(t.defaultPrevented)break;if(s(this.view,t)){t.preventDefault();break}}}}ensureHandlers(e){let t=EQ(e),i=this.handlers,s=this.view.contentDOM;for(let r in t)if(r!="scroll"){let o=!t[r].handlers.length,l=i[r];l&&o!=!l.handlers.length&&(s.removeEventListener(r,this.handleEvent),l=null),l||s.addEventListener(r,this.handleEvent,{passive:o})}for(let r in i)r!="scroll"&&!t[r]&&s.removeEventListener(r,this.handleEvent);this.handlers=t}keydown(e){if(this.lastKeyCode=e.keyCode,this.lastKeyTime=Date.now(),e.keyCode==9&&this.tabFocusMode>-1&&(!this.tabFocusMode||Date.now()<=this.tabFocusMode))return!0;if(this.tabFocusMode>0&&e.keyCode!=27&&VQ.indexOf(e.keyCode)<0&&(this.tabFocusMode=-1),A.android&&A.chrome&&!e.synthetic&&(e.keyCode==13||e.keyCode==8))return this.view.observer.delayAndroidKey(e.key,e.keyCode),!0;let t;return A.ios&&!e.synthetic&&!e.altKey&&!e.metaKey&&((t=u0.find(i=>i.keyCode==e.keyCode))&&!e.ctrlKey||WQ.indexOf(e.key)>-1&&e.ctrlKey&&!e.shiftKey)?(this.pendingIOSKey=t||e,setTimeout(()=>this.flushIOSKey(),250),!0):(e.keyCode!=229&&this.view.observer.forceFlush(),!1)}flushIOSKey(e){let t=this.pendingIOSKey;return!t||t.key=="Enter"&&e&&e.from0?!0:A.safari&&!A.ios&&this.compositionPendingKey&&Date.now()-this.compositionEndedAt<100?(this.compositionPendingKey=!1,!0):!1}startMouseSelection(e){this.mouseSelection&&this.mouseSelection.destroy(),this.mouseSelection=e}update(e){this.view.observer.update(e),this.mouseSelection&&this.mouseSelection.update(e),this.draggedContent&&e.docChanged&&(this.draggedContent=this.draggedContent.map(e.changes)),e.transactions.length&&(this.lastKeyCode=this.lastSelectionTime=0)}destroy(){this.mouseSelection&&this.mouseSelection.destroy()}};function bd(n,e){return(t,i)=>{try{return e.call(n,i,t)}catch(s){si(t.state,s)}}}function EQ(n){let e=Object.create(null);function t(i){return e[i]||(e[i]={observers:[],handlers:[]})}for(let i of n){let s=i.spec,r=s&&s.plugin.domEventHandlers,o=s&&s.plugin.domEventObservers;if(r)for(let l in r){let a=r[l];a&&t(l).handlers.push(bd(i.value,a))}if(o)for(let l in o){let a=o[l];a&&t(l).observers.push(bd(i.value,a))}}for(let i in St)t(i).handlers.push(St[i]);for(let i in lt)t(i).observers.push(lt[i]);return e}const u0=[{key:"Backspace",keyCode:8,inputType:"deleteContentBackward"},{key:"Enter",keyCode:13,inputType:"insertParagraph"},{key:"Enter",keyCode:13,inputType:"insertLineBreak"},{key:"Delete",keyCode:46,inputType:"deleteContentForward"}],WQ="dthko",VQ=[16,17,18,20,91,92,224,225],xr=6;function wr(n){return Math.max(0,n)*.7+8}function zQ(n,e){return Math.max(Math.abs(n.clientX-e.clientX),Math.abs(n.clientY-e.clientY))}let qQ=class{constructor(e,t,i,s){this.view=e,this.startEvent=t,this.style=i,this.mustSelect=s,this.scrollSpeed={x:0,y:0},this.scrolling=-1,this.lastEvent=t,this.scrollParents=qk(e.contentDOM),this.atoms=e.state.facet(Gn).map(o=>o(e));let r=e.contentDOM.ownerDocument;r.addEventListener("mousemove",this.move=this.move.bind(this)),r.addEventListener("mouseup",this.up=this.up.bind(this)),this.extend=t.shiftKey,this.multiple=e.state.facet(L.allowMultipleSelections)&&IQ(e,t),this.dragging=YQ(e,t)&&g0(t)==1?null:!1}start(e){this.dragging===!1&&this.select(e)}move(e){if(e.buttons==0)return this.destroy();if(this.dragging||this.dragging==null&&zQ(this.startEvent,e)<10)return;this.select(this.lastEvent=e);let t=0,i=0,s=0,r=0,o=this.view.win.innerWidth,l=this.view.win.innerHeight;this.scrollParents.x&&({left:s,right:o}=this.scrollParents.x.getBoundingClientRect()),this.scrollParents.y&&({top:r,bottom:l}=this.scrollParents.y.getBoundingClientRect());let a=KO(this.view);e.clientX-a.left<=s+xr?t=-wr(s-e.clientX):e.clientX+a.right>=o-xr&&(t=wr(e.clientX-o)),e.clientY-a.top<=r+xr?i=-wr(r-e.clientY):e.clientY+a.bottom>=l-xr&&(i=wr(e.clientY-l)),this.setScrollSpeed(t,i)}up(e){this.dragging==null&&this.select(this.lastEvent),this.dragging||e.preventDefault(),this.destroy()}destroy(){this.setScrollSpeed(0,0);let e=this.view.contentDOM.ownerDocument;e.removeEventListener("mousemove",this.move),e.removeEventListener("mouseup",this.up),this.view.inputState.mouseSelection=this.view.inputState.draggedContent=null}setScrollSpeed(e,t){this.scrollSpeed={x:e,y:t},e||t?this.scrolling<0&&(this.scrolling=setInterval(()=>this.scroll(),50)):this.scrolling>-1&&(clearInterval(this.scrolling),this.scrolling=-1)}scroll(){let{x:e,y:t}=this.scrollSpeed;e&&this.scrollParents.x&&(this.scrollParents.x.scrollLeft+=e,e=0),t&&this.scrollParents.y&&(this.scrollParents.y.scrollTop+=t,t=0),(e||t)&&this.view.win.scrollBy(e,t),this.dragging===!1&&this.select(this.lastEvent)}select(e){let{view:t}=this,i=l0(this.atoms,this.style.get(e,this.extend,this.multiple));(this.mustSelect||!i.eq(t.state.selection,this.dragging===!1))&&this.view.dispatch({selection:i,userEvent:"select.pointer"}),this.mustSelect=!1}update(e){e.transactions.some(t=>t.isUserEvent("input.type"))?this.destroy():this.style.update(e)&&setTimeout(()=>this.select(this.lastEvent),20)}};function IQ(n,e){let t=n.state.facet(WO);return t.length?t[0](e):A.mac?e.metaKey:e.ctrlKey}function _Q(n,e){let t=n.state.facet(VO);return t.length?t[0](e):A.mac?!e.altKey:!e.ctrlKey}function YQ(n,e){let{main:t}=n.state.selection;if(t.empty)return!1;let i=ks(n.root);if(!i||i.rangeCount==0)return!0;let s=i.getRangeAt(0).getClientRects();for(let r=0;r=e.clientX&&o.top<=e.clientY&&o.bottom>=e.clientY)return!0}return!1}function NQ(n,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;for(let t=e.target,i;t!=n.contentDOM;t=t.parentNode)if(!t||t.nodeType==11||(i=pe.get(t))&&i.isWidget()&&!i.isHidden&&i.widget.ignoreEvent(e))return!1;return!0}const St=Object.create(null),lt=Object.create(null),d0=A.ie&&A.ie_version<15||A.ios&&A.webkit_version<604;function jQ(n){let e=n.dom.parentNode;if(!e)return;let t=e.appendChild(document.createElement("textarea"));t.style.cssText="position: fixed; left: -10000px; top: 10px",t.focus(),setTimeout(()=>{n.focus(),t.remove(),p0(n,t.value)},50)}function yl(n,e,t){for(let i of n.facet(e))t=i(t,n);return t}function p0(n,e){e=yl(n.state,Jc,e);let{state:t}=n,i,s=1,r=t.toText(e),o=r.lines==t.selection.ranges.length;if(_h!=null&&t.selection.ranges.every(a=>a.empty)&&_h==r.toString()){let a=-1;i=t.changeByRange(h=>{let c=t.doc.lineAt(h.from);if(c.from==a)return{range:h};a=c.from;let f=t.toText((o?r.line(s++).text:e)+t.lineBreak);return{changes:{from:c.from,insert:f},range:S.cursor(h.from+f.length)}})}else o?i=t.changeByRange(a=>{let h=r.line(s++);return{changes:{from:a.from,to:a.to,insert:h.text},range:S.cursor(a.from+h.length)}}):i=t.replaceSelection(r);n.dispatch(i,{userEvent:"input.paste",scrollIntoView:!0})}lt.scroll=n=>{n.inputState.lastScrollTop=n.scrollDOM.scrollTop,n.inputState.lastScrollLeft=n.scrollDOM.scrollLeft};St.keydown=(n,e)=>(n.inputState.setSelectionOrigin("select"),e.keyCode==27&&n.inputState.tabFocusMode!=0&&(n.inputState.tabFocusMode=Date.now()+2e3),!1);lt.touchstart=(n,e)=>{n.inputState.lastTouchTime=Date.now(),n.inputState.setSelectionOrigin("select.pointer")};lt.touchmove=n=>{n.inputState.setSelectionOrigin("select.pointer")};St.mousedown=(n,e)=>{if(n.observer.flush(),n.inputState.lastTouchTime>Date.now()-2e3)return!1;let t=null;for(let i of n.state.facet(zO))if(t=i(n,e),t)break;if(!t&&e.button==0&&(t=HQ(n,e)),t){let i=!n.hasFocus;n.inputState.startMouseSelection(new qQ(n,e,t,i)),i&&n.observer.ignore(()=>{MO(n.contentDOM);let r=n.root.activeElement;r&&!r.contains(n.contentDOM)&&r.blur()});let s=n.inputState.mouseSelection;if(s)return s.start(e),s.dragging===!1}else n.inputState.setSelectionOrigin("select.pointer");return!1};function Sd(n,e,t,i){if(i==1)return S.cursor(e,t);if(i==2)return QQ(n.state,e,t);{let s=n.docView.lineAt(e,t),r=n.state.doc.lineAt(s?s.posAtEnd:e),o=s?s.posAtStart:r.from,l=s?s.posAtEnd:r.to;return lDate.now()-400&&Math.abs(e.clientX-n.clientX)<2&&Math.abs(e.clientY-n.clientY)<2?(xd+1)%3:1}function HQ(n,e){let t=n.posAndSideAtCoords({x:e.clientX,y:e.clientY},!1),i=g0(e),s=n.state.selection;return{update(r){r.docChanged&&(t.pos=r.changes.mapPos(t.pos),s=s.map(r.changes))},get(r,o,l){let a=n.posAndSideAtCoords({x:r.clientX,y:r.clientY},!1),h,c=Sd(n,a.pos,a.assoc,i);if(t.pos!=a.pos&&!o){let f=Sd(n,t.pos,t.assoc,i),u=Math.min(f.from,c.from),d=Math.max(f.to,c.to);c=u1&&(h=FQ(s,a.pos))?h:l?s.addRange(c):S.create([c])}}}function FQ(n,e){for(let t=0;t=e)return S.create(n.ranges.slice(0,t).concat(n.ranges.slice(t+1)),n.mainIndex==t?0:n.mainIndex-(n.mainIndex>t?1:0))}return null}St.dragstart=(n,e)=>{let{selection:{main:t}}=n.state;if(e.target.draggable){let s=n.docView.tile.nearest(e.target);if(s&&s.isWidget()){let r=s.posAtStart,o=r+s.length;(r>=t.to||o<=t.from)&&(t=S.range(r,o))}}let{inputState:i}=n;return i.mouseSelection&&(i.mouseSelection.dragging=!0),i.draggedContent=t,e.dataTransfer&&(e.dataTransfer.setData("Text",yl(n.state,ef,n.state.sliceDoc(t.from,t.to))),e.dataTransfer.effectAllowed="copyMove"),!1};St.dragend=n=>(n.inputState.draggedContent=null,!1);function kd(n,e,t,i){if(t=yl(n.state,Jc,t),!t)return;let s=n.posAtCoords({x:e.clientX,y:e.clientY},!1),{draggedContent:r}=n.inputState,o=i&&r&&_Q(n,e)?{from:r.from,to:r.to}:null,l={from:s,insert:t},a=n.state.changes(o?[o,l]:l);n.focus(),n.dispatch({changes:a,selection:{anchor:a.mapPos(s,-1),head:a.mapPos(s,1)},userEvent:o?"move.drop":"input.drop"}),n.inputState.draggedContent=null}St.drop=(n,e)=>{if(!e.dataTransfer)return!1;if(n.state.readOnly)return!0;let t=e.dataTransfer.files;if(t&&t.length){let i=Array(t.length),s=0,r=()=>{++s==t.length&&kd(n,e,i.filter(o=>o!=null).join(n.state.lineBreak),!1)};for(let o=0;o{/[\x00-\x08\x0e-\x1f]{2}/.test(l.result)||(i[o]=l.result),r()},l.readAsText(t[o])}return!0}else{let i=e.dataTransfer.getData("Text");if(i)return kd(n,e,i,!0),!0}return!1};St.paste=(n,e)=>{if(n.state.readOnly)return!0;n.observer.flush();let t=d0?null:e.clipboardData;return t?(p0(n,t.getData("text/plain")||t.getData("text/uri-list")),!0):(jQ(n),!1)};function UQ(n,e){let t=n.dom.parentNode;if(!t)return;let i=t.appendChild(document.createElement("textarea"));i.style.cssText="position: fixed; left: -10000px; top: 10px",i.value=e,i.focus(),i.selectionEnd=e.length,i.selectionStart=0,setTimeout(()=>{i.remove(),n.focus()},50)}function KQ(n){let e=[],t=[],i=!1;for(let s of n.selection.ranges)s.empty||(e.push(n.sliceDoc(s.from,s.to)),t.push(s));if(!e.length){let s=-1;for(let{from:r}of n.selection.ranges){let o=n.doc.lineAt(r);o.number>s&&(e.push(o.text),t.push({from:o.from,to:Math.min(n.doc.length,o.to+1)})),s=o.number}i=!0}return{text:yl(n,ef,e.join(n.lineBreak)),ranges:t,linewise:i}}let _h=null;St.copy=St.cut=(n,e)=>{let t=ks(n.root);if(t&&!gn(n.contentDOM,t))return!1;let{text:i,ranges:s,linewise:r}=KQ(n.state);if(!i&&!r)return!1;_h=r?i:null,e.type=="cut"&&!n.state.readOnly&&n.dispatch({changes:s,scrollIntoView:!0,userEvent:"delete.cut"});let o=d0?null:e.clipboardData;return o?(o.clearData(),o.setData("text/plain",i),!0):(UQ(n,i),!1)};const m0=wt.define();function O0(n,e){let t=[];for(let i of n.facet(_O)){let s=i(n,e);s&&t.push(s)}return t.length?n.update({effects:t,annotations:m0.of(!0)}):null}function b0(n){setTimeout(()=>{let e=n.hasFocus;if(e!=n.inputState.notifiedFocused){let t=O0(n.state,e);t?n.dispatch(t):n.update([])}},10)}lt.focus=n=>{n.inputState.lastFocusTime=Date.now(),!n.scrollDOM.scrollTop&&(n.inputState.lastScrollTop||n.inputState.lastScrollLeft)&&(n.scrollDOM.scrollTop=n.inputState.lastScrollTop,n.scrollDOM.scrollLeft=n.inputState.lastScrollLeft),b0(n)};lt.blur=n=>{n.observer.clearSelectionRange(),b0(n)};lt.compositionstart=lt.compositionupdate=n=>{n.observer.editContext||(n.inputState.compositionFirstChange==null&&(n.inputState.compositionFirstChange=!0),n.inputState.composing<0&&(n.inputState.composing=0))};lt.compositionend=n=>{n.observer.editContext||(n.inputState.composing=-1,n.inputState.compositionEndedAt=Date.now(),n.inputState.compositionPendingKey=!0,n.inputState.compositionPendingChange=n.observer.pendingRecords().length>0,n.inputState.compositionFirstChange=null,A.chrome&&A.android?n.observer.flushSoon():n.inputState.compositionPendingChange?Promise.resolve().then(()=>n.observer.flush()):setTimeout(()=>{n.inputState.composing<0&&n.docView.hasComposition&&n.update([])},50))};lt.contextmenu=n=>{n.inputState.lastContextMenu=Date.now()};St.beforeinput=(n,e)=>{var t,i;if((e.inputType=="insertText"||e.inputType=="insertCompositionText")&&(n.inputState.insertingText=e.data,n.inputState.insertingTextAt=Date.now()),e.inputType=="insertReplacementText"&&n.observer.editContext){let r=(t=e.dataTransfer)===null||t===void 0?void 0:t.getData("text/plain"),o=e.getTargetRanges();if(r&&o.length){let l=o[0],a=n.posAtDOM(l.startContainer,l.startOffset),h=n.posAtDOM(l.endContainer,l.endOffset);return nf(n,{from:a,to:h,insert:n.state.toText(r)},null),!0}}let s;if(A.chrome&&A.android&&(s=u0.find(r=>r.inputType==e.inputType))&&(n.observer.delayAndroidKey(s.key,s.keyCode),s.key=="Backspace"||s.key=="Delete")){let r=((i=window.visualViewport)===null||i===void 0?void 0:i.height)||0;setTimeout(()=>{var o;(((o=window.visualViewport)===null||o===void 0?void 0:o.height)||0)>r+10&&n.hasFocus&&(n.contentDOM.blur(),n.focus())},100)}return A.ios&&e.inputType=="deleteContentForward"&&n.observer.flushSoon(),A.safari&&e.inputType=="insertText"&&n.inputState.composing>=0&&setTimeout(()=>lt.compositionend(n,e),20),!1};const Qd=new Set;function JQ(n){Qd.has(n)||(Qd.add(n),n.addEventListener("copy",()=>{}),n.addEventListener("cut",()=>{}))}const vd=["pre-wrap","normal","pre-line","break-spaces"];let vs=!1;function $d(){vs=!1}let ev=class{constructor(e){this.lineWrapping=e,this.doc=Z.empty,this.heightSamples={},this.lineHeight=14,this.charWidth=7,this.textHeight=14,this.lineLength=30}heightForGap(e,t){let i=this.doc.lineAt(t).number-this.doc.lineAt(e).number+1;return this.lineWrapping&&(i+=Math.max(0,Math.ceil((t-e-i*this.lineLength*.5)/this.lineLength))),this.lineHeight*i}heightForLine(e){return this.lineWrapping?(1+Math.max(0,Math.ceil((e-this.lineLength)/Math.max(1,this.lineLength-5))))*this.lineHeight:this.lineHeight}setDoc(e){return this.doc=e,this}mustRefreshForWrapping(e){return vd.indexOf(e)>-1!=this.lineWrapping}mustRefreshForHeights(e){let t=!1;for(let i=0;i-1,a=Math.abs(t-this.lineHeight)>.3||this.lineWrapping!=l||Math.abs(i-this.charWidth)>.1;if(this.lineWrapping=l,this.lineHeight=t,this.charWidth=i,this.textHeight=s,this.lineLength=r,a){this.heightSamples={};for(let h=0;h0}set outdated(e){this.flags=(e?2:0)|this.flags&-3}setHeight(e){this.height!=e&&(Math.abs(this.height-e)>Kr&&(vs=!0),this.height=e)}replace(e,t,i){return Jr.of(i)}decomposeLeft(e,t){t.push(this)}decomposeRight(e,t){t.push(this)}applyChanges(e,t,i,s){let r=this,o=i.doc;for(let l=s.length-1;l>=0;l--){let{fromA:a,toA:h,fromB:c,toB:f}=s[l],u=r.lineAt(a,ne.ByPosNoHeight,i.setDoc(t),0,0),d=u.to>=h?u:r.lineAt(h,ne.ByPosNoHeight,i,0,0);for(f+=d.to-h,h=d.to;l>0&&u.from<=s[l-1].toA;)a=s[l-1].fromA,c=s[l-1].fromB,l--,ar*2){let l=e[t-1];l.break?e.splice(--t,1,l.left,null,l.right):e.splice(--t,1,l.left,l.right),i+=1+l.break,s-=l.size}else if(r>s*2){let l=e[i];l.break?e.splice(i,1,l.left,null,l.right):e.splice(i,1,l.left,l.right),i+=2+l.break,r-=l.size}else break;else if(s=r&&o(this.lineAt(0,ne.ByPos,i,s,r))}setMeasuredHeight(e){let t=e.heights[e.index++];t<0?(this.spaceAbove=-t,t=e.heights[e.index++]):this.spaceAbove=0,this.setHeight(t)}updateHeight(e,t=0,i=!1,s){return s&&s.from<=t&&s.more&&this.setMeasuredHeight(s),this.outdated=!1,this}toString(){return`block(${this.length})`}},Zt=class Yh extends y0{constructor(e,t,i){super(e,t,null),this.collapsed=0,this.widgetHeight=0,this.breaks=0,this.spaceAbove=i}mainBlock(e,t){return new Kt(t,this.length,e+this.spaceAbove,this.height-this.spaceAbove,this.breaks)}replace(e,t,i){let s=i[0];return i.length==1&&(s instanceof Yh||s instanceof cs&&s.flags&4)&&Math.abs(this.length-s.length)<10?(s instanceof cs?s=new Yh(s.length,this.height,this.spaceAbove):s.height=this.height,this.outdated||(s.outdated=!1),s):Ot.of(i)}updateHeight(e,t=0,i=!1,s){return s&&s.from<=t&&s.more?this.setMeasuredHeight(s):(i||this.outdated)&&(this.spaceAbove=0,this.setHeight(Math.max(this.widgetHeight,e.heightForLine(this.length-this.collapsed))+this.breaks*e.lineHeight)),this.outdated=!1,this}toString(){return`line(${this.length}${this.collapsed?-this.collapsed:""}${this.widgetHeight?":"+this.widgetHeight:""})`}},cs=class ft extends Ot{constructor(e){super(e,0)}heightMetrics(e,t){let i=e.doc.lineAt(t).number,s=e.doc.lineAt(t+this.length).number,r=s-i+1,o,l=0;if(e.lineWrapping){let a=Math.min(this.height,e.lineHeight*r);o=a/r,this.length>r+1&&(l=(this.height-a)/(this.length-r-1))}else o=this.height/r;return{firstLine:i,lastLine:s,perLine:o,perChar:l}}blockAt(e,t,i,s){let{firstLine:r,lastLine:o,perLine:l,perChar:a}=this.heightMetrics(t,s);if(t.lineWrapping){let h=s+(e0){let r=i[i.length-1];r instanceof ft?i[i.length-1]=new ft(r.length+s):i.push(null,new ft(s-1))}if(e>0){let r=i[0];r instanceof ft?i[0]=new ft(e+r.length):i.unshift(new ft(e-1),null)}return Ot.of(i)}decomposeLeft(e,t){t.push(new ft(e-1),null)}decomposeRight(e,t){t.push(null,new ft(this.length-e-1))}updateHeight(e,t=0,i=!1,s){let r=t+this.length;if(s&&s.from<=t+this.length&&s.more){let o=[],l=Math.max(t,s.from),a=-1;for(s.from>t&&o.push(new ft(s.from-t-1).updateHeight(e,t));l<=r&&s.more;){let c=e.doc.lineAt(l).length;o.length&&o.push(null);let f=s.heights[s.index++],u=0;f<0&&(u=-f,f=s.heights[s.index++]),a==-1?a=f:Math.abs(f-a)>=Kr&&(a=-2);let d=new Zt(c,f,u);d.outdated=!1,o.push(d),l+=c+1}l<=r&&o.push(null,new ft(r-l).updateHeight(e,l));let h=Ot.of(o);return(a<0||Math.abs(h.height-this.height)>=Kr||Math.abs(a-this.heightMetrics(e,t).perLine)>=Kr)&&(vs=!0),Ao(this,h)}else(i||this.outdated)&&(this.setHeight(e.heightForGap(t,t+this.length)),this.outdated=!1);return this}toString(){return`gap(${this.length})`}},sv=class extends Ot{constructor(e,t,i){super(e.length+t+i.length,e.height+i.height,t|(e.outdated||i.outdated?2:0)),this.left=e,this.right=i,this.size=e.size+i.size}get break(){return this.flags&1}blockAt(e,t,i,s){let r=i+this.left.height;return el))return h;let c=t==ne.ByPosNoHeight?ne.ByPosNoHeight:ne.ByPos;return a?h.join(this.right.lineAt(l,c,i,o,l)):this.left.lineAt(l,c,i,s,r).join(h)}forEachLine(e,t,i,s,r,o){let l=s+this.left.height,a=r+this.left.length+this.break;if(this.break)e=a&&this.right.forEachLine(e,t,i,l,a,o);else{let h=this.lineAt(a,ne.ByPos,i,s,r);e=e&&h.from<=t&&o(h),t>h.to&&this.right.forEachLine(h.to+1,t,i,l,a,o)}}replace(e,t,i){let s=this.left.length+this.break;if(tthis.left.length)return this.balanced(this.left,this.right.replace(e-s,t-s,i));let r=[];e>0&&this.decomposeLeft(e,r);let o=r.length;for(let l of i)r.push(l);if(e>0&&Pd(r,o-1),t=i&&t.push(null)),e>i&&this.right.decomposeLeft(e-i,t)}decomposeRight(e,t){let i=this.left.length,s=i+this.break;if(e>=s)return this.right.decomposeRight(e-s,t);e2*t.size||t.size>2*e.size?Ot.of(this.break?[e,null,t]:[e,t]):(this.left=Ao(this.left,e),this.right=Ao(this.right,t),this.setHeight(e.height+t.height),this.outdated=e.outdated||t.outdated,this.size=e.size+t.size,this.length=e.length+this.break+t.length,this)}updateHeight(e,t=0,i=!1,s){let{left:r,right:o}=this,l=t+r.length+this.break,a=null;return s&&s.from<=t+r.length&&s.more?a=r=r.updateHeight(e,t,i,s):r.updateHeight(e,t,i),s&&s.from<=l+o.length&&s.more?a=o=o.updateHeight(e,l,i,s):o.updateHeight(e,l,i),a?this.balanced(r,o):(this.height=this.left.height+this.right.height,this.outdated=!1,this)}toString(){return this.left+(this.break?" ":"-")+this.right}};function Pd(n,e){let t,i;n[e]==null&&(t=n[e-1])instanceof cs&&(i=n[e+1])instanceof cs&&n.splice(e-1,3,new cs(t.length+1+i.length))}const nv=5;let rv=class x0{constructor(e,t){this.pos=e,this.oracle=t,this.nodes=[],this.lineStart=-1,this.lineEnd=-1,this.covering=null,this.writtenTo=e}get isCovered(){return this.covering&&this.nodes[this.nodes.length-1]==this.covering}span(e,t){if(this.lineStart>-1){let i=Math.min(t,this.lineEnd),s=this.nodes[this.nodes.length-1];s instanceof Zt?s.length+=i-this.pos:(i>this.pos||!this.isCovered)&&this.nodes.push(new Zt(i-this.pos,-1,0)),this.writtenTo=i,t>i&&(this.nodes.push(null),this.writtenTo++,this.lineStart=-1)}this.pos=t}point(e,t,i){if(e=nv)&&this.addLineDeco(s,r,o)}else t>e&&this.span(e,t);this.lineEnd>-1&&this.lineEnd-1)return;let{from:e,to:t}=this.oracle.doc.lineAt(this.pos);this.lineStart=e,this.lineEnd=t,this.writtenToe&&this.nodes.push(new Zt(this.pos-e,-1,0)),this.writtenTo=this.pos}blankContent(e,t){let i=new cs(t-e);return this.oracle.doc.lineAt(e).to==t&&(i.flags|=4),i}ensureLine(){this.enterLine();let e=this.nodes.length?this.nodes[this.nodes.length-1]:null;if(e instanceof Zt)return e;let t=new Zt(0,-1,0);return this.nodes.push(t),t}addBlock(e){this.enterLine();let t=e.deco;t&&t.startSide>0&&!this.isCovered&&this.ensureLine(),this.nodes.push(e),this.writtenTo=this.pos=this.pos+e.length,t&&t.endSide>0&&(this.covering=e)}addLineDeco(e,t,i){let s=this.ensureLine();s.length+=i,s.collapsed+=i,s.widgetHeight=Math.max(s.widgetHeight,e),s.breaks+=t,this.writtenTo=this.pos=this.pos+i}finish(e){let t=this.nodes.length==0?null:this.nodes[this.nodes.length-1];this.lineStart>-1&&!(t instanceof Zt)&&!this.isCovered?this.nodes.push(new Zt(0,-1,0)):(this.writtenToc.clientHeight||c.scrollWidth>c.clientWidth)&&f.overflow!="visible"){let u=c.getBoundingClientRect();r=Math.max(r,u.left),o=Math.min(o,u.right),l=Math.max(l,u.top),a=Math.min(h==n.parentNode?s.innerHeight:a,u.bottom)}h=f.position=="absolute"||f.position=="fixed"?c.offsetParent:c.parentNode}else if(h.nodeType==11)h=h.host;else break;return{left:r-t.left,right:Math.max(r,o)-t.left,top:l-(t.top+e),bottom:Math.max(l,a)-(t.top+e)}}function hv(n){let e=n.getBoundingClientRect(),t=n.ownerDocument.defaultView||window;return e.left0&&e.top0}function cv(n,e){let t=n.getBoundingClientRect();return{left:0,right:t.right-t.left,top:e,bottom:t.bottom-(t.top+e)}}let oa=class{constructor(e,t,i,s){this.from=e,this.to=t,this.size=i,this.displaySize=s}static same(e,t){if(e.length!=t.length)return!1;for(let i=0;itypeof i!="function"&&i.class=="cm-lineWrapping");this.heightOracle=new ev(t),this.stateDeco=Md(e),this.heightMap=Ot.empty().applyChanges(this.stateDeco,Z.empty,this.heightOracle.setDoc(e.doc),[new ni(0,0,0,e.doc.length)]);for(let i=0;i<2&&(this.viewport=this.getViewport(0,null),!!this.updateForViewport());i++);this.updateViewportLines(),this.lineGaps=this.ensureLineGaps([]),this.lineGapDeco=We.set(this.lineGaps.map(i=>i.draw(this,!1))),this.computeVisibleRanges()}updateForViewport(){let e=[this.viewport],{main:t}=this.state.selection;for(let i=0;i<=1;i++){let s=i?t.head:t.anchor;if(!e.some(({from:r,to:o})=>s>=r&&s<=o)){let{from:r,to:o}=this.lineBlockAt(s);e.push(new kr(r,o))}}return this.viewports=e.sort((i,s)=>i.from-s.from),this.updateScaler()}updateScaler(){let e=this.scaler;return this.scaler=this.heightMap.height<=7e6?Td:new pv(this.heightOracle,this.heightMap,this.viewports),e.eq(this.scaler)?0:2}updateViewportLines(){this.viewportLines=[],this.heightMap.forEachLine(this.viewport.from,this.viewport.to,this.heightOracle.setDoc(this.state.doc),0,0,e=>{this.viewportLines.push(nn(e,this.scaler))})}update(e,t=null){this.state=e.state;let i=this.stateDeco;this.stateDeco=Md(this.state);let s=e.changedRanges,r=ni.extendWithRanges(s,ov(i,this.stateDeco,e?e.changes:he.empty(this.state.doc.length))),o=this.heightMap.height,l=this.scrolledToBottom?null:this.scrollAnchorAt(this.scrollTop);$d(),this.heightMap=this.heightMap.applyChanges(this.stateDeco,e.startState.doc,this.heightOracle.setDoc(this.state.doc),r),(this.heightMap.height!=o||vs)&&(e.flags|=2),l?(this.scrollAnchorPos=e.changes.mapPos(l.from,-1),this.scrollAnchorHeight=l.top):(this.scrollAnchorPos=-1,this.scrollAnchorHeight=o);let a=r.length?this.mapViewport(this.viewport,e.changes):this.viewport;(t&&(t.range.heada.to)||!this.viewportIsAppropriate(a))&&(a=this.getViewport(0,t));let h=a.from!=this.viewport.from||a.to!=this.viewport.to;this.viewport=a,e.flags|=this.updateForViewport(),(h||!e.changes.empty||e.flags&2)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps,e.changes))),e.flags|=this.computeVisibleRanges(e.changes),t&&(this.scrollTarget=t),!this.mustEnforceCursorAssoc&&(e.selectionSet||e.focusChanged)&&e.view.lineWrapping&&e.state.selection.main.empty&&e.state.selection.main.assoc&&!e.state.facet(tQ)&&(this.mustEnforceCursorAssoc=!0)}measure(e){let t=e.contentDOM,i=window.getComputedStyle(t),s=this.heightOracle,r=i.whiteSpace;this.defaultTextDirection=i.direction=="rtl"?be.RTL:be.LTR;let o=this.heightOracle.mustRefreshForWrapping(r)||this.mustMeasureContent,l=t.getBoundingClientRect(),a=o||this.mustMeasureContent||this.contentDOMHeight!=l.height;this.contentDOMHeight=l.height,this.mustMeasureContent=!1;let h=0,c=0;if(l.width&&l.height){let{scaleX:v,scaleY:w}=TO(t,l);(v>.005&&Math.abs(this.scaleX-v)>.005||w>.005&&Math.abs(this.scaleY-w)>.005)&&(this.scaleX=v,this.scaleY=w,h|=16,o=a=!0)}let f=(parseInt(i.paddingTop)||0)*this.scaleY,u=(parseInt(i.paddingBottom)||0)*this.scaleY;(this.paddingTop!=f||this.paddingBottom!=u)&&(this.paddingTop=f,this.paddingBottom=u,h|=18),this.editorWidth!=e.scrollDOM.clientWidth&&(s.lineWrapping&&(a=!0),this.editorWidth=e.scrollDOM.clientWidth,h|=16);let d=e.scrollDOM.scrollTop*this.scaleY;this.scrollTop!=d&&(this.scrollAnchorHeight=-1,this.scrollTop=d),this.scrolledToBottom=AO(e.scrollDOM);let p=(this.printing?cv:av)(t,this.paddingTop),g=p.top-this.pixelViewport.top,m=p.bottom-this.pixelViewport.bottom;this.pixelViewport=p;let O=this.pixelViewport.bottom>this.pixelViewport.top&&this.pixelViewport.right>this.pixelViewport.left;if(O!=this.inView&&(this.inView=O,O&&(a=!0)),!this.inView&&!this.scrollTarget&&!hv(e.dom))return 0;let y=l.width;if((this.contentDOMWidth!=y||this.editorHeight!=e.scrollDOM.clientHeight)&&(this.contentDOMWidth=l.width,this.editorHeight=e.scrollDOM.clientHeight,h|=16),a){let v=e.docView.measureVisibleLineHeights(this.viewport);if(s.mustRefreshForHeights(v)&&(o=!0),o||s.lineWrapping&&Math.abs(y-this.contentDOMWidth)>s.charWidth){let{lineHeight:w,charWidth:Q,textHeight:$}=e.docView.measureTextSize();o=w>0&&s.refresh(r,w,Q,$,Math.max(5,y/Q),v),o&&(e.docView.minWidth=0,h|=16)}g>0&&m>0?c=Math.max(g,m):g<0&&m<0&&(c=Math.min(g,m)),$d();for(let w of this.viewports){let Q=w.from==this.viewport.from?v:e.docView.measureVisibleLineHeights(w);this.heightMap=(o?Ot.empty().applyChanges(this.stateDeco,Z.empty,this.heightOracle,[new ni(0,0,0,e.state.doc.length)]):this.heightMap).updateHeight(s,0,o,new tv(w.from,Q))}vs&&(h|=2)}let x=!this.viewportIsAppropriate(this.viewport,c)||this.scrollTarget&&(this.scrollTarget.range.headthis.viewport.to);return x&&(h&2&&(h|=this.updateScaler()),this.viewport=this.getViewport(c,this.scrollTarget),h|=this.updateForViewport()),(h&2||x)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(o?[]:this.lineGaps,e)),h|=this.computeVisibleRanges(),this.mustEnforceCursorAssoc&&(this.mustEnforceCursorAssoc=!1,e.docView.enforceCursorAssoc()),h}get visibleTop(){return this.scaler.fromDOM(this.pixelViewport.top)}get visibleBottom(){return this.scaler.fromDOM(this.pixelViewport.bottom)}getViewport(e,t){let i=.5-Math.max(-.5,Math.min(.5,e/1e3/2)),s=this.heightMap,r=this.heightOracle,{visibleTop:o,visibleBottom:l}=this,a=new kr(s.lineAt(o-i*1e3,ne.ByHeight,r,0,0).from,s.lineAt(l+(1-i)*1e3,ne.ByHeight,r,0,0).to);if(t){let{head:h}=t.range;if(ha.to){let c=Math.min(this.editorHeight,this.pixelViewport.bottom-this.pixelViewport.top),f=s.lineAt(h,ne.ByPos,r,0,0),u;t.y=="center"?u=(f.top+f.bottom)/2-c/2:t.y=="start"||t.y=="nearest"&&h=l+Math.max(10,Math.min(i,250)))&&s>o-2*1e3&&r>1,o=s<<1;if(this.defaultTextDirection!=be.LTR&&!i)return[];let l=[],a=(c,f,u,d)=>{if(f-cc&&OO.from>=u.from&&O.to<=u.to&&Math.abs(O.from-c)O.fromy));if(!m){if(fx.from<=f&&x.to>=f)){let x=t.moveToLineBoundary(S.cursor(f),!1,!0).head;x>c&&(f=x)}let O=this.gapSize(u,c,f,d),y=i||O<2e6?O:2e6;m=new oa(c,f,O,y)}l.push(m)},h=c=>{if(c.length2e6)for(let Q of e)Q.from>=c.from&&Q.fromc.from&&a(c.from,d,c,f),pt.draw(this,this.heightOracle.lineWrapping))))}computeVisibleRanges(e){let t=this.stateDeco;this.lineGaps.length&&(t=t.concat(this.lineGapDeco));let i=[];M.spans(t,this.viewport.from,this.viewport.to,{span(r,o){i.push({from:r,to:o})},point(){}},20);let s=0;if(i.length!=this.visibleRanges.length)s=12;else for(let r=0;r=this.viewport.from&&e<=this.viewport.to&&this.viewportLines.find(t=>t.from<=e&&t.to>=e)||nn(this.heightMap.lineAt(e,ne.ByPos,this.heightOracle,0,0),this.scaler)}lineBlockAtHeight(e){return e>=this.viewportLines[0].top&&e<=this.viewportLines[this.viewportLines.length-1].bottom&&this.viewportLines.find(t=>t.top<=e&&t.bottom>=e)||nn(this.heightMap.lineAt(this.scaler.fromDOM(e),ne.ByHeight,this.heightOracle,0,0),this.scaler)}scrollAnchorAt(e){let t=this.lineBlockAtHeight(e+8);return t.from>=this.viewport.from||this.viewportLines[0].top-e>200?t:this.viewportLines[0]}elementAtHeight(e){return nn(this.heightMap.blockAt(this.scaler.fromDOM(e),this.heightOracle,0,0),this.scaler)}get docHeight(){return this.scaler.toDOM(this.heightMap.height)}get contentHeight(){return this.docHeight+this.paddingTop+this.paddingBottom}},kr=class{constructor(e,t){this.from=e,this.to=t}};function uv(n,e,t){let i=[],s=n,r=0;return M.spans(t,n,e,{span(){},point(o,l){o>s&&(i.push({from:s,to:o}),r+=o-s),s=l}},20),s=1)return e[e.length-1].to;let i=Math.floor(n*t);for(let s=0;;s++){let{from:r,to:o}=e[s],l=o-r;if(i<=l)return r+i;i-=l}}function vr(n,e){let t=0;for(let{from:i,to:s}of n.ranges){if(e<=s){t+=e-i;break}t+=s-i}return t/n.total}function dv(n,e){for(let t of n)if(e(t))return t}const Td={toDOM(n){return n},fromDOM(n){return n},scale:1,eq(n){return n==this}};function Md(n){let e=n.facet(Ol).filter(i=>typeof i!="function"),t=n.facet(sf).filter(i=>typeof i!="function");return t.length&&e.push(M.join(t)),e}let pv=class w0{constructor(e,t,i){let s=0,r=0,o=0;this.viewports=i.map(({from:l,to:a})=>{let h=t.lineAt(l,ne.ByPos,e,0,0).top,c=t.lineAt(a,ne.ByPos,e,0,0).bottom;return s+=c-h,{from:l,to:a,top:h,bottom:c,domTop:0,domBottom:0}}),this.scale=(7e6-s)/(t.height-s);for(let l of this.viewports)l.domTop=o+(l.top-r)*this.scale,o=l.domBottom=l.domTop+(l.bottom-l.top),r=l.bottom}toDOM(e){for(let t=0,i=0,s=0;;t++){let r=tt.from==e.viewports[i].from&&t.to==e.viewports[i].to):!1}};function nn(n,e){if(e.scale==1)return n;let t=e.toDOM(n.top),i=e.toDOM(n.bottom);return new Kt(n.from,n.length,t,i-t,Array.isArray(n._content)?n._content.map(s=>nn(s,e)):n._content)}const $r=k.define({combine:n=>n.join(" ")}),Nh=k.define({combine:n=>n.indexOf(!0)>-1}),jh=me.newName(),k0=me.newName(),Q0=me.newName(),v0={"&light":"."+k0,"&dark":"."+Q0};function Gh(n,e,t){return new me(e,{finish(i){return/&/.test(i)?i.replace(/&\w*/,s=>{if(s=="&")return n;if(!t||!t[s])throw new RangeError(`Unsupported selector: ${s}`);return t[s]}):n+" "+i}})}const gv=Gh("."+jh,{"&":{position:"relative !important",boxSizing:"border-box","&.cm-focused":{outline:"1px dotted #212121"},display:"flex !important",flexDirection:"column"},".cm-scroller":{display:"flex !important",alignItems:"flex-start !important",fontFamily:"monospace",lineHeight:1.4,height:"100%",overflowX:"auto",position:"relative",zIndex:0,overflowAnchor:"none"},".cm-content":{margin:0,flexGrow:2,flexShrink:0,display:"block",whiteSpace:"pre",wordWrap:"normal",boxSizing:"border-box",minHeight:"100%",padding:"4px 0",outline:"none","&[contenteditable=true]":{WebkitUserModify:"read-write-plaintext-only"}},".cm-lineWrapping":{whiteSpace_fallback:"pre-wrap",whiteSpace:"break-spaces",wordBreak:"break-word",overflowWrap:"anywhere",flexShrink:1},"&light .cm-content":{caretColor:"black"},"&dark .cm-content":{caretColor:"white"},".cm-line":{display:"block",padding:"0 2px 0 6px"},".cm-layer":{position:"absolute",left:0,top:0,contain:"size style","& > *":{position:"absolute"}},"&light .cm-selectionBackground":{background:"#d9d9d9"},"&dark .cm-selectionBackground":{background:"#222"},"&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#d7d4f0"},"&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#233"},".cm-cursorLayer":{pointerEvents:"none"},"&.cm-focused > .cm-scroller > .cm-cursorLayer":{animation:"steps(1) cm-blink 1.2s infinite"},"@keyframes cm-blink":{"0%":{},"50%":{opacity:0},"100%":{}},"@keyframes cm-blink2":{"0%":{},"50%":{opacity:0},"100%":{}},".cm-cursor, .cm-dropCursor":{borderLeft:"1.2px solid black",marginLeft:"-0.6px",pointerEvents:"none"},".cm-cursor":{display:"none"},"&dark .cm-cursor":{borderLeftColor:"#ddd"},".cm-dropCursor":{position:"absolute"},"&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor":{display:"block"},".cm-iso":{unicodeBidi:"isolate"},".cm-announced":{position:"fixed",top:"-10000px"},"@media print":{".cm-announced":{display:"none"}},"&light .cm-activeLine":{backgroundColor:"#cceeff44"},"&dark .cm-activeLine":{backgroundColor:"#99eeff33"},"&light .cm-specialChar":{color:"red"},"&dark .cm-specialChar":{color:"#f78"},".cm-gutters":{flexShrink:0,display:"flex",height:"100%",boxSizing:"border-box",zIndex:200},".cm-gutters-before":{insetInlineStart:0},".cm-gutters-after":{insetInlineEnd:0},"&light .cm-gutters":{backgroundColor:"#f5f5f5",color:"#6c6c6c",border:"0px solid #ddd","&.cm-gutters-before":{borderRightWidth:"1px"},"&.cm-gutters-after":{borderLeftWidth:"1px"}},"&dark .cm-gutters":{backgroundColor:"#333338",color:"#ccc"},".cm-gutter":{display:"flex !important",flexDirection:"column",flexShrink:0,boxSizing:"border-box",minHeight:"100%",overflow:"hidden"},".cm-gutterElement":{boxSizing:"border-box"},".cm-lineNumbers .cm-gutterElement":{padding:"0 3px 0 5px",minWidth:"20px",textAlign:"right",whiteSpace:"nowrap"},"&light .cm-activeLineGutter":{backgroundColor:"#e2f2ff"},"&dark .cm-activeLineGutter":{backgroundColor:"#222227"},".cm-panels":{boxSizing:"border-box",position:"sticky",left:0,right:0,zIndex:300},"&light .cm-panels":{backgroundColor:"#f5f5f5",color:"black"},"&light .cm-panels-top":{borderBottom:"1px solid #ddd"},"&light .cm-panels-bottom":{borderTop:"1px solid #ddd"},"&dark .cm-panels":{backgroundColor:"#333338",color:"white"},".cm-dialog":{padding:"2px 19px 4px 6px",position:"relative","& label":{fontSize:"80%"}},".cm-dialog-close":{position:"absolute",top:"3px",right:"4px",backgroundColor:"inherit",border:"none",font:"inherit",fontSize:"14px",padding:"0"},".cm-tab":{display:"inline-block",overflow:"hidden",verticalAlign:"bottom"},".cm-widgetBuffer":{verticalAlign:"text-top",height:"1em",width:0,display:"inline"},".cm-placeholder":{color:"#888",display:"inline-block",verticalAlign:"top",userSelect:"none"},".cm-highlightSpace":{backgroundImage:"radial-gradient(circle at 50% 55%, #aaa 20%, transparent 5%)",backgroundPosition:"center"},".cm-highlightTab":{backgroundImage:`url('data:image/svg+xml,')`,backgroundSize:"auto 100%",backgroundPosition:"right 90%",backgroundRepeat:"no-repeat"},".cm-trailingSpace":{backgroundColor:"#ff332255"},".cm-button":{verticalAlign:"middle",color:"inherit",fontSize:"70%",padding:".2em 1em",borderRadius:"1px"},"&light .cm-button":{backgroundImage:"linear-gradient(#eff1f5, #d9d9df)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#b4b4b4, #d0d3d6)"}},"&dark .cm-button":{backgroundImage:"linear-gradient(#393939, #111)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#111, #333)"}},".cm-textfield":{verticalAlign:"middle",color:"inherit",fontSize:"70%",border:"1px solid silver",padding:".2em .5em"},"&light .cm-textfield":{backgroundColor:"white"},"&dark .cm-textfield":{border:"1px solid #555",backgroundColor:"inherit"}},v0),mv={childList:!0,characterData:!0,subtree:!0,attributes:!0,characterDataOldValue:!0},la=A.ie&&A.ie_version<=11;let Ov=class{constructor(e){this.view=e,this.active=!1,this.editContext=null,this.selectionRange=new Ik,this.selectionChanged=!1,this.delayedFlush=-1,this.resizeTimeout=-1,this.queue=[],this.delayedAndroidKey=null,this.flushingAndroidKey=-1,this.lastChange=0,this.scrollTargets=[],this.intersection=null,this.resizeScroll=null,this.intersecting=!1,this.gapIntersection=null,this.gaps=[],this.printQuery=null,this.parentCheck=-1,this.dom=e.contentDOM,this.observer=new MutationObserver(t=>{for(let i of t)this.queue.push(i);(A.ie&&A.ie_version<=11||A.ios&&e.composing)&&t.some(i=>i.type=="childList"&&i.removedNodes.length||i.type=="characterData"&&i.oldValue.length>i.target.nodeValue.length)?this.flushSoon():this.flush()}),window.EditContext&&A.android&&e.constructor.EDIT_CONTEXT!==!1&&!(A.chrome&&A.chrome_version<126)&&(this.editContext=new Sv(e),e.state.facet(Ut)&&(e.contentDOM.editContext=this.editContext.editContext)),la&&(this.onCharData=t=>{this.queue.push({target:t.target,type:"characterData",oldValue:t.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this),this.onResize=this.onResize.bind(this),this.onPrint=this.onPrint.bind(this),this.onScroll=this.onScroll.bind(this),window.matchMedia&&(this.printQuery=window.matchMedia("print")),typeof ResizeObserver=="function"&&(this.resizeScroll=new ResizeObserver(()=>{var t;((t=this.view.docView)===null||t===void 0?void 0:t.lastUpdate){this.parentCheck<0&&(this.parentCheck=setTimeout(this.listenForScroll.bind(this),1e3)),t.length>0&&t[t.length-1].intersectionRatio>0!=this.intersecting&&(this.intersecting=!this.intersecting,this.intersecting!=this.view.inView&&this.onScrollChanged(document.createEvent("Event")))},{threshold:[0,.001]}),this.intersection.observe(this.dom),this.gapIntersection=new IntersectionObserver(t=>{t.length>0&&t[t.length-1].intersectionRatio>0&&this.onScrollChanged(document.createEvent("Event"))},{})),this.listenForScroll(),this.readSelectionRange()}onScrollChanged(e){this.view.inputState.runHandlers("scroll",e),this.intersecting&&this.view.measure()}onScroll(e){this.intersecting&&this.flush(!1),this.editContext&&this.view.requestMeasure(this.editContext.measureReq),this.onScrollChanged(e)}onResize(){this.resizeTimeout<0&&(this.resizeTimeout=setTimeout(()=>{this.resizeTimeout=-1,this.view.requestMeasure()},50))}onPrint(e){(e.type=="change"||!e.type)&&!e.matches||(this.view.viewState.printing=!0,this.view.measure(),setTimeout(()=>{this.view.viewState.printing=!1,this.view.requestMeasure()},500))}updateGaps(e){if(this.gapIntersection&&(e.length!=this.gaps.length||this.gaps.some((t,i)=>t!=e[i]))){this.gapIntersection.disconnect();for(let t of e)this.gapIntersection.observe(t);this.gaps=e}}onSelectionChange(e){let t=this.selectionChanged;if(!this.readSelectionRange()||this.delayedAndroidKey)return;let{view:i}=this,s=this.selectionRange;if(i.state.facet(Ut)?i.root.activeElement!=this.dom:!gn(this.dom,s))return;let r=s.anchorNode&&i.docView.tile.nearest(s.anchorNode);if(r&&r.isWidget()&&r.widget.ignoreEvent(e)){t||(this.selectionChanged=!1);return}(A.ie&&A.ie_version<=11||A.android&&A.chrome)&&!i.state.selection.main.empty&&s.focusNode&&mn(s.focusNode,s.focusOffset,s.anchorNode,s.anchorOffset)?this.flushSoon():this.flush(!1)}readSelectionRange(){let{view:e}=this,t=ks(e.root);if(!t)return!1;let i=A.safari&&e.root.nodeType==11&&e.root.activeElement==this.dom&&bv(this.view,t)||t;if(!i||this.selectionRange.eq(i))return!1;let s=gn(this.dom,i);return s&&!this.selectionChanged&&e.inputState.lastFocusTime>Date.now()-200&&e.inputState.lastTouchTime{let r=this.delayedAndroidKey;r&&(this.clearDelayedAndroidKey(),this.view.inputState.lastKeyCode=r.keyCode,this.view.inputState.lastKeyTime=Date.now(),!this.flush()&&r.force&&as(this.dom,r.key,r.keyCode))};this.flushingAndroidKey=this.view.win.requestAnimationFrame(s)}(!this.delayedAndroidKey||e=="Enter")&&(this.delayedAndroidKey={key:e,keyCode:t,force:this.lastChange{this.delayedFlush=-1,this.flush()}))}forceFlush(){this.delayedFlush>=0&&(this.view.win.cancelAnimationFrame(this.delayedFlush),this.delayedFlush=-1),this.flush()}pendingRecords(){for(let e of this.observer.takeRecords())this.queue.push(e);return this.queue}processRecords(){let e=this.pendingRecords();e.length&&(this.queue=[]);let t=-1,i=-1,s=!1;for(let r of e){let o=this.readMutation(r);o&&(o.typeOver&&(s=!0),t==-1?{from:t,to:i}=o:(t=Math.min(o.from,t),i=Math.max(o.to,i)))}return{from:t,to:i,typeOver:s}}readChange(){let{from:e,to:t,typeOver:i}=this.processRecords(),s=this.selectionChanged&&gn(this.dom,this.selectionRange);if(e<0&&!s)return null;e>-1&&(this.lastChange=Date.now()),this.view.inputState.lastFocusTime=0,this.selectionChanged=!1;let r=new DQ(this.view,e,t,i);return this.view.docView.domChanged={newSel:r.newSel?r.newSel.main:null},r}flush(e=!0){if(this.delayedFlush>=0||this.delayedAndroidKey)return!1;e&&this.readSelectionRange();let t=this.readChange();if(!t)return this.view.requestMeasure(),!1;let i=this.view.state,s=c0(this.view,t);return this.view.state==i&&(t.domChanged||t.newSel&&!Mo(this.view.state.selection,t.newSel.main))&&this.view.update([]),s}readMutation(e){let t=this.view.docView.tile.nearest(e.target);if(!t||t.isWidget())return null;if(t.markDirty(e.type=="attributes"),e.type=="childList"){let i=Ad(t,e.previousSibling||e.target.previousSibling,-1),s=Ad(t,e.nextSibling||e.target.nextSibling,1);return{from:i?t.posAfter(i):t.posAtStart,to:s?t.posBefore(s):t.posAtEnd,typeOver:!1}}else return e.type=="characterData"?{from:t.posAtStart,to:t.posAtEnd,typeOver:e.target.nodeValue==e.oldValue}:null}setWindow(e){e!=this.win&&(this.removeWindowListeners(this.win),this.win=e,this.addWindowListeners(this.win))}addWindowListeners(e){e.addEventListener("resize",this.onResize),this.printQuery?this.printQuery.addEventListener?this.printQuery.addEventListener("change",this.onPrint):this.printQuery.addListener(this.onPrint):e.addEventListener("beforeprint",this.onPrint),e.addEventListener("scroll",this.onScroll),e.document.addEventListener("selectionchange",this.onSelectionChange)}removeWindowListeners(e){e.removeEventListener("scroll",this.onScroll),e.removeEventListener("resize",this.onResize),this.printQuery?this.printQuery.removeEventListener?this.printQuery.removeEventListener("change",this.onPrint):this.printQuery.removeListener(this.onPrint):e.removeEventListener("beforeprint",this.onPrint),e.document.removeEventListener("selectionchange",this.onSelectionChange)}update(e){this.editContext&&(this.editContext.update(e),e.startState.facet(Ut)!=e.state.facet(Ut)&&(e.view.contentDOM.editContext=e.state.facet(Ut)?this.editContext.editContext:null))}destroy(){var e,t,i;this.stop(),(e=this.intersection)===null||e===void 0||e.disconnect(),(t=this.gapIntersection)===null||t===void 0||t.disconnect(),(i=this.resizeScroll)===null||i===void 0||i.disconnect();for(let s of this.scrollTargets)s.removeEventListener("scroll",this.onScroll);this.removeWindowListeners(this.win),clearTimeout(this.parentCheck),clearTimeout(this.resizeTimeout),this.win.cancelAnimationFrame(this.delayedFlush),this.win.cancelAnimationFrame(this.flushingAndroidKey),this.editContext&&(this.view.contentDOM.editContext=null,this.editContext.destroy())}};function Ad(n,e,t){for(;e;){let i=pe.get(e);if(i&&i.parent==n)return i;let s=e.parentNode;e=s!=n.dom?s:t>0?e.nextSibling:e.previousSibling}return null}function Rd(n,e){let t=e.startContainer,i=e.startOffset,s=e.endContainer,r=e.endOffset,o=n.docView.domAtPos(n.state.selection.main.anchor,1);return mn(o.node,o.offset,s,r)&&([t,i,s,r]=[s,r,t,i]),{anchorNode:t,anchorOffset:i,focusNode:s,focusOffset:r}}function bv(n,e){if(e.getComposedRanges){let s=e.getComposedRanges(n.root)[0];if(s)return Rd(n,s)}let t=null;function i(s){s.preventDefault(),s.stopImmediatePropagation(),t=s.getTargetRanges()[0]}return n.contentDOM.addEventListener("beforeinput",i,!0),n.dom.ownerDocument.execCommand("indent"),n.contentDOM.removeEventListener("beforeinput",i,!0),t?Rd(n,t):null}let Sv=class{constructor(e){this.from=0,this.to=0,this.pendingContextChange=null,this.handlers=Object.create(null),this.composing=null,this.resetRange(e.state);let t=this.editContext=new window.EditContext({text:e.state.doc.sliceString(this.from,this.to),selectionStart:this.toContextPos(Math.max(this.from,Math.min(this.to,e.state.selection.main.anchor))),selectionEnd:this.toContextPos(e.state.selection.main.head)});this.handlers.textupdate=i=>{let s=e.state.selection.main,{anchor:r,head:o}=s,l=this.toEditorPos(i.updateRangeStart),a=this.toEditorPos(i.updateRangeEnd);e.inputState.composing>=0&&!this.composing&&(this.composing={contextBase:i.updateRangeStart,editorBase:l,drifted:!1});let h=a-l>i.text.length;l==this.from&&rthis.to&&(a=r);let c=f0(e.state.sliceDoc(l,a),i.text,(h?s.from:s.to)-l,h?"end":null);if(!c){let u=S.single(this.toEditorPos(i.selectionStart),this.toEditorPos(i.selectionEnd));Mo(u,s)||e.dispatch({selection:u,userEvent:"select"});return}let f={from:c.from+l,to:c.toA+l,insert:Z.of(i.text.slice(c.from,c.toB).split(` `))};if((A.mac||A.android)&&f.from==o-1&&/^\. ?$/.test(i.text)&&e.contentDOM.getAttribute("autocorrect")=="off"&&(f={from:l,to:a,insert:Z.of([i.text.replace("."," ")])}),this.pendingContextChange=f,!e.state.readOnly){let u=this.to-this.from+(f.to-f.from+f.insert.length);nf(e,f,S.single(this.toEditorPos(i.selectionStart,u),this.toEditorPos(i.selectionEnd,u)))}this.pendingContextChange&&(this.revertPending(e.state),this.setSelection(e.state)),f.from=0&&!/[\\p{Alphabetic}\\p{Number}_]/.test(t.text.slice(Math.max(0,i.updateRangeStart-1),Math.min(t.text.length,i.updateRangeStart+1)))&&this.handlers.compositionend(i)},this.handlers.characterboundsupdate=i=>{let s=[],r=null;for(let o=this.toEditorPos(i.rangeStart),l=this.toEditorPos(i.rangeEnd);o{let s=[];for(let r of i.getTextFormats()){let o=r.underlineStyle,l=r.underlineThickness;if(!/none/i.test(o)&&!/none/i.test(l)){let a=this.toEditorPos(r.rangeStart),h=this.toEditorPos(r.rangeEnd);if(a{e.inputState.composing<0&&(e.inputState.composing=0,e.inputState.compositionFirstChange=!0)},this.handlers.compositionend=()=>{if(e.inputState.composing=-1,e.inputState.compositionFirstChange=null,this.composing){let{drifted:i}=this.composing;this.composing=null,i&&this.reset(e.state)}};for(let i in this.handlers)t.addEventListener(i,this.handlers[i]);this.measureReq={read:i=>{this.editContext.updateControlBounds(i.contentDOM.getBoundingClientRect());let s=ks(i.root);s&&s.rangeCount&&this.editContext.updateSelectionBounds(s.getRangeAt(0).getBoundingClientRect())}}}applyEdits(e){let t=0,i=!1,s=this.pendingContextChange;return e.changes.iterChanges((r,o,l,a,h)=>{if(i)return;let c=h.length-(o-r);if(s&&o>=s.to)if(s.from==r&&s.to==o&&s.insert.eq(h)){s=this.pendingContextChange=null,t+=c,this.to+=c;return}else s=null,this.revertPending(e.state);if(r+=t,o+=t,o<=this.from)this.from+=c,this.to+=c;else if(rthis.to||this.to-this.from+h.length>3e4){i=!0;return}this.editContext.updateText(this.toContextPos(r),this.toContextPos(o),h.toString()),this.to+=c}t+=c}),s&&!i&&this.revertPending(e.state),!i}update(e){let t=this.pendingContextChange,i=e.startState.selection.main;this.composing&&(this.composing.drifted||!e.changes.touchesRange(i.from,i.to)&&e.transactions.some(s=>!s.isUserEvent("input.type")&&s.changes.touchesRange(this.from,this.to)))?(this.composing.drifted=!0,this.composing.editorBase=e.changes.mapPos(this.composing.editorBase)):!this.applyEdits(e)||!this.rangeIsValid(e.state)?(this.pendingContextChange=null,this.reset(e.state)):(e.docChanged||e.selectionSet||t)&&this.setSelection(e.state),(e.geometryChanged||e.docChanged||e.selectionSet)&&e.view.requestMeasure(this.measureReq)}resetRange(e){let{head:t}=e.selection.main;this.from=Math.max(0,t-1e4),this.to=Math.min(e.doc.length,t+1e4)}reset(e){this.resetRange(e),this.editContext.updateText(0,this.editContext.text.length,e.doc.sliceString(this.from,this.to)),this.setSelection(e)}revertPending(e){let t=this.pendingContextChange;this.pendingContextChange=null,this.editContext.updateText(this.toContextPos(t.from),this.toContextPos(t.from+t.insert.length),e.doc.sliceString(t.from,t.to))}setSelection(e){let{main:t}=e.selection,i=this.toContextPos(Math.max(this.from,Math.min(this.to,t.anchor))),s=this.toContextPos(t.head);(this.editContext.selectionStart!=i||this.editContext.selectionEnd!=s)&&this.editContext.updateSelection(i,s)}rangeIsValid(e){let{head:t}=e.selection.main;return!(this.from>0&&t-this.from<500||this.to1e4*3)}toEditorPos(e,t=this.to-this.from){e=Math.min(e,t);let i=this.composing;return i&&i.drifted?i.editorBase+(e-i.contextBase):e+this.from}toContextPos(e){let t=this.composing;return t&&t.drifted?t.contextBase+(e-t.editorBase):e-this.from}destroy(){for(let e in this.handlers)this.editContext.removeEventListener(e,this.handlers[e])}},Y=class Hh{get state(){return this.viewState.state}get viewport(){return this.viewState.viewport}get visibleRanges(){return this.viewState.visibleRanges}get inView(){return this.viewState.inView}get composing(){return!!this.inputState&&this.inputState.composing>0}get compositionStarted(){return!!this.inputState&&this.inputState.composing>=0}get root(){return this._root}get win(){return this.dom.ownerDocument.defaultView||window}constructor(e={}){var t;this.plugins=[],this.pluginMap=new Map,this.editorAttrs={},this.contentAttrs={},this.bidiCache=[],this.destroyed=!1,this.updateState=2,this.measureScheduled=-1,this.measureRequests=[],this.contentDOM=document.createElement("div"),this.scrollDOM=document.createElement("div"),this.scrollDOM.tabIndex=-1,this.scrollDOM.className="cm-scroller",this.scrollDOM.appendChild(this.contentDOM),this.announceDOM=document.createElement("div"),this.announceDOM.className="cm-announced",this.announceDOM.setAttribute("aria-live","polite"),this.dom=document.createElement("div"),this.dom.appendChild(this.announceDOM),this.dom.appendChild(this.scrollDOM),e.parent&&e.parent.appendChild(this.dom);let{dispatch:i}=e;this.dispatchTransactions=e.dispatchTransactions||i&&(s=>s.forEach(r=>i(r,this)))||(s=>this.update(s)),this.dispatch=this.dispatch.bind(this),this._root=e.root||_k(e.parent)||document,this.viewState=new Cd(e.state||L.create(e)),e.scrollTo&&e.scrollTo.is(yr)&&(this.viewState.scrollTarget=e.scrollTo.value.clip(this.viewState.state)),this.plugins=this.state.facet(Ki).map(s=>new ta(s));for(let s of this.plugins)s.update(this);this.observer=new Ov(this),this.inputState=new LQ(this),this.inputState.ensureHandlers(this.plugins),this.docView=new pd(this),this.mountStyles(),this.updateAttrs(),this.updateState=0,this.requestMeasure(),!((t=document.fonts)===null||t===void 0)&&t.ready&&document.fonts.ready.then(()=>{this.viewState.mustMeasureContent=!0,this.requestMeasure()})}dispatch(...e){let t=e.length==1&&e[0]instanceof ce?e:e.length==1&&Array.isArray(e[0])?e[0]:[this.state.update(...e)];this.dispatchTransactions(t,this)}update(e){if(this.updateState!=0)throw new Error("Calls to EditorView.update are not allowed while an update is in progress");let t=!1,i=!1,s,r=this.state;for(let u of e){if(u.startState!=r)throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");r=u.state}if(this.destroyed){this.viewState.state=r;return}let o=this.hasFocus,l=0,a=null;e.some(u=>u.annotation(m0))?(this.inputState.notifiedFocused=o,l=1):o!=this.inputState.notifiedFocused&&(this.inputState.notifiedFocused=o,a=O0(r,o),a||(l=1));let h=this.observer.delayedAndroidKey,c=null;if(h?(this.observer.clearDelayedAndroidKey(),c=this.observer.readChange(),(c&&!this.state.doc.eq(r.doc)||!this.state.selection.eq(r.selection))&&(c=null)):this.observer.clear(),r.facet(L.phrases)!=this.state.facet(L.phrases))return this.setState(r);s=fd.create(this,r,e),s.flags|=l;let f=this.viewState.scrollTarget;try{this.updateState=2;for(let u of e){if(f&&(f=f.map(u.changes)),u.scrollIntoView){let{main:d}=u.state.selection;f=new ea(d.empty?d:S.cursor(d.head,d.head>d.anchor?-1:1))}for(let d of u.effects)d.is(yr)&&(f=d.value.clip(this.state))}this.viewState.update(s,f),this.bidiCache=Zd.update(this.bidiCache,s.changes),s.empty||(this.updatePlugins(s),this.inputState.update(s)),t=this.docView.update(s),this.state.facet(en)!=this.styleModules&&this.mountStyles(),i=this.updateAttrs(),this.showAnnouncements(e),this.docView.updateSelection(t,e.some(u=>u.isUserEvent("select.pointer")))}finally{this.updateState=0}if(s.startState.facet($r)!=s.state.facet($r)&&(this.viewState.mustMeasureContent=!0),(t||i||f||this.viewState.mustEnforceCursorAssoc||this.viewState.mustMeasureContent)&&this.requestMeasure(),t&&this.docViewUpdate(),!s.empty)for(let u of this.state.facet(Eh))try{u(s)}catch(d){si(this.state,d,"update listener")}(a||c)&&Promise.resolve().then(()=>{a&&this.state==a.startState&&this.dispatch(a),c&&!c0(this,c)&&h.force&&as(this.contentDOM,h.key,h.keyCode)})}setState(e){if(this.updateState!=0)throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");if(this.destroyed){this.viewState.state=e;return}this.updateState=2;let t=this.hasFocus;try{for(let i of this.plugins)i.destroy(this);this.viewState=new Cd(e),this.plugins=e.facet(Ki).map(i=>new ta(i)),this.pluginMap.clear();for(let i of this.plugins)i.update(this);this.docView.destroy(),this.docView=new pd(this),this.inputState.ensureHandlers(this.plugins),this.mountStyles(),this.updateAttrs(),this.bidiCache=[]}finally{this.updateState=0}t&&this.focus(),this.requestMeasure()}updatePlugins(e){let t=e.startState.facet(Ki),i=e.state.facet(Ki);if(t!=i){let s=[];for(let r of i){let o=t.indexOf(r);if(o<0)s.push(new ta(r));else{let l=this.plugins[o];l.mustUpdate=e,s.push(l)}}for(let r of this.plugins)r.mustUpdate!=e&&r.destroy(this);this.plugins=s,this.pluginMap.clear()}else for(let s of this.plugins)s.mustUpdate=e;for(let s=0;s-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.observer.delayedAndroidKey){this.measureScheduled=-1,this.requestMeasure();return}this.measureScheduled=0,e&&this.observer.forceFlush();let t=null,i=this.scrollDOM,s=i.scrollTop*this.scaleY,{scrollAnchorPos:r,scrollAnchorHeight:o}=this.viewState;Math.abs(s-this.viewState.scrollTop)>1&&(o=-1),this.viewState.scrollAnchorHeight=-1;try{for(let l=0;;l++){if(o<0)if(AO(i))r=-1,o=this.viewState.heightMap.height;else{let d=this.viewState.scrollAnchorAt(s);r=d.from,o=d.top}this.updateState=1;let a=this.viewState.measure(this);if(!a&&!this.measureRequests.length&&this.viewState.scrollTarget==null)break;if(l>5){console.warn(this.measureRequests.length?"Measure loop restarted more than 5 times":"Viewport failed to stabilize");break}let h=[];a&4||([this.measureRequests,h]=[h,this.measureRequests]);let c=h.map(d=>{try{return d.read(this)}catch(p){return si(this.state,p),Dd}}),f=fd.create(this,this.state,[]),u=!1;f.flags|=a,t?t.flags|=a:t=f,this.updateState=2,f.empty||(this.updatePlugins(f),this.inputState.update(f),this.updateAttrs(),u=this.docView.update(f),u&&this.docViewUpdate());for(let d=0;d1||p<-1){s=s+p,i.scrollTop=s/this.scaleY,o=-1;continue}}break}}}finally{this.updateState=0,this.measureScheduled=-1}if(t&&!t.empty)for(let l of this.state.facet(Eh))l(t)}get themeClasses(){return jh+" "+(this.state.facet(Nh)?Q0:k0)+" "+this.state.facet($r)}updateAttrs(){let e=Bd(this,GO,{class:"cm-editor"+(this.hasFocus?" cm-focused ":" ")+this.themeClasses}),t={spellcheck:"false",autocorrect:"off",autocapitalize:"off",writingsuggestions:"false",translate:"no",contenteditable:this.state.facet(Ut)?"true":"false",class:"cm-content",style:`${A.tabSize}: ${this.state.tabSize}`,role:"textbox","aria-multiline":"true"};this.state.readOnly&&(t["aria-readonly"]="true"),Bd(this,tf,t);let i=this.observer.ignore(()=>{let s=rd(this.contentDOM,this.contentAttrs,t),r=rd(this.dom,this.editorAttrs,e);return s||r});return this.editorAttrs=e,this.contentAttrs=t,i}showAnnouncements(e){let t=!0;for(let i of e)for(let s of i.effects)if(s.is(Hh.announce)){t&&(this.announceDOM.textContent=""),t=!1;let r=this.announceDOM.appendChild(document.createElement("div"));r.textContent=s.value}}mountStyles(){this.styleModules=this.state.facet(en);let e=this.state.facet(Hh.cspNonce);me.mount(this.root,this.styleModules.concat(gv).reverse(),e?{nonce:e}:void 0)}readMeasured(){if(this.updateState==2)throw new Error("Reading the editor layout isn't allowed during an update");this.updateState==0&&this.measureScheduled>-1&&this.measure(!1)}requestMeasure(e){if(this.measureScheduled<0&&(this.measureScheduled=this.win.requestAnimationFrame(()=>this.measure())),e){if(this.measureRequests.indexOf(e)>-1)return;if(e.key!=null){for(let t=0;ti.plugin==e)||null),t&&t.update(this).value}get documentTop(){return this.contentDOM.getBoundingClientRect().top+this.viewState.paddingTop}get documentPadding(){return{top:this.viewState.paddingTop,bottom:this.viewState.paddingBottom}}get scaleX(){return this.viewState.scaleX}get scaleY(){return this.viewState.scaleY}elementAtHeight(e){return this.readMeasured(),this.viewState.elementAtHeight(e)}lineBlockAtHeight(e){return this.readMeasured(),this.viewState.lineBlockAtHeight(e)}get viewportLineBlocks(){return this.viewState.viewportLines}lineBlockAt(e){return this.viewState.lineBlockAt(e)}get contentHeight(){return this.viewState.contentHeight}moveByChar(e,t,i){return ra(this,e,gd(this,e,t,i))}moveByGroup(e,t){return ra(this,e,gd(this,e,t,i=>CQ(this,e.head,i)))}visualLineSide(e,t){let i=this.bidiSpans(e),s=this.textDirectionAt(e.from),r=i[t?i.length-1:0];return S.cursor(r.side(t,s)+e.from,r.forward(!t,s)?1:-1)}moveToLineBoundary(e,t,i=!0){return PQ(this,e,t,i)}moveVertically(e,t,i){return ra(this,e,TQ(this,e,t,i))}domAtPos(e,t=1){return this.docView.domAtPos(e,t)}posAtDOM(e,t=0){return this.docView.posFromDOM(e,t)}posAtCoords(e,t=!0){this.readMeasured();let i=Ih(this,e,t);return i&&i.pos}posAndSideAtCoords(e,t=!0){return this.readMeasured(),Ih(this,e,t)}coordsAtPos(e,t=1){this.readMeasured();let i=this.docView.coordsAt(e,t);if(!i||i.left==i.right)return i;let s=this.state.doc.lineAt(e),r=this.bidiSpans(s),o=r[ii.find(r,e-s.from,-1,t)];return $o(i,o.dir==be.LTR==t>0)}coordsForChar(e){return this.readMeasured(),this.docView.coordsForChar(e)}get defaultCharacterWidth(){return this.viewState.heightOracle.charWidth}get defaultLineHeight(){return this.viewState.heightOracle.lineHeight}get textDirection(){return this.viewState.defaultTextDirection}textDirectionAt(e){return!this.state.facet(YO)||ethis.viewport.to?this.textDirection:(this.readMeasured(),this.docView.textDirectionAt(e))}get lineWrapping(){return this.viewState.heightOracle.lineWrapping}bidiSpans(e){if(e.length>yv)return LO(e.length);let t=this.textDirectionAt(e.from),i;for(let r of this.bidiCache)if(r.from==e.from&&r.dir==t&&(r.fresh||XO(r.isolates,i=cd(this,e))))return r.order;i||(i=cd(this,e));let s=Kk(e.text,t,i);return this.bidiCache.push(new Zd(e.from,e.to,t,i,!0,s)),s}get hasFocus(){var e;return(this.dom.ownerDocument.hasFocus()||A.safari&&((e=this.inputState)===null||e===void 0?void 0:e.lastContextMenu)>Date.now()-3e4)&&this.root.activeElement==this.contentDOM}focus(){this.observer.ignore(()=>{MO(this.contentDOM),this.docView.updateSelection()})}setRoot(e){this._root!=e&&(this._root=e,this.observer.setWindow((e.nodeType==9?e:e.ownerDocument).defaultView||window),this.mountStyles())}destroy(){this.root.activeElement==this.contentDOM&&this.contentDOM.blur();for(let e of this.plugins)e.destroy(this);this.plugins=[],this.inputState.destroy(),this.docView.destroy(),this.dom.remove(),this.observer.destroy(),this.measureScheduled>-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.destroyed=!0}static scrollIntoView(e,t={}){return yr.of(new ea(typeof e=="number"?S.cursor(e):e,t.y,t.x,t.yMargin,t.xMargin))}scrollSnapshot(){let{scrollTop:e,scrollLeft:t}=this.scrollDOM,i=this.viewState.scrollAnchorAt(e);return yr.of(new ea(S.cursor(i.from),"start","start",i.top-e,t,!0))}setTabFocusMode(e){e==null?this.inputState.tabFocusMode=this.inputState.tabFocusMode<0?0:-1:typeof e=="boolean"?this.inputState.tabFocusMode=e?0:-1:this.inputState.tabFocusMode!=0&&(this.inputState.tabFocusMode=Date.now()+e)}static domEventHandlers(e){return hd.define(()=>({}),{eventHandlers:e})}static domEventObservers(e){return hd.define(()=>({}),{eventObservers:e})}static theme(e,t){let i=me.newName(),s=[$r.of(i),en.of(Gh(`.${i}`,e))];return t&&t.dark&&s.push(Nh.of(!0)),s}static baseTheme(e){return xt.lowest(en.of(Gh("."+jh,e,v0)))}static findFromDOM(e){var t;let i=e.querySelector(".cm-content"),s=i&&pe.get(i)||pe.get(e);return((t=s?.root)===null||t===void 0?void 0:t.view)||null}};Y.styleModule=en;Y.inputHandler=IO;Y.clipboardInputFilter=Jc;Y.clipboardOutputFilter=ef;Y.scrollHandler=NO;Y.focusChangeEffect=_O;Y.perLineTextDirection=YO;Y.exceptionSink=qO;Y.updateListener=Eh;Y.editable=Ut;Y.mouseSelectionStyle=zO;Y.dragMovesSelection=VO;Y.clickAddsSelectionRange=WO;Y.decorations=Ol;Y.blockWrappers=HO;Y.outerDecorations=sf;Y.atomicRanges=Gn;Y.bidiIsolatedRanges=FO;Y.scrollMargins=UO;Y.darkTheme=Nh;Y.cspNonce=k.define({combine:n=>n.length?n[0]:""});Y.contentAttributes=tf;Y.editorAttributes=GO;Y.lineWrapping=Y.contentAttributes.of({class:"cm-lineWrapping"});Y.announce=B.define();const yv=4096,Dd={};let Zd=class $0{constructor(e,t,i,s,r,o){this.from=e,this.to=t,this.dir=i,this.isolates=s,this.fresh=r,this.order=o}static update(e,t){if(t.empty&&!e.some(r=>r.fresh))return e;let i=[],s=e.length?e[e.length-1].dir:be.LTR;for(let r=Math.max(0,e.length-10);r=0;s--){let r=i[s],o=typeof r=="function"?r(n):r;o&&Gc(o,t)}return t}let $s=class extends Ve{compare(e){return this==e||this.constructor==e.constructor&&this.eq(e)}eq(e){return!1}destroy(e){}};$s.prototype.elementClass="";$s.prototype.toDOM=void 0;$s.prototype.mapMode=te.TrackBefore;$s.prototype.startSide=$s.prototype.endSide=-1;$s.prototype.point=!0;const xv=n=>{let{state:e}=n,t=e.doc.lineAt(e.selection.main.from),i=of(n.state,t.from);return i.line?wv(n):i.block?Qv(n):!1};function rf(n,e){return({state:t,dispatch:i})=>{if(t.readOnly)return!1;let s=n(e,t);return s?(i(t.update(s)),!0):!1}}const wv=rf(Pv,0),kv=rf(P0,0),Qv=rf((n,e)=>P0(n,e,$v(e)),0);function of(n,e){let t=n.languageDataAt("commentTokens",e,1);return t.length?t[0]:{}}const Ns=50;function vv(n,{open:e,close:t},i,s){let r=n.sliceDoc(i-Ns,i),o=n.sliceDoc(s,s+Ns),l=/\s*$/.exec(r)[0].length,a=/^\s*/.exec(o)[0].length,h=r.length-l;if(r.slice(h-e.length,h)==e&&o.slice(a,a+t.length)==t)return{open:{pos:i-l,margin:l&&1},close:{pos:s+a,margin:a&&1}};let c,f;s-i<=2*Ns?c=f=n.sliceDoc(i,s):(c=n.sliceDoc(i,i+Ns),f=n.sliceDoc(s-Ns,s));let u=/^\s*/.exec(c)[0].length,d=/\s*$/.exec(f)[0].length,p=f.length-d-t.length;return c.slice(u,u+e.length)==e&&f.slice(p,p+t.length)==t?{open:{pos:i+u+e.length,margin:/\s/.test(c.charAt(u+e.length))?1:0},close:{pos:s-d-t.length,margin:/\s/.test(f.charAt(p-1))?1:0}}:null}function $v(n){let e=[];for(let t of n.selection.ranges){let i=n.doc.lineAt(t.from),s=t.to<=i.to?i:n.doc.lineAt(t.to);s.from>i.from&&s.from==t.to&&(s=t.to==i.to+1?i:n.doc.lineAt(t.to-1));let r=e.length-1;r>=0&&e[r].to>i.from?e[r].to=s.to:e.push({from:i.from+/^\s*/.exec(i.text)[0].length,to:s.to})}return e}function P0(n,e,t=e.selection.ranges){let i=t.map(r=>of(e,r.from).block);if(!i.every(r=>r))return null;let s=t.map((r,o)=>vv(e,i[o],r.from,r.to));if(n!=2&&!s.every(r=>r))return{changes:e.changes(t.map((r,o)=>s[o]?[]:[{from:r.from,insert:i[o].open+" "},{from:r.to,insert:" "+i[o].close}]))};if(n!=1&&s.some(r=>r)){let r=[];for(let o=0,l;os&&(r==o||o>f.from)){s=f.from;let u=/^\s*/.exec(f.text)[0].length,d=u==f.length,p=f.text.slice(u,u+h.length)==h?u:-1;ur.comment<0&&(!r.empty||r.single))){let r=[];for(let{line:l,token:a,indent:h,empty:c,single:f}of i)(f||!c)&&r.push({from:l.from+h,insert:a+" "});let o=e.changes(r);return{changes:o,selection:e.selection.map(o,1)}}else if(n!=1&&i.some(r=>r.comment>=0)){let r=[];for(let{line:o,comment:l,token:a}of i)if(l>=0){let h=o.from+l,c=h+a.length;o.text[c-o.from]==" "&&c++,r.push({from:h,to:c})}return{changes:r}}return null}const Fh=wt.define(),Cv=wt.define(),Tv=k.define(),C0=k.define({combine(n){return _t(n,{minDepth:100,newGroupDelay:500,joinToEvent:(e,t)=>t},{minDepth:Math.max,newGroupDelay:Math.min,joinToEvent:(e,t)=>(i,s)=>e(i,s)||t(i,s)})}}),T0=Se.define({create(){return zt.empty},update(n,e){let t=e.state.facet(C0),i=e.annotation(Fh);if(i){let a=Ee.fromTransaction(e,i.selection),h=i.side,c=h==0?n.undone:n.done;return a?c=Ro(c,c.length,t.minDepth,a):c=R0(c,e.startState.selection),new zt(h==0?i.rest:c,h==0?c:i.rest)}let s=e.annotation(Cv);if((s=="full"||s=="before")&&(n=n.isolate()),e.annotation(ce.addToHistory)===!1)return e.changes.empty?n:n.addMapping(e.changes.desc);let r=Ee.fromTransaction(e),o=e.annotation(ce.time),l=e.annotation(ce.userEvent);return r?n=n.addChanges(r,o,l,t,e):e.selection&&(n=n.addSelection(e.startState.selection,o,l,t.newGroupDelay)),(s=="full"||s=="after")&&(n=n.isolate()),n},toJSON(n){return{done:n.done.map(e=>e.toJSON()),undone:n.undone.map(e=>e.toJSON())}},fromJSON(n){return new zt(n.done.map(Ee.fromJSON),n.undone.map(Ee.fromJSON))}});function Mv(n={}){return[T0,C0.of(n),Y.domEventHandlers({beforeinput(e,t){let i=e.inputType=="historyUndo"?M0:e.inputType=="historyRedo"?Uh:null;return i?(e.preventDefault(),i(t)):!1}})]}function xl(n,e){return function({state:t,dispatch:i}){if(!e&&t.readOnly)return!1;let s=t.field(T0,!1);if(!s)return!1;let r=s.pop(n,t,e);return r?(i(r),!0):!1}}const M0=xl(0,!1),Uh=xl(1,!1),Av=xl(0,!0),Rv=xl(1,!0);class Ee{constructor(e,t,i,s,r){this.changes=e,this.effects=t,this.mapped=i,this.startSelection=s,this.selectionsAfter=r}setSelAfter(e){return new Ee(this.changes,this.effects,this.mapped,this.startSelection,e)}toJSON(){var e,t,i;return{changes:(e=this.changes)===null||e===void 0?void 0:e.toJSON(),mapped:(t=this.mapped)===null||t===void 0?void 0:t.toJSON(),startSelection:(i=this.startSelection)===null||i===void 0?void 0:i.toJSON(),selectionsAfter:this.selectionsAfter.map(s=>s.toJSON())}}static fromJSON(e){return new Ee(e.changes&&he.fromJSON(e.changes),[],e.mapped&&qt.fromJSON(e.mapped),e.startSelection&&S.fromJSON(e.startSelection),e.selectionsAfter.map(S.fromJSON))}static fromTransaction(e,t){let i=tt;for(let s of e.startState.facet(Tv)){let r=s(e);r.length&&(i=i.concat(r))}return!i.length&&e.changes.empty?null:new Ee(e.changes.invert(e.startState.doc),i,void 0,t||e.startState.selection,tt)}static selection(e){return new Ee(void 0,tt,void 0,void 0,e)}}function Ro(n,e,t,i){let s=e+1>t+20?e-t-1:0,r=n.slice(s,e);return r.push(i),r}function Dv(n,e){let t=[],i=!1;return n.iterChangedRanges((s,r)=>t.push(s,r)),e.iterChangedRanges((s,r,o,l)=>{for(let a=0;a=h&&o<=c&&(i=!0)}}),i}function Zv(n,e){return n.ranges.length==e.ranges.length&&n.ranges.filter((t,i)=>t.empty!=e.ranges[i].empty).length===0}function A0(n,e){return n.length?e.length?n.concat(e):n:e}const tt=[],Bv=200;function R0(n,e){if(n.length){let t=n[n.length-1],i=t.selectionsAfter.slice(Math.max(0,t.selectionsAfter.length-Bv));return i.length&&i[i.length-1].eq(e)?n:(i.push(e),Ro(n,n.length-1,1e9,t.setSelAfter(i)))}else return[Ee.selection([e])]}function Xv(n){let e=n[n.length-1],t=n.slice();return t[n.length-1]=e.setSelAfter(e.selectionsAfter.slice(0,e.selectionsAfter.length-1)),t}function aa(n,e){if(!n.length)return n;let t=n.length,i=tt;for(;t;){let s=Lv(n[t-1],e,i);if(s.changes&&!s.changes.empty||s.effects.length){let r=n.slice(0,t);return r[t-1]=s,r}else e=s.mapped,t--,i=s.selectionsAfter}return i.length?[Ee.selection(i)]:tt}function Lv(n,e,t){let i=A0(n.selectionsAfter.length?n.selectionsAfter.map(l=>l.map(e)):tt,t);if(!n.changes)return Ee.selection(i);let s=n.changes.map(e),r=e.mapDesc(n.changes,!0),o=n.mapped?n.mapped.composeDesc(r):r;return new Ee(s,B.mapEffects(n.effects,e),o,n.startSelection.map(r),i)}const Ev=/^(input\.type|delete)($|\.)/;class zt{constructor(e,t,i=0,s=void 0){this.done=e,this.undone=t,this.prevTime=i,this.prevUserEvent=s}isolate(){return this.prevTime?new zt(this.done,this.undone):this}addChanges(e,t,i,s,r){let o=this.done,l=o[o.length-1];return l&&l.changes&&!l.changes.empty&&e.changes&&(!i||Ev.test(i))&&(!l.selectionsAfter.length&&t-this.prevTime0&&t-this.prevTimet.empty?n.moveByChar(t,e):wl(t,e))}function $e(n){return n.textDirectionAt(n.state.selection.main.head)==be.LTR}const Z0=n=>D0(n,!$e(n)),B0=n=>D0(n,$e(n));function X0(n,e){return Qt(n,t=>t.empty?n.moveByGroup(t,e):wl(t,e))}const Vv=n=>X0(n,!$e(n)),zv=n=>X0(n,$e(n));function qv(n,e,t){if(e.type.prop(t))return!0;let i=e.to-e.from;return i&&(i>2||/[^\s,.;:]/.test(n.sliceDoc(e.from,e.to)))||e.firstChild}function kl(n,e,t){let i=fe(n).resolveInner(e.head),s=t?V.closedBy:V.openedBy;for(let a=e.head;;){let h=t?i.childAfter(a):i.childBefore(a);if(!h)break;qv(n,h,s)?i=h:a=t?h.to:h.from}let r=i.type.prop(s),o,l;return r&&(o=t?Vt(n,i.from,1):Vt(n,i.to,-1))&&o.matched?l=t?o.end.to:o.end.from:l=t?i.to:i.from,S.cursor(l,t?-1:1)}const Iv=n=>Qt(n,e=>kl(n.state,e,!$e(n))),_v=n=>Qt(n,e=>kl(n.state,e,$e(n)));function L0(n,e){return Qt(n,t=>{if(!t.empty)return wl(t,e);let i=n.moveVertically(t,e);return i.head!=t.head?i:n.moveToLineBoundary(t,e)})}const E0=n=>L0(n,!1),W0=n=>L0(n,!0);function V0(n){let e=n.scrollDOM.clientHeighto.empty?n.moveVertically(o,e,t.height):wl(o,e));if(s.eq(i.selection))return!1;let r;if(t.selfScroll){let o=n.coordsAtPos(i.selection.main.head),l=n.scrollDOM.getBoundingClientRect(),a=l.top+t.marginTop,h=l.bottom-t.marginBottom;o&&o.top>a&&o.bottomz0(n,!1),Kh=n=>z0(n,!0);function Ci(n,e,t){let i=n.lineBlockAt(e.head),s=n.moveToLineBoundary(e,t);if(s.head==e.head&&s.head!=(t?i.to:i.from)&&(s=n.moveToLineBoundary(e,t,!1)),!t&&s.head==i.from&&i.length){let r=/^\s*/.exec(n.state.sliceDoc(i.from,Math.min(i.from+100,i.to)))[0].length;r&&e.head!=i.from+r&&(s=S.cursor(i.from+r))}return s}const Yv=n=>Qt(n,e=>Ci(n,e,!0)),Nv=n=>Qt(n,e=>Ci(n,e,!1)),jv=n=>Qt(n,e=>Ci(n,e,!$e(n))),Gv=n=>Qt(n,e=>Ci(n,e,$e(n))),Hv=n=>Qt(n,e=>S.cursor(n.lineBlockAt(e.head).from,1)),Fv=n=>Qt(n,e=>S.cursor(n.lineBlockAt(e.head).to,-1));function Uv(n,e,t){let i=!1,s=Es(n.selection,r=>{let o=Vt(n,r.head,-1)||Vt(n,r.head,1)||r.head>0&&Vt(n,r.head-1,1)||r.headUv(n,e);function ht(n,e){let t=Es(n.state.selection,i=>{let s=e(i);return S.range(i.anchor,s.head,s.goalColumn,s.bidiLevel||void 0)});return t.eq(n.state.selection)?!1:(n.dispatch(kt(n.state,t)),!0)}function q0(n,e){return ht(n,t=>n.moveByChar(t,e))}const I0=n=>q0(n,!$e(n)),_0=n=>q0(n,$e(n));function Y0(n,e){return ht(n,t=>n.moveByGroup(t,e))}const Jv=n=>Y0(n,!$e(n)),e$=n=>Y0(n,$e(n)),t$=n=>ht(n,e=>kl(n.state,e,!$e(n))),i$=n=>ht(n,e=>kl(n.state,e,$e(n)));function N0(n,e){return ht(n,t=>n.moveVertically(t,e))}const j0=n=>N0(n,!1),G0=n=>N0(n,!0);function H0(n,e){return ht(n,t=>n.moveVertically(t,e,V0(n).height))}const Ld=n=>H0(n,!1),Ed=n=>H0(n,!0),s$=n=>ht(n,e=>Ci(n,e,!0)),n$=n=>ht(n,e=>Ci(n,e,!1)),r$=n=>ht(n,e=>Ci(n,e,!$e(n))),o$=n=>ht(n,e=>Ci(n,e,$e(n))),l$=n=>ht(n,e=>S.cursor(n.lineBlockAt(e.head).from)),a$=n=>ht(n,e=>S.cursor(n.lineBlockAt(e.head).to)),Wd=({state:n,dispatch:e})=>(e(kt(n,{anchor:0})),!0),Vd=({state:n,dispatch:e})=>(e(kt(n,{anchor:n.doc.length})),!0),zd=({state:n,dispatch:e})=>(e(kt(n,{anchor:n.selection.main.anchor,head:0})),!0),qd=({state:n,dispatch:e})=>(e(kt(n,{anchor:n.selection.main.anchor,head:n.doc.length})),!0),h$=({state:n,dispatch:e})=>(e(n.update({selection:{anchor:0,head:n.doc.length},userEvent:"select"})),!0),c$=({state:n,dispatch:e})=>{let t=Ql(n).map(({from:i,to:s})=>S.range(i,Math.min(s+1,n.doc.length)));return e(n.update({selection:S.create(t),userEvent:"select"})),!0},f$=({state:n,dispatch:e})=>{let t=Es(n.selection,i=>{let s=fe(n),r=s.resolveStack(i.from,1);if(i.empty){let o=s.resolveStack(i.from,-1);o.node.from>=r.node.from&&o.node.to<=r.node.to&&(r=o)}for(let o=r;o;o=o.next){let{node:l}=o;if((l.from=i.to||l.to>i.to&&l.from<=i.from)&&o.next)return S.range(l.to,l.from)}return i});return t.eq(n.selection)?!1:(e(kt(n,t)),!0)};function F0(n,e){let{state:t}=n,i=t.selection,s=t.selection.ranges.slice();for(let r of t.selection.ranges){let o=t.doc.lineAt(r.head);if(e?o.to0)for(let l=r;;){let a=n.moveVertically(l,e);if(a.heado.to){s.some(h=>h.head==a.head)||s.push(a);break}else{if(a.head==l.head)break;l=a}}}return s.length==i.ranges.length?!1:(n.dispatch(kt(t,S.create(s,s.length-1))),!0)}const u$=n=>F0(n,!1),d$=n=>F0(n,!0),p$=({state:n,dispatch:e})=>{let t=n.selection,i=null;return t.ranges.length>1?i=S.create([t.main]):t.main.empty||(i=S.create([S.cursor(t.main.head)])),i?(e(kt(n,i)),!0):!1};function Hn(n,e){if(n.state.readOnly)return!1;let t="delete.selection",{state:i}=n,s=i.changeByRange(r=>{let{from:o,to:l}=r;if(o==l){let a=e(r);ao&&(t="delete.forward",a=Pr(n,a,!0)),o=Math.min(o,a),l=Math.max(l,a)}else o=Pr(n,o,!1),l=Pr(n,l,!0);return o==l?{range:r}:{changes:{from:o,to:l},range:S.cursor(o,os(n)))i.between(e,e,(s,r)=>{se&&(e=t?r:s)});return e}const U0=(n,e,t)=>Hn(n,i=>{let s=i.from,{state:r}=n,o=r.doc.lineAt(s),l,a;if(t&&!e&&s>o.from&&sU0(n,!1,!0),K0=n=>U0(n,!0,!1),J0=(n,e)=>Hn(n,t=>{let i=t.head,{state:s}=n,r=s.doc.lineAt(i),o=s.charCategorizer(i);for(let l=null;;){if(i==(e?r.to:r.from)){i==t.head&&r.number!=(e?s.doc.lines:1)&&(i+=e?1:-1);break}let a=_(r.text,i-r.from,e)+r.from,h=r.text.slice(Math.min(i,a)-r.from,Math.max(i,a)-r.from),c=o(h);if(l!=null&&c!=l)break;(h!=" "||i!=t.head)&&(l=c),i=a}return i}),eb=n=>J0(n,!1),g$=n=>J0(n,!0),m$=n=>Hn(n,e=>{let t=n.lineBlockAt(e.head).to;return e.headHn(n,e=>{let t=n.moveToLineBoundary(e,!1).head;return e.head>t?t:Math.max(0,e.head-1)}),b$=n=>Hn(n,e=>{let t=n.moveToLineBoundary(e,!0).head;return e.head{if(n.readOnly)return!1;let t=n.changeByRange(i=>({changes:{from:i.from,to:i.to,insert:Z.of(["",""])},range:S.cursor(i.from)}));return e(n.update(t,{scrollIntoView:!0,userEvent:"input"})),!0},y$=({state:n,dispatch:e})=>{if(n.readOnly)return!1;let t=n.changeByRange(i=>{if(!i.empty||i.from==0||i.from==n.doc.length)return{range:i};let s=i.from,r=n.doc.lineAt(s),o=s==r.from?s-1:_(r.text,s-r.from,!1)+r.from,l=s==r.to?s+1:_(r.text,s-r.from,!0)+r.from;return{changes:{from:o,to:l,insert:n.doc.slice(s,l).append(n.doc.slice(o,s))},range:S.cursor(l)}});return t.changes.empty?!1:(e(n.update(t,{scrollIntoView:!0,userEvent:"move.character"})),!0)};function Ql(n){let e=[],t=-1;for(let i of n.selection.ranges){let s=n.doc.lineAt(i.from),r=n.doc.lineAt(i.to);if(!i.empty&&i.to==r.from&&(r=n.doc.lineAt(i.to-1)),t>=s.number){let o=e[e.length-1];o.to=r.to,o.ranges.push(i)}else e.push({from:s.from,to:r.to,ranges:[i]});t=r.number+1}return e}function tb(n,e,t){if(n.readOnly)return!1;let i=[],s=[];for(let r of Ql(n)){if(t?r.to==n.doc.length:r.from==0)continue;let o=n.doc.lineAt(t?r.to+1:r.from-1),l=o.length+1;if(t){i.push({from:r.to,to:o.to},{from:r.from,insert:o.text+n.lineBreak});for(let a of r.ranges)s.push(S.range(Math.min(n.doc.length,a.anchor+l),Math.min(n.doc.length,a.head+l)))}else{i.push({from:o.from,to:r.from},{from:r.to,insert:n.lineBreak+o.text});for(let a of r.ranges)s.push(S.range(a.anchor-l,a.head-l))}}return i.length?(e(n.update({changes:i,scrollIntoView:!0,selection:S.create(s,n.selection.mainIndex),userEvent:"move.line"})),!0):!1}const x$=({state:n,dispatch:e})=>tb(n,e,!1),w$=({state:n,dispatch:e})=>tb(n,e,!0);function ib(n,e,t){if(n.readOnly)return!1;let i=[];for(let r of Ql(n))t?i.push({from:r.from,insert:n.doc.slice(r.from,r.to)+n.lineBreak}):i.push({from:r.to,insert:n.lineBreak+n.doc.slice(r.from,r.to)});let s=n.changes(i);return e(n.update({changes:s,selection:n.selection.map(s,t?1:-1),scrollIntoView:!0,userEvent:"input.copyline"})),!0}const k$=({state:n,dispatch:e})=>ib(n,e,!1),Q$=({state:n,dispatch:e})=>ib(n,e,!0),v$=n=>{if(n.state.readOnly)return!1;let{state:e}=n,t=e.changes(Ql(e).map(({from:s,to:r})=>(s>0?s--:r{let r;if(n.lineWrapping){let o=n.lineBlockAt(s.head),l=n.coordsAtPos(s.head,s.assoc||1);l&&(r=o.bottom+n.documentTop-l.bottom+n.defaultLineHeight/2)}return n.moveVertically(s,!0,r)}).map(t);return n.dispatch({changes:t,selection:i,scrollIntoView:!0,userEvent:"delete.line"}),!0};function $$(n,e){if(/\(\)|\[\]|\{\}/.test(n.sliceDoc(e-1,e+1)))return{from:e,to:e};let t=fe(n).resolveInner(e),i=t.childBefore(e),s=t.childAfter(e),r;return i&&s&&i.to<=e&&s.from>=e&&(r=i.type.prop(V.closedBy))&&r.indexOf(s.name)>-1&&n.doc.lineAt(i.to).from==n.doc.lineAt(s.from).from&&!/\S/.test(n.sliceDoc(i.to,s.from))?{from:i.to,to:s.from}:null}const Id=sb(!1),P$=sb(!0);function sb(n){return({state:e,dispatch:t})=>{if(e.readOnly)return!1;let i=e.changeByRange(s=>{let{from:r,to:o}=s,l=e.doc.lineAt(r),a=!n&&r==o&&$$(e,r);n&&(r=o=(o<=l.to?l:e.doc.lineAt(o)).to);let h=new fl(e,{simulateBreak:r,simulateDoubleBreak:!!a}),c=Nc(h,r);for(c==null&&(c=Ls(/^\s*/.exec(e.doc.lineAt(r).text)[0],e.tabSize));ol.from&&r{let s=[];for(let o=i.from;o<=i.to;){let l=n.doc.lineAt(o);l.number>t&&(i.empty||i.to>l.from)&&(e(l,s,i),t=l.number),o=l.to+1}let r=n.changes(s);return{changes:s,range:S.range(r.mapPos(i.anchor,1),r.mapPos(i.head,1))}})}const C$=({state:n,dispatch:e})=>{if(n.readOnly)return!1;let t=Object.create(null),i=new fl(n,{overrideIndentation:r=>{let o=t[r];return o??-1}}),s=lf(n,(r,o,l)=>{let a=Nc(i,r.from);if(a==null)return;/\S/.test(r.text)||(a=0);let h=/^\s*/.exec(r.text)[0],c=Mn(n,a);(h!=c||l.fromn.readOnly?!1:(e(n.update(lf(n,(t,i)=>{i.push({from:t.from,insert:n.facet(cl)})}),{userEvent:"input.indent"})),!0),rb=({state:n,dispatch:e})=>n.readOnly?!1:(e(n.update(lf(n,(t,i)=>{let s=/^\s*/.exec(t.text)[0];if(!s)return;let r=Ls(s,n.tabSize),o=0,l=Mn(n,Math.max(0,r-xo(n)));for(;o(n.setTabFocusMode(),!0),M$=[{key:"Ctrl-b",run:Z0,shift:I0,preventDefault:!0},{key:"Ctrl-f",run:B0,shift:_0},{key:"Ctrl-p",run:E0,shift:j0},{key:"Ctrl-n",run:W0,shift:G0},{key:"Ctrl-a",run:Hv,shift:l$},{key:"Ctrl-e",run:Fv,shift:a$},{key:"Ctrl-d",run:K0},{key:"Ctrl-h",run:Jh},{key:"Ctrl-k",run:m$},{key:"Ctrl-Alt-h",run:eb},{key:"Ctrl-o",run:S$},{key:"Ctrl-t",run:y$},{key:"Ctrl-v",run:Kh}],A$=[{key:"ArrowLeft",run:Z0,shift:I0,preventDefault:!0},{key:"Mod-ArrowLeft",mac:"Alt-ArrowLeft",run:Vv,shift:Jv,preventDefault:!0},{mac:"Cmd-ArrowLeft",run:jv,shift:r$,preventDefault:!0},{key:"ArrowRight",run:B0,shift:_0,preventDefault:!0},{key:"Mod-ArrowRight",mac:"Alt-ArrowRight",run:zv,shift:e$,preventDefault:!0},{mac:"Cmd-ArrowRight",run:Gv,shift:o$,preventDefault:!0},{key:"ArrowUp",run:E0,shift:j0,preventDefault:!0},{mac:"Cmd-ArrowUp",run:Wd,shift:zd},{mac:"Ctrl-ArrowUp",run:Xd,shift:Ld},{key:"ArrowDown",run:W0,shift:G0,preventDefault:!0},{mac:"Cmd-ArrowDown",run:Vd,shift:qd},{mac:"Ctrl-ArrowDown",run:Kh,shift:Ed},{key:"PageUp",run:Xd,shift:Ld},{key:"PageDown",run:Kh,shift:Ed},{key:"Home",run:Nv,shift:n$,preventDefault:!0},{key:"Mod-Home",run:Wd,shift:zd},{key:"End",run:Yv,shift:s$,preventDefault:!0},{key:"Mod-End",run:Vd,shift:qd},{key:"Enter",run:Id,shift:Id},{key:"Mod-a",run:h$},{key:"Backspace",run:Jh,shift:Jh,preventDefault:!0},{key:"Delete",run:K0,preventDefault:!0},{key:"Mod-Backspace",mac:"Alt-Backspace",run:eb,preventDefault:!0},{key:"Mod-Delete",mac:"Alt-Delete",run:g$,preventDefault:!0},{mac:"Mod-Backspace",run:O$,preventDefault:!0},{mac:"Mod-Delete",run:b$,preventDefault:!0}].concat(M$.map(n=>({mac:n.key,run:n.run,shift:n.shift}))),R$=[{key:"Alt-ArrowLeft",mac:"Ctrl-ArrowLeft",run:Iv,shift:t$},{key:"Alt-ArrowRight",mac:"Ctrl-ArrowRight",run:_v,shift:i$},{key:"Alt-ArrowUp",run:x$},{key:"Shift-Alt-ArrowUp",run:k$},{key:"Alt-ArrowDown",run:w$},{key:"Shift-Alt-ArrowDown",run:Q$},{key:"Mod-Alt-ArrowUp",run:u$},{key:"Mod-Alt-ArrowDown",run:d$},{key:"Escape",run:p$},{key:"Mod-Enter",run:P$},{key:"Alt-l",mac:"Ctrl-l",run:c$},{key:"Mod-i",run:f$,preventDefault:!0},{key:"Mod-[",run:rb},{key:"Mod-]",run:nb},{key:"Mod-Alt-\\",run:C$},{key:"Shift-Mod-k",run:v$},{key:"Shift-Mod-\\",run:Kv},{key:"Mod-/",run:xv},{key:"Alt-A",run:kv},{key:"Ctrl-m",mac:"Shift-Alt-m",run:T$}].concat(A$),D$={key:"Tab",run:nb,shift:rb};let Te=typeof navigator<"u"?navigator:{userAgent:"",vendor:"",platform:""},ec=typeof document<"u"?document:{documentElement:{style:{}}};const tc=/Edge\/(\d+)/.exec(Te.userAgent),ob=/MSIE \d/.test(Te.userAgent),ic=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(Te.userAgent),vl=!!(ob||ic||tc),_d=!vl&&/gecko\/(\d+)/i.test(Te.userAgent),ha=!vl&&/Chrome\/(\d+)/.exec(Te.userAgent),Z$="webkitFontSmoothing"in ec.documentElement.style,sc=!vl&&/Apple Computer/.test(Te.vendor),Yd=sc&&(/Mobile\/\w+/.test(Te.userAgent)||Te.maxTouchPoints>2);var T={mac:Yd||/Mac/.test(Te.platform),windows:/Win/.test(Te.platform),linux:/Linux|X11/.test(Te.platform),ie:vl,ie_version:ob?ec.documentMode||6:ic?+ic[1]:tc?+tc[1]:0,gecko:_d,gecko_version:_d?+(/Firefox\/(\d+)/.exec(Te.userAgent)||[0,0])[1]:0,chrome:!!ha,chrome_version:ha?+ha[1]:0,ios:Yd,android:/Android\b/.test(Te.userAgent),webkit_version:Z$?+(/\bAppleWebKit\/(\d+)/.exec(Te.userAgent)||[0,0])[1]:0,safari:sc,safari_version:sc?+(/\bVersion\/(\d+(\.\d+)?)/.exec(Te.userAgent)||[0,0])[1]:0,tabSize:ec.documentElement.style.tabSize!=null?"tab-size":"-moz-tab-size"};function af(n,e){for(let t in n)t=="class"&&e.class?e.class+=" "+n.class:t=="style"&&e.style?e.style+=";"+n.style:e[t]=n[t];return e}const Do=Object.create(null);function hf(n,e,t){if(n==e)return!0;n||(n=Do),e||(e=Do);let i=Object.keys(n),s=Object.keys(e);if(i.length-0!=s.length-0)return!1;for(let r of i)if(r!=t&&(s.indexOf(r)==-1||n[r]!==e[r]))return!1;return!0}function B$(n,e){for(let t=n.attributes.length-1;t>=0;t--){let i=n.attributes[t].name;e[i]==null&&n.removeAttribute(i)}for(let t in e){let i=e[t];t=="style"?n.style.cssText=i:n.getAttribute(t)!=i&&n.setAttribute(t,i)}}function Nd(n,e,t){let i=!1;if(e)for(let s in e)t&&s in t||(i=!0,s=="style"?n.style.cssText="":n.removeAttribute(s));if(t)for(let s in t)e&&e[s]==t[s]||(i=!0,s=="style"?n.style.cssText=t[s]:n.setAttribute(s,t[s]));return i}function X$(n){let e=Object.create(null);for(let t=0;t0?3e8:-4e8:t>0?1e8:-1e8,new Ii(e,t,t,i,e.widget||null,!1)}static replace(e){let t=!!e.block,i,s;if(e.isBlockGap)i=-5e8,s=4e8;else{let{start:r,end:o}=lb(e,t);i=(r?t?-3e8:-1:5e8)-1,s=(o?t?2e8:1:-6e8)+1}return new Ii(e,i,s,t,e.widget||null,!0)}static line(e){return new Un(e)}static set(e,t=!1){return M.of(e,t)}hasHeight(){return this.widget?this.widget.estimatedHeight>-1:!1}}ee.none=M.empty;class Fn extends ee{constructor(e){let{start:t,end:i}=lb(e);super(t?-1:5e8,i?1:-6e8,null,e),this.tagName=e.tagName||"span",this.attrs=e.class&&e.attributes?af(e.attributes,{class:e.class}):e.class?{class:e.class}:e.attributes||Do}eq(e){return this==e||e instanceof Fn&&this.tagName==e.tagName&&hf(this.attrs,e.attrs)}range(e,t=e){if(e>=t)throw new RangeError("Mark decorations may not be empty");return super.range(e,t)}}Fn.prototype.point=!1;class Un extends ee{constructor(e){super(-2e8,-2e8,null,e)}eq(e){return e instanceof Un&&this.spec.class==e.spec.class&&hf(this.spec.attributes,e.spec.attributes)}range(e,t=e){if(t!=e)throw new RangeError("Line decoration ranges must be zero-length");return super.range(e,t)}}Un.prototype.mapMode=te.TrackBefore;Un.prototype.point=!0;class Ii extends ee{constructor(e,t,i,s,r,o){super(t,i,r,e),this.block=s,this.isReplace=o,this.mapMode=s?t<=0?te.TrackBefore:te.TrackAfter:te.TrackDel}get type(){return this.startSide!=this.endSide?rt.WidgetRange:this.startSide<=0?rt.WidgetBefore:rt.WidgetAfter}get heightRelevant(){return this.block||!!this.widget&&(this.widget.estimatedHeight>=5||this.widget.lineBreaks>0)}eq(e){return e instanceof Ii&&L$(this.widget,e.widget)&&this.block==e.block&&this.startSide==e.startSide&&this.endSide==e.endSide}range(e,t=e){if(this.isReplace&&(e>t||e==t&&this.startSide>0&&this.endSide<=0))throw new RangeError("Invalid range for replacement decoration");if(!this.isReplace&&t!=e)throw new RangeError("Widget decorations can only have zero-length ranges");return super.range(e,t)}}Ii.prototype.point=!0;function lb(n,e=!1){let{inclusiveStart:t,inclusiveEnd:i}=n;return t==null&&(t=n.inclusive),i==null&&(i=n.inclusive),{start:t??e,end:i??e}}function L$(n,e){return n==e||!!(n&&e&&n.compare(e))}function fs(n,e,t,i=0){let s=t.length-1;s>=0&&t[s]+i>=n?t[s]=Math.max(t[s],e):t.push(n,e)}class Zn extends Ve{constructor(e,t){super(),this.tagName=e,this.attributes=t}eq(e){return e==this||e instanceof Zn&&this.tagName==e.tagName&&hf(this.attributes,e.attributes)}static create(e){return new Zn(e.tagName,e.attributes||Do)}static set(e,t=!1){return M.of(e,t)}}Zn.prototype.startSide=Zn.prototype.endSide=-1;function Ps(n){let e;return n.nodeType==11?e=n.getSelection?n:n.ownerDocument:e=n,e.getSelection()}function nc(n,e){return e?n==e||n.contains(e.nodeType!=1?e.parentNode:e):!1}function bn(n,e){if(!e.anchorNode)return!1;try{return nc(n,e.anchorNode)}catch{return!1}}function eo(n){return n.nodeType==3?Bn(n,0,n.nodeValue.length).getClientRects():n.nodeType==1?n.getClientRects():[]}function Sn(n,e,t,i){return t?jd(n,e,t,i,-1)||jd(n,e,t,i,1):!1}function $i(n){for(var e=0;;e++)if(n=n.previousSibling,!n)return e}function Zo(n){return n.nodeType==1&&/^(DIV|P|LI|UL|OL|BLOCKQUOTE|DD|DT|H\d|SECTION|PRE)$/.test(n.nodeName)}function jd(n,e,t,i,s){for(;;){if(n==t&&e==i)return!0;if(e==(s<0?0:ui(n))){if(n.nodeName=="DIV")return!1;let r=n.parentNode;if(!r||r.nodeType!=1)return!1;e=$i(n)+(s<0?0:1),n=r}else if(n.nodeType==1){if(n=n.childNodes[e+(s<0?-1:0)],n.nodeType==1&&n.contentEditable=="false")return!1;e=s<0?ui(n):0}else return!1}}function ui(n){return n.nodeType==3?n.nodeValue.length:n.childNodes.length}function Bo(n,e){let t=e?n.left:n.right;return{left:t,right:t,top:n.top,bottom:n.bottom}}function E$(n){let e=n.visualViewport;return e?{left:0,right:e.width,top:0,bottom:e.height}:{left:0,right:n.innerWidth,top:0,bottom:n.innerHeight}}function ab(n,e){let t=e.width/n.offsetWidth,i=e.height/n.offsetHeight;return(t>.995&&t<1.005||!isFinite(t)||Math.abs(e.width-n.offsetWidth)<1)&&(t=1),(i>.995&&i<1.005||!isFinite(i)||Math.abs(e.height-n.offsetHeight)<1)&&(i=1),{scaleX:t,scaleY:i}}function W$(n,e,t,i,s,r,o,l){let a=n.ownerDocument,h=a.defaultView||window;for(let c=n,f=!1;c&&!f;)if(c.nodeType==1){let u,d=c==a.body,p=1,g=1;if(d)u=E$(h);else{if(/^(fixed|sticky)$/.test(getComputedStyle(c).position)&&(f=!0),c.scrollHeight<=c.clientHeight&&c.scrollWidth<=c.clientWidth){c=c.assignedSlot||c.parentNode;continue}let y=c.getBoundingClientRect();({scaleX:p,scaleY:g}=ab(c,y)),u={left:y.left,right:y.left+c.clientWidth*p,top:y.top,bottom:y.top+c.clientHeight*g}}let m=0,O=0;if(s=="nearest")e.top0&&e.bottom>u.bottom+O&&(O=e.bottom-u.bottom+o)):e.bottom>u.bottom&&(O=e.bottom-u.bottom+o,t<0&&e.top-O0&&e.right>u.right+m&&(m=e.right-u.right+r)):e.right>u.right&&(m=e.right-u.right+r,t<0&&e.leftu.bottom||e.leftu.right)&&(e={left:Math.max(e.left,u.left),right:Math.min(e.right,u.right),top:Math.max(e.top,u.top),bottom:Math.min(e.bottom,u.bottom)}),c=c.assignedSlot||c.parentNode}else if(c.nodeType==11)c=c.host;else break}function V$(n){let e=n.ownerDocument,t,i;for(let s=n.parentNode;s&&!(s==e.body||t&&i);)if(s.nodeType==1)!i&&s.scrollHeight>s.clientHeight&&(i=s),!t&&s.scrollWidth>s.clientWidth&&(t=s),s=s.assignedSlot||s.parentNode;else if(s.nodeType==11)s=s.host;else break;return{x:t,y:i}}class z${constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}eq(e){return this.anchorNode==e.anchorNode&&this.anchorOffset==e.anchorOffset&&this.focusNode==e.focusNode&&this.focusOffset==e.focusOffset}setRange(e){let{anchorNode:t,focusNode:i}=e;this.set(t,Math.min(e.anchorOffset,t?ui(t):0),i,Math.min(e.focusOffset,i?ui(i):0))}set(e,t,i,s){this.anchorNode=e,this.anchorOffset=t,this.focusNode=i,this.focusOffset=s}}let Di=null;T.safari&&T.safari_version>=26&&(Di=!1);function hb(n){if(n.setActive)return n.setActive();if(Di)return n.focus(Di);let e=[];for(let t=n;t&&(e.push(t,t.scrollTop,t.scrollLeft),t!=t.ownerDocument);t=t.parentNode);if(n.focus(Di==null?{get preventScroll(){return Di={preventScroll:!0},!0}}:void 0),!Di){Di=!1;for(let t=0;tMath.max(1,n.scrollHeight-n.clientHeight-4)}function fb(n,e){for(let t=n,i=e;;){if(t.nodeType==3&&i>0)return{node:t,offset:i};if(t.nodeType==1&&i>0){if(t.contentEditable=="false")return null;t=t.childNodes[i-1],i=ui(t)}else if(t.parentNode&&!Zo(t))i=$i(t),t=t.parentNode;else return null}}function ub(n,e){for(let t=n,i=e;;){if(t.nodeType==3&&i=t){if(l.level==i)return o;(r<0||(s!=0?s<0?l.fromt:e[r].level>l.level))&&(r=o)}}if(r<0)throw new RangeError("Index out of range");return r}}function gb(n,e){if(n.length!=e.length)return!1;for(let t=0;t=0;g-=3)if(At[g+1]==-d){let m=At[g+2],O=m&2?s:m&4?m&1?r:s:0;O&&(J[f]=J[At[g]]=O),l=g;break}}else{if(At.length==189)break;At[l++]=f,At[l++]=u,At[l++]=a}else if((p=J[f])==2||p==1){let g=p==s;a=g?0:1;for(let m=l-3;m>=0;m-=3){let O=At[m+2];if(O&2)break;if(g)At[m+2]|=2;else{if(O&4)break;At[m+2]|=4}}}}}function H$(n,e,t,i){for(let s=0,r=i;s<=t.length;s++){let o=s?t[s-1].to:n,l=sa;)p==m&&(p=t[--g].from,m=g?t[g-1].to:n),J[--p]=d;a=c}else r=h,a++}}}function oc(n,e,t,i,s,r,o){let l=i%2?2:1;if(i%2==s%2)for(let a=e,h=0;aa&&o.push(new ri(a,g.from,d));let m=g.direction==_i!=!(d%2);lc(n,m?i+1:i,s,g.inner,g.from,g.to,o),a=g.to}p=g.to}else{if(p==t||(c?J[p]!=l:J[p]==l))break;p++}u?oc(n,a,p,i+1,s,u,o):ae;){let c=!0,f=!1;if(!h||a>r[h-1].to){let g=J[a-1];g!=l&&(c=!1,f=g==16)}let u=!c&&l==1?[]:null,d=c?i:i+1,p=a;e:for(;;)if(h&&p==r[h-1].to){if(f)break e;let g=r[--h];if(!c)for(let m=g.from,O=h;;){if(m==e)break e;if(O&&r[O-1].to==m)m=r[--O].from;else{if(J[m-1]==l)break e;break}}if(u)u.push(g);else{g.toJ.length;)J[J.length]=256;let i=[],s=e==_i?0:1;return lc(n,s,s,t,0,n.length,i),i}function mb(n){return[new ri(0,n,0)]}let Ob="";function U$(n,e,t,i,s){var r;let o=i.head-n.from,l=ri.find(e,o,(r=i.bidiLevel)!==null&&r!==void 0?r:-1,i.assoc),a=e[l],h=a.side(s,t);if(o==h){let u=l+=s?1:-1;if(u<0||u>=e.length)return null;a=e[l=u],o=a.side(!s,t),h=a.side(s,t)}let c=_(n.text,o,a.forward(s,t));(ca.to)&&(c=h),Ob=n.text.slice(Math.min(o,c),Math.max(o,c));let f=l==(s?e.length-1:0)?null:e[l+(s?1:-1)];return f&&c==h&&f.level+(s?0:1)n.some(e=>e)}),J$=k.define({combine:n=>n.some(e=>e)}),vb=k.define();class ds{constructor(e,t="nearest",i="nearest",s=5,r=5,o=!1){this.range=e,this.y=t,this.x=i,this.yMargin=s,this.xMargin=r,this.isSnapshot=o}map(e){return e.empty?this:new ds(this.range.map(e),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}clip(e){return this.range.to<=e.doc.length?this:new ds(S.cursor(e.doc.length),this.y,this.x,this.yMargin,this.xMargin,this.isSnapshot)}}const Cr=B.define({map:(n,e)=>n.map(e)}),$b=B.define();function oi(n,e,t){let i=n.facet(xb);i.length?i[0](e):window.onerror&&window.onerror(String(e),t,void 0,void 0,e)||(t?console.error(t+":",e):console.error(e))}const Jt=k.define({combine:n=>n.length?n[0]:!0});let eP=0;const Ji=k.define({combine(n){return n.filter((e,t)=>{for(let i=0;i{let a=[];return o&&a.push(Pl.of(h=>{let c=h.plugin(l);return c?o(c):ee.none})),r&&a.push(r(l)),a})}static fromClass(e,t){return Pi.define((i,s)=>new e(i,s),t)}}class ca{constructor(e){this.spec=e,this.mustUpdate=null,this.value=null}get plugin(){return this.spec&&this.spec.plugin}update(e){if(this.value){if(this.mustUpdate){let t=this.mustUpdate;if(this.mustUpdate=null,this.value.update)try{this.value.update(t)}catch(i){if(oi(t.state,i,"CodeMirror plugin crashed"),this.value.destroy)try{this.value.destroy()}catch{}this.deactivate()}}}else if(this.spec)try{this.value=this.spec.plugin.create(e,this.spec.arg)}catch(t){oi(e.state,t,"CodeMirror plugin crashed"),this.deactivate()}return this}destroy(e){var t;if(!((t=this.value)===null||t===void 0)&&t.destroy)try{this.value.destroy()}catch(i){oi(e.state,i,"CodeMirror plugin crashed")}}deactivate(){this.spec=this.value=null}}const Pb=k.define(),df=k.define(),Pl=k.define(),Cb=k.define(),pf=k.define(),Kn=k.define(),Tb=k.define();function Hd(n,e){let t=n.state.facet(Tb);if(!t.length)return t;let i=t.map(r=>r instanceof Function?r(n):r),s=[];return M.spans(i,e.from,e.to,{point(){},span(r,o,l,a){let h=r-e.from,c=o-e.from,f=s;for(let u=l.length-1;u>=0;u--,a--){let d=l[u].spec.bidiIsolate,p;if(d==null&&(d=K$(e.text,h,c)),a>0&&f.length&&(p=f[f.length-1]).to==h&&p.direction==d)p.to=c,f=p.inner;else{let g={from:h,to:c,direction:d,inner:[]};f.push(g),f=g.inner}}}}),s}const Mb=k.define();function Ab(n){let e=0,t=0,i=0,s=0;for(let r of n.state.facet(Mb)){let o=r(n);o&&(o.left!=null&&(e=Math.max(e,o.left)),o.right!=null&&(t=Math.max(t,o.right)),o.top!=null&&(i=Math.max(i,o.top)),o.bottom!=null&&(s=Math.max(s,o.bottom)))}return{left:e,right:t,top:i,bottom:s}}const rn=k.define();class it{constructor(e,t,i,s){this.fromA=e,this.toA=t,this.fromB=i,this.toB=s}join(e){return new it(Math.min(this.fromA,e.fromA),Math.max(this.toA,e.toA),Math.min(this.fromB,e.fromB),Math.max(this.toB,e.toB))}addToSet(e){let t=e.length,i=this;for(;t>0;t--){let s=e[t-1];if(!(s.fromA>i.toA)){if(s.toAs.push(new it(r,o,l,a))),this.changedRanges=s}static create(e,t,i){return new Xo(e,t,i)}get viewportChanged(){return(this.flags&4)>0}get viewportMoved(){return(this.flags&8)>0}get heightChanged(){return(this.flags&2)>0}get geometryChanged(){return this.docChanged||(this.flags&18)>0}get focusChanged(){return(this.flags&1)>0}get docChanged(){return!this.changes.empty}get selectionSet(){return this.transactions.some(e=>e.selection)}get empty(){return this.flags==0&&this.transactions.length==0}}const tP=[];class ge{constructor(e,t,i=0){this.dom=e,this.length=t,this.flags=i,this.parent=null,e.cmTile=this}get breakAfter(){return this.flags&1}get children(){return tP}isWidget(){return!1}get isHidden(){return!1}isComposite(){return!1}isLine(){return!1}isText(){return!1}isBlock(){return!1}get domAttrs(){return null}sync(e){if(this.flags|=2,this.flags&4){this.flags&=-5;let t=this.domAttrs;t&&B$(this.dom,t)}}toString(){return this.constructor.name+(this.children.length?`(${this.children})`:"")+(this.breakAfter?"#":"")}destroy(){this.parent=null}setDOM(e){this.dom=e,e.cmTile=this}get posAtStart(){return this.parent?this.parent.posBefore(this):0}get posAtEnd(){return this.posAtStart+this.length}posBefore(e,t=this.posAtStart){let i=t;for(let s of this.children){if(s==e)return i;i+=s.length+s.breakAfter}throw new RangeError("Invalid child in posBefore")}posAfter(e){return this.posBefore(e)+e.length}covers(e){return!0}coordsIn(e,t){return null}domPosFor(e,t){let i=$i(this.dom),s=this.length?e>0:t>0;return new gt(this.parent.dom,i+(s?1:0),e==0||e==this.length)}markDirty(e){this.flags&=-3,e&&(this.flags|=4),this.parent&&this.parent.flags&2&&this.parent.markDirty(!1)}get overrideDOMText(){return null}get root(){for(let e=this;e;e=e.parent)if(e instanceof Tl)return e;return null}static get(e){return e.cmTile}}class Cl extends ge{constructor(e){super(e,0),this._children=[]}isComposite(){return!0}get children(){return this._children}get lastChild(){return this.children.length?this.children[this.children.length-1]:null}append(e){this.children.push(e),e.parent=this}sync(e){if(this.flags&2)return;super.sync(e);let t=this.dom,i=null,s,r=e?.node==t?e:null,o=0;for(let l of this.children){if(l.sync(e),o+=l.length+l.breakAfter,s=i?i.nextSibling:t.firstChild,r&&s!=l.dom&&(r.written=!0),l.dom.parentNode==t)for(;s&&s!=l.dom;)s=Fd(s);else t.insertBefore(l.dom,s);i=l.dom}for(s=i?i.nextSibling:t.firstChild,r&&s&&(r.written=!0);s;)s=Fd(s);this.length=o}}function Fd(n){let e=n.nextSibling;return n.parentNode.removeChild(n),e}class Tl extends Cl{constructor(e,t){super(t),this.view=e}owns(e){for(;e;e=e.parent)if(e==this)return!0;return!1}isBlock(){return!0}nearest(e){for(;;){if(!e)return null;let t=ge.get(e);if(t&&this.owns(t))return t;e=e.parentNode}}blockTiles(e){for(let t=[],i=this,s=0,r=0;;)if(s==i.children.length){if(!t.length)return;i=i.parent,i.breakAfter&&r++,s=t.pop()}else{let o=i.children[s++];if(o instanceof yi)t.push(s),i=o,s=0;else{let l=r+o.length,a=e(o,r);if(a!==void 0)return a;r=l+o.breakAfter}}}resolveBlock(e,t){let i,s=-1,r,o=-1;if(this.blockTiles((l,a)=>{let h=a+l.length;if(e>=a&&e<=h){if(l.isWidget()&&t>=-1&&t<=1){if(l.flags&32)return!0;l.flags&16&&(i=void 0)}(ae||e==a&&(t>1?l.length:l.covers(-1)))&&(!r||!l.isWidget()&&r.isWidget())&&(r=l,o=e-a)}}),!i&&!r)throw new Error("No tile at position "+e);return i&&t<0||!r?{tile:i,offset:s}:{tile:r,offset:o}}}class yi extends Cl{constructor(e,t){super(e),this.wrapper=t}isBlock(){return!0}covers(e){return this.children.length?e<0?this.children[0].covers(-1):this.lastChild.covers(1):!1}get domAttrs(){return this.wrapper.attributes}static of(e,t){let i=new yi(t||document.createElement(e.tagName),e);return t||(i.flags|=4),i}}class Cs extends Cl{constructor(e,t){super(e),this.attrs=t}isLine(){return!0}static start(e,t,i){let s=new Cs(t||document.createElement("div"),e);return(!t||!i)&&(s.flags|=4),s}get domAttrs(){return this.attrs}resolveInline(e,t,i){let s=null,r=-1,o=null,l=-1;function a(c,f){for(let u=0,d=0;u=f&&(p.isComposite()?a(p,f-d):(!o||o.isHidden&&(t>0||i&&sP(o,p)))&&(g>f||p.flags&32)?(o=p,l=f-d):(di&&(e=i);let s=e,r=e,o=0;e==0&&t<0||e==i&&t>=0?T.chrome||T.gecko||(e?(s--,o=1):r=0)?0:l.length-1];return T.safari&&!o&&a.width==0&&(a=Array.prototype.find.call(l,h=>h.width)||a),o?Bo(a,o<0):a||null}static of(e,t){let i=new Li(t||document.createTextNode(e),e);return t||(i.flags|=2),i}}class Yi extends ge{constructor(e,t,i,s){super(e,t,s),this.widget=i}isWidget(){return!0}get isHidden(){return this.widget.isHidden}covers(e){return this.flags&48?!1:(this.flags&(e<0?64:128))>0}coordsIn(e,t){return this.coordsInWidget(e,t,!1)}coordsInWidget(e,t,i){let s=this.widget.coordsAt(this.dom,e,t);if(s)return s;if(i)return Bo(this.dom.getBoundingClientRect(),this.length?e==0:t<=0);{let r=this.dom.getClientRects(),o=null;if(!r.length)return null;let l=this.flags&16?!0:this.flags&32?!1:e>0;for(let a=l?r.length-1:0;o=r[a],!(e>0?a==0:a==r.length-1||o.top0;)if(s.isComposite())if(o){if(!e)break;i&&i.break(),e--,o=!1}else if(r==s.children.length){if(!e&&!l.length)break;i&&i.leave(s),o=!!s.breakAfter,{tile:s,index:r}=l.pop(),r++}else{let a=s.children[r],h=a.breakAfter;(t>0?a.length<=e:a.length=0;l--){let a=t.marks[l],h=s.lastChild;if(h instanceof Xe&&h.mark.eq(a.mark))h.dom!=a.dom&&h.setDOM(fa(a.dom)),s=h;else{if(this.cache.reused.get(a)){let f=ge.get(a.dom);f&&f.setDOM(fa(a.dom))}let c=Xe.of(a.mark,a.dom);s.append(c),s=c}this.cache.reused.set(a,2)}let r=ge.get(e.text);r&&this.cache.reused.set(r,2);let o=new Li(e.text,e.text.nodeValue);o.flags|=8,s.append(o)}addInlineWidget(e,t,i){let s=this.afterWidget&&e.flags&48&&(this.afterWidget.flags&48)==(e.flags&48);s||this.flushBuffer();let r=this.ensureMarks(t,i);!s&&!(e.flags&16)&&r.append(this.getBuffer(1)),r.append(e),this.pos+=e.length,this.afterWidget=e}addMark(e,t,i){this.flushBuffer(),this.ensureMarks(t,i).append(e),this.pos+=e.length,this.afterWidget=null}addBlockWidget(e){this.getBlockPos().append(e),this.pos+=e.length,this.lastBlock=e,this.endLine()}continueWidget(e){let t=this.afterWidget||this.lastBlock;t.length+=e,this.pos+=e}addLineStart(e,t){var i;e||(e=Rb);let s=Cs.start(e,t||((i=this.cache.find(Cs))===null||i===void 0?void 0:i.dom),!!t);this.getBlockPos().append(this.lastBlock=this.curLine=s)}addLine(e){this.getBlockPos().append(e),this.pos+=e.length,this.lastBlock=e,this.endLine()}addBreak(){this.lastBlock.flags|=1,this.endLine(),this.pos++}addLineStartIfNotCovered(e){this.blockPosCovered()||this.addLineStart(e)}ensureLine(e){this.curLine||this.addLineStart(e)}ensureMarks(e,t){var i;let s=this.curLine;for(let r=e.length-1;r>=0;r--){let o=e[r],l;if(t>0&&(l=s.lastChild)&&l instanceof Xe&&l.mark.eq(o))s=l,t--;else{let a=Xe.of(o,(i=this.cache.find(Xe,h=>h.mark.eq(o)))===null||i===void 0?void 0:i.dom);s.append(a),s=a,t=0}}return s}endLine(){if(this.curLine){this.flushBuffer();let e=this.curLine.lastChild;(!e||!Ud(this.curLine,!1)||e.dom.nodeName!="BR"&&e.isWidget()&&!(T.ios&&Ud(this.curLine,!0)))&&this.curLine.append(this.cache.findWidget(ua,0,32)||new Yi(ua.toDOM(),0,ua,32)),this.curLine=this.afterWidget=null}}updateBlockWrappers(){this.wrapperPos>this.pos+1e4&&(this.blockWrappers.goto(this.pos),this.wrappers.length=0);for(let e=this.wrappers.length-1;e>=0;e--)this.wrappers[e].to=this.pos){let t=new rP(e.from,e.to,e.value,e.rank),i=this.wrappers.length;for(;i>0&&(this.wrappers[i-1].rank-t.rank||this.wrappers[i-1].to-t.to)<0;)i--;this.wrappers.splice(i,0,t)}this.wrapperPos=this.pos}getBlockPos(){var e;this.updateBlockWrappers();let t=this.root;for(let i of this.wrappers){let s=t.lastChild;if(i.fromo.wrapper.eq(i.wrapper)))===null||e===void 0?void 0:e.dom);t.append(r),t=r}}return t}blockPosCovered(){let e=this.lastBlock;return e!=null&&!e.breakAfter&&(!e.isWidget()||(e.flags&160)>0)}getBuffer(e){let t=2|(e<0?16:32),i=this.cache.find(Lo,void 0,1);return i&&(i.flags=t),i||new Lo(t)}flushBuffer(){this.afterWidget&&!(this.afterWidget.flags&32)&&(this.afterWidget.parent.append(this.getBuffer(-1)),this.afterWidget=null)}}class lP{constructor(e){this.skipCount=0,this.text="",this.textOff=0,this.cursor=e.iter()}skip(e){this.textOff+e<=this.text.length?this.textOff+=e:(this.skipCount+=e-(this.text.length-this.textOff),this.text="",this.textOff=0)}next(e){if(this.textOff==this.text.length){let{value:s,lineBreak:r,done:o}=this.cursor.next(this.skipCount);if(this.skipCount=0,o)throw new Error("Ran out of text content when drawing inline views");this.text=s;let l=this.textOff=Math.min(e,s.length);return r?null:s.slice(0,l)}let t=Math.min(this.text.length,this.textOff+e),i=this.text.slice(this.textOff,t);return this.textOff=t,i}}const Eo=[Yi,Cs,Li,Xe,Lo,yi,Tl];for(let n=0;n[]),this.index=Eo.map(()=>0),this.reused=new Map}add(e){let t=e.constructor.bucket,i=this.buckets[t];i.length<6?i.push(e):i[this.index[t]=(this.index[t]+1)%6]=e}find(e,t,i=2){let s=e.bucket,r=this.buckets[s],o=this.index[s];for(let l=r.length-1;l>=0;l--){let a=(l+o)%r.length,h=r[a];if((!t||t(h))&&!this.reused.has(h))return r.splice(a,1),a{if(this.cache.add(o),o.isComposite())return!1},enter:o=>this.cache.add(o),leave:()=>{},break:()=>{}}}run(e,t){let i=t&&this.getCompositionContext(t.text);for(let s=0,r=0,o=0;;){let l=os){let h=a-s;this.preserve(h,!o,!l),s=a,r+=h}if(!l)break;t&&l.fromA<=t.range.fromA&&l.toA>=t.range.toA?(this.forward(l.fromA,t.range.fromA,t.range.fromA{if(o.isWidget())if(this.openWidget)this.builder.continueWidget(a-l);else{let h=a>0||l{o.isLine()?this.builder.addLineStart(o.attrs,this.cache.maybeReuse(o)):(this.cache.add(o),o instanceof Xe&&s.unshift(o.mark)),this.openWidget=!1},leave:o=>{o.isLine()?s.length&&(s.length=r=0):o instanceof Xe&&(s.shift(),r=Math.min(r,s.length))},break:()=>{this.builder.addBreak(),this.openWidget=!1}}),this.text.skip(e)}emit(e,t){let i=null,s=this.builder,r=0,o=M.spans(this.decorations,e,t,{point:(l,a,h,c,f,u)=>{if(h instanceof Ii){if(this.disallowBlockEffectsFor[u]){if(h.block)throw new RangeError("Block decorations may not be specified via plugins");if(a>this.view.state.doc.lineAt(l).to)throw new RangeError("Decorations that replace line breaks may not be specified via plugins")}if(r=c.length,f>c.length)s.continueWidget(a-l);else{let d=h.widget||(h.block?Ts.block:Ts.inline),p=cP(h),g=this.cache.findWidget(d,a-l,p)||Yi.of(d,this.view,a-l,p);h.block?(h.startSide>0&&s.addLineStartIfNotCovered(i),s.addBlockWidget(g)):(s.ensureLine(i),s.addInlineWidget(g,c,f))}i=null}else i=fP(i,h);a>l&&this.text.skip(a-l)},span:(l,a,h,c)=>{for(let f=l;fr,this.openMarks=o}forward(e,t,i=1){t-e<=10?this.old.advance(t-e,i,this.reuseWalker):(this.old.advance(5,-1,this.reuseWalker),this.old.advance(t-e-10,-1),this.old.advance(5,i,this.reuseWalker))}getCompositionContext(e){let t=[],i=null;for(let s=e.parentNode;;s=s.parentNode){let r=ge.get(s);if(s==this.view.contentDOM)break;r instanceof Xe?t.push(r):r?.isLine()?i=r:s.nodeName=="DIV"&&!i&&s!=this.view.contentDOM?i=new Cs(s,Rb):t.push(Xe.of(new Fn({tagName:s.nodeName.toLowerCase(),attributes:X$(s)}),s))}return{line:i,marks:t}}}function Ud(n,e){let t=i=>{for(let s of i.children)if((e?s.isText():s.length)||t(s))return!0;return!1};return t(n)}function cP(n){let e=n.isReplace?(n.startSide<0?64:0)|(n.endSide>0?128:0):n.startSide>0?32:16;return n.block&&(e|=256),e}const Rb={class:"cm-line"};function fP(n,e){let t=e.spec.attributes,i=e.spec.class;return!t&&!i||(n||(n={class:"cm-line"}),t&&af(t,n),i&&(n.class+=" "+i)),n}function uP(n){let e=[];for(let t=n.parents.length;t>1;t--){let i=t==n.parents.length?n.tile:n.parents[t].tile;i instanceof Xe&&e.push(i.mark)}return e}function fa(n){let e=ge.get(n);return e&&e.setDOM(n.cloneNode()),n}class Ts extends $l{constructor(e){super(),this.tag=e}eq(e){return e.tag==this.tag}toDOM(){return document.createElement(this.tag)}updateDOM(e){return e.nodeName.toLowerCase()==this.tag}get isHidden(){return!0}}Ts.inline=new Ts("span");Ts.block=new Ts("div");const ua=new class extends $l{toDOM(){return document.createElement("br")}get isHidden(){return!0}get editable(){return!0}};class Kd{constructor(e){this.view=e,this.decorations=[],this.blockWrappers=[],this.dynamicDecorationMap=[!1],this.domChanged=null,this.hasComposition=null,this.editContextFormatting=ee.none,this.lastCompositionAfterCursor=!1,this.minWidth=0,this.minWidthFrom=0,this.minWidthTo=0,this.impreciseAnchor=null,this.impreciseHead=null,this.forceSelection=!1,this.lastUpdate=Date.now(),this.updateDeco(),this.tile=new Tl(e,e.contentDOM),this.updateInner([new it(0,0,0,e.state.doc.length)],null)}update(e){var t;let i=e.changedRanges;this.minWidth>0&&i.length&&(i.every(({fromA:c,toA:f})=>fthis.minWidthTo)?(this.minWidthFrom=e.changes.mapPos(this.minWidthFrom,1),this.minWidthTo=e.changes.mapPos(this.minWidthTo,1)):this.minWidth=this.minWidthFrom=this.minWidthTo=0),this.updateEditContextFormatting(e);let s=-1;this.view.inputState.composing>=0&&!this.view.observer.editContext&&(!((t=this.domChanged)===null||t===void 0)&&t.newSel?s=this.domChanged.newSel.head:!xP(e.changes,this.hasComposition)&&!e.selectionSet&&(s=e.state.selection.main.head));let r=s>-1?pP(this.view,e.changes,s):null;if(this.domChanged=null,this.hasComposition){let{from:c,to:f}=this.hasComposition;i=new it(c,f,e.changes.mapPos(c,-1),e.changes.mapPos(f,1)).addToSet(i.slice())}this.hasComposition=r?{from:r.range.fromB,to:r.range.toB}:null,(T.ie||T.chrome)&&!r&&e&&e.state.doc.lines!=e.startState.doc.lines&&(this.forceSelection=!0);let o=this.decorations,l=this.blockWrappers;this.updateDeco();let a=OP(o,this.decorations,e.changes);a.length&&(i=it.extendWithRanges(i,a));let h=SP(l,this.blockWrappers,e.changes);return h.length&&(i=it.extendWithRanges(i,h)),r&&!i.some(c=>c.fromA<=r.range.fromA&&c.toA>=r.range.toA)&&(i=r.range.addToSet(i.slice())),this.tile.flags&2&&i.length==0?!1:(this.updateInner(i,r),e.transactions.length&&(this.lastUpdate=Date.now()),!0)}updateInner(e,t){this.view.viewState.mustMeasureContent=!0;let{observer:i}=this.view;i.ignore(()=>{if(t||e.length){let o=this.tile,l=new hP(this.view,o,this.blockWrappers,this.decorations,this.dynamicDecorationMap);this.tile=l.run(e,t),hc(o,l.cache.reused)}this.tile.dom.style.height=this.view.viewState.contentHeight/this.view.scaleY+"px",this.tile.dom.style.flexBasis=this.minWidth?this.minWidth+"px":"";let r=T.chrome||T.ios?{node:i.selectionRange.focusNode,written:!1}:void 0;this.tile.sync(r),r&&(r.written||i.selectionRange.focusNode!=r.node||!this.tile.dom.contains(r.node))&&(this.forceSelection=!0),this.tile.dom.style.height=""});let s=[];if(this.view.viewport.from||this.view.viewport.to-1)&&bn(i,this.view.observer.selectionRange)&&!(s&&i.contains(s));if(!(r||t||o))return;let l=this.forceSelection;this.forceSelection=!1;let a=this.view.state.selection.main,h,c;if(a.empty?c=h=this.inlineDOMNearPos(a.anchor,a.assoc||1):(c=this.inlineDOMNearPos(a.head,a.head==a.from?1:-1),h=this.inlineDOMNearPos(a.anchor,a.anchor==a.from?1:-1)),T.gecko&&a.empty&&!this.hasComposition&&dP(h)){let u=document.createTextNode("");this.view.observer.ignore(()=>h.node.insertBefore(u,h.node.childNodes[h.offset]||null)),h=c=new gt(u,0),l=!0}let f=this.view.observer.selectionRange;(l||!f.focusNode||(!Sn(h.node,h.offset,f.anchorNode,f.anchorOffset)||!Sn(c.node,c.offset,f.focusNode,f.focusOffset))&&!this.suppressWidgetCursorChange(f,a))&&(this.view.observer.ignore(()=>{T.android&&T.chrome&&i.contains(f.focusNode)&&yP(f.focusNode,i)&&(i.blur(),i.focus({preventScroll:!0}));let u=Ps(this.view.root);if(u)if(a.empty){if(T.gecko){let d=gP(h.node,h.offset);if(d&&d!=3){let p=(d==1?fb:ub)(h.node,h.offset);p&&(h=new gt(p.node,p.offset))}}u.collapse(h.node,h.offset),a.bidiLevel!=null&&u.caretBidiLevel!==void 0&&(u.caretBidiLevel=a.bidiLevel)}else if(u.extend){u.collapse(h.node,h.offset);try{u.extend(c.node,c.offset)}catch{}}else{let d=document.createRange();a.anchor>a.head&&([h,c]=[c,h]),d.setEnd(c.node,c.offset),d.setStart(h.node,h.offset),u.removeAllRanges(),u.addRange(d)}o&&this.view.root.activeElement==i&&(i.blur(),s&&s.focus())}),this.view.observer.setSelectionRange(h,c)),this.impreciseAnchor=h.precise?null:new gt(f.anchorNode,f.anchorOffset),this.impreciseHead=c.precise?null:new gt(f.focusNode,f.focusOffset)}suppressWidgetCursorChange(e,t){return this.hasComposition&&t.empty&&Sn(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset)&&this.posFromDOM(e.focusNode,e.focusOffset)==t.head}enforceCursorAssoc(){if(this.hasComposition)return;let{view:e}=this,t=e.state.selection.main,i=Ps(e.root),{anchorNode:s,anchorOffset:r}=e.observer.selectionRange;if(!i||!t.empty||!t.assoc||!i.modify)return;let o=this.lineAt(t.head,t.assoc);if(!o)return;let l=o.posAtStart;if(t.head==l||t.head==l+o.length)return;let a=this.coordsAt(t.head,-1),h=this.coordsAt(t.head,1);if(!a||!h||a.bottom>h.top)return;let c=this.domAtPos(t.head+t.assoc,t.assoc);i.collapse(c.node,c.offset),i.modify("move",t.assoc<0?"forward":"backward","lineboundary"),e.observer.readSelectionRange();let f=e.observer.selectionRange;e.docView.posFromDOM(f.anchorNode,f.anchorOffset)!=t.from&&i.collapse(s,r)}posFromDOM(e,t){let i=this.tile.nearest(e);if(!i)return this.tile.dom.compareDocumentPosition(e)&2?0:this.view.state.doc.length;let s=i.posAtStart;if(i.isComposite()){let r;if(e==i.dom)r=i.dom.childNodes[t];else{let o=ui(e)==0?0:t==0?-1:1;for(;;){let l=e.parentNode;if(l==i.dom)break;o==0&&l.firstChild!=l.lastChild&&(e==l.firstChild?o=-1:o=1),e=l}o<0?r=e:r=e.nextSibling}if(r==i.dom.firstChild)return s;for(;r&&!ge.get(r);)r=r.nextSibling;if(!r)return s+i.length;for(let o=0,l=s;;o++){let a=i.children[o];if(a.dom==r)return l;l+=a.length+a.breakAfter}}else return i.isText()?e==i.dom?s+t:s+(t?i.length:0):s}domAtPos(e,t){let{tile:i,offset:s}=this.tile.resolveBlock(e,t);return i.isWidget()?i.domPosFor(e,t):i.domIn(s,t)}inlineDOMNearPos(e,t){let i,s=-1,r=!1,o,l=-1,a=!1;return this.tile.blockTiles((h,c)=>{if(h.isWidget()){if(h.flags&32&&c>=e)return!0;h.flags&16&&(r=!0)}else{let f=c+h.length;if(c<=e&&(i=h,s=e-c,r=f=e&&!o&&(o=h,l=e-c,a=c>e),c>e&&o)return!0}}),!i&&!o?this.domAtPos(e,t):(r&&o?i=null:a&&i&&(o=null),i&&t<0||!o?i.domIn(s,t):o.domIn(l,t))}coordsAt(e,t){let{tile:i,offset:s}=this.tile.resolveBlock(e,t);return i.isWidget()?i.widget instanceof da?null:i.coordsInWidget(s,t,!0):i.coordsIn(s,t)}lineAt(e,t){let{tile:i}=this.tile.resolveBlock(e,t);return i.isLine()?i:null}coordsForChar(e){let{tile:t,offset:i}=this.tile.resolveBlock(e,1);if(!t.isLine())return null;function s(r,o){if(r.isComposite())for(let l of r.children){if(l.length>=o){let a=s(l,o);if(a)return a}if(o-=l.length,o<0)break}else if(r.isText()&&oMath.max(this.view.scrollDOM.clientWidth,this.minWidth)+1,l=-1,a=this.view.textDirection==we.LTR,h=0,c=(f,u,d)=>{for(let p=0;ps);p++){let g=f.children[p],m=u+g.length,O=g.dom.getBoundingClientRect(),{height:y}=O;if(d&&!p&&(h+=O.top-d.top),g instanceof yi)m>i&&c(g,u,O);else if(u>=i&&(h>0&&t.push(-h),t.push(y+h),h=0,o)){let x=g.dom.lastChild,v=x?eo(x):[];if(v.length){let w=v[v.length-1],Q=a?w.right-O.left:O.right-w.left;Q>l&&(l=Q,this.minWidth=r,this.minWidthFrom=u,this.minWidthTo=m)}}d&&p==f.children.length-1&&(h+=d.bottom-O.bottom),u=m+g.breakAfter}};return c(this.tile,0,null),t}textDirectionAt(e){let{tile:t}=this.tile.resolveBlock(e,1);return getComputedStyle(t.dom).direction=="rtl"?we.RTL:we.LTR}measureTextSize(){let e=this.tile.blockTiles(o=>{if(o.isLine()&&o.children.length&&o.length<=20){let l=0,a;for(let h of o.children){if(!h.isText()||/[^ -~]/.test(h.text))return;let c=eo(h.dom);if(c.length!=1)return;l+=c[0].width,a=c[0].height}if(l)return{lineHeight:o.dom.getBoundingClientRect().height,charWidth:l/o.length,textHeight:a}}});if(e)return e;let t=document.createElement("div"),i,s,r;return t.className="cm-line",t.style.width="99999px",t.style.position="absolute",t.textContent="abc def ghi jkl mno pqr stu",this.view.observer.ignore(()=>{this.tile.dom.appendChild(t);let o=eo(t.firstChild)[0];i=t.getBoundingClientRect().height,s=o&&o.width?o.width/27:7,r=o&&o.height?o.height:i,t.remove()}),{lineHeight:i,charWidth:s,textHeight:r}}computeBlockGapDeco(){let e=[],t=this.view.viewState;for(let i=0,s=0;;s++){let r=s==t.viewports.length?null:t.viewports[s],o=r?r.from-1:this.view.state.doc.length;if(o>i){let l=(t.lineBlockAt(o).bottom-t.lineBlockAt(i).top)/this.view.scaleY;e.push(ee.replace({widget:new da(l),block:!0,inclusive:!0,isBlockGap:!0}).range(i,o))}if(!r)break;i=r.to+1}return ee.set(e)}updateDeco(){let e=1,t=this.view.state.facet(Pl).map(r=>(this.dynamicDecorationMap[e++]=typeof r=="function")?r(this.view):r),i=!1,s=this.view.state.facet(pf).map((r,o)=>{let l=typeof r=="function";return l&&(i=!0),l?r(this.view):r});for(s.length&&(this.dynamicDecorationMap[e++]=i,t.push(M.join(s))),this.decorations=[this.editContextFormatting,...t,this.computeBlockGapDeco(),this.view.viewState.lineGapDeco];etypeof r=="function"?r(this.view):r)}scrollIntoView(e){if(e.isSnapshot){let h=this.view.viewState.lineBlockAt(e.range.head);this.view.scrollDOM.scrollTop=h.top-e.yMargin,this.view.scrollDOM.scrollLeft=e.xMargin;return}for(let h of this.view.state.facet(vb))try{if(h(this.view,e.range,e))return!0}catch(c){oi(this.view.state,c,"scroll handler")}let{range:t}=e,i=this.coordsAt(t.head,t.empty?t.assoc:t.head>t.anchor?-1:1),s;if(!i)return;!t.empty&&(s=this.coordsAt(t.anchor,t.anchor>t.head?-1:1))&&(i={left:Math.min(i.left,s.left),top:Math.min(i.top,s.top),right:Math.max(i.right,s.right),bottom:Math.max(i.bottom,s.bottom)});let r=Ab(this.view),o={left:i.left-r.left,top:i.top-r.top,right:i.right+r.right,bottom:i.bottom+r.bottom},{offsetWidth:l,offsetHeight:a}=this.view.scrollDOM;W$(this.view.scrollDOM,o,t.headi.isWidget()||i.children.some(t);return t(this.tile.resolveBlock(e,1).tile)}destroy(){hc(this.tile)}}function hc(n,e){let t=e?.get(n);if(t!=1){t==null&&n.destroy();for(let i of n.children)hc(i,e)}}function dP(n){return n.node.nodeType==1&&n.node.firstChild&&(n.offset==0||n.node.childNodes[n.offset-1].contentEditable=="false")&&(n.offset==n.node.childNodes.length||n.node.childNodes[n.offset].contentEditable=="false")}function Db(n,e){let t=n.observer.selectionRange;if(!t.focusNode)return null;let i=fb(t.focusNode,t.focusOffset),s=ub(t.focusNode,t.focusOffset),r=i||s;if(s&&i&&s.node!=i.node){let l=ge.get(s.node);if(!l||l.isText()&&l.text!=s.node.nodeValue)r=s;else if(n.docView.lastCompositionAfterCursor){let a=ge.get(i.node);!a||a.isText()&&a.text!=i.node.nodeValue||(r=s)}}if(n.docView.lastCompositionAfterCursor=r!=i,!r)return null;let o=e-r.offset;return{from:o,to:o+r.node.nodeValue.length,node:r.node}}function pP(n,e,t){let i=Db(n,t);if(!i)return null;let{node:s,from:r,to:o}=i,l=s.nodeValue;if(/[\n\r]/.test(l)||n.state.doc.sliceString(i.from,i.to)!=l)return null;let a=e.invertedDesc;return{range:new it(a.mapPos(r),a.mapPos(o),r,o),text:s}}function gP(n,e){return n.nodeType!=1?0:(e&&n.childNodes[e-1].contentEditable=="false"?1:0)|(e{ie.from&&(t=!0)}),t}class da extends $l{constructor(e){super(),this.height=e}toDOM(){let e=document.createElement("div");return e.className="cm-gap",this.updateDOM(e),e}eq(e){return e.height==this.height}updateDOM(e){return e.style.height=this.height+"px",!0}get editable(){return!0}get estimatedHeight(){return this.height}ignoreEvent(){return!1}}function wP(n,e,t=1){let i=n.charCategorizer(e),s=n.doc.lineAt(e),r=e-s.from;if(s.length==0)return S.cursor(e);r==0?t=1:r==s.length&&(t=-1);let o=r,l=r;t<0?o=_(s.text,r,!1):l=_(s.text,r);let a=i(s.text.slice(o,l));for(;o>0;){let h=_(s.text,o,!1);if(i(s.text.slice(h,o))!=a)break;o=h}for(;ln.defaultLineHeight*1.5){let l=n.viewState.heightOracle.textHeight,a=Math.floor((s-t.top-(n.defaultLineHeight-l)*.5)/l);r+=a*n.viewState.heightOracle.lineLength}let o=n.state.sliceDoc(t.from,t.to);return t.from+kn(o,r,n.state.tabSize)}function QP(n,e,t){let i=n.lineBlockAt(e);if(Array.isArray(i.type)){let s;for(let r of i.type){if(r.from>e)break;if(!(r.toe)return r;(!s||r.type==rt.Text&&(s.type!=r.type||(t<0?r.frome)))&&(s=r)}}return s||i}return i}function vP(n,e,t,i){let s=QP(n,e.head,e.assoc||-1),r=!i||s.type!=rt.Text||!(n.lineWrapping||s.widgetLineBreaks)?null:n.coordsAtPos(e.assoc<0&&e.head>s.from?e.head-1:e.head);if(r){let o=n.dom.getBoundingClientRect(),l=n.textDirectionAt(s.from),a=n.posAtCoords({x:t==(l==we.LTR)?o.right-1:o.left+1,y:(r.top+r.bottom)/2});if(a!=null)return S.cursor(a,t?-1:1)}return S.cursor(t?s.to:s.from,t?-1:1)}function Jd(n,e,t,i){let s=n.state.doc.lineAt(e.head),r=n.bidiSpans(s),o=n.textDirectionAt(s.from);for(let l=e,a=null;;){let h=U$(s,r,o,l,t),c=Ob;if(!h){if(s.number==(t?n.state.doc.lines:1))return l;c=` `,s=n.state.doc.line(s.number+(t?1:-1)),r=n.bidiSpans(s),h=n.visualLineSide(s,!t)}if(a){if(!a(c))return l}else{if(!i)return h;a=i(c)}l=h}}function $P(n,e,t){let i=n.state.charCategorizer(e),s=i(t);return r=>{let o=i(r);return s==oe.Space&&(s=o),s==o}}function PP(n,e,t,i){let s=e.head,r=t?1:-1;if(s==(t?n.state.doc.length:0))return S.cursor(s,e.assoc);let o=e.goalColumn,l,a=n.contentDOM.getBoundingClientRect(),h=n.coordsAtPos(s,(e.empty?e.assoc:0)||(t?1:-1)),c=n.documentTop;if(h)o==null&&(o=h.left-a.left),l=r<0?h.top:h.bottom;else{let p=n.viewState.lineBlockAt(s);o==null&&(o=Math.min(a.right-a.left,n.defaultCharacterWidth*(s-p.from))),l=(r<0?p.top:p.bottom)+c}let f=a.left+o,u=i??n.viewState.heightOracle.textHeight>>1,d=cc(n,{x:f,y:l+u*r},!1,r);return S.cursor(d.pos,d.assoc,void 0,o)}function yn(n,e,t){for(;;){let i=0;for(let s of n)s.between(e-1,e+1,(r,o,l)=>{if(e>r&&es(n)),t.from,e.head>t.from?-1:1);return i==t.from?t:S.cursor(i,in.viewState.docHeight)return new Et(n.state.doc.length,-1);if(h=n.elementAtHeight(a),i==null)break;if(h.type==rt.Text){if(i<0?h.ton.viewport.to)break;let u=n.docView.coordsAt(i<0?h.from:h.to,i>0?-1:1);if(u&&(i<0?u.top<=a+r:u.bottom>=a+r))break}let f=n.viewState.heightOracle.textHeight/2;a=i>0?h.bottom+f:h.top-f}if(n.viewport.from>=h.to||n.viewport.to<=h.from){if(t)return null;if(h.type==rt.Text){let f=kP(n,s,h,o,l);return new Et(f,f==h.from?1:-1)}}if(h.type!=rt.Text)return a<(h.top+h.bottom)/2?new Et(h.from,1):new Et(h.to,-1);let c=n.docView.lineAt(h.from,2);return(!c||c.length!=h.length)&&(c=n.docView.lineAt(h.from,-2)),Bb(n,c,h.from,o,l)}function Bb(n,e,t,i,s){let r=-1,o=null,l=1e9,a=1e9,h=s,c=s,f=(u,d)=>{for(let p=0;pi?g.left-i:g.rights?g.top-s:g.bottom=h&&(h=Math.min(g.top,h),c=Math.max(g.bottom,c),O=0),(r<0||(O-a||m-l)<0)&&(r>=0&&a&&l=h+2?a=0:(r=d,l=m,a=O,o=g))}};if(e.isText()){for(let d=0;d(o.left+o.right)/2==(ep(n,r+t)==we.LTR)?new Et(t+_(e.text,r),-1):new Et(t+r,1)}else{if(!e.length)return new Et(t,1);for(let g=0;g(o.left+o.right)/2==(ep(n,r+t)==we.LTR)?new Et(d+u.length,-1):new Et(d,1)}}function ep(n,e){let t=n.state.doc.lineAt(e);return n.bidiSpans(t)[ri.find(n.bidiSpans(t),e-t.from,-1,1)].dir}const on="￿";class CP{constructor(e,t){this.points=e,this.view=t,this.text="",this.lineSeparator=t.state.facet(L.lineSeparator)}append(e){this.text+=e}lineBreak(){this.text+=on}readRange(e,t){if(!e)return this;let i=e.parentNode;for(let s=e;;){this.findPointBefore(i,s);let r=this.text.length;this.readNode(s);let o=ge.get(s),l=s.nextSibling;if(l==t){o?.breakAfter&&!l&&i!=this.view.contentDOM&&this.lineBreak();break}let a=ge.get(l);(o&&a?o.breakAfter:(o?o.breakAfter:Zo(s))||Zo(l)&&(s.nodeName!="BR"||o?.isWidget())&&this.text.length>r)&&!MP(l,t)&&this.lineBreak(),s=l}return this.findPointBefore(i,t),this}readTextNode(e){let t=e.nodeValue;for(let i of this.points)i.node==e&&(i.pos=this.text.length+Math.min(i.offset,t.length));for(let i=0,s=this.lineSeparator?null:/\r\n?|\n/g;;){let r=-1,o=1,l;if(this.lineSeparator?(r=t.indexOf(this.lineSeparator,i),o=this.lineSeparator.length):(l=s.exec(t))&&(r=l.index,o=l[0].length),this.append(t.slice(i,r<0?t.length:r)),r<0)break;if(this.lineBreak(),o>1)for(let a of this.points)a.node==e&&a.pos>this.text.length&&(a.pos-=o-1);i=r+o}}readNode(e){let t=ge.get(e),i=t&&t.overrideDOMText;if(i!=null){this.findPointInside(e,i.length);for(let s=i.iter();!s.next().done;)s.lineBreak?this.lineBreak():this.append(s.value)}else e.nodeType==3?this.readTextNode(e):e.nodeName=="BR"?e.nextSibling&&this.lineBreak():e.nodeType==1&&this.readRange(e.firstChild,null)}findPointBefore(e,t){for(let i of this.points)i.node==e&&e.childNodes[i.offset]==t&&(i.pos=this.text.length)}findPointInside(e,t){for(let i of this.points)(e.nodeType==3?i.node==e:e.contains(i.node))&&(i.pos=this.text.length+(TP(e,i.node,i.offset)?t:0))}}function TP(n,e,t){for(;;){if(!e||t-1;let{impreciseHead:r,impreciseAnchor:o}=e.docView;if(e.state.readOnly&&t>-1)this.newSel=null;else if(t>-1&&(this.bounds=Xb(e.docView.tile,t,i,0))){let l=r||o?[]:DP(e),a=new CP(l,e);a.readRange(this.bounds.startDOM,this.bounds.endDOM),this.text=a.text,this.newSel=ZP(l,this.bounds.from)}else{let l=e.observer.selectionRange,a=r&&r.node==l.focusNode&&r.offset==l.focusOffset||!nc(e.contentDOM,l.focusNode)?e.state.selection.main.head:e.docView.posFromDOM(l.focusNode,l.focusOffset),h=o&&o.node==l.anchorNode&&o.offset==l.anchorOffset||!nc(e.contentDOM,l.anchorNode)?e.state.selection.main.anchor:e.docView.posFromDOM(l.anchorNode,l.anchorOffset),c=e.viewport;if((T.ios||T.chrome)&&e.state.selection.main.empty&&a!=h&&(c.from>0||c.to-1&&e.state.selection.ranges.length>1?this.newSel=e.state.selection.replaceRange(S.range(h,a)):this.newSel=S.single(h,a)}}}function Xb(n,e,t,i){if(n.isComposite()){let s=-1,r=-1,o=-1,l=-1;for(let a=0,h=i,c=i;at)return Xb(f,e,t,h);if(u>=e&&s==-1&&(s=a,r=h),h>t&&f.dom.parentNode==n.dom){o=a,l=c;break}c=u,h=u+f.breakAfter}return{from:r,to:l<0?i+n.length:l,startDOM:(s?n.children[s-1].dom.nextSibling:null)||n.dom.firstChild,endDOM:o=0?n.children[o].dom:null}}else return n.isText()?{from:i,to:i+n.length,startDOM:n.dom,endDOM:n.dom.nextSibling}:null}function Lb(n,e){let t,{newSel:i}=e,s=n.state.selection.main,r=n.inputState.lastKeyTime>Date.now()-100?n.inputState.lastKeyCode:-1;if(e.bounds){let{from:o,to:l}=e.bounds,a=s.from,h=null;(r===8||T.android&&e.text.length=s.from&&t.to<=s.to&&(t.from!=s.from||t.to!=s.to)&&s.to-s.from-(t.to-t.from)<=4?t={from:s.from,to:s.to,insert:n.state.doc.slice(s.from,t.from).append(t.insert).append(n.state.doc.slice(t.to,s.to))}:n.state.doc.lineAt(s.from).toDate.now()-50?t={from:s.from,to:s.to,insert:n.state.toText(n.inputState.insertingText)}:T.chrome&&t&&t.from==t.to&&t.from==s.head&&t.insert.toString()==` `&&n.lineWrapping&&(i&&(i=S.single(i.main.anchor-1,i.main.head-1)),t={from:s.from,to:s.to,insert:Z.of([" "])}),t)return gf(n,t,i,r);if(i&&!Wo(i,s)){let o=!1,l="select";return n.inputState.lastSelectionTime>Date.now()-50&&(n.inputState.lastSelectionOrigin=="select"&&(o=!0),l=n.inputState.lastSelectionOrigin,l=="select.pointer"&&(i=Zb(n.state.facet(Kn).map(a=>a(n)),i))),n.dispatch({selection:i,scrollIntoView:o,userEvent:l}),!0}else return!1}function gf(n,e,t,i=-1){if(T.ios&&n.inputState.flushIOSKey(e))return!0;let s=n.state.selection.main;if(T.android&&(e.to==s.to&&(e.from==s.from||e.from==s.from-1&&n.state.sliceDoc(e.from,s.from)==" ")&&e.insert.length==1&&e.insert.lines==2&&us(n.contentDOM,"Enter",13)||(e.from==s.from-1&&e.to==s.to&&e.insert.length==0||i==8&&e.insert.lengths.head)&&us(n.contentDOM,"Backspace",8)||e.from==s.from&&e.to==s.to+1&&e.insert.length==0&&us(n.contentDOM,"Delete",46)))return!0;let r=e.insert.toString();n.inputState.composing>=0&&n.inputState.composing++;let o,l=()=>o||(o=RP(n,e,t));return n.state.facet(wb).some(a=>a(n,e.from,e.to,r,l))||n.dispatch(l()),!0}function RP(n,e,t){let i,s=n.state,r=s.selection.main,o=-1;if(e.from==e.to&&e.fromr.to){let a=e.fromf(n)),h,a);e.from==c&&(o=c)}if(o>-1)i={changes:e,selection:S.cursor(e.from+e.insert.length,-1)};else if(e.from>=r.from&&e.to<=r.to&&e.to-e.from>=(r.to-r.from)/3&&(!t||t.main.empty&&t.main.from==e.from+e.insert.length)&&n.inputState.composing<0){let a=r.frome.to?s.sliceDoc(e.to,r.to):"";i=s.replaceSelection(n.state.toText(a+e.insert.sliceString(0,void 0,n.state.lineBreak)+h))}else{let a=s.changes(e),h=t&&t.main.to<=a.newLength?t.main:void 0;if(s.selection.ranges.length>1&&(n.inputState.composing>=0||n.inputState.compositionPendingChange)&&e.to<=r.to+10&&e.to>=r.to-10){let c=n.state.sliceDoc(e.from,e.to),f,u=t&&Db(n,t.main.head);if(u){let p=e.insert.length-(e.to-e.from);f={from:u.from,to:u.to-p}}else f=n.state.doc.lineAt(r.head);let d=r.to-e.to;i=s.changeByRange(p=>{if(p.from==r.from&&p.to==r.to)return{changes:a,range:h||p.map(a)};let g=p.to-d,m=g-c.length;if(n.state.sliceDoc(m,g)!=c||g>=f.from&&m<=f.to)return{range:p};let O=s.changes({from:m,to:g,insert:e.insert}),y=p.to-r.to;return{changes:O,range:h?S.range(Math.max(0,h.anchor+y),Math.max(0,h.head+y)):p.map(O)}})}else i={changes:a,selection:h&&s.selection.replaceRange(h)}}let l="input.type";return(n.composing||n.inputState.compositionPendingChange&&n.inputState.compositionEndedAt>Date.now()-50)&&(n.inputState.compositionPendingChange=!1,l+=".compose",n.inputState.compositionFirstChange&&(l+=".start",n.inputState.compositionFirstChange=!1)),s.update(i,{userEvent:l,scrollIntoView:!0})}function Eb(n,e,t,i){let s=Math.min(n.length,e.length),r=0;for(;r0&&l>0&&n.charCodeAt(o-1)==e.charCodeAt(l-1);)o--,l--;if(i=="end"){let a=Math.max(0,r-Math.min(o,l));t-=o+a-r}if(o=o?r-t:0;r-=a,l=r+(l-o),o=r}else if(l=l?r-t:0;r-=a,o=r+(o-l),l=r}return{from:r,toA:o,toB:l}}function DP(n){let e=[];if(n.root.activeElement!=n.contentDOM)return e;let{anchorNode:t,anchorOffset:i,focusNode:s,focusOffset:r}=n.observer.selectionRange;return t&&(e.push(new tp(t,i)),(s!=t||r!=i)&&e.push(new tp(s,r))),e}function ZP(n,e){if(n.length==0)return null;let t=n[0].pos,i=n.length==2?n[1].pos:t;return t>-1&&i>-1?S.single(t+e,i+e):null}function Wo(n,e){return e.head==n.main.head&&e.anchor==n.main.anchor}class BP{setSelectionOrigin(e){this.lastSelectionOrigin=e,this.lastSelectionTime=Date.now()}constructor(e){this.view=e,this.lastKeyCode=0,this.lastKeyTime=0,this.lastTouchTime=0,this.lastFocusTime=0,this.lastScrollTop=0,this.lastScrollLeft=0,this.pendingIOSKey=void 0,this.tabFocusMode=-1,this.lastSelectionOrigin=null,this.lastSelectionTime=0,this.lastContextMenu=0,this.scrollHandlers=[],this.handlers=Object.create(null),this.composing=-1,this.compositionFirstChange=null,this.compositionEndedAt=0,this.compositionPendingKey=!1,this.compositionPendingChange=!1,this.insertingText="",this.insertingTextAt=0,this.mouseSelection=null,this.draggedContent=null,this.handleEvent=this.handleEvent.bind(this),this.notifiedFocused=e.hasFocus,T.safari&&e.contentDOM.addEventListener("input",()=>null),T.gecko&&FP(e.contentDOM.ownerDocument)}handleEvent(e){!IP(this.view,e)||this.ignoreDuringComposition(e)||e.type=="keydown"&&this.keydown(e)||(this.view.updateState!=0?Promise.resolve().then(()=>this.runHandlers(e.type,e)):this.runHandlers(e.type,e))}runHandlers(e,t){let i=this.handlers[e];if(i){for(let s of i.observers)s(this.view,t);for(let s of i.handlers){if(t.defaultPrevented)break;if(s(this.view,t)){t.preventDefault();break}}}}ensureHandlers(e){let t=XP(e),i=this.handlers,s=this.view.contentDOM;for(let r in t)if(r!="scroll"){let o=!t[r].handlers.length,l=i[r];l&&o!=!l.handlers.length&&(s.removeEventListener(r,this.handleEvent),l=null),l||s.addEventListener(r,this.handleEvent,{passive:o})}for(let r in i)r!="scroll"&&!t[r]&&s.removeEventListener(r,this.handleEvent);this.handlers=t}keydown(e){if(this.lastKeyCode=e.keyCode,this.lastKeyTime=Date.now(),e.keyCode==9&&this.tabFocusMode>-1&&(!this.tabFocusMode||Date.now()<=this.tabFocusMode))return!0;if(this.tabFocusMode>0&&e.keyCode!=27&&Vb.indexOf(e.keyCode)<0&&(this.tabFocusMode=-1),T.android&&T.chrome&&!e.synthetic&&(e.keyCode==13||e.keyCode==8))return this.view.observer.delayAndroidKey(e.key,e.keyCode),!0;let t;return T.ios&&!e.synthetic&&!e.altKey&&!e.metaKey&&((t=Wb.find(i=>i.keyCode==e.keyCode))&&!e.ctrlKey||LP.indexOf(e.key)>-1&&e.ctrlKey&&!e.shiftKey)?(this.pendingIOSKey=t||e,setTimeout(()=>this.flushIOSKey(),250),!0):(e.keyCode!=229&&this.view.observer.forceFlush(),!1)}flushIOSKey(e){let t=this.pendingIOSKey;return!t||t.key=="Enter"&&e&&e.from0?!0:T.safari&&!T.ios&&this.compositionPendingKey&&Date.now()-this.compositionEndedAt<100?(this.compositionPendingKey=!1,!0):!1}startMouseSelection(e){this.mouseSelection&&this.mouseSelection.destroy(),this.mouseSelection=e}update(e){this.view.observer.update(e),this.mouseSelection&&this.mouseSelection.update(e),this.draggedContent&&e.docChanged&&(this.draggedContent=this.draggedContent.map(e.changes)),e.transactions.length&&(this.lastKeyCode=this.lastSelectionTime=0)}destroy(){this.mouseSelection&&this.mouseSelection.destroy()}}function ip(n,e){return(t,i)=>{try{return e.call(n,i,t)}catch(s){oi(t.state,s)}}}function XP(n){let e=Object.create(null);function t(i){return e[i]||(e[i]={observers:[],handlers:[]})}for(let i of n){let s=i.spec,r=s&&s.plugin.domEventHandlers,o=s&&s.plugin.domEventObservers;if(r)for(let l in r){let a=r[l];a&&t(l).handlers.push(ip(i.value,a))}if(o)for(let l in o){let a=o[l];a&&t(l).observers.push(ip(i.value,a))}}for(let i in yt)t(i).handlers.push(yt[i]);for(let i in at)t(i).observers.push(at[i]);return e}const Wb=[{key:"Backspace",keyCode:8,inputType:"deleteContentBackward"},{key:"Enter",keyCode:13,inputType:"insertParagraph"},{key:"Enter",keyCode:13,inputType:"insertLineBreak"},{key:"Delete",keyCode:46,inputType:"deleteContentForward"}],LP="dthko",Vb=[16,17,18,20,91,92,224,225],Tr=6;function Mr(n){return Math.max(0,n)*.7+8}function EP(n,e){return Math.max(Math.abs(n.clientX-e.clientX),Math.abs(n.clientY-e.clientY))}class WP{constructor(e,t,i,s){this.view=e,this.startEvent=t,this.style=i,this.mustSelect=s,this.scrollSpeed={x:0,y:0},this.scrolling=-1,this.lastEvent=t,this.scrollParents=V$(e.contentDOM),this.atoms=e.state.facet(Kn).map(o=>o(e));let r=e.contentDOM.ownerDocument;r.addEventListener("mousemove",this.move=this.move.bind(this)),r.addEventListener("mouseup",this.up=this.up.bind(this)),this.extend=t.shiftKey,this.multiple=e.state.facet(L.allowMultipleSelections)&&VP(e,t),this.dragging=qP(e,t)&&Ib(t)==1?null:!1}start(e){this.dragging===!1&&this.select(e)}move(e){if(e.buttons==0)return this.destroy();if(this.dragging||this.dragging==null&&EP(this.startEvent,e)<10)return;this.select(this.lastEvent=e);let t=0,i=0,s=0,r=0,o=this.view.win.innerWidth,l=this.view.win.innerHeight;this.scrollParents.x&&({left:s,right:o}=this.scrollParents.x.getBoundingClientRect()),this.scrollParents.y&&({top:r,bottom:l}=this.scrollParents.y.getBoundingClientRect());let a=Ab(this.view);e.clientX-a.left<=s+Tr?t=-Mr(s-e.clientX):e.clientX+a.right>=o-Tr&&(t=Mr(e.clientX-o)),e.clientY-a.top<=r+Tr?i=-Mr(r-e.clientY):e.clientY+a.bottom>=l-Tr&&(i=Mr(e.clientY-l)),this.setScrollSpeed(t,i)}up(e){this.dragging==null&&this.select(this.lastEvent),this.dragging||e.preventDefault(),this.destroy()}destroy(){this.setScrollSpeed(0,0);let e=this.view.contentDOM.ownerDocument;e.removeEventListener("mousemove",this.move),e.removeEventListener("mouseup",this.up),this.view.inputState.mouseSelection=this.view.inputState.draggedContent=null}setScrollSpeed(e,t){this.scrollSpeed={x:e,y:t},e||t?this.scrolling<0&&(this.scrolling=setInterval(()=>this.scroll(),50)):this.scrolling>-1&&(clearInterval(this.scrolling),this.scrolling=-1)}scroll(){let{x:e,y:t}=this.scrollSpeed;e&&this.scrollParents.x&&(this.scrollParents.x.scrollLeft+=e,e=0),t&&this.scrollParents.y&&(this.scrollParents.y.scrollTop+=t,t=0),(e||t)&&this.view.win.scrollBy(e,t),this.dragging===!1&&this.select(this.lastEvent)}select(e){let{view:t}=this,i=Zb(this.atoms,this.style.get(e,this.extend,this.multiple));(this.mustSelect||!i.eq(t.state.selection,this.dragging===!1))&&this.view.dispatch({selection:i,userEvent:"select.pointer"}),this.mustSelect=!1}update(e){e.transactions.some(t=>t.isUserEvent("input.type"))?this.destroy():this.style.update(e)&&setTimeout(()=>this.select(this.lastEvent),20)}}function VP(n,e){let t=n.state.facet(bb);return t.length?t[0](e):T.mac?e.metaKey:e.ctrlKey}function zP(n,e){let t=n.state.facet(Sb);return t.length?t[0](e):T.mac?!e.altKey:!e.ctrlKey}function qP(n,e){let{main:t}=n.state.selection;if(t.empty)return!1;let i=Ps(n.root);if(!i||i.rangeCount==0)return!0;let s=i.getRangeAt(0).getClientRects();for(let r=0;r=e.clientX&&o.top<=e.clientY&&o.bottom>=e.clientY)return!0}return!1}function IP(n,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;for(let t=e.target,i;t!=n.contentDOM;t=t.parentNode)if(!t||t.nodeType==11||(i=ge.get(t))&&i.isWidget()&&!i.isHidden&&i.widget.ignoreEvent(e))return!1;return!0}const yt=Object.create(null),at=Object.create(null),zb=T.ie&&T.ie_version<15||T.ios&&T.webkit_version<604;function _P(n){let e=n.dom.parentNode;if(!e)return;let t=e.appendChild(document.createElement("textarea"));t.style.cssText="position: fixed; left: -10000px; top: 10px",t.focus(),setTimeout(()=>{n.focus(),t.remove(),qb(n,t.value)},50)}function Ml(n,e,t){for(let i of n.facet(e))t=i(t,n);return t}function qb(n,e){e=Ml(n.state,ff,e);let{state:t}=n,i,s=1,r=t.toText(e),o=r.lines==t.selection.ranges.length;if(fc!=null&&t.selection.ranges.every(a=>a.empty)&&fc==r.toString()){let a=-1;i=t.changeByRange(h=>{let c=t.doc.lineAt(h.from);if(c.from==a)return{range:h};a=c.from;let f=t.toText((o?r.line(s++).text:e)+t.lineBreak);return{changes:{from:c.from,insert:f},range:S.cursor(h.from+f.length)}})}else o?i=t.changeByRange(a=>{let h=r.line(s++);return{changes:{from:a.from,to:a.to,insert:h.text},range:S.cursor(a.from+h.length)}}):i=t.replaceSelection(r);n.dispatch(i,{userEvent:"input.paste",scrollIntoView:!0})}at.scroll=n=>{n.inputState.lastScrollTop=n.scrollDOM.scrollTop,n.inputState.lastScrollLeft=n.scrollDOM.scrollLeft};yt.keydown=(n,e)=>(n.inputState.setSelectionOrigin("select"),e.keyCode==27&&n.inputState.tabFocusMode!=0&&(n.inputState.tabFocusMode=Date.now()+2e3),!1);at.touchstart=(n,e)=>{n.inputState.lastTouchTime=Date.now(),n.inputState.setSelectionOrigin("select.pointer")};at.touchmove=n=>{n.inputState.setSelectionOrigin("select.pointer")};yt.mousedown=(n,e)=>{if(n.observer.flush(),n.inputState.lastTouchTime>Date.now()-2e3)return!1;let t=null;for(let i of n.state.facet(yb))if(t=i(n,e),t)break;if(!t&&e.button==0&&(t=NP(n,e)),t){let i=!n.hasFocus;n.inputState.startMouseSelection(new WP(n,e,t,i)),i&&n.observer.ignore(()=>{hb(n.contentDOM);let r=n.root.activeElement;r&&!r.contains(n.contentDOM)&&r.blur()});let s=n.inputState.mouseSelection;if(s)return s.start(e),s.dragging===!1}else n.inputState.setSelectionOrigin("select.pointer");return!1};function sp(n,e,t,i){if(i==1)return S.cursor(e,t);if(i==2)return wP(n.state,e,t);{let s=n.docView.lineAt(e,t),r=n.state.doc.lineAt(s?s.posAtEnd:e),o=s?s.posAtStart:r.from,l=s?s.posAtEnd:r.to;return lDate.now()-400&&Math.abs(e.clientX-n.clientX)<2&&Math.abs(e.clientY-n.clientY)<2?(rp+1)%3:1}function NP(n,e){let t=n.posAndSideAtCoords({x:e.clientX,y:e.clientY},!1),i=Ib(e),s=n.state.selection;return{update(r){r.docChanged&&(t.pos=r.changes.mapPos(t.pos),s=s.map(r.changes))},get(r,o,l){let a=n.posAndSideAtCoords({x:r.clientX,y:r.clientY},!1),h,c=sp(n,a.pos,a.assoc,i);if(t.pos!=a.pos&&!o){let f=sp(n,t.pos,t.assoc,i),u=Math.min(f.from,c.from),d=Math.max(f.to,c.to);c=u1&&(h=jP(s,a.pos))?h:l?s.addRange(c):S.create([c])}}}function jP(n,e){for(let t=0;t=e)return S.create(n.ranges.slice(0,t).concat(n.ranges.slice(t+1)),n.mainIndex==t?0:n.mainIndex-(n.mainIndex>t?1:0))}return null}yt.dragstart=(n,e)=>{let{selection:{main:t}}=n.state;if(e.target.draggable){let s=n.docView.tile.nearest(e.target);if(s&&s.isWidget()){let r=s.posAtStart,o=r+s.length;(r>=t.to||o<=t.from)&&(t=S.range(r,o))}}let{inputState:i}=n;return i.mouseSelection&&(i.mouseSelection.dragging=!0),i.draggedContent=t,e.dataTransfer&&(e.dataTransfer.setData("Text",Ml(n.state,uf,n.state.sliceDoc(t.from,t.to))),e.dataTransfer.effectAllowed="copyMove"),!1};yt.dragend=n=>(n.inputState.draggedContent=null,!1);function lp(n,e,t,i){if(t=Ml(n.state,ff,t),!t)return;let s=n.posAtCoords({x:e.clientX,y:e.clientY},!1),{draggedContent:r}=n.inputState,o=i&&r&&zP(n,e)?{from:r.from,to:r.to}:null,l={from:s,insert:t},a=n.state.changes(o?[o,l]:l);n.focus(),n.dispatch({changes:a,selection:{anchor:a.mapPos(s,-1),head:a.mapPos(s,1)},userEvent:o?"move.drop":"input.drop"}),n.inputState.draggedContent=null}yt.drop=(n,e)=>{if(!e.dataTransfer)return!1;if(n.state.readOnly)return!0;let t=e.dataTransfer.files;if(t&&t.length){let i=Array(t.length),s=0,r=()=>{++s==t.length&&lp(n,e,i.filter(o=>o!=null).join(n.state.lineBreak),!1)};for(let o=0;o{/[\x00-\x08\x0e-\x1f]{2}/.test(l.result)||(i[o]=l.result),r()},l.readAsText(t[o])}return!0}else{let i=e.dataTransfer.getData("Text");if(i)return lp(n,e,i,!0),!0}return!1};yt.paste=(n,e)=>{if(n.state.readOnly)return!0;n.observer.flush();let t=zb?null:e.clipboardData;return t?(qb(n,t.getData("text/plain")||t.getData("text/uri-list")),!0):(_P(n),!1)};function GP(n,e){let t=n.dom.parentNode;if(!t)return;let i=t.appendChild(document.createElement("textarea"));i.style.cssText="position: fixed; left: -10000px; top: 10px",i.value=e,i.focus(),i.selectionEnd=e.length,i.selectionStart=0,setTimeout(()=>{i.remove(),n.focus()},50)}function HP(n){let e=[],t=[],i=!1;for(let s of n.selection.ranges)s.empty||(e.push(n.sliceDoc(s.from,s.to)),t.push(s));if(!e.length){let s=-1;for(let{from:r}of n.selection.ranges){let o=n.doc.lineAt(r);o.number>s&&(e.push(o.text),t.push({from:o.from,to:Math.min(n.doc.length,o.to+1)})),s=o.number}i=!0}return{text:Ml(n,uf,e.join(n.lineBreak)),ranges:t,linewise:i}}let fc=null;yt.copy=yt.cut=(n,e)=>{let t=Ps(n.root);if(t&&!bn(n.contentDOM,t))return!1;let{text:i,ranges:s,linewise:r}=HP(n.state);if(!i&&!r)return!1;fc=r?i:null,e.type=="cut"&&!n.state.readOnly&&n.dispatch({changes:s,scrollIntoView:!0,userEvent:"delete.cut"});let o=zb?null:e.clipboardData;return o?(o.clearData(),o.setData("text/plain",i),!0):(GP(n,i),!1)};const _b=wt.define();function Yb(n,e){let t=[];for(let i of n.facet(kb)){let s=i(n,e);s&&t.push(s)}return t.length?n.update({effects:t,annotations:_b.of(!0)}):null}function Nb(n){setTimeout(()=>{let e=n.hasFocus;if(e!=n.inputState.notifiedFocused){let t=Yb(n.state,e);t?n.dispatch(t):n.update([])}},10)}at.focus=n=>{n.inputState.lastFocusTime=Date.now(),!n.scrollDOM.scrollTop&&(n.inputState.lastScrollTop||n.inputState.lastScrollLeft)&&(n.scrollDOM.scrollTop=n.inputState.lastScrollTop,n.scrollDOM.scrollLeft=n.inputState.lastScrollLeft),Nb(n)};at.blur=n=>{n.observer.clearSelectionRange(),Nb(n)};at.compositionstart=at.compositionupdate=n=>{n.observer.editContext||(n.inputState.compositionFirstChange==null&&(n.inputState.compositionFirstChange=!0),n.inputState.composing<0&&(n.inputState.composing=0))};at.compositionend=n=>{n.observer.editContext||(n.inputState.composing=-1,n.inputState.compositionEndedAt=Date.now(),n.inputState.compositionPendingKey=!0,n.inputState.compositionPendingChange=n.observer.pendingRecords().length>0,n.inputState.compositionFirstChange=null,T.chrome&&T.android?n.observer.flushSoon():n.inputState.compositionPendingChange?Promise.resolve().then(()=>n.observer.flush()):setTimeout(()=>{n.inputState.composing<0&&n.docView.hasComposition&&n.update([])},50))};at.contextmenu=n=>{n.inputState.lastContextMenu=Date.now()};yt.beforeinput=(n,e)=>{var t,i;if((e.inputType=="insertText"||e.inputType=="insertCompositionText")&&(n.inputState.insertingText=e.data,n.inputState.insertingTextAt=Date.now()),e.inputType=="insertReplacementText"&&n.observer.editContext){let r=(t=e.dataTransfer)===null||t===void 0?void 0:t.getData("text/plain"),o=e.getTargetRanges();if(r&&o.length){let l=o[0],a=n.posAtDOM(l.startContainer,l.startOffset),h=n.posAtDOM(l.endContainer,l.endOffset);return gf(n,{from:a,to:h,insert:n.state.toText(r)},null),!0}}let s;if(T.chrome&&T.android&&(s=Wb.find(r=>r.inputType==e.inputType))&&(n.observer.delayAndroidKey(s.key,s.keyCode),s.key=="Backspace"||s.key=="Delete")){let r=((i=window.visualViewport)===null||i===void 0?void 0:i.height)||0;setTimeout(()=>{var o;(((o=window.visualViewport)===null||o===void 0?void 0:o.height)||0)>r+10&&n.hasFocus&&(n.contentDOM.blur(),n.focus())},100)}return T.ios&&e.inputType=="deleteContentForward"&&n.observer.flushSoon(),T.safari&&e.inputType=="insertText"&&n.inputState.composing>=0&&setTimeout(()=>at.compositionend(n,e),20),!1};const ap=new Set;function FP(n){ap.has(n)||(ap.add(n),n.addEventListener("copy",()=>{}),n.addEventListener("cut",()=>{}))}const hp=["pre-wrap","normal","pre-line","break-spaces"];let Ms=!1;function cp(){Ms=!1}class UP{constructor(e){this.lineWrapping=e,this.doc=Z.empty,this.heightSamples={},this.lineHeight=14,this.charWidth=7,this.textHeight=14,this.lineLength=30}heightForGap(e,t){let i=this.doc.lineAt(t).number-this.doc.lineAt(e).number+1;return this.lineWrapping&&(i+=Math.max(0,Math.ceil((t-e-i*this.lineLength*.5)/this.lineLength))),this.lineHeight*i}heightForLine(e){return this.lineWrapping?(1+Math.max(0,Math.ceil((e-this.lineLength)/Math.max(1,this.lineLength-5))))*this.lineHeight:this.lineHeight}setDoc(e){return this.doc=e,this}mustRefreshForWrapping(e){return hp.indexOf(e)>-1!=this.lineWrapping}mustRefreshForHeights(e){let t=!1;for(let i=0;i-1,a=Math.abs(t-this.lineHeight)>.3||this.lineWrapping!=l||Math.abs(i-this.charWidth)>.1;if(this.lineWrapping=l,this.lineHeight=t,this.charWidth=i,this.textHeight=s,this.lineLength=r,a){this.heightSamples={};for(let h=0;h0}set outdated(e){this.flags=(e?2:0)|this.flags&-3}setHeight(e){this.height!=e&&(Math.abs(this.height-e)>to&&(Ms=!0),this.height=e)}replace(e,t,i){return Ae.of(i)}decomposeLeft(e,t){t.push(this)}decomposeRight(e,t){t.push(this)}applyChanges(e,t,i,s){let r=this,o=i.doc;for(let l=s.length-1;l>=0;l--){let{fromA:a,toA:h,fromB:c,toB:f}=s[l],u=r.lineAt(a,re.ByPosNoHeight,i.setDoc(t),0,0),d=u.to>=h?u:r.lineAt(h,re.ByPosNoHeight,i,0,0);for(f+=d.to-h,h=d.to;l>0&&u.from<=s[l-1].toA;)a=s[l-1].fromA,c=s[l-1].fromB,l--,ar*2){let l=e[t-1];l.break?e.splice(--t,1,l.left,null,l.right):e.splice(--t,1,l.left,l.right),i+=1+l.break,s-=l.size}else if(r>s*2){let l=e[i];l.break?e.splice(i,1,l.left,null,l.right):e.splice(i,1,l.left,l.right),i+=2+l.break,r-=l.size}else break;else if(s=r&&o(this.lineAt(0,re.ByPos,i,s,r))}setMeasuredHeight(e){let t=e.heights[e.index++];t<0?(this.spaceAbove=-t,t=e.heights[e.index++]):this.spaceAbove=0,this.setHeight(t)}updateHeight(e,t=0,i=!1,s){return s&&s.from<=t&&s.more&&this.setMeasuredHeight(s),this.outdated=!1,this}toString(){return`block(${this.length})`}}class Ye extends jb{constructor(e,t,i){super(e,t,null),this.collapsed=0,this.widgetHeight=0,this.breaks=0,this.spaceAbove=i}mainBlock(e,t){return new dt(t,this.length,e+this.spaceAbove,this.height-this.spaceAbove,this.breaks)}replace(e,t,i){let s=i[0];return i.length==1&&(s instanceof Ye||s instanceof xe&&s.flags&4)&&Math.abs(this.length-s.length)<10?(s instanceof xe?s=new Ye(s.length,this.height,this.spaceAbove):s.height=this.height,this.outdated||(s.outdated=!1),s):Ae.of(i)}updateHeight(e,t=0,i=!1,s){return s&&s.from<=t&&s.more?this.setMeasuredHeight(s):(i||this.outdated)&&(this.spaceAbove=0,this.setHeight(Math.max(this.widgetHeight,e.heightForLine(this.length-this.collapsed))+this.breaks*e.lineHeight)),this.outdated=!1,this}toString(){return`line(${this.length}${this.collapsed?-this.collapsed:""}${this.widgetHeight?":"+this.widgetHeight:""})`}}class xe extends Ae{constructor(e){super(e,0)}heightMetrics(e,t){let i=e.doc.lineAt(t).number,s=e.doc.lineAt(t+this.length).number,r=s-i+1,o,l=0;if(e.lineWrapping){let a=Math.min(this.height,e.lineHeight*r);o=a/r,this.length>r+1&&(l=(this.height-a)/(this.length-r-1))}else o=this.height/r;return{firstLine:i,lastLine:s,perLine:o,perChar:l}}blockAt(e,t,i,s){let{firstLine:r,lastLine:o,perLine:l,perChar:a}=this.heightMetrics(t,s);if(t.lineWrapping){let h=s+(e0){let r=i[i.length-1];r instanceof xe?i[i.length-1]=new xe(r.length+s):i.push(null,new xe(s-1))}if(e>0){let r=i[0];r instanceof xe?i[0]=new xe(e+r.length):i.unshift(new xe(e-1),null)}return Ae.of(i)}decomposeLeft(e,t){t.push(new xe(e-1),null)}decomposeRight(e,t){t.push(null,new xe(this.length-e-1))}updateHeight(e,t=0,i=!1,s){let r=t+this.length;if(s&&s.from<=t+this.length&&s.more){let o=[],l=Math.max(t,s.from),a=-1;for(s.from>t&&o.push(new xe(s.from-t-1).updateHeight(e,t));l<=r&&s.more;){let c=e.doc.lineAt(l).length;o.length&&o.push(null);let f=s.heights[s.index++],u=0;f<0&&(u=-f,f=s.heights[s.index++]),a==-1?a=f:Math.abs(f-a)>=to&&(a=-2);let d=new Ye(c,f,u);d.outdated=!1,o.push(d),l+=c+1}l<=r&&o.push(null,new xe(r-l).updateHeight(e,l));let h=Ae.of(o);return(a<0||Math.abs(h.height-this.height)>=to||Math.abs(a-this.heightMetrics(e,t).perLine)>=to)&&(Ms=!0),Vo(this,h)}else(i||this.outdated)&&(this.setHeight(e.heightForGap(t,t+this.length)),this.outdated=!1);return this}toString(){return`gap(${this.length})`}}class eC extends Ae{constructor(e,t,i){super(e.length+t+i.length,e.height+i.height,t|(e.outdated||i.outdated?2:0)),this.left=e,this.right=i,this.size=e.size+i.size}get break(){return this.flags&1}blockAt(e,t,i,s){let r=i+this.left.height;return el))return h;let c=t==re.ByPosNoHeight?re.ByPosNoHeight:re.ByPos;return a?h.join(this.right.lineAt(l,c,i,o,l)):this.left.lineAt(l,c,i,s,r).join(h)}forEachLine(e,t,i,s,r,o){let l=s+this.left.height,a=r+this.left.length+this.break;if(this.break)e=a&&this.right.forEachLine(e,t,i,l,a,o);else{let h=this.lineAt(a,re.ByPos,i,s,r);e=e&&h.from<=t&&o(h),t>h.to&&this.right.forEachLine(h.to+1,t,i,l,a,o)}}replace(e,t,i){let s=this.left.length+this.break;if(tthis.left.length)return this.balanced(this.left,this.right.replace(e-s,t-s,i));let r=[];e>0&&this.decomposeLeft(e,r);let o=r.length;for(let l of i)r.push(l);if(e>0&&fp(r,o-1),t=i&&t.push(null)),e>i&&this.right.decomposeLeft(e-i,t)}decomposeRight(e,t){let i=this.left.length,s=i+this.break;if(e>=s)return this.right.decomposeRight(e-s,t);e2*t.size||t.size>2*e.size?Ae.of(this.break?[e,null,t]:[e,t]):(this.left=Vo(this.left,e),this.right=Vo(this.right,t),this.setHeight(e.height+t.height),this.outdated=e.outdated||t.outdated,this.size=e.size+t.size,this.length=e.length+this.break+t.length,this)}updateHeight(e,t=0,i=!1,s){let{left:r,right:o}=this,l=t+r.length+this.break,a=null;return s&&s.from<=t+r.length&&s.more?a=r=r.updateHeight(e,t,i,s):r.updateHeight(e,t,i),s&&s.from<=l+o.length&&s.more?a=o=o.updateHeight(e,l,i,s):o.updateHeight(e,l,i),a?this.balanced(r,o):(this.height=this.left.height+this.right.height,this.outdated=!1,this)}toString(){return this.left+(this.break?" ":"-")+this.right}}function fp(n,e){let t,i;n[e]==null&&(t=n[e-1])instanceof xe&&(i=n[e+1])instanceof xe&&n.splice(e-1,3,new xe(t.length+1+i.length))}const tC=5;class mf{constructor(e,t){this.pos=e,this.oracle=t,this.nodes=[],this.lineStart=-1,this.lineEnd=-1,this.covering=null,this.writtenTo=e}get isCovered(){return this.covering&&this.nodes[this.nodes.length-1]==this.covering}span(e,t){if(this.lineStart>-1){let i=Math.min(t,this.lineEnd),s=this.nodes[this.nodes.length-1];s instanceof Ye?s.length+=i-this.pos:(i>this.pos||!this.isCovered)&&this.nodes.push(new Ye(i-this.pos,-1,0)),this.writtenTo=i,t>i&&(this.nodes.push(null),this.writtenTo++,this.lineStart=-1)}this.pos=t}point(e,t,i){if(e=tC)&&this.addLineDeco(s,r,o)}else t>e&&this.span(e,t);this.lineEnd>-1&&this.lineEnd-1)return;let{from:e,to:t}=this.oracle.doc.lineAt(this.pos);this.lineStart=e,this.lineEnd=t,this.writtenToe&&this.nodes.push(new Ye(this.pos-e,-1,0)),this.writtenTo=this.pos}blankContent(e,t){let i=new xe(t-e);return this.oracle.doc.lineAt(e).to==t&&(i.flags|=4),i}ensureLine(){this.enterLine();let e=this.nodes.length?this.nodes[this.nodes.length-1]:null;if(e instanceof Ye)return e;let t=new Ye(0,-1,0);return this.nodes.push(t),t}addBlock(e){this.enterLine();let t=e.deco;t&&t.startSide>0&&!this.isCovered&&this.ensureLine(),this.nodes.push(e),this.writtenTo=this.pos=this.pos+e.length,t&&t.endSide>0&&(this.covering=e)}addLineDeco(e,t,i){let s=this.ensureLine();s.length+=i,s.collapsed+=i,s.widgetHeight=Math.max(s.widgetHeight,e),s.breaks+=t,this.writtenTo=this.pos=this.pos+i}finish(e){let t=this.nodes.length==0?null:this.nodes[this.nodes.length-1];this.lineStart>-1&&!(t instanceof Ye)&&!this.isCovered?this.nodes.push(new Ye(0,-1,0)):(this.writtenToc.clientHeight||c.scrollWidth>c.clientWidth)&&f.overflow!="visible"){let u=c.getBoundingClientRect();r=Math.max(r,u.left),o=Math.min(o,u.right),l=Math.max(l,u.top),a=Math.min(h==n.parentNode?s.innerHeight:a,u.bottom)}h=f.position=="absolute"||f.position=="fixed"?c.offsetParent:c.parentNode}else if(h.nodeType==11)h=h.host;else break;return{left:r-t.left,right:Math.max(r,o)-t.left,top:l-(t.top+e),bottom:Math.max(l,a)-(t.top+e)}}function rC(n){let e=n.getBoundingClientRect(),t=n.ownerDocument.defaultView||window;return e.left0&&e.top0}function oC(n,e){let t=n.getBoundingClientRect();return{left:0,right:t.right-t.left,top:e,bottom:t.bottom-(t.top+e)}}class ga{constructor(e,t,i,s){this.from=e,this.to=t,this.size=i,this.displaySize=s}static same(e,t){if(e.length!=t.length)return!1;for(let i=0;itypeof i!="function"&&i.class=="cm-lineWrapping");this.heightOracle=new UP(t),this.stateDeco=pp(e),this.heightMap=Ae.empty().applyChanges(this.stateDeco,Z.empty,this.heightOracle.setDoc(e.doc),[new it(0,0,0,e.doc.length)]);for(let i=0;i<2&&(this.viewport=this.getViewport(0,null),!!this.updateForViewport());i++);this.updateViewportLines(),this.lineGaps=this.ensureLineGaps([]),this.lineGapDeco=ee.set(this.lineGaps.map(i=>i.draw(this,!1))),this.computeVisibleRanges()}updateForViewport(){let e=[this.viewport],{main:t}=this.state.selection;for(let i=0;i<=1;i++){let s=i?t.head:t.anchor;if(!e.some(({from:r,to:o})=>s>=r&&s<=o)){let{from:r,to:o}=this.lineBlockAt(s);e.push(new Ar(r,o))}}return this.viewports=e.sort((i,s)=>i.from-s.from),this.updateScaler()}updateScaler(){let e=this.scaler;return this.scaler=this.heightMap.height<=7e6?dp:new Of(this.heightOracle,this.heightMap,this.viewports),e.eq(this.scaler)?0:2}updateViewportLines(){this.viewportLines=[],this.heightMap.forEachLine(this.viewport.from,this.viewport.to,this.heightOracle.setDoc(this.state.doc),0,0,e=>{this.viewportLines.push(ln(e,this.scaler))})}update(e,t=null){this.state=e.state;let i=this.stateDeco;this.stateDeco=pp(this.state);let s=e.changedRanges,r=it.extendWithRanges(s,iC(i,this.stateDeco,e?e.changes:he.empty(this.state.doc.length))),o=this.heightMap.height,l=this.scrolledToBottom?null:this.scrollAnchorAt(this.scrollTop);cp(),this.heightMap=this.heightMap.applyChanges(this.stateDeco,e.startState.doc,this.heightOracle.setDoc(this.state.doc),r),(this.heightMap.height!=o||Ms)&&(e.flags|=2),l?(this.scrollAnchorPos=e.changes.mapPos(l.from,-1),this.scrollAnchorHeight=l.top):(this.scrollAnchorPos=-1,this.scrollAnchorHeight=o);let a=r.length?this.mapViewport(this.viewport,e.changes):this.viewport;(t&&(t.range.heada.to)||!this.viewportIsAppropriate(a))&&(a=this.getViewport(0,t));let h=a.from!=this.viewport.from||a.to!=this.viewport.to;this.viewport=a,e.flags|=this.updateForViewport(),(h||!e.changes.empty||e.flags&2)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(this.mapLineGaps(this.lineGaps,e.changes))),e.flags|=this.computeVisibleRanges(e.changes),t&&(this.scrollTarget=t),!this.mustEnforceCursorAssoc&&(e.selectionSet||e.focusChanged)&&e.view.lineWrapping&&e.state.selection.main.empty&&e.state.selection.main.assoc&&!e.state.facet(J$)&&(this.mustEnforceCursorAssoc=!0)}measure(e){let t=e.contentDOM,i=window.getComputedStyle(t),s=this.heightOracle,r=i.whiteSpace;this.defaultTextDirection=i.direction=="rtl"?we.RTL:we.LTR;let o=this.heightOracle.mustRefreshForWrapping(r)||this.mustMeasureContent,l=t.getBoundingClientRect(),a=o||this.mustMeasureContent||this.contentDOMHeight!=l.height;this.contentDOMHeight=l.height,this.mustMeasureContent=!1;let h=0,c=0;if(l.width&&l.height){let{scaleX:v,scaleY:w}=ab(t,l);(v>.005&&Math.abs(this.scaleX-v)>.005||w>.005&&Math.abs(this.scaleY-w)>.005)&&(this.scaleX=v,this.scaleY=w,h|=16,o=a=!0)}let f=(parseInt(i.paddingTop)||0)*this.scaleY,u=(parseInt(i.paddingBottom)||0)*this.scaleY;(this.paddingTop!=f||this.paddingBottom!=u)&&(this.paddingTop=f,this.paddingBottom=u,h|=18),this.editorWidth!=e.scrollDOM.clientWidth&&(s.lineWrapping&&(a=!0),this.editorWidth=e.scrollDOM.clientWidth,h|=16);let d=e.scrollDOM.scrollTop*this.scaleY;this.scrollTop!=d&&(this.scrollAnchorHeight=-1,this.scrollTop=d),this.scrolledToBottom=cb(e.scrollDOM);let p=(this.printing?oC:nC)(t,this.paddingTop),g=p.top-this.pixelViewport.top,m=p.bottom-this.pixelViewport.bottom;this.pixelViewport=p;let O=this.pixelViewport.bottom>this.pixelViewport.top&&this.pixelViewport.right>this.pixelViewport.left;if(O!=this.inView&&(this.inView=O,O&&(a=!0)),!this.inView&&!this.scrollTarget&&!rC(e.dom))return 0;let y=l.width;if((this.contentDOMWidth!=y||this.editorHeight!=e.scrollDOM.clientHeight)&&(this.contentDOMWidth=l.width,this.editorHeight=e.scrollDOM.clientHeight,h|=16),a){let v=e.docView.measureVisibleLineHeights(this.viewport);if(s.mustRefreshForHeights(v)&&(o=!0),o||s.lineWrapping&&Math.abs(y-this.contentDOMWidth)>s.charWidth){let{lineHeight:w,charWidth:Q,textHeight:$}=e.docView.measureTextSize();o=w>0&&s.refresh(r,w,Q,$,Math.max(5,y/Q),v),o&&(e.docView.minWidth=0,h|=16)}g>0&&m>0?c=Math.max(g,m):g<0&&m<0&&(c=Math.min(g,m)),cp();for(let w of this.viewports){let Q=w.from==this.viewport.from?v:e.docView.measureVisibleLineHeights(w);this.heightMap=(o?Ae.empty().applyChanges(this.stateDeco,Z.empty,this.heightOracle,[new it(0,0,0,e.state.doc.length)]):this.heightMap).updateHeight(s,0,o,new KP(w.from,Q))}Ms&&(h|=2)}let x=!this.viewportIsAppropriate(this.viewport,c)||this.scrollTarget&&(this.scrollTarget.range.headthis.viewport.to);return x&&(h&2&&(h|=this.updateScaler()),this.viewport=this.getViewport(c,this.scrollTarget),h|=this.updateForViewport()),(h&2||x)&&this.updateViewportLines(),(this.lineGaps.length||this.viewport.to-this.viewport.from>4e3)&&this.updateLineGaps(this.ensureLineGaps(o?[]:this.lineGaps,e)),h|=this.computeVisibleRanges(),this.mustEnforceCursorAssoc&&(this.mustEnforceCursorAssoc=!1,e.docView.enforceCursorAssoc()),h}get visibleTop(){return this.scaler.fromDOM(this.pixelViewport.top)}get visibleBottom(){return this.scaler.fromDOM(this.pixelViewport.bottom)}getViewport(e,t){let i=.5-Math.max(-.5,Math.min(.5,e/1e3/2)),s=this.heightMap,r=this.heightOracle,{visibleTop:o,visibleBottom:l}=this,a=new Ar(s.lineAt(o-i*1e3,re.ByHeight,r,0,0).from,s.lineAt(l+(1-i)*1e3,re.ByHeight,r,0,0).to);if(t){let{head:h}=t.range;if(ha.to){let c=Math.min(this.editorHeight,this.pixelViewport.bottom-this.pixelViewport.top),f=s.lineAt(h,re.ByPos,r,0,0),u;t.y=="center"?u=(f.top+f.bottom)/2-c/2:t.y=="start"||t.y=="nearest"&&h=l+Math.max(10,Math.min(i,250)))&&s>o-2*1e3&&r>1,o=s<<1;if(this.defaultTextDirection!=we.LTR&&!i)return[];let l=[],a=(c,f,u,d)=>{if(f-cc&&OO.from>=u.from&&O.to<=u.to&&Math.abs(O.from-c)O.fromy));if(!m){if(fx.from<=f&&x.to>=f)){let x=t.moveToLineBoundary(S.cursor(f),!1,!0).head;x>c&&(f=x)}let O=this.gapSize(u,c,f,d),y=i||O<2e6?O:2e6;m=new ga(c,f,O,y)}l.push(m)},h=c=>{if(c.length2e6)for(let Q of e)Q.from>=c.from&&Q.fromc.from&&a(c.from,d,c,f),pt.draw(this,this.heightOracle.lineWrapping))))}computeVisibleRanges(e){let t=this.stateDeco;this.lineGaps.length&&(t=t.concat(this.lineGapDeco));let i=[];M.spans(t,this.viewport.from,this.viewport.to,{span(r,o){i.push({from:r,to:o})},point(){}},20);let s=0;if(i.length!=this.visibleRanges.length)s=12;else for(let r=0;r=this.viewport.from&&e<=this.viewport.to&&this.viewportLines.find(t=>t.from<=e&&t.to>=e)||ln(this.heightMap.lineAt(e,re.ByPos,this.heightOracle,0,0),this.scaler)}lineBlockAtHeight(e){return e>=this.viewportLines[0].top&&e<=this.viewportLines[this.viewportLines.length-1].bottom&&this.viewportLines.find(t=>t.top<=e&&t.bottom>=e)||ln(this.heightMap.lineAt(this.scaler.fromDOM(e),re.ByHeight,this.heightOracle,0,0),this.scaler)}scrollAnchorAt(e){let t=this.lineBlockAtHeight(e+8);return t.from>=this.viewport.from||this.viewportLines[0].top-e>200?t:this.viewportLines[0]}elementAtHeight(e){return ln(this.heightMap.blockAt(this.scaler.fromDOM(e),this.heightOracle,0,0),this.scaler)}get docHeight(){return this.scaler.toDOM(this.heightMap.height)}get contentHeight(){return this.docHeight+this.paddingTop+this.paddingBottom}}class Ar{constructor(e,t){this.from=e,this.to=t}}function aC(n,e,t){let i=[],s=n,r=0;return M.spans(t,n,e,{span(){},point(o,l){o>s&&(i.push({from:s,to:o}),r+=o-s),s=l}},20),s=1)return e[e.length-1].to;let i=Math.floor(n*t);for(let s=0;;s++){let{from:r,to:o}=e[s],l=o-r;if(i<=l)return r+i;i-=l}}function Dr(n,e){let t=0;for(let{from:i,to:s}of n.ranges){if(e<=s){t+=e-i;break}t+=s-i}return t/n.total}function hC(n,e){for(let t of n)if(e(t))return t}const dp={toDOM(n){return n},fromDOM(n){return n},scale:1,eq(n){return n==this}};function pp(n){let e=n.facet(Pl).filter(i=>typeof i!="function"),t=n.facet(pf).filter(i=>typeof i!="function");return t.length&&e.push(M.join(t)),e}class Of{constructor(e,t,i){let s=0,r=0,o=0;this.viewports=i.map(({from:l,to:a})=>{let h=t.lineAt(l,re.ByPos,e,0,0).top,c=t.lineAt(a,re.ByPos,e,0,0).bottom;return s+=c-h,{from:l,to:a,top:h,bottom:c,domTop:0,domBottom:0}}),this.scale=(7e6-s)/(t.height-s);for(let l of this.viewports)l.domTop=o+(l.top-r)*this.scale,o=l.domBottom=l.domTop+(l.bottom-l.top),r=l.bottom}toDOM(e){for(let t=0,i=0,s=0;;t++){let r=tt.from==e.viewports[i].from&&t.to==e.viewports[i].to):!1}}function ln(n,e){if(e.scale==1)return n;let t=e.toDOM(n.top),i=e.toDOM(n.bottom);return new dt(n.from,n.length,t,i-t,Array.isArray(n._content)?n._content.map(s=>ln(s,e)):n._content)}const Zr=k.define({combine:n=>n.join(" ")}),uc=k.define({combine:n=>n.indexOf(!0)>-1}),dc=me.newName(),Gb=me.newName(),Hb=me.newName(),Fb={"&light":"."+Gb,"&dark":"."+Hb};function pc(n,e,t){return new me(e,{finish(i){return/&/.test(i)?i.replace(/&\w*/,s=>{if(s=="&")return n;if(!t||!t[s])throw new RangeError(`Unsupported selector: ${s}`);return t[s]}):n+" "+i}})}const cC=pc("."+dc,{"&":{position:"relative !important",boxSizing:"border-box","&.cm-focused":{outline:"1px dotted #212121"},display:"flex !important",flexDirection:"column"},".cm-scroller":{display:"flex !important",alignItems:"flex-start !important",fontFamily:"monospace",lineHeight:1.4,height:"100%",overflowX:"auto",position:"relative",zIndex:0,overflowAnchor:"none"},".cm-content":{margin:0,flexGrow:2,flexShrink:0,display:"block",whiteSpace:"pre",wordWrap:"normal",boxSizing:"border-box",minHeight:"100%",padding:"4px 0",outline:"none","&[contenteditable=true]":{WebkitUserModify:"read-write-plaintext-only"}},".cm-lineWrapping":{whiteSpace_fallback:"pre-wrap",whiteSpace:"break-spaces",wordBreak:"break-word",overflowWrap:"anywhere",flexShrink:1},"&light .cm-content":{caretColor:"black"},"&dark .cm-content":{caretColor:"white"},".cm-line":{display:"block",padding:"0 2px 0 6px"},".cm-layer":{position:"absolute",left:0,top:0,contain:"size style","& > *":{position:"absolute"}},"&light .cm-selectionBackground":{background:"#d9d9d9"},"&dark .cm-selectionBackground":{background:"#222"},"&light.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#d7d4f0"},"&dark.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground":{background:"#233"},".cm-cursorLayer":{pointerEvents:"none"},"&.cm-focused > .cm-scroller > .cm-cursorLayer":{animation:"steps(1) cm-blink 1.2s infinite"},"@keyframes cm-blink":{"0%":{},"50%":{opacity:0},"100%":{}},"@keyframes cm-blink2":{"0%":{},"50%":{opacity:0},"100%":{}},".cm-cursor, .cm-dropCursor":{borderLeft:"1.2px solid black",marginLeft:"-0.6px",pointerEvents:"none"},".cm-cursor":{display:"none"},"&dark .cm-cursor":{borderLeftColor:"#ddd"},".cm-dropCursor":{position:"absolute"},"&.cm-focused > .cm-scroller > .cm-cursorLayer .cm-cursor":{display:"block"},".cm-iso":{unicodeBidi:"isolate"},".cm-announced":{position:"fixed",top:"-10000px"},"@media print":{".cm-announced":{display:"none"}},"&light .cm-activeLine":{backgroundColor:"#cceeff44"},"&dark .cm-activeLine":{backgroundColor:"#99eeff33"},"&light .cm-specialChar":{color:"red"},"&dark .cm-specialChar":{color:"#f78"},".cm-gutters":{flexShrink:0,display:"flex",height:"100%",boxSizing:"border-box",zIndex:200},".cm-gutters-before":{insetInlineStart:0},".cm-gutters-after":{insetInlineEnd:0},"&light .cm-gutters":{backgroundColor:"#f5f5f5",color:"#6c6c6c",border:"0px solid #ddd","&.cm-gutters-before":{borderRightWidth:"1px"},"&.cm-gutters-after":{borderLeftWidth:"1px"}},"&dark .cm-gutters":{backgroundColor:"#333338",color:"#ccc"},".cm-gutter":{display:"flex !important",flexDirection:"column",flexShrink:0,boxSizing:"border-box",minHeight:"100%",overflow:"hidden"},".cm-gutterElement":{boxSizing:"border-box"},".cm-lineNumbers .cm-gutterElement":{padding:"0 3px 0 5px",minWidth:"20px",textAlign:"right",whiteSpace:"nowrap"},"&light .cm-activeLineGutter":{backgroundColor:"#e2f2ff"},"&dark .cm-activeLineGutter":{backgroundColor:"#222227"},".cm-panels":{boxSizing:"border-box",position:"sticky",left:0,right:0,zIndex:300},"&light .cm-panels":{backgroundColor:"#f5f5f5",color:"black"},"&light .cm-panels-top":{borderBottom:"1px solid #ddd"},"&light .cm-panels-bottom":{borderTop:"1px solid #ddd"},"&dark .cm-panels":{backgroundColor:"#333338",color:"white"},".cm-dialog":{padding:"2px 19px 4px 6px",position:"relative","& label":{fontSize:"80%"}},".cm-dialog-close":{position:"absolute",top:"3px",right:"4px",backgroundColor:"inherit",border:"none",font:"inherit",fontSize:"14px",padding:"0"},".cm-tab":{display:"inline-block",overflow:"hidden",verticalAlign:"bottom"},".cm-widgetBuffer":{verticalAlign:"text-top",height:"1em",width:0,display:"inline"},".cm-placeholder":{color:"#888",display:"inline-block",verticalAlign:"top",userSelect:"none"},".cm-highlightSpace":{backgroundImage:"radial-gradient(circle at 50% 55%, #aaa 20%, transparent 5%)",backgroundPosition:"center"},".cm-highlightTab":{backgroundImage:`url('data:image/svg+xml,')`,backgroundSize:"auto 100%",backgroundPosition:"right 90%",backgroundRepeat:"no-repeat"},".cm-trailingSpace":{backgroundColor:"#ff332255"},".cm-button":{verticalAlign:"middle",color:"inherit",fontSize:"70%",padding:".2em 1em",borderRadius:"1px"},"&light .cm-button":{backgroundImage:"linear-gradient(#eff1f5, #d9d9df)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#b4b4b4, #d0d3d6)"}},"&dark .cm-button":{backgroundImage:"linear-gradient(#393939, #111)",border:"1px solid #888","&:active":{backgroundImage:"linear-gradient(#111, #333)"}},".cm-textfield":{verticalAlign:"middle",color:"inherit",fontSize:"70%",border:"1px solid silver",padding:".2em .5em"},"&light .cm-textfield":{backgroundColor:"white"},"&dark .cm-textfield":{border:"1px solid #555",backgroundColor:"inherit"}},Fb),fC={childList:!0,characterData:!0,subtree:!0,attributes:!0,characterDataOldValue:!0},ma=T.ie&&T.ie_version<=11;class uC{constructor(e){this.view=e,this.active=!1,this.editContext=null,this.selectionRange=new z$,this.selectionChanged=!1,this.delayedFlush=-1,this.resizeTimeout=-1,this.queue=[],this.delayedAndroidKey=null,this.flushingAndroidKey=-1,this.lastChange=0,this.scrollTargets=[],this.intersection=null,this.resizeScroll=null,this.intersecting=!1,this.gapIntersection=null,this.gaps=[],this.printQuery=null,this.parentCheck=-1,this.dom=e.contentDOM,this.observer=new MutationObserver(t=>{for(let i of t)this.queue.push(i);(T.ie&&T.ie_version<=11||T.ios&&e.composing)&&t.some(i=>i.type=="childList"&&i.removedNodes.length||i.type=="characterData"&&i.oldValue.length>i.target.nodeValue.length)?this.flushSoon():this.flush()}),window.EditContext&&T.android&&e.constructor.EDIT_CONTEXT!==!1&&!(T.chrome&&T.chrome_version<126)&&(this.editContext=new pC(e),e.state.facet(Jt)&&(e.contentDOM.editContext=this.editContext.editContext)),ma&&(this.onCharData=t=>{this.queue.push({target:t.target,type:"characterData",oldValue:t.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this),this.onResize=this.onResize.bind(this),this.onPrint=this.onPrint.bind(this),this.onScroll=this.onScroll.bind(this),window.matchMedia&&(this.printQuery=window.matchMedia("print")),typeof ResizeObserver=="function"&&(this.resizeScroll=new ResizeObserver(()=>{var t;((t=this.view.docView)===null||t===void 0?void 0:t.lastUpdate){this.parentCheck<0&&(this.parentCheck=setTimeout(this.listenForScroll.bind(this),1e3)),t.length>0&&t[t.length-1].intersectionRatio>0!=this.intersecting&&(this.intersecting=!this.intersecting,this.intersecting!=this.view.inView&&this.onScrollChanged(document.createEvent("Event")))},{threshold:[0,.001]}),this.intersection.observe(this.dom),this.gapIntersection=new IntersectionObserver(t=>{t.length>0&&t[t.length-1].intersectionRatio>0&&this.onScrollChanged(document.createEvent("Event"))},{})),this.listenForScroll(),this.readSelectionRange()}onScrollChanged(e){this.view.inputState.runHandlers("scroll",e),this.intersecting&&this.view.measure()}onScroll(e){this.intersecting&&this.flush(!1),this.editContext&&this.view.requestMeasure(this.editContext.measureReq),this.onScrollChanged(e)}onResize(){this.resizeTimeout<0&&(this.resizeTimeout=setTimeout(()=>{this.resizeTimeout=-1,this.view.requestMeasure()},50))}onPrint(e){(e.type=="change"||!e.type)&&!e.matches||(this.view.viewState.printing=!0,this.view.measure(),setTimeout(()=>{this.view.viewState.printing=!1,this.view.requestMeasure()},500))}updateGaps(e){if(this.gapIntersection&&(e.length!=this.gaps.length||this.gaps.some((t,i)=>t!=e[i]))){this.gapIntersection.disconnect();for(let t of e)this.gapIntersection.observe(t);this.gaps=e}}onSelectionChange(e){let t=this.selectionChanged;if(!this.readSelectionRange()||this.delayedAndroidKey)return;let{view:i}=this,s=this.selectionRange;if(i.state.facet(Jt)?i.root.activeElement!=this.dom:!bn(this.dom,s))return;let r=s.anchorNode&&i.docView.tile.nearest(s.anchorNode);if(r&&r.isWidget()&&r.widget.ignoreEvent(e)){t||(this.selectionChanged=!1);return}(T.ie&&T.ie_version<=11||T.android&&T.chrome)&&!i.state.selection.main.empty&&s.focusNode&&Sn(s.focusNode,s.focusOffset,s.anchorNode,s.anchorOffset)?this.flushSoon():this.flush(!1)}readSelectionRange(){let{view:e}=this,t=Ps(e.root);if(!t)return!1;let i=T.safari&&e.root.nodeType==11&&e.root.activeElement==this.dom&&dC(this.view,t)||t;if(!i||this.selectionRange.eq(i))return!1;let s=bn(this.dom,i);return s&&!this.selectionChanged&&e.inputState.lastFocusTime>Date.now()-200&&e.inputState.lastTouchTime{let r=this.delayedAndroidKey;r&&(this.clearDelayedAndroidKey(),this.view.inputState.lastKeyCode=r.keyCode,this.view.inputState.lastKeyTime=Date.now(),!this.flush()&&r.force&&us(this.dom,r.key,r.keyCode))};this.flushingAndroidKey=this.view.win.requestAnimationFrame(s)}(!this.delayedAndroidKey||e=="Enter")&&(this.delayedAndroidKey={key:e,keyCode:t,force:this.lastChange{this.delayedFlush=-1,this.flush()}))}forceFlush(){this.delayedFlush>=0&&(this.view.win.cancelAnimationFrame(this.delayedFlush),this.delayedFlush=-1),this.flush()}pendingRecords(){for(let e of this.observer.takeRecords())this.queue.push(e);return this.queue}processRecords(){let e=this.pendingRecords();e.length&&(this.queue=[]);let t=-1,i=-1,s=!1;for(let r of e){let o=this.readMutation(r);o&&(o.typeOver&&(s=!0),t==-1?{from:t,to:i}=o:(t=Math.min(o.from,t),i=Math.max(o.to,i)))}return{from:t,to:i,typeOver:s}}readChange(){let{from:e,to:t,typeOver:i}=this.processRecords(),s=this.selectionChanged&&bn(this.dom,this.selectionRange);if(e<0&&!s)return null;e>-1&&(this.lastChange=Date.now()),this.view.inputState.lastFocusTime=0,this.selectionChanged=!1;let r=new AP(this.view,e,t,i);return this.view.docView.domChanged={newSel:r.newSel?r.newSel.main:null},r}flush(e=!0){if(this.delayedFlush>=0||this.delayedAndroidKey)return!1;e&&this.readSelectionRange();let t=this.readChange();if(!t)return this.view.requestMeasure(),!1;let i=this.view.state,s=Lb(this.view,t);return this.view.state==i&&(t.domChanged||t.newSel&&!Wo(this.view.state.selection,t.newSel.main))&&this.view.update([]),s}readMutation(e){let t=this.view.docView.tile.nearest(e.target);if(!t||t.isWidget())return null;if(t.markDirty(e.type=="attributes"),e.type=="childList"){let i=gp(t,e.previousSibling||e.target.previousSibling,-1),s=gp(t,e.nextSibling||e.target.nextSibling,1);return{from:i?t.posAfter(i):t.posAtStart,to:s?t.posBefore(s):t.posAtEnd,typeOver:!1}}else return e.type=="characterData"?{from:t.posAtStart,to:t.posAtEnd,typeOver:e.target.nodeValue==e.oldValue}:null}setWindow(e){e!=this.win&&(this.removeWindowListeners(this.win),this.win=e,this.addWindowListeners(this.win))}addWindowListeners(e){e.addEventListener("resize",this.onResize),this.printQuery?this.printQuery.addEventListener?this.printQuery.addEventListener("change",this.onPrint):this.printQuery.addListener(this.onPrint):e.addEventListener("beforeprint",this.onPrint),e.addEventListener("scroll",this.onScroll),e.document.addEventListener("selectionchange",this.onSelectionChange)}removeWindowListeners(e){e.removeEventListener("scroll",this.onScroll),e.removeEventListener("resize",this.onResize),this.printQuery?this.printQuery.removeEventListener?this.printQuery.removeEventListener("change",this.onPrint):this.printQuery.removeListener(this.onPrint):e.removeEventListener("beforeprint",this.onPrint),e.document.removeEventListener("selectionchange",this.onSelectionChange)}update(e){this.editContext&&(this.editContext.update(e),e.startState.facet(Jt)!=e.state.facet(Jt)&&(e.view.contentDOM.editContext=e.state.facet(Jt)?this.editContext.editContext:null))}destroy(){var e,t,i;this.stop(),(e=this.intersection)===null||e===void 0||e.disconnect(),(t=this.gapIntersection)===null||t===void 0||t.disconnect(),(i=this.resizeScroll)===null||i===void 0||i.disconnect();for(let s of this.scrollTargets)s.removeEventListener("scroll",this.onScroll);this.removeWindowListeners(this.win),clearTimeout(this.parentCheck),clearTimeout(this.resizeTimeout),this.win.cancelAnimationFrame(this.delayedFlush),this.win.cancelAnimationFrame(this.flushingAndroidKey),this.editContext&&(this.view.contentDOM.editContext=null,this.editContext.destroy())}}function gp(n,e,t){for(;e;){let i=ge.get(e);if(i&&i.parent==n)return i;let s=e.parentNode;e=s!=n.dom?s:t>0?e.nextSibling:e.previousSibling}return null}function mp(n,e){let t=e.startContainer,i=e.startOffset,s=e.endContainer,r=e.endOffset,o=n.docView.domAtPos(n.state.selection.main.anchor,1);return Sn(o.node,o.offset,s,r)&&([t,i,s,r]=[s,r,t,i]),{anchorNode:t,anchorOffset:i,focusNode:s,focusOffset:r}}function dC(n,e){if(e.getComposedRanges){let s=e.getComposedRanges(n.root)[0];if(s)return mp(n,s)}let t=null;function i(s){s.preventDefault(),s.stopImmediatePropagation(),t=s.getTargetRanges()[0]}return n.contentDOM.addEventListener("beforeinput",i,!0),n.dom.ownerDocument.execCommand("indent"),n.contentDOM.removeEventListener("beforeinput",i,!0),t?mp(n,t):null}class pC{constructor(e){this.from=0,this.to=0,this.pendingContextChange=null,this.handlers=Object.create(null),this.composing=null,this.resetRange(e.state);let t=this.editContext=new window.EditContext({text:e.state.doc.sliceString(this.from,this.to),selectionStart:this.toContextPos(Math.max(this.from,Math.min(this.to,e.state.selection.main.anchor))),selectionEnd:this.toContextPos(e.state.selection.main.head)});this.handlers.textupdate=i=>{let s=e.state.selection.main,{anchor:r,head:o}=s,l=this.toEditorPos(i.updateRangeStart),a=this.toEditorPos(i.updateRangeEnd);e.inputState.composing>=0&&!this.composing&&(this.composing={contextBase:i.updateRangeStart,editorBase:l,drifted:!1});let h=a-l>i.text.length;l==this.from&&rthis.to&&(a=r);let c=Eb(e.state.sliceDoc(l,a),i.text,(h?s.from:s.to)-l,h?"end":null);if(!c){let u=S.single(this.toEditorPos(i.selectionStart),this.toEditorPos(i.selectionEnd));Wo(u,s)||e.dispatch({selection:u,userEvent:"select"});return}let f={from:c.from+l,to:c.toA+l,insert:Z.of(i.text.slice(c.from,c.toB).split(` `))};if((T.mac||T.android)&&f.from==o-1&&/^\. ?$/.test(i.text)&&e.contentDOM.getAttribute("autocorrect")=="off"&&(f={from:l,to:a,insert:Z.of([i.text.replace("."," ")])}),this.pendingContextChange=f,!e.state.readOnly){let u=this.to-this.from+(f.to-f.from+f.insert.length);gf(e,f,S.single(this.toEditorPos(i.selectionStart,u),this.toEditorPos(i.selectionEnd,u)))}this.pendingContextChange&&(this.revertPending(e.state),this.setSelection(e.state)),f.from=0&&!/[\\p{Alphabetic}\\p{Number}_]/.test(t.text.slice(Math.max(0,i.updateRangeStart-1),Math.min(t.text.length,i.updateRangeStart+1)))&&this.handlers.compositionend(i)},this.handlers.characterboundsupdate=i=>{let s=[],r=null;for(let o=this.toEditorPos(i.rangeStart),l=this.toEditorPos(i.rangeEnd);o{let s=[];for(let r of i.getTextFormats()){let o=r.underlineStyle,l=r.underlineThickness;if(!/none/i.test(o)&&!/none/i.test(l)){let a=this.toEditorPos(r.rangeStart),h=this.toEditorPos(r.rangeEnd);if(a{e.inputState.composing<0&&(e.inputState.composing=0,e.inputState.compositionFirstChange=!0)},this.handlers.compositionend=()=>{if(e.inputState.composing=-1,e.inputState.compositionFirstChange=null,this.composing){let{drifted:i}=this.composing;this.composing=null,i&&this.reset(e.state)}};for(let i in this.handlers)t.addEventListener(i,this.handlers[i]);this.measureReq={read:i=>{this.editContext.updateControlBounds(i.contentDOM.getBoundingClientRect());let s=Ps(i.root);s&&s.rangeCount&&this.editContext.updateSelectionBounds(s.getRangeAt(0).getBoundingClientRect())}}}applyEdits(e){let t=0,i=!1,s=this.pendingContextChange;return e.changes.iterChanges((r,o,l,a,h)=>{if(i)return;let c=h.length-(o-r);if(s&&o>=s.to)if(s.from==r&&s.to==o&&s.insert.eq(h)){s=this.pendingContextChange=null,t+=c,this.to+=c;return}else s=null,this.revertPending(e.state);if(r+=t,o+=t,o<=this.from)this.from+=c,this.to+=c;else if(rthis.to||this.to-this.from+h.length>3e4){i=!0;return}this.editContext.updateText(this.toContextPos(r),this.toContextPos(o),h.toString()),this.to+=c}t+=c}),s&&!i&&this.revertPending(e.state),!i}update(e){let t=this.pendingContextChange,i=e.startState.selection.main;this.composing&&(this.composing.drifted||!e.changes.touchesRange(i.from,i.to)&&e.transactions.some(s=>!s.isUserEvent("input.type")&&s.changes.touchesRange(this.from,this.to)))?(this.composing.drifted=!0,this.composing.editorBase=e.changes.mapPos(this.composing.editorBase)):!this.applyEdits(e)||!this.rangeIsValid(e.state)?(this.pendingContextChange=null,this.reset(e.state)):(e.docChanged||e.selectionSet||t)&&this.setSelection(e.state),(e.geometryChanged||e.docChanged||e.selectionSet)&&e.view.requestMeasure(this.measureReq)}resetRange(e){let{head:t}=e.selection.main;this.from=Math.max(0,t-1e4),this.to=Math.min(e.doc.length,t+1e4)}reset(e){this.resetRange(e),this.editContext.updateText(0,this.editContext.text.length,e.doc.sliceString(this.from,this.to)),this.setSelection(e)}revertPending(e){let t=this.pendingContextChange;this.pendingContextChange=null,this.editContext.updateText(this.toContextPos(t.from),this.toContextPos(t.from+t.insert.length),e.doc.sliceString(t.from,t.to))}setSelection(e){let{main:t}=e.selection,i=this.toContextPos(Math.max(this.from,Math.min(this.to,t.anchor))),s=this.toContextPos(t.head);(this.editContext.selectionStart!=i||this.editContext.selectionEnd!=s)&&this.editContext.updateSelection(i,s)}rangeIsValid(e){let{head:t}=e.selection.main;return!(this.from>0&&t-this.from<500||this.to1e4*3)}toEditorPos(e,t=this.to-this.from){e=Math.min(e,t);let i=this.composing;return i&&i.drifted?i.editorBase+(e-i.contextBase):e+this.from}toContextPos(e){let t=this.composing;return t&&t.drifted?t.contextBase+(e-t.editorBase):e-this.from}destroy(){for(let e in this.handlers)this.editContext.removeEventListener(e,this.handlers[e])}}class z{get state(){return this.viewState.state}get viewport(){return this.viewState.viewport}get visibleRanges(){return this.viewState.visibleRanges}get inView(){return this.viewState.inView}get composing(){return!!this.inputState&&this.inputState.composing>0}get compositionStarted(){return!!this.inputState&&this.inputState.composing>=0}get root(){return this._root}get win(){return this.dom.ownerDocument.defaultView||window}constructor(e={}){var t;this.plugins=[],this.pluginMap=new Map,this.editorAttrs={},this.contentAttrs={},this.bidiCache=[],this.destroyed=!1,this.updateState=2,this.measureScheduled=-1,this.measureRequests=[],this.contentDOM=document.createElement("div"),this.scrollDOM=document.createElement("div"),this.scrollDOM.tabIndex=-1,this.scrollDOM.className="cm-scroller",this.scrollDOM.appendChild(this.contentDOM),this.announceDOM=document.createElement("div"),this.announceDOM.className="cm-announced",this.announceDOM.setAttribute("aria-live","polite"),this.dom=document.createElement("div"),this.dom.appendChild(this.announceDOM),this.dom.appendChild(this.scrollDOM),e.parent&&e.parent.appendChild(this.dom);let{dispatch:i}=e;this.dispatchTransactions=e.dispatchTransactions||i&&(s=>s.forEach(r=>i(r,this)))||(s=>this.update(s)),this.dispatch=this.dispatch.bind(this),this._root=e.root||q$(e.parent)||document,this.viewState=new up(e.state||L.create(e)),e.scrollTo&&e.scrollTo.is(Cr)&&(this.viewState.scrollTarget=e.scrollTo.value.clip(this.viewState.state)),this.plugins=this.state.facet(Ji).map(s=>new ca(s));for(let s of this.plugins)s.update(this);this.observer=new uC(this),this.inputState=new BP(this),this.inputState.ensureHandlers(this.plugins),this.docView=new Kd(this),this.mountStyles(),this.updateAttrs(),this.updateState=0,this.requestMeasure(),!((t=document.fonts)===null||t===void 0)&&t.ready&&document.fonts.ready.then(()=>{this.viewState.mustMeasureContent=!0,this.requestMeasure()})}dispatch(...e){let t=e.length==1&&e[0]instanceof ce?e:e.length==1&&Array.isArray(e[0])?e[0]:[this.state.update(...e)];this.dispatchTransactions(t,this)}update(e){if(this.updateState!=0)throw new Error("Calls to EditorView.update are not allowed while an update is in progress");let t=!1,i=!1,s,r=this.state;for(let u of e){if(u.startState!=r)throw new RangeError("Trying to update state with a transaction that doesn't start from the previous state.");r=u.state}if(this.destroyed){this.viewState.state=r;return}let o=this.hasFocus,l=0,a=null;e.some(u=>u.annotation(_b))?(this.inputState.notifiedFocused=o,l=1):o!=this.inputState.notifiedFocused&&(this.inputState.notifiedFocused=o,a=Yb(r,o),a||(l=1));let h=this.observer.delayedAndroidKey,c=null;if(h?(this.observer.clearDelayedAndroidKey(),c=this.observer.readChange(),(c&&!this.state.doc.eq(r.doc)||!this.state.selection.eq(r.selection))&&(c=null)):this.observer.clear(),r.facet(L.phrases)!=this.state.facet(L.phrases))return this.setState(r);s=Xo.create(this,r,e),s.flags|=l;let f=this.viewState.scrollTarget;try{this.updateState=2;for(let u of e){if(f&&(f=f.map(u.changes)),u.scrollIntoView){let{main:d}=u.state.selection;f=new ds(d.empty?d:S.cursor(d.head,d.head>d.anchor?-1:1))}for(let d of u.effects)d.is(Cr)&&(f=d.value.clip(this.state))}this.viewState.update(s,f),this.bidiCache=zo.update(this.bidiCache,s.changes),s.empty||(this.updatePlugins(s),this.inputState.update(s)),t=this.docView.update(s),this.state.facet(rn)!=this.styleModules&&this.mountStyles(),i=this.updateAttrs(),this.showAnnouncements(e),this.docView.updateSelection(t,e.some(u=>u.isUserEvent("select.pointer")))}finally{this.updateState=0}if(s.startState.facet(Zr)!=s.state.facet(Zr)&&(this.viewState.mustMeasureContent=!0),(t||i||f||this.viewState.mustEnforceCursorAssoc||this.viewState.mustMeasureContent)&&this.requestMeasure(),t&&this.docViewUpdate(),!s.empty)for(let u of this.state.facet(ac))try{u(s)}catch(d){oi(this.state,d,"update listener")}(a||c)&&Promise.resolve().then(()=>{a&&this.state==a.startState&&this.dispatch(a),c&&!Lb(this,c)&&h.force&&us(this.contentDOM,h.key,h.keyCode)})}setState(e){if(this.updateState!=0)throw new Error("Calls to EditorView.setState are not allowed while an update is in progress");if(this.destroyed){this.viewState.state=e;return}this.updateState=2;let t=this.hasFocus;try{for(let i of this.plugins)i.destroy(this);this.viewState=new up(e),this.plugins=e.facet(Ji).map(i=>new ca(i)),this.pluginMap.clear();for(let i of this.plugins)i.update(this);this.docView.destroy(),this.docView=new Kd(this),this.inputState.ensureHandlers(this.plugins),this.mountStyles(),this.updateAttrs(),this.bidiCache=[]}finally{this.updateState=0}t&&this.focus(),this.requestMeasure()}updatePlugins(e){let t=e.startState.facet(Ji),i=e.state.facet(Ji);if(t!=i){let s=[];for(let r of i){let o=t.indexOf(r);if(o<0)s.push(new ca(r));else{let l=this.plugins[o];l.mustUpdate=e,s.push(l)}}for(let r of this.plugins)r.mustUpdate!=e&&r.destroy(this);this.plugins=s,this.pluginMap.clear()}else for(let s of this.plugins)s.mustUpdate=e;for(let s=0;s-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.observer.delayedAndroidKey){this.measureScheduled=-1,this.requestMeasure();return}this.measureScheduled=0,e&&this.observer.forceFlush();let t=null,i=this.scrollDOM,s=i.scrollTop*this.scaleY,{scrollAnchorPos:r,scrollAnchorHeight:o}=this.viewState;Math.abs(s-this.viewState.scrollTop)>1&&(o=-1),this.viewState.scrollAnchorHeight=-1;try{for(let l=0;;l++){if(o<0)if(cb(i))r=-1,o=this.viewState.heightMap.height;else{let d=this.viewState.scrollAnchorAt(s);r=d.from,o=d.top}this.updateState=1;let a=this.viewState.measure(this);if(!a&&!this.measureRequests.length&&this.viewState.scrollTarget==null)break;if(l>5){console.warn(this.measureRequests.length?"Measure loop restarted more than 5 times":"Viewport failed to stabilize");break}let h=[];a&4||([this.measureRequests,h]=[h,this.measureRequests]);let c=h.map(d=>{try{return d.read(this)}catch(p){return oi(this.state,p),Op}}),f=Xo.create(this,this.state,[]),u=!1;f.flags|=a,t?t.flags|=a:t=f,this.updateState=2,f.empty||(this.updatePlugins(f),this.inputState.update(f),this.updateAttrs(),u=this.docView.update(f),u&&this.docViewUpdate());for(let d=0;d1||p<-1){s=s+p,i.scrollTop=s/this.scaleY,o=-1;continue}}break}}}finally{this.updateState=0,this.measureScheduled=-1}if(t&&!t.empty)for(let l of this.state.facet(ac))l(t)}get themeClasses(){return dc+" "+(this.state.facet(uc)?Hb:Gb)+" "+this.state.facet(Zr)}updateAttrs(){let e=bp(this,Pb,{class:"cm-editor"+(this.hasFocus?" cm-focused ":" ")+this.themeClasses}),t={spellcheck:"false",autocorrect:"off",autocapitalize:"off",writingsuggestions:"false",translate:"no",contenteditable:this.state.facet(Jt)?"true":"false",class:"cm-content",style:`${T.tabSize}: ${this.state.tabSize}`,role:"textbox","aria-multiline":"true"};this.state.readOnly&&(t["aria-readonly"]="true"),bp(this,df,t);let i=this.observer.ignore(()=>{let s=Nd(this.contentDOM,this.contentAttrs,t),r=Nd(this.dom,this.editorAttrs,e);return s||r});return this.editorAttrs=e,this.contentAttrs=t,i}showAnnouncements(e){let t=!0;for(let i of e)for(let s of i.effects)if(s.is(z.announce)){t&&(this.announceDOM.textContent=""),t=!1;let r=this.announceDOM.appendChild(document.createElement("div"));r.textContent=s.value}}mountStyles(){this.styleModules=this.state.facet(rn);let e=this.state.facet(z.cspNonce);me.mount(this.root,this.styleModules.concat(cC).reverse(),e?{nonce:e}:void 0)}readMeasured(){if(this.updateState==2)throw new Error("Reading the editor layout isn't allowed during an update");this.updateState==0&&this.measureScheduled>-1&&this.measure(!1)}requestMeasure(e){if(this.measureScheduled<0&&(this.measureScheduled=this.win.requestAnimationFrame(()=>this.measure())),e){if(this.measureRequests.indexOf(e)>-1)return;if(e.key!=null){for(let t=0;ti.plugin==e)||null),t&&t.update(this).value}get documentTop(){return this.contentDOM.getBoundingClientRect().top+this.viewState.paddingTop}get documentPadding(){return{top:this.viewState.paddingTop,bottom:this.viewState.paddingBottom}}get scaleX(){return this.viewState.scaleX}get scaleY(){return this.viewState.scaleY}elementAtHeight(e){return this.readMeasured(),this.viewState.elementAtHeight(e)}lineBlockAtHeight(e){return this.readMeasured(),this.viewState.lineBlockAtHeight(e)}get viewportLineBlocks(){return this.viewState.viewportLines}lineBlockAt(e){return this.viewState.lineBlockAt(e)}get contentHeight(){return this.viewState.contentHeight}moveByChar(e,t,i){return pa(this,e,Jd(this,e,t,i))}moveByGroup(e,t){return pa(this,e,Jd(this,e,t,i=>$P(this,e.head,i)))}visualLineSide(e,t){let i=this.bidiSpans(e),s=this.textDirectionAt(e.from),r=i[t?i.length-1:0];return S.cursor(r.side(t,s)+e.from,r.forward(!t,s)?1:-1)}moveToLineBoundary(e,t,i=!0){return vP(this,e,t,i)}moveVertically(e,t,i){return pa(this,e,PP(this,e,t,i))}domAtPos(e,t=1){return this.docView.domAtPos(e,t)}posAtDOM(e,t=0){return this.docView.posFromDOM(e,t)}posAtCoords(e,t=!0){this.readMeasured();let i=cc(this,e,t);return i&&i.pos}posAndSideAtCoords(e,t=!0){return this.readMeasured(),cc(this,e,t)}coordsAtPos(e,t=1){this.readMeasured();let i=this.docView.coordsAt(e,t);if(!i||i.left==i.right)return i;let s=this.state.doc.lineAt(e),r=this.bidiSpans(s),o=r[ri.find(r,e-s.from,-1,t)];return Bo(i,o.dir==we.LTR==t>0)}coordsForChar(e){return this.readMeasured(),this.docView.coordsForChar(e)}get defaultCharacterWidth(){return this.viewState.heightOracle.charWidth}get defaultLineHeight(){return this.viewState.heightOracle.lineHeight}get textDirection(){return this.viewState.defaultTextDirection}textDirectionAt(e){return!this.state.facet(Qb)||ethis.viewport.to?this.textDirection:(this.readMeasured(),this.docView.textDirectionAt(e))}get lineWrapping(){return this.viewState.heightOracle.lineWrapping}bidiSpans(e){if(e.length>gC)return mb(e.length);let t=this.textDirectionAt(e.from),i;for(let r of this.bidiCache)if(r.from==e.from&&r.dir==t&&(r.fresh||gb(r.isolates,i=Hd(this,e))))return r.order;i||(i=Hd(this,e));let s=F$(e.text,t,i);return this.bidiCache.push(new zo(e.from,e.to,t,i,!0,s)),s}get hasFocus(){var e;return(this.dom.ownerDocument.hasFocus()||T.safari&&((e=this.inputState)===null||e===void 0?void 0:e.lastContextMenu)>Date.now()-3e4)&&this.root.activeElement==this.contentDOM}focus(){this.observer.ignore(()=>{hb(this.contentDOM),this.docView.updateSelection()})}setRoot(e){this._root!=e&&(this._root=e,this.observer.setWindow((e.nodeType==9?e:e.ownerDocument).defaultView||window),this.mountStyles())}destroy(){this.root.activeElement==this.contentDOM&&this.contentDOM.blur();for(let e of this.plugins)e.destroy(this);this.plugins=[],this.inputState.destroy(),this.docView.destroy(),this.dom.remove(),this.observer.destroy(),this.measureScheduled>-1&&this.win.cancelAnimationFrame(this.measureScheduled),this.destroyed=!0}static scrollIntoView(e,t={}){return Cr.of(new ds(typeof e=="number"?S.cursor(e):e,t.y,t.x,t.yMargin,t.xMargin))}scrollSnapshot(){let{scrollTop:e,scrollLeft:t}=this.scrollDOM,i=this.viewState.scrollAnchorAt(e);return Cr.of(new ds(S.cursor(i.from),"start","start",i.top-e,t,!0))}setTabFocusMode(e){e==null?this.inputState.tabFocusMode=this.inputState.tabFocusMode<0?0:-1:typeof e=="boolean"?this.inputState.tabFocusMode=e?0:-1:this.inputState.tabFocusMode!=0&&(this.inputState.tabFocusMode=Date.now()+e)}static domEventHandlers(e){return Pi.define(()=>({}),{eventHandlers:e})}static domEventObservers(e){return Pi.define(()=>({}),{eventObservers:e})}static theme(e,t){let i=me.newName(),s=[Zr.of(i),rn.of(pc(`.${i}`,e))];return t&&t.dark&&s.push(uc.of(!0)),s}static baseTheme(e){return xt.lowest(rn.of(pc("."+dc,e,Fb)))}static findFromDOM(e){var t;let i=e.querySelector(".cm-content"),s=i&&ge.get(i)||ge.get(e);return((t=s?.root)===null||t===void 0?void 0:t.view)||null}}z.styleModule=rn;z.inputHandler=wb;z.clipboardInputFilter=ff;z.clipboardOutputFilter=uf;z.scrollHandler=vb;z.focusChangeEffect=kb;z.perLineTextDirection=Qb;z.exceptionSink=xb;z.updateListener=ac;z.editable=Jt;z.mouseSelectionStyle=yb;z.dragMovesSelection=Sb;z.clickAddsSelectionRange=bb;z.decorations=Pl;z.blockWrappers=Cb;z.outerDecorations=pf;z.atomicRanges=Kn;z.bidiIsolatedRanges=Tb;z.scrollMargins=Mb;z.darkTheme=uc;z.cspNonce=k.define({combine:n=>n.length?n[0]:""});z.contentAttributes=df;z.editorAttributes=Pb;z.lineWrapping=z.contentAttributes.of({class:"cm-lineWrapping"});z.announce=B.define();const gC=4096,Op={};class zo{constructor(e,t,i,s,r,o){this.from=e,this.to=t,this.dir=i,this.isolates=s,this.fresh=r,this.order=o}static update(e,t){if(t.empty&&!e.some(r=>r.fresh))return e;let i=[],s=e.length?e[e.length-1].dir:we.LTR;for(let r=Math.max(0,e.length-10);r=0;s--){let r=i[s],o=typeof r=="function"?r(n):r;o&&af(o,t)}return t}const mC=T.mac?"mac":T.windows?"win":T.linux?"linux":"key";function OC(n,e){const t=n.split(/-(?!$)/);let i=t[t.length-1];i=="Space"&&(i=" ");let s,r,o,l;for(let a=0;ai.concat(s),[]))),t}function yC(n,e,t){return Kb(Ub(n.state),e,n,t)}let mi=null;const xC=4e3;function wC(n,e=mC){let t=Object.create(null),i=Object.create(null),s=(o,l)=>{let a=i[o];if(a==null)i[o]=l;else if(a!=l)throw new Error("Key binding "+o+" is used both as a regular binding and as a multi-stroke prefix")},r=(o,l,a,h,c)=>{var f,u;let d=t[o]||(t[o]=Object.create(null)),p=l.split(/ (?!$)/).map(O=>OC(O,e));for(let O=1;O{let v=mi={view:x,prefix:y,scope:o};return setTimeout(()=>{mi==v&&(mi=null)},xC),!0}]})}let g=p.join(" ");s(g,!1);let m=d[g]||(d[g]={preventDefault:!1,stopPropagation:!1,run:((u=(f=d._any)===null||f===void 0?void 0:f.run)===null||u===void 0?void 0:u.slice())||[]});a&&m.run.push(a),h&&(m.preventDefault=!0),c&&(m.stopPropagation=!0)};for(let o of n){let l=o.scope?o.scope.split(" "):["editor"];if(o.any)for(let h of l){let c=t[h]||(t[h]=Object.create(null));c._any||(c._any={preventDefault:!1,stopPropagation:!1,run:[]});let{any:f}=o;for(let u in c)c[u].run.push(d=>f(d,gc))}let a=o[e]||o.key;if(a)for(let h of l)r(h,a,o.run,o.preventDefault,o.stopPropagation),o.shift&&r(h,"Shift-"+a,o.shift,o.preventDefault,o.stopPropagation)}return t}let gc=null;function Kb(n,e,t,i){gc=e;let s=mg(e),r=Me(s,0),o=ut(r)==s.length&&s!=" ",l="",a=!1,h=!1,c=!1;mi&&mi.view==t&&mi.scope==i&&(l=mi.prefix+" ",Vb.indexOf(e.keyCode)<0&&(h=!0,mi=null));let f=new Set,u=m=>{if(m){for(let O of m.run)if(!f.has(O)&&(f.add(O),O(t)))return m.stopPropagation&&(c=!0),!0;m.preventDefault&&(m.stopPropagation&&(c=!0),h=!0)}return!1},d=n[i],p,g;return d&&(u(d[l+Br(s,e,!o)])?a=!0:o&&(e.altKey||e.metaKey||e.ctrlKey)&&!(T.windows&&e.ctrlKey&&e.altKey)&&!(T.mac&&e.altKey&&!(e.ctrlKey||e.metaKey))&&(p=Og[e.keyCode])&&p!=s?(u(d[l+Br(p,e,!0)])||e.shiftKey&&(g=bg[e.keyCode])!=s&&g!=p&&u(d[l+Br(g,e,!1)]))&&(a=!0):o&&e.shiftKey&&u(d[l+Br(s,e,!0)])&&(a=!0),!a&&u(d._any)&&(a=!0)),h&&(a=!0),a&&c&&e.stopPropagation(),gc=null,a}const yp=k.define({combine(n){let e,t;for(let i of n)e=e||i.topContainer,t=t||i.bottomContainer;return{topContainer:e,bottomContainer:t}}});function Jb(n,e){let t=n.plugin(e1),i=t?t.specs.indexOf(e):-1;return i>-1?t.panels[i]:null}const e1=Pi.fromClass(class{constructor(n){this.input=n.state.facet(qo),this.specs=this.input.filter(t=>t),this.panels=this.specs.map(t=>t(n));let e=n.state.facet(yp);this.top=new Xr(n,!0,e.topContainer),this.bottom=new Xr(n,!1,e.bottomContainer),this.top.sync(this.panels.filter(t=>t.top)),this.bottom.sync(this.panels.filter(t=>!t.top));for(let t of this.panels)t.dom.classList.add("cm-panel"),t.mount&&t.mount()}update(n){let e=n.state.facet(yp);this.top.container!=e.topContainer&&(this.top.sync([]),this.top=new Xr(n.view,!0,e.topContainer)),this.bottom.container!=e.bottomContainer&&(this.bottom.sync([]),this.bottom=new Xr(n.view,!1,e.bottomContainer)),this.top.syncClasses(),this.bottom.syncClasses();let t=n.state.facet(qo);if(t!=this.input){let i=t.filter(a=>a),s=[],r=[],o=[],l=[];for(let a of i){let h=this.specs.indexOf(a),c;h<0?(c=a(n.view),l.push(c)):(c=this.panels[h],c.update&&c.update(n)),s.push(c),(c.top?r:o).push(c)}this.specs=i,this.panels=s,this.top.sync(r),this.bottom.sync(o);for(let a of l)a.dom.classList.add("cm-panel"),a.mount&&a.mount()}else for(let i of this.panels)i.update&&i.update(n)}destroy(){this.top.sync([]),this.bottom.sync([])}},{provide:n=>z.scrollMargins.of(e=>{let t=e.plugin(n);return t&&{top:t.top.scrollMargin(),bottom:t.bottom.scrollMargin()}})});class Xr{constructor(e,t,i){this.view=e,this.top=t,this.container=i,this.dom=void 0,this.classes="",this.panels=[],this.syncClasses()}sync(e){for(let t of this.panels)t.destroy&&e.indexOf(t)<0&&t.destroy();this.panels=e,this.syncDOM()}syncDOM(){if(this.panels.length==0){this.dom&&(this.dom.remove(),this.dom=void 0);return}if(!this.dom){this.dom=document.createElement("div"),this.dom.className=this.top?"cm-panels cm-panels-top":"cm-panels cm-panels-bottom",this.dom.style[this.top?"top":"bottom"]="0";let t=this.container||this.view.dom;t.insertBefore(this.dom,this.top?t.firstChild:null)}let e=this.dom.firstChild;for(let t of this.panels)if(t.dom.parentNode==this.dom){for(;e!=t.dom;)e=xp(e);e=e.nextSibling}else this.dom.insertBefore(t.dom,e);for(;e;)e=xp(e)}scrollMargin(){return!this.dom||this.container?0:Math.max(0,this.top?this.dom.getBoundingClientRect().bottom-Math.max(0,this.view.scrollDOM.getBoundingClientRect().top):Math.min(innerHeight,this.view.scrollDOM.getBoundingClientRect().bottom)-this.dom.getBoundingClientRect().top)}syncClasses(){if(!(!this.container||this.classes==this.view.themeClasses)){for(let e of this.classes.split(" "))e&&this.container.classList.remove(e);for(let e of(this.classes=this.view.themeClasses).split(" "))e&&this.container.classList.add(e)}}}function xp(n){let e=n.nextSibling;return n.remove(),e}const qo=k.define({enables:e1});function kC(n,e){let t,i=new Promise(o=>t=o),s=o=>QC(o,e,t);n.state.field(Oa,!1)?n.dispatch({effects:t1.of(s)}):n.dispatch({effects:B.appendConfig.of(Oa.init(()=>[s]))});let r=i1.of(s);return{close:r,result:i.then(o=>((n.win.queueMicrotask||(a=>n.win.setTimeout(a,10)))(()=>{n.state.field(Oa).indexOf(s)>-1&&n.dispatch({effects:r})}),o))}}const Oa=Se.define({create(){return[]},update(n,e){for(let t of e.effects)t.is(t1)?n=[t.value].concat(n):t.is(i1)&&(n=n.filter(i=>i!=t.value));return n},provide:n=>qo.computeN([n],e=>e.field(n))}),t1=B.define(),i1=B.define();function QC(n,e,t){let i=e.content?e.content(n,()=>o(null)):null;if(!i){if(i=H("form"),e.input){let l=H("input",e.input);/^(text|password|number|email|tel|url)$/.test(l.type)&&l.classList.add("cm-textfield"),l.name||(l.name="input"),i.appendChild(H("label",(e.label||"")+": ",l))}else i.appendChild(document.createTextNode(e.label||""));i.appendChild(document.createTextNode(" ")),i.appendChild(H("button",{class:"cm-button",type:"submit"},e.submitLabel||"OK"))}let s=i.nodeName=="FORM"?[i]:i.querySelectorAll("form");for(let l=0;l{h.keyCode==27?(h.preventDefault(),o(null)):h.keyCode==13&&(h.preventDefault(),o(a))}),a.addEventListener("submit",h=>{h.preventDefault(),o(a)})}let r=H("div",i,H("button",{onclick:()=>o(null),"aria-label":n.state.phrase("close"),class:"cm-dialog-close",type:"button"},["×"]));e.class&&(r.className=e.class),r.classList.add("cm-dialog");function o(l){r.contains(r.ownerDocument.activeElement)&&n.focus(),t(l)}return{dom:r,top:e.top,mount:()=>{if(e.focus){let l;typeof e.focus=="string"?l=i.querySelector(e.focus):l=i.querySelector("input")||i.querySelector("button"),l&&"select"in l?l.select():l&&"focus"in l&&l.focus()}}}}class As extends Ve{compare(e){return this==e||this.constructor==e.constructor&&this.eq(e)}eq(e){return!1}destroy(e){}}As.prototype.elementClass="";As.prototype.toDOM=void 0;As.prototype.mapMode=te.TrackBefore;As.prototype.startSide=As.prototype.endSide=-1;As.prototype.point=!0;const wp=typeof String.prototype.normalize=="function"?n=>n.normalize("NFKD"):n=>n;class Rs{constructor(e,t,i=0,s=e.length,r,o){this.test=o,this.value={from:0,to:0},this.done=!1,this.matches=[],this.buffer="",this.bufferPos=0,this.iter=e.iterRange(i,s),this.bufferStart=i,this.normalize=r?l=>r(wp(l)):wp,this.query=this.normalize(t)}peek(){if(this.bufferPos==this.buffer.length){if(this.bufferStart+=this.buffer.length,this.iter.next(),this.iter.done)return-1;this.bufferPos=0,this.buffer=this.iter.value}return Me(this.buffer,this.bufferPos)}next(){for(;this.matches.length;)this.matches.pop();return this.nextOverlapping()}nextOverlapping(){for(;;){let e=this.peek();if(e<0)return this.done=!0,this;let t=wc(e),i=this.bufferStart+this.bufferPos;this.bufferPos+=ut(e);let s=this.normalize(t);if(s.length)for(let r=0,o=i;;r++){let l=s.charCodeAt(r),a=this.match(l,o,this.bufferPos+this.bufferStart);if(r==s.length-1){if(a)return this.value=a,this;break}o==i&&rthis.to&&(this.curLine=this.curLine.slice(0,this.to-this.curLineStart)),this.iter.next())}nextLine(){this.curLineStart=this.curLineStart+this.curLine.length+1,this.curLineStart>this.to?this.curLine="":this.getLine(0)}next(){for(let e=this.matchPos-this.curLineStart;;){this.re.lastIndex=e;let t=this.matchPos<=this.to&&this.re.exec(this.curLine);if(t){let i=this.curLineStart+t.index,s=i+t[0].length;if(this.matchPos=Io(this.text,s+(i==s?1:0)),i==this.curLineStart+this.curLine.length&&this.nextLine(),(ithis.value.to)&&(!this.test||this.test(i,s,t)))return this.value={from:i,to:s,match:t},this;e=this.matchPos-this.curLineStart}else if(this.curLineStart+this.curLine.length=i||s.to<=t){let l=new ps(t,e.sliceString(t,i));return ba.set(e,l),l}if(s.from==t&&s.to==i)return s;let{text:r,from:o}=s;return o>t&&(r=e.sliceString(t,o)+r,o=t),s.to=this.to?this.to:this.text.lineAt(e).to}next(){for(;;){let e=this.re.lastIndex=this.matchPos-this.flat.from,t=this.re.exec(this.flat.text);if(t&&!t[0]&&t.index==e&&(this.re.lastIndex=e+1,t=this.re.exec(this.flat.text)),t){let i=this.flat.from+t.index,s=i+t[0].length;if((this.flat.to>=this.to||t.index+t[0].length<=this.flat.text.length-10)&&(!this.test||this.test(i,s,t)))return this.value={from:i,to:s,match:t},this.matchPos=Io(this.text,s+(i==s?1:0)),this}if(this.flat.to==this.to)return this.done=!0,this;this.flat=ps.get(this.text,this.flat.from,this.chunkEnd(this.flat.from+this.flat.text.length*2))}}}typeof Symbol<"u"&&(n1.prototype[Symbol.iterator]=r1.prototype[Symbol.iterator]=function(){return this});function vC(n){try{return new RegExp(n,bf),!0}catch{return!1}}function Io(n,e){if(e>=n.length)return e;let t=n.lineAt(e),i;for(;e=56320&&i<57344;)e++;return e}const $C=n=>{let{state:e}=n,t=String(e.doc.lineAt(n.state.selection.main.head).number),{close:i,result:s}=kC(n,{label:e.phrase("Go to line"),input:{type:"text",name:"line",value:t},focus:!0,submitLabel:e.phrase("go")});return s.then(r=>{let o=r&&/^([+-])?(\d+)?(:\d+)?(%)?$/.exec(r.elements.line.value);if(!o){n.dispatch({effects:i});return}let l=e.doc.lineAt(e.selection.main.head),[,a,h,c,f]=o,u=c?+c.slice(1):0,d=h?+h:l.number;if(h&&f){let m=d/100;a&&(m=m*(a=="-"?-1:1)+l.number/e.doc.lines),d=Math.round(e.doc.lines*m)}else h&&a&&(d=d*(a=="-"?-1:1)+l.number);let p=e.doc.line(Math.max(1,Math.min(e.doc.lines,d))),g=S.cursor(p.from+Math.max(0,Math.min(u,p.length)));n.dispatch({effects:[i,z.scrollIntoView(g.from,{y:"center"})],selection:g})}),!0},PC={highlightWordAroundCursor:!1,minSelectionLength:1,maxMatches:100,wholeWords:!1},CC=k.define({combine(n){return _t(n,PC,{highlightWordAroundCursor:(e,t)=>e||t,minSelectionLength:Math.min,maxMatches:Math.min})}});function TC(n){return[ZC,DC]}const MC=ee.mark({class:"cm-selectionMatch"}),AC=ee.mark({class:"cm-selectionMatch cm-selectionMatch-main"});function kp(n,e,t,i){return(t==0||n(e.sliceDoc(t-1,t))!=oe.Word)&&(i==e.doc.length||n(e.sliceDoc(i,i+1))!=oe.Word)}function RC(n,e,t,i){return n(e.sliceDoc(t,t+1))==oe.Word&&n(e.sliceDoc(i-1,i))==oe.Word}const DC=Pi.fromClass(class{constructor(n){this.decorations=this.getDeco(n)}update(n){(n.selectionSet||n.docChanged||n.viewportChanged)&&(this.decorations=this.getDeco(n.view))}getDeco(n){let e=n.state.facet(CC),{state:t}=n,i=t.selection;if(i.ranges.length>1)return ee.none;let s=i.main,r,o=null;if(s.empty){if(!e.highlightWordAroundCursor)return ee.none;let a=t.wordAt(s.head);if(!a)return ee.none;o=t.charCategorizer(s.head),r=t.sliceDoc(a.from,a.to)}else{let a=s.to-s.from;if(a200)return ee.none;if(e.wholeWords){if(r=t.sliceDoc(s.from,s.to),o=t.charCategorizer(s.head),!(kp(o,t,s.from,s.to)&&RC(o,t,s.from,s.to)))return ee.none}else if(r=t.sliceDoc(s.from,s.to),!r)return ee.none}let l=[];for(let a of n.visibleRanges){let h=new Rs(t.doc,r,a.from,a.to);for(;!h.next().done;){let{from:c,to:f}=h.value;if((!o||kp(o,t,c,f))&&(s.empty&&c<=s.from&&f>=s.to?l.push(AC.range(c,f)):(c>=s.to||f<=s.from)&&l.push(MC.range(c,f)),l.length>e.maxMatches))return ee.none}}return ee.set(l)}},{decorations:n=>n.decorations}),ZC=z.baseTheme({".cm-selectionMatch":{backgroundColor:"#99ff7780"},".cm-searchMatch .cm-selectionMatch":{backgroundColor:"transparent"}}),BC=({state:n,dispatch:e})=>{let{selection:t}=n,i=S.create(t.ranges.map(s=>n.wordAt(s.head)||S.cursor(s.head)),t.mainIndex);return i.eq(t)?!1:(e(n.update({selection:i})),!0)};function XC(n,e){let{main:t,ranges:i}=n.selection,s=n.wordAt(t.head),r=s&&s.from==t.from&&s.to==t.to;for(let o=!1,l=new Rs(n.doc,e,i[i.length-1].to);;)if(l.next(),l.done){if(o)return null;l=new Rs(n.doc,e,0,Math.max(0,i[i.length-1].from-1)),o=!0}else{if(o&&i.some(a=>a.from==l.value.from))continue;if(r){let a=n.wordAt(l.value.from);if(!a||a.from!=l.value.from||a.to!=l.value.to)continue}return l.value}}const LC=({state:n,dispatch:e})=>{let{ranges:t}=n.selection;if(t.some(r=>r.from===r.to))return BC({state:n,dispatch:e});let i=n.sliceDoc(t[0].from,t[0].to);if(n.selection.ranges.some(r=>n.sliceDoc(r.from,r.to)!=i))return!1;let s=XC(n,i);return s?(e(n.update({selection:n.selection.addRange(S.range(s.from,s.to),!1),effects:z.scrollIntoView(s.to)})),!0):!1},Ws=k.define({combine(n){return _t(n,{top:!1,caseSensitive:!1,literal:!1,regexp:!1,wholeWord:!1,createPanel:e=>new UC(e),scrollToMatch:e=>z.scrollIntoView(e)})}});class o1{constructor(e){this.search=e.search,this.caseSensitive=!!e.caseSensitive,this.literal=!!e.literal,this.regexp=!!e.regexp,this.replace=e.replace||"",this.valid=!!this.search&&(!this.regexp||vC(this.search)),this.unquoted=this.unquote(this.search),this.wholeWord=!!e.wholeWord,this.test=e.test}unquote(e){return this.literal?e:e.replace(/\\([nrt\\])/g,(t,i)=>i=="n"?` `:i=="r"?"\r":i=="t"?" ":"\\")}eq(e){return this.search==e.search&&this.replace==e.replace&&this.caseSensitive==e.caseSensitive&&this.regexp==e.regexp&&this.wholeWord==e.wholeWord&&this.test==e.test}create(){return this.regexp?new IC(this):new VC(this)}getCursor(e,t=0,i){let s=e.doc?e:L.create({doc:e});return i==null&&(i=s.doc.length),this.regexp?Gi(this,s,t,i):ji(this,s,t,i)}}class l1{constructor(e){this.spec=e}}function EC(n,e,t){return(i,s,r,o)=>{if(t&&!t(i,s,r,o))return!1;let l=i>=o&&s<=o+r.length?r.slice(i-o,s-o):e.doc.sliceString(i,s);return n(l,e,i,s)}}function ji(n,e,t,i){let s;return n.wholeWord&&(s=WC(e.doc,e.charCategorizer(e.selection.main.head))),n.test&&(s=EC(n.test,e,s)),new Rs(e.doc,n.unquoted,t,i,n.caseSensitive?void 0:r=>r.toLowerCase(),s)}function WC(n,e){return(t,i,s,r)=>((r>t||r+s.length=t)return null;s.push(i.value)}return s}highlight(e,t,i,s){let r=ji(this.spec,e,Math.max(0,t-this.spec.unquoted.length),Math.min(i+this.spec.unquoted.length,e.doc.length));for(;!r.next().done;)s(r.value.from,r.value.to)}}function zC(n,e,t){return(i,s,r)=>(!t||t(i,s,r))&&n(r[0],e,i,s)}function Gi(n,e,t,i){let s;return n.wholeWord&&(s=qC(e.charCategorizer(e.selection.main.head))),n.test&&(s=zC(n.test,e,s)),new n1(e.doc,n.search,{ignoreCase:!n.caseSensitive,test:s},t,i)}function _o(n,e){return n.slice(_(n,e,!1),e)}function Yo(n,e){return n.slice(e,_(n,e))}function qC(n){return(e,t,i)=>!i[0].length||(n(_o(i.input,i.index))!=oe.Word||n(Yo(i.input,i.index))!=oe.Word)&&(n(Yo(i.input,i.index+i[0].length))!=oe.Word||n(_o(i.input,i.index+i[0].length))!=oe.Word)}class IC extends l1{nextMatch(e,t,i){let s=Gi(this.spec,e,i,e.doc.length).next();return s.done&&(s=Gi(this.spec,e,0,t).next()),s.done?null:s.value}prevMatchInRange(e,t,i){for(let s=1;;s++){let r=Math.max(t,i-s*1e4),o=Gi(this.spec,e,r,i),l=null;for(;!o.next().done;)l=o.value;if(l&&(r==t||l.from>r+10))return l;if(r==t)return null}}prevMatch(e,t,i){return this.prevMatchInRange(e,0,t)||this.prevMatchInRange(e,i,e.doc.length)}getReplacement(e){return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g,(t,i)=>{if(i=="&")return e.match[0];if(i=="$")return"$";for(let s=i.length;s>0;s--){let r=+i.slice(0,s);if(r>0&&r=t)return null;s.push(i.value)}return s}highlight(e,t,i,s){let r=Gi(this.spec,e,Math.max(0,t-250),Math.min(i+250,e.doc.length));for(;!r.next().done;)s(r.value.from,r.value.to)}}const Xn=B.define(),Sf=B.define(),xi=Se.define({create(n){return new Sa(mc(n).create(),null)},update(n,e){for(let t of e.effects)t.is(Xn)?n=new Sa(t.value.create(),n.panel):t.is(Sf)&&(n=new Sa(n.query,t.value?yf:null));return n},provide:n=>qo.from(n,e=>e.panel)});class Sa{constructor(e,t){this.query=e,this.panel=t}}const _C=ee.mark({class:"cm-searchMatch"}),YC=ee.mark({class:"cm-searchMatch cm-searchMatch-selected"}),NC=Pi.fromClass(class{constructor(n){this.view=n,this.decorations=this.highlight(n.state.field(xi))}update(n){let e=n.state.field(xi);(e!=n.startState.field(xi)||n.docChanged||n.selectionSet||n.viewportChanged)&&(this.decorations=this.highlight(e))}highlight({query:n,panel:e}){if(!e||!n.spec.valid)return ee.none;let{view:t}=this,i=new ai;for(let s=0,r=t.visibleRanges,o=r.length;sr[s+1].from-500;)a=r[++s].to;n.highlight(t.state,l,a,(h,c)=>{let f=t.state.selection.ranges.some(u=>u.from==h&&u.to==c);i.add(h,c,f?YC:_C)})}return i.finish()}},{decorations:n=>n.decorations});function Jn(n){return e=>{let t=e.state.field(xi,!1);return t&&t.query.spec.valid?n(e,t):c1(e)}}const No=Jn((n,{query:e})=>{let{to:t}=n.state.selection.main,i=e.nextMatch(n.state,t,t);if(!i)return!1;let s=S.single(i.from,i.to),r=n.state.facet(Ws);return n.dispatch({selection:s,effects:[xf(n,i),r.scrollToMatch(s.main,n)],userEvent:"select.search"}),h1(n),!0}),jo=Jn((n,{query:e})=>{let{state:t}=n,{from:i}=t.selection.main,s=e.prevMatch(t,i,i);if(!s)return!1;let r=S.single(s.from,s.to),o=n.state.facet(Ws);return n.dispatch({selection:r,effects:[xf(n,s),o.scrollToMatch(r.main,n)],userEvent:"select.search"}),h1(n),!0}),jC=Jn((n,{query:e})=>{let t=e.matchAll(n.state,1e3);return!t||!t.length?!1:(n.dispatch({selection:S.create(t.map(i=>S.range(i.from,i.to))),userEvent:"select.search.matches"}),!0)}),GC=({state:n,dispatch:e})=>{let t=n.selection;if(t.ranges.length>1||t.main.empty)return!1;let{from:i,to:s}=t.main,r=[],o=0;for(let l=new Rs(n.doc,n.sliceDoc(i,s));!l.next().done;){if(r.length>1e3)return!1;l.value.from==i&&(o=r.length),r.push(S.range(l.value.from,l.value.to))}return e(n.update({selection:S.create(r,o),userEvent:"select.search.matches"})),!0},Qp=Jn((n,{query:e})=>{let{state:t}=n,{from:i,to:s}=t.selection.main;if(t.readOnly)return!1;let r=e.nextMatch(t,i,i);if(!r)return!1;let o=r,l=[],a,h,c=[];o.from==i&&o.to==s&&(h=t.toText(e.getReplacement(o)),l.push({from:o.from,to:o.to,insert:h}),o=e.nextMatch(t,o.from,o.to),c.push(z.announce.of(t.phrase("replaced match on line $",t.doc.lineAt(i).number)+".")));let f=n.state.changes(l);return o&&(a=S.single(o.from,o.to).map(f),c.push(xf(n,o)),c.push(t.facet(Ws).scrollToMatch(a.main,n))),n.dispatch({changes:f,selection:a,effects:c,userEvent:"input.replace"}),!0}),HC=Jn((n,{query:e})=>{if(n.state.readOnly)return!1;let t=e.matchAll(n.state,1e9).map(s=>{let{from:r,to:o}=s;return{from:r,to:o,insert:e.getReplacement(s)}});if(!t.length)return!1;let i=n.state.phrase("replaced $ matches",t.length)+".";return n.dispatch({changes:t,effects:z.announce.of(i),userEvent:"input.replace.all"}),!0});function yf(n){return n.state.facet(Ws).createPanel(n)}function mc(n,e){var t,i,s,r,o;let l=n.selection.main,a=l.empty||l.to>l.from+100?"":n.sliceDoc(l.from,l.to);if(e&&!a)return e;let h=n.facet(Ws);return new o1({search:((t=e?.literal)!==null&&t!==void 0?t:h.literal)?a:a.replace(/\n/g,"\\n"),caseSensitive:(i=e?.caseSensitive)!==null&&i!==void 0?i:h.caseSensitive,literal:(s=e?.literal)!==null&&s!==void 0?s:h.literal,regexp:(r=e?.regexp)!==null&&r!==void 0?r:h.regexp,wholeWord:(o=e?.wholeWord)!==null&&o!==void 0?o:h.wholeWord})}function a1(n){let e=Jb(n,yf);return e&&e.dom.querySelector("[main-field]")}function h1(n){let e=a1(n);e&&e==n.root.activeElement&&e.select()}const c1=n=>{let e=n.state.field(xi,!1);if(e&&e.panel){let t=a1(n);if(t&&t!=n.root.activeElement){let i=mc(n.state,e.query.spec);i.valid&&n.dispatch({effects:Xn.of(i)}),t.focus(),t.select()}}else n.dispatch({effects:[Sf.of(!0),e?Xn.of(mc(n.state,e.query.spec)):B.appendConfig.of(JC)]});return!0},f1=n=>{let e=n.state.field(xi,!1);if(!e||!e.panel)return!1;let t=Jb(n,yf);return t&&t.dom.contains(n.root.activeElement)&&n.focus(),n.dispatch({effects:Sf.of(!1)}),!0},FC=[{key:"Mod-f",run:c1,scope:"editor search-panel"},{key:"F3",run:No,shift:jo,scope:"editor search-panel",preventDefault:!0},{key:"Mod-g",run:No,shift:jo,scope:"editor search-panel",preventDefault:!0},{key:"Escape",run:f1,scope:"editor search-panel"},{key:"Mod-Shift-l",run:GC},{key:"Mod-Alt-g",run:$C},{key:"Mod-d",run:LC,preventDefault:!0}];class UC{constructor(e){this.view=e;let t=this.query=e.state.field(xi).query.spec;this.commit=this.commit.bind(this),this.searchField=H("input",{value:t.search,placeholder:qe(e,"Find"),"aria-label":qe(e,"Find"),class:"cm-textfield",name:"search",form:"","main-field":"true",onchange:this.commit,onkeyup:this.commit}),this.replaceField=H("input",{value:t.replace,placeholder:qe(e,"Replace"),"aria-label":qe(e,"Replace"),class:"cm-textfield",name:"replace",form:"",onchange:this.commit,onkeyup:this.commit}),this.caseField=H("input",{type:"checkbox",name:"case",form:"",checked:t.caseSensitive,onchange:this.commit}),this.reField=H("input",{type:"checkbox",name:"re",form:"",checked:t.regexp,onchange:this.commit}),this.wordField=H("input",{type:"checkbox",name:"word",form:"",checked:t.wholeWord,onchange:this.commit});function i(s,r,o){return H("button",{class:"cm-button",name:s,onclick:r,type:"button"},o)}this.dom=H("div",{onkeydown:s=>this.keydown(s),class:"cm-search"},[this.searchField,i("next",()=>No(e),[qe(e,"next")]),i("prev",()=>jo(e),[qe(e,"previous")]),i("select",()=>jC(e),[qe(e,"all")]),H("label",null,[this.caseField,qe(e,"match case")]),H("label",null,[this.reField,qe(e,"regexp")]),H("label",null,[this.wordField,qe(e,"by word")]),...e.state.readOnly?[]:[H("br"),this.replaceField,i("replace",()=>Qp(e),[qe(e,"replace")]),i("replaceAll",()=>HC(e),[qe(e,"replace all")])],H("button",{name:"close",onclick:()=>f1(e),"aria-label":qe(e,"close"),type:"button"},["×"])])}commit(){let e=new o1({search:this.searchField.value,caseSensitive:this.caseField.checked,regexp:this.reField.checked,wholeWord:this.wordField.checked,replace:this.replaceField.value});e.eq(this.query)||(this.query=e,this.view.dispatch({effects:Xn.of(e)}))}keydown(e){yC(this.view,e,"search-panel")?e.preventDefault():e.keyCode==13&&e.target==this.searchField?(e.preventDefault(),(e.shiftKey?jo:No)(this.view)):e.keyCode==13&&e.target==this.replaceField&&(e.preventDefault(),Qp(this.view))}update(e){for(let t of e.transactions)for(let i of t.effects)i.is(Xn)&&!i.value.eq(this.query)&&this.setQuery(i.value)}setQuery(e){this.query=e,this.searchField.value=e.search,this.replaceField.value=e.replace,this.caseField.checked=e.caseSensitive,this.reField.checked=e.regexp,this.wordField.checked=e.wholeWord}mount(){this.searchField.select()}get pos(){return 80}get top(){return this.view.state.facet(Ws).top}}function qe(n,e){return n.state.phrase(e)}const Lr=30,Er=/[\s\.,:;?!]/;function xf(n,{from:e,to:t}){let i=n.state.doc.lineAt(e),s=n.state.doc.lineAt(t).to,r=Math.max(i.from,e-Lr),o=Math.min(s,t+Lr),l=n.state.sliceDoc(r,o);if(r!=i.from){for(let a=0;al.length-Lr;a--)if(!Er.test(l[a-1])&&Er.test(l[a])){l=l.slice(0,a);break}}return z.announce.of(`${n.state.phrase("current match")}. ${l} ${n.state.phrase("on line")} ${i.number}.`)}const KC=z.baseTheme({".cm-panel.cm-search":{padding:"2px 6px 4px",position:"relative","& [name=close]":{position:"absolute",top:"0",right:"4px",backgroundColor:"inherit",border:"none",font:"inherit",padding:0,margin:0},"& input, & button, & label":{margin:".2em .6em .2em 0"},"& input[type=checkbox]":{marginRight:".2em"},"& label":{fontSize:"80%",whiteSpace:"pre"}},"&light .cm-searchMatch":{backgroundColor:"#ffff0054"},"&dark .cm-searchMatch":{backgroundColor:"#00ffff8a"},"&light .cm-searchMatch-selected":{backgroundColor:"#ff6a0054"},"&dark .cm-searchMatch-selected":{backgroundColor:"#ff00ff8a"}}),JC=[xi,xt.low(NC),KC];class u1{constructor(e,t,i,s){this.state=e,this.pos=t,this.explicit=i,this.view=s,this.abortListeners=[],this.abortOnDocChange=!1}tokenBefore(e){let t=fe(this.state).resolveInner(this.pos,-1);for(;t&&e.indexOf(t.name)<0;)t=t.parent;return t?{from:t.from,to:this.pos,text:this.state.sliceDoc(t.from,this.pos),type:t.type}:null}matchBefore(e){let t=this.state.doc.lineAt(this.pos),i=Math.max(t.from,this.pos-250),s=t.text.slice(i-t.from,this.pos-t.from),r=s.search(p1(e,!1));return r<0?null:{from:i+r,to:this.pos,text:s.slice(r)}}get aborted(){return this.abortListeners==null}addEventListener(e,t,i){e=="abort"&&this.abortListeners&&(this.abortListeners.push(t),i&&i.onDocChange&&(this.abortOnDocChange=!0))}}function vp(n){let e=Object.keys(n).join(""),t=/\w/.test(e);return t&&(e=e.replace(/\w/g,"")),`[${t?"\\w":""}${e.replace(/[^\w\s]/g,"\\$&")}]`}function eT(n){let e=Object.create(null),t=Object.create(null);for(let{label:s}of n){e[s[0]]=!0;for(let r=1;rtypeof s=="string"?{label:s}:s),[t,i]=e.every(s=>/^\w+$/.test(s.label))?[/\w*$/,/\w+$/]:eT(e);return s=>{let r=s.matchBefore(i);return r||s.explicit?{from:r?r.from:s.pos,options:e,validFor:t}:null}}function tT(n,e){return t=>{for(let i=fe(t.state).resolveInner(t.pos,-1);i;i=i.parent){if(n.indexOf(i.name)>-1)return null;if(i.type.isTop)break}return e(t)}}class $p{constructor(e,t,i,s){this.completion=e,this.source=t,this.match=i,this.score=s}}function Wi(n){return n.selection.main.from}function p1(n,e){var t;let{source:i}=n,s=e&&i[0]!="^",r=i[i.length-1]!="$";return!s&&!r?n:new RegExp(`${s?"^":""}(?:${i})${r?"$":""}`,(t=n.flags)!==null&&t!==void 0?t:n.ignoreCase?"i":"")}const wf=wt.define();function iT(n,e,t,i){let{main:s}=n.selection,r=t-s.from,o=i-s.from;return{...n.changeByRange(l=>{if(l!=s&&t!=i&&n.sliceDoc(l.from+r,l.from+o)!=n.sliceDoc(t,i))return{range:l};let a=n.toText(e);return{changes:{from:l.from+r,to:i==s.from?l.to:l.from+o,insert:a},range:S.cursor(l.from+r+a.length)}}),scrollIntoView:!0,userEvent:"input.complete"}}const Pp=new WeakMap;function sT(n){if(!Array.isArray(n))return n;let e=Pp.get(n);return e||Pp.set(n,e=d1(n)),e}const Go=B.define(),Ln=B.define();class nT{constructor(e){this.pattern=e,this.chars=[],this.folded=[],this.any=[],this.precise=[],this.byWord=[],this.score=0,this.matched=[];for(let t=0;t=48&&w<=57||w>=97&&w<=122?2:w>=65&&w<=90?1:0:(Q=wc(w))!=Q.toLowerCase()?1:Q!=Q.toUpperCase()?2:0;(!y||$==1&&m||v==0&&$!=0)&&(t[f]==w||i[f]==w&&(u=!0)?o[f++]=y:o.length&&(O=!1)),v=$,y+=ut(w)}return f==a&&o[0]==0&&O?this.result(-100+(u?-200:0),o,e):d==a&&p==0?this.ret(-200-e.length+(g==e.length?0:-100),[0,g]):l>-1?this.ret(-700-e.length,[l,l+this.pattern.length]):d==a?this.ret(-900-e.length,[p,g]):f==a?this.result(-100+(u?-200:0)+-700+(O?0:-1100),o,e):t.length==2?null:this.result((s[0]?-700:0)+-200+-1100,s,e)}result(e,t,i){let s=[],r=0;for(let o of t){let l=o+(this.astral?ut(Me(i,o)):1);r&&s[r-1]==o?s[r-1]=l:(s[r++]=o,s[r++]=l)}return this.ret(e-i.length,s)}}class rT{constructor(e){this.pattern=e,this.matched=[],this.score=0,this.folded=e.toLowerCase()}match(e){if(e.length!1,activateOnTypingDelay:100,selectOnOpen:!0,override:null,closeOnBlur:!0,maxRenderedOptions:100,defaultKeymap:!0,tooltipClass:()=>"",optionClass:()=>"",aboveCursor:!1,icons:!0,addToOptions:[],positionInfo:oT,filterStrict:!1,compareCompletions:(e,t)=>(e.sortText||e.label).localeCompare(t.sortText||t.label),interactionDelay:75,updateSyncTime:100},{defaultKeymap:(e,t)=>e&&t,closeOnBlur:(e,t)=>e&&t,icons:(e,t)=>e&&t,tooltipClass:(e,t)=>i=>Cp(e(i),t(i)),optionClass:(e,t)=>i=>Cp(e(i),t(i)),addToOptions:(e,t)=>e.concat(t),filterStrict:(e,t)=>e||t})}});function Cp(n,e){return n?e?n+" "+e:n:e}function oT(n,e,t,i,s,r){let o=n.textDirection==le.RTL,l=o,a=!1,h="top",c,f,u=e.left-s.left,d=s.right-e.right,p=i.right-i.left,g=i.bottom-i.top;if(l&&u=g||y>e.top?c=t.bottom-e.top:(h="bottom",c=e.bottom-t.top)}let m=(e.bottom-e.top)/r.offsetHeight,O=(e.right-e.left)/r.offsetWidth;return{style:`${h}: ${c/m}px; max-width: ${f/O}px`,class:"cm-completionInfo-"+(a?o?"left-narrow":"right-narrow":l?"left":"right")}}function lT(n){let e=n.addToOptions.slice();return n.icons&&e.push({render(t){let i=document.createElement("div");return i.classList.add("cm-completionIcon"),t.type&&i.classList.add(...t.type.split(/\s+/g).map(s=>"cm-completionIcon-"+s)),i.setAttribute("aria-hidden","true"),i},position:20}),e.push({render(t,i,s,r){let o=document.createElement("span");o.className="cm-completionLabel";let l=t.displayLabel||t.label,a=0;for(let h=0;ha&&o.appendChild(document.createTextNode(l.slice(a,c)));let u=o.appendChild(document.createElement("span"));u.appendChild(document.createTextNode(l.slice(c,f))),u.className="cm-completionMatchedText",a=f}return at.position-i.position).map(t=>t.render)}function ya(n,e,t){if(n<=t)return{from:0,to:n};if(e<0&&(e=0),e<=n>>1){let s=Math.floor(e/t);return{from:s*t,to:(s+1)*t}}let i=Math.floor((n-e)/t);return{from:n-(i+1)*t,to:n-i*t}}class aT{constructor(e,t,i){this.view=e,this.stateField=t,this.applyCompletion=i,this.info=null,this.infoDestroy=null,this.placeInfoReq={read:()=>this.measureInfo(),write:a=>this.placeInfo(a),key:this},this.space=null,this.currentClass="";let s=e.state.field(t),{options:r,selected:o}=s.open,l=e.state.facet(Oe);this.optionContent=lT(l),this.optionClass=l.optionClass,this.tooltipClass=l.tooltipClass,this.range=ya(r.length,o,l.maxRenderedOptions),this.dom=document.createElement("div"),this.dom.className="cm-tooltip-autocomplete",this.updateTooltipClass(e.state),this.dom.addEventListener("mousedown",a=>{let{options:h}=e.state.field(t).open;for(let c=a.target,f;c&&c!=this.dom;c=c.parentNode)if(c.nodeName=="LI"&&(f=/-(\d+)$/.exec(c.id))&&+f[1]{let h=e.state.field(this.stateField,!1);h&&h.tooltip&&e.state.facet(Oe).closeOnBlur&&a.relatedTarget!=e.contentDOM&&e.dispatch({effects:Ln.of(null)})}),this.showOptions(r,s.id)}mount(){this.updateSel()}showOptions(e,t){this.list&&this.list.remove(),this.list=this.dom.appendChild(this.createListBox(e,t,this.range)),this.list.addEventListener("scroll",()=>{this.info&&this.view.requestMeasure(this.placeInfoReq)})}update(e){var t;let i=e.state.field(this.stateField),s=e.startState.field(this.stateField);if(this.updateTooltipClass(e.state),i!=s){let{options:r,selected:o,disabled:l}=i.open;(!s.open||s.open.options!=r)&&(this.range=ya(r.length,o,e.state.facet(Oe).maxRenderedOptions),this.showOptions(r,i.id)),this.updateSel(),l!=((t=s.open)===null||t===void 0?void 0:t.disabled)&&this.dom.classList.toggle("cm-tooltip-autocomplete-disabled",!!l)}}updateTooltipClass(e){let t=this.tooltipClass(e);if(t!=this.currentClass){for(let i of this.currentClass.split(" "))i&&this.dom.classList.remove(i);for(let i of t.split(" "))i&&this.dom.classList.add(i);this.currentClass=t}}positioned(e){this.space=e,this.info&&this.view.requestMeasure(this.placeInfoReq)}updateSel(){let e=this.view.state.field(this.stateField),t=e.open;(t.selected>-1&&t.selected=this.range.to)&&(this.range=ya(t.options.length,t.selected,this.view.state.facet(Oe).maxRenderedOptions),this.showOptions(t.options,e.id));let i=this.updateSelectedOption(t.selected);if(i){this.destroyInfo();let{completion:s}=t.options[t.selected],{info:r}=s;if(!r)return;let o=typeof r=="string"?document.createTextNode(r):r(s);if(!o)return;"then"in o?o.then(l=>{l&&this.view.state.field(this.stateField,!1)==e&&this.addInfoPane(l,s)}).catch(l=>Le(this.view.state,l,"completion info")):(this.addInfoPane(o,s),i.setAttribute("aria-describedby",this.info.id))}}addInfoPane(e,t){this.destroyInfo();let i=this.info=document.createElement("div");if(i.className="cm-tooltip cm-completionInfo",i.id="cm-completionInfo-"+Math.floor(Math.random()*65535).toString(16),e.nodeType!=null)i.appendChild(e),this.infoDestroy=null;else{let{dom:s,destroy:r}=e;i.appendChild(s),this.infoDestroy=r||null}this.dom.appendChild(i),this.view.requestMeasure(this.placeInfoReq)}updateSelectedOption(e){let t=null;for(let i=this.list.firstChild,s=this.range.from;i;i=i.nextSibling,s++)i.nodeName!="LI"||!i.id?s--:s==e?i.hasAttribute("aria-selected")||(i.setAttribute("aria-selected","true"),t=i):i.hasAttribute("aria-selected")&&(i.removeAttribute("aria-selected"),i.removeAttribute("aria-describedby"));return t&&cT(this.list,t),t}measureInfo(){let e=this.dom.querySelector("[aria-selected]");if(!e||!this.info)return null;let t=this.dom.getBoundingClientRect(),i=this.info.getBoundingClientRect(),s=e.getBoundingClientRect(),r=this.space;if(!r){let o=this.dom.ownerDocument.documentElement;r={left:0,top:0,right:o.clientWidth,bottom:o.clientHeight}}return s.top>Math.min(r.bottom,t.bottom)-10||s.bottom{o.target==s&&o.preventDefault()});let r=null;for(let o=i.from;oi.from||i.from==0))if(r=u,typeof h!="string"&&h.header)s.appendChild(h.header(h));else{let d=s.appendChild(document.createElement("completion-section"));d.textContent=u}}const c=s.appendChild(document.createElement("li"));c.id=t+"-"+o,c.setAttribute("role","option");let f=this.optionClass(l);f&&(c.className=f);for(let u of this.optionContent){let d=u(l,this.view.state,this.view,a);d&&c.appendChild(d)}}return i.from&&s.classList.add("cm-completionListIncompleteTop"),i.tonew aT(t,n,e)}function cT(n,e){let t=n.getBoundingClientRect(),i=e.getBoundingClientRect(),s=t.height/n.offsetHeight;i.topt.bottom&&(n.scrollTop+=(i.bottom-t.bottom)/s)}function Tp(n){return(n.boost||0)*100+(n.apply?10:0)+(n.info?5:0)+(n.type?1:0)}function fT(n,e){let t=[],i=null,s=null,r=c=>{t.push(c);let{section:f}=c.completion;if(f){i||(i=[]);let u=typeof f=="string"?f:f.name;i.some(d=>d.name==u)||i.push(typeof f=="string"?{name:u}:f)}},o=e.facet(Oe);for(let c of n)if(c.hasResult()){let f=c.result.getMatch;if(c.result.filter===!1)for(let u of c.result.options)r(new $p(u,c.source,f?f(u):[],1e9-t.length));else{let u=e.sliceDoc(c.from,c.to),d,p=o.filterStrict?new rT(u):new nT(u);for(let g of c.result.options)if(d=p.match(g.label)){let m=g.displayLabel?f?f(g,d.matched):[]:d.matched,O=d.score+(g.boost||0);if(r(new $p(g,c.source,m,O)),typeof g.section=="object"&&g.section.rank==="dynamic"){let{name:y}=g.section;s||(s=Object.create(null)),s[y]=Math.max(O,s[y]||-1e9)}}}}if(i){let c=Object.create(null),f=0,u=(d,p)=>(d.rank==="dynamic"&&p.rank==="dynamic"?s[p.name]-s[d.name]:0)||(typeof d.rank=="number"?d.rank:1e9)-(typeof p.rank=="number"?p.rank:1e9)||(d.nameu.score-f.score||h(f.completion,u.completion))){let f=c.completion;!a||a.label!=f.label||a.detail!=f.detail||a.type!=null&&f.type!=null&&a.type!=f.type||a.apply!=f.apply||a.boost!=f.boost?l.push(c):Tp(c.completion)>Tp(a)&&(l[l.length-1]=c),a=c.completion}return l}class es{constructor(e,t,i,s,r,o){this.options=e,this.attrs=t,this.tooltip=i,this.timestamp=s,this.selected=r,this.disabled=o}setSelected(e,t){return e==this.selected||e>=this.options.length?this:new es(this.options,Mp(t,e),this.tooltip,this.timestamp,e,this.disabled)}static build(e,t,i,s,r,o){if(s&&!o&&e.some(h=>h.isPending))return s.setDisabled();let l=fT(e,t);if(!l.length)return s&&e.some(h=>h.isPending)?s.setDisabled():null;let a=t.facet(Oe).selectOnOpen?0:-1;if(s&&s.selected!=a&&s.selected!=-1){let h=s.options[s.selected].completion;for(let c=0;cc.hasResult()?Math.min(h,c.from):h,1e8),create:OT,above:r.aboveCursor},s?s.timestamp:Date.now(),a,!1)}map(e){return new es(this.options,this.attrs,{...this.tooltip,pos:e.mapPos(this.tooltip.pos)},this.timestamp,this.selected,this.disabled)}setDisabled(){return new es(this.options,this.attrs,this.tooltip,this.timestamp,this.selected,!0)}}class Ho{constructor(e,t,i){this.active=e,this.id=t,this.open=i}static start(){return new Ho(gT,"cm-ac-"+Math.floor(Math.random()*2e6).toString(36),null)}update(e){let{state:t}=e,i=t.facet(Oe),r=(i.override||t.languageDataAt("autocomplete",Wi(t)).map(sT)).map(a=>(this.active.find(c=>c.source==a)||new st(a,this.active.some(c=>c.state!=0)?1:0)).update(e,i));r.length==this.active.length&&r.every((a,h)=>a==this.active[h])&&(r=this.active);let o=this.open,l=e.effects.some(a=>a.is(kf));o&&e.docChanged&&(o=o.map(e.changes)),e.selection||r.some(a=>a.hasResult()&&e.changes.touchesRange(a.from,a.to))||!uT(r,this.active)||l?o=es.build(r,t,this.id,o,i,l):o&&o.disabled&&!r.some(a=>a.isPending)&&(o=null),!o&&r.every(a=>!a.isPending)&&r.some(a=>a.hasResult())&&(r=r.map(a=>a.hasResult()?new st(a.source,0):a));for(let a of e.effects)a.is(m1)&&(o=o&&o.setSelected(a.value,this.id));return r==this.active&&o==this.open?this:new Ho(r,this.id,o)}get tooltip(){return this.open?this.open.tooltip:null}get attrs(){return this.open?this.open.attrs:this.active.length?dT:pT}}function uT(n,e){if(n==e)return!0;for(let t=0,i=0;;){for(;t-1&&(t["aria-activedescendant"]=n+"-"+e),t}const gT=[];function g1(n,e){if(n.isUserEvent("input.complete")){let i=n.annotation(wf);if(i&&e.activateOnCompletion(i))return 12}let t=n.isUserEvent("input.type");return t&&e.activateOnTyping?5:t?1:n.isUserEvent("delete.backward")?2:n.selection?8:n.docChanged?16:0}class st{constructor(e,t,i=!1){this.source=e,this.state=t,this.explicit=i}hasResult(){return!1}get isPending(){return this.state==1}update(e,t){let i=g1(e,t),s=this;(i&8||i&16&&this.touches(e))&&(s=new st(s.source,0)),i&4&&s.state==0&&(s=new st(this.source,1)),s=s.updateFor(e,i);for(let r of e.effects)if(r.is(Go))s=new st(s.source,1,r.value);else if(r.is(Ln))s=new st(s.source,0);else if(r.is(kf))for(let o of r.value)o.source==s.source&&(s=o);return s}updateFor(e,t){return this.map(e.changes)}map(e){return this}touches(e){return e.changes.touchesRange(Wi(e.state))}}class gs extends st{constructor(e,t,i,s,r,o){super(e,3,t),this.limit=i,this.result=s,this.from=r,this.to=o}hasResult(){return!0}updateFor(e,t){var i;if(!(t&3))return this.map(e.changes);let s=this.result;s.map&&!e.changes.empty&&(s=s.map(s,e.changes));let r=e.changes.mapPos(this.from),o=e.changes.mapPos(this.to,1),l=Wi(e.state);if(l>o||!s||t&2&&(Wi(e.startState)==this.from||lt.map(e))}}),m1=B.define(),Be=Se.define({create(){return Ho.start()},update(n,e){return n.update(e)},provide:n=>[Wc.from(n,e=>e.tooltip),D.contentAttributes.from(n,e=>e.attrs)]});function Qf(n,e){const t=e.completion.apply||e.completion.label;let i=n.state.field(Be).active.find(s=>s.source==e.source);return i instanceof gs?(typeof t=="string"?n.dispatch({...iT(n.state,t,i.from,i.to),annotations:wf.of(e.completion)}):t(n,e.completion,i.from,i.to),!0):!1}const OT=hT(Be,Qf);function Wr(n,e="option"){return t=>{let i=t.state.field(Be,!1);if(!i||!i.open||i.open.disabled||Date.now()-i.open.timestamp-1?i.open.selected+s*(n?1:-1):n?0:o-1;return l<0?l=e=="page"?0:o-1:l>=o&&(l=e=="page"?o-1:0),t.dispatch({effects:m1.of(l)}),!0}}const bT=n=>{let e=n.state.field(Be,!1);return n.state.readOnly||!e||!e.open||e.open.selected<0||e.open.disabled||Date.now()-e.open.timestampn.state.field(Be,!1)?(n.dispatch({effects:Go.of(!0)}),!0):!1,ST=n=>{let e=n.state.field(Be,!1);return!e||!e.active.some(t=>t.state!=0)?!1:(n.dispatch({effects:Ln.of(null)}),!0)};class yT{constructor(e,t){this.active=e,this.context=t,this.time=Date.now(),this.updates=[],this.done=void 0}}const xT=50,wT=1e3,kT=Re.fromClass(class{constructor(n){this.view=n,this.debounceUpdate=-1,this.running=[],this.debounceAccept=-1,this.pendingStart=!1,this.composing=0;for(let e of n.state.field(Be).active)e.isPending&&this.startQuery(e)}update(n){let e=n.state.field(Be),t=n.state.facet(Oe);if(!n.selectionSet&&!n.docChanged&&n.startState.field(Be)==e)return;let i=n.transactions.some(r=>{let o=g1(r,t);return o&8||(r.selection||r.docChanged)&&!(o&3)});for(let r=0;rxT&&Date.now()-o.time>wT){for(let l of o.context.abortListeners)try{l()}catch(a){Le(this.view.state,a)}o.context.abortListeners=null,this.running.splice(r--,1)}else o.updates.push(...n.transactions)}this.debounceUpdate>-1&&clearTimeout(this.debounceUpdate),n.transactions.some(r=>r.effects.some(o=>o.is(Go)))&&(this.pendingStart=!0);let s=this.pendingStart?50:t.activateOnTypingDelay;if(this.debounceUpdate=e.active.some(r=>r.isPending&&!this.running.some(o=>o.active.source==r.source))?setTimeout(()=>this.startUpdate(),s):-1,this.composing!=0)for(let r of n.transactions)r.isUserEvent("input.type")?this.composing=2:this.composing==2&&r.selection&&(this.composing=3)}startUpdate(){this.debounceUpdate=-1,this.pendingStart=!1;let{state:n}=this.view,e=n.field(Be);for(let t of e.active)t.isPending&&!this.running.some(i=>i.active.source==t.source)&&this.startQuery(t);this.running.length&&e.open&&e.open.disabled&&(this.debounceAccept=setTimeout(()=>this.accept(),this.view.state.facet(Oe).updateSyncTime))}startQuery(n){let{state:e}=this.view,t=Wi(e),i=new u1(e,t,n.explicit,this.view),s=new yT(n,i);this.running.push(s),Promise.resolve(n.source(i)).then(r=>{s.context.aborted||(s.done=r||null,this.scheduleAccept())},r=>{this.view.dispatch({effects:Ln.of(null)}),Le(this.view.state,r)})}scheduleAccept(){this.running.every(n=>n.done!==void 0)?this.accept():this.debounceAccept<0&&(this.debounceAccept=setTimeout(()=>this.accept(),this.view.state.facet(Oe).updateSyncTime))}accept(){var n;this.debounceAccept>-1&&clearTimeout(this.debounceAccept),this.debounceAccept=-1;let e=[],t=this.view.state.facet(Oe),i=this.view.state.field(Be);for(let s=0;sl.source==r.active.source);if(o&&o.isPending)if(r.done==null){let l=new st(r.active.source,0);for(let a of r.updates)l=l.update(a,t);l.isPending||e.push(l)}else this.startQuery(o)}(e.length||i.open&&i.open.disabled)&&this.view.dispatch({effects:kf.of(e)})}},{eventHandlers:{blur(n){let e=this.view.state.field(Be,!1);if(e&&e.tooltip&&this.view.state.facet(Oe).closeOnBlur){let t=e.open&&Gm(this.view,e.open.tooltip);(!t||!t.dom.contains(n.relatedTarget))&&setTimeout(()=>this.view.dispatch({effects:Ln.of(null)}),10)}},compositionstart(){this.composing=1},compositionend(){this.composing==3&&setTimeout(()=>this.view.dispatch({effects:Go.of(!1)}),20),this.composing=0}}}),QT=typeof navigator=="object"&&/Win/.test(navigator.platform),vT=xt.highest(D.domEventHandlers({keydown(n,e){let t=e.state.field(Be,!1);if(!t||!t.open||t.open.disabled||t.open.selected<0||n.key.length>1||n.ctrlKey&&!(QT&&n.altKey)||n.metaKey)return!1;let i=t.open.options[t.open.selected],s=t.active.find(o=>o.source==i.source),r=i.completion.commitCharacters||s.result.commitCharacters;return r&&r.indexOf(n.key)>-1&&Qf(e,i),!1}})),O1=D.baseTheme({".cm-tooltip.cm-tooltip-autocomplete":{"& > ul":{fontFamily:"monospace",whiteSpace:"nowrap",overflow:"hidden auto",maxWidth_fallback:"700px",maxWidth:"min(700px, 95vw)",minWidth:"250px",maxHeight:"10em",height:"100%",listStyle:"none",margin:0,padding:0,"& > li, & > completion-section":{padding:"1px 3px",lineHeight:1.2},"& > li":{overflowX:"hidden",textOverflow:"ellipsis",cursor:"pointer"},"& > completion-section":{display:"list-item",borderBottom:"1px solid silver",paddingLeft:"0.5em",opacity:.7}}},"&light .cm-tooltip-autocomplete ul li[aria-selected]":{background:"#17c",color:"white"},"&light .cm-tooltip-autocomplete-disabled ul li[aria-selected]":{background:"#777"},"&dark .cm-tooltip-autocomplete ul li[aria-selected]":{background:"#347",color:"white"},"&dark .cm-tooltip-autocomplete-disabled ul li[aria-selected]":{background:"#444"},".cm-completionListIncompleteTop:before, .cm-completionListIncompleteBottom:after":{content:'"···"',opacity:.5,display:"block",textAlign:"center"},".cm-tooltip.cm-completionInfo":{position:"absolute",padding:"3px 9px",width:"max-content",maxWidth:"400px",boxSizing:"border-box",whiteSpace:"pre-line"},".cm-completionInfo.cm-completionInfo-left":{right:"100%"},".cm-completionInfo.cm-completionInfo-right":{left:"100%"},".cm-completionInfo.cm-completionInfo-left-narrow":{right:"30px"},".cm-completionInfo.cm-completionInfo-right-narrow":{left:"30px"},"&light .cm-snippetField":{backgroundColor:"#00000022"},"&dark .cm-snippetField":{backgroundColor:"#ffffff22"},".cm-snippetFieldPosition":{verticalAlign:"text-top",width:0,height:"1.15em",display:"inline-block",margin:"0 -0.7px -.7em",borderLeft:"1.4px dotted #888"},".cm-completionMatchedText":{textDecoration:"underline"},".cm-completionDetail":{marginLeft:"0.5em",fontStyle:"italic"},".cm-completionIcon":{fontSize:"90%",width:".8em",display:"inline-block",textAlign:"center",paddingRight:".6em",opacity:"0.6",boxSizing:"content-box"},".cm-completionIcon-function, .cm-completionIcon-method":{"&:after":{content:"'ƒ'"}},".cm-completionIcon-class":{"&:after":{content:"'○'"}},".cm-completionIcon-interface":{"&:after":{content:"'◌'"}},".cm-completionIcon-variable":{"&:after":{content:"'𝑥'"}},".cm-completionIcon-constant":{"&:after":{content:"'𝐶'"}},".cm-completionIcon-type":{"&:after":{content:"'𝑡'"}},".cm-completionIcon-enum":{"&:after":{content:"'∪'"}},".cm-completionIcon-property":{"&:after":{content:"'□'"}},".cm-completionIcon-keyword":{"&:after":{content:"'🔑︎'"}},".cm-completionIcon-namespace":{"&:after":{content:"'▢'"}},".cm-completionIcon-text":{"&:after":{content:"'abc'",fontSize:"50%",verticalAlign:"middle"}}});class $T{constructor(e,t,i,s){this.field=e,this.line=t,this.from=i,this.to=s}}class vf{constructor(e,t,i){this.field=e,this.from=t,this.to=i}map(e){let t=e.mapPos(this.from,-1,te.TrackDel),i=e.mapPos(this.to,1,te.TrackDel);return t==null||i==null?null:new vf(this.field,t,i)}}class $f{constructor(e,t){this.lines=e,this.fieldPositions=t}instantiate(e,t){let i=[],s=[t],r=e.doc.lineAt(t),o=/^\s*/.exec(r.text)[0];for(let a of this.lines){if(i.length){let h=o,c=/^\t*/.exec(a)[0].length;for(let f=0;fnew vf(a.field,s[a.line]+a.from,s[a.line]+a.to));return{text:i,ranges:l}}static parse(e){let t=[],i=[],s=[],r;for(let o of e.split(/\r\n?|\n/)){for(;r=/[#$]\{(?:(\d+)(?::([^{}]*))?|((?:\\[{}]|[^{}])*))\}/.exec(o);){let l=r[1]?+r[1]:null,a=r[2]||r[3]||"",h=-1,c=a.replace(/\\[{}]/g,f=>f[1]);for(let f=0;f=h&&u.field++}for(let f of s)if(f.line==i.length&&f.from>r.index){let u=r[2]?3+(r[1]||"").length:2;f.from-=u,f.to-=u}s.push(new $T(h,i.length,r.index,r.index+c.length)),o=o.slice(0,r.index)+a+o.slice(r.index+r[0].length)}o=o.replace(/\\([{}])/g,(l,a,h)=>{for(let c of s)c.line==i.length&&c.from>h&&(c.from--,c.to--);return a}),i.push(o)}return new $f(i,s)}}let PT=E.widget({widget:new class extends Yt{toDOM(){let n=document.createElement("span");return n.className="cm-snippetFieldPosition",n}ignoreEvent(){return!1}}}),CT=E.mark({class:"cm-snippetField"});class Vs{constructor(e,t){this.ranges=e,this.active=t,this.deco=E.set(e.map(i=>(i.from==i.to?PT:CT).range(i.from,i.to)),!0)}map(e){let t=[];for(let i of this.ranges){let s=i.map(e);if(!s)return null;t.push(s)}return new Vs(t,this.active)}selectionInsideField(e){return e.ranges.every(t=>this.ranges.some(i=>i.field==this.active&&i.from<=t.from&&i.to>=t.to))}}const er=B.define({map(n,e){return n&&n.map(e)}}),TT=B.define(),En=Se.define({create(){return null},update(n,e){for(let t of e.effects){if(t.is(er))return t.value;if(t.is(TT)&&n)return new Vs(n.ranges,t.value)}return n&&e.docChanged&&(n=n.map(e.changes)),n&&e.selection&&!n.selectionInsideField(e.selection)&&(n=null),n},provide:n=>D.decorations.from(n,e=>e?e.deco:E.none)});function Pf(n,e){return S.create(n.filter(t=>t.field==e).map(t=>S.range(t.from,t.to)))}function MT(n){let e=$f.parse(n);return(t,i,s,r)=>{let{text:o,ranges:l}=e.instantiate(t.state,s),{main:a}=t.state.selection,h={changes:{from:s,to:r==a.from?a.to:r,insert:Z.of(o)},scrollIntoView:!0,annotations:i?[wf.of(i),ce.userEvent.of("input.complete")]:void 0};if(l.length&&(h.selection=Pf(l,0)),l.some(c=>c.field>0)){let c=new Vs(l,0),f=h.effects=[er.of(c)];t.state.field(En,!1)===void 0&&f.push(B.appendConfig.of([En,BT,XT,O1]))}t.dispatch(t.state.update(h))}}function b1(n){return({state:e,dispatch:t})=>{let i=e.field(En,!1);if(!i||n<0&&i.active==0)return!1;let s=i.active+n,r=n>0&&!i.ranges.some(o=>o.field==s+n);return t(e.update({selection:Pf(i.ranges,s),effects:er.of(r?null:new Vs(i.ranges,s)),scrollIntoView:!0})),!0}}const AT=({state:n,dispatch:e})=>n.field(En,!1)?(e(n.update({effects:er.of(null)})),!0):!1,RT=b1(1),DT=b1(-1),ZT=[{key:"Tab",run:RT,shift:DT},{key:"Escape",run:AT}],Ap=k.define({combine(n){return n.length?n[0]:ZT}}),BT=xt.highest(_n.compute([Ap],n=>n.facet(Ap)));function Ze(n,e){return{...e,apply:MT(n)}}const XT=D.domEventHandlers({mousedown(n,e){let t=e.state.field(En,!1),i;if(!t||(i=e.posAtCoords({x:n.clientX,y:n.clientY}))==null)return!1;let s=t.ranges.find(r=>r.from<=i&&r.to>=i);return!s||s.field==t.active?!1:(e.dispatch({selection:Pf(t.ranges,s.field),effects:er.of(t.ranges.some(r=>r.field>s.field)?new Vs(t.ranges,s.field):null),scrollIntoView:!0}),!0)}}),Wn={brackets:["(","[","{","'",'"'],before:")]}:;>",stringPrefixes:[]},Ei=B.define({map(n,e){let t=e.mapPos(n,-1,te.TrackAfter);return t??void 0}}),Cf=new class extends Ve{};Cf.startSide=1;Cf.endSide=-1;const S1=Se.define({create(){return M.empty},update(n,e){if(n=n.map(e.changes),e.selection){let t=e.state.doc.lineAt(e.selection.main.head);n=n.update({filter:i=>i>=t.from&&i<=t.to})}for(let t of e.effects)t.is(Ei)&&(n=n.update({add:[Cf.range(t.value,t.value+1)]}));return n}});function LT(){return[WT,S1]}const wa="()[]{}<>«»»«[]{}";function y1(n){for(let e=0;e{if((ET?n.composing:n.compositionStarted)||n.state.readOnly)return!1;let s=n.state.selection.main;if(i.length>2||i.length==2&&ut(Me(i,0))==1||e!=s.from||t!=s.to)return!1;let r=qT(n.state,i);return r?(n.dispatch(r),!0):!1}),VT=({state:n,dispatch:e})=>{if(n.readOnly)return!1;let i=x1(n,n.selection.main.head).brackets||Wn.brackets,s=null,r=n.changeByRange(o=>{if(o.empty){let l=IT(n.doc,o.head);for(let a of i)if(a==l&&Al(n.doc,o.head)==y1(Me(a,0)))return{changes:{from:o.head-a.length,to:o.head+a.length},range:S.cursor(o.head-a.length)}}return{range:s=o}});return s||e(n.update(r,{scrollIntoView:!0,userEvent:"delete.backward"})),!s},zT=[{key:"Backspace",run:VT}];function qT(n,e){let t=x1(n,n.selection.main.head),i=t.brackets||Wn.brackets;for(let s of i){let r=y1(Me(s,0));if(e==s)return r==s?NT(n,s,i.indexOf(s+s+s)>-1,t):_T(n,s,r,t.before||Wn.before);if(e==r&&w1(n,n.selection.main.from))return YT(n,s,r)}return null}function w1(n,e){let t=!1;return n.field(S1).between(0,n.doc.length,i=>{i==e&&(t=!0)}),t}function Al(n,e){let t=n.sliceString(e,e+2);return t.slice(0,ut(Me(t,0)))}function IT(n,e){let t=n.sliceString(e-2,e);return ut(Me(t,0))==t.length?t:t.slice(1)}function _T(n,e,t,i){let s=null,r=n.changeByRange(o=>{if(!o.empty)return{changes:[{insert:e,from:o.from},{insert:t,from:o.to}],effects:Ei.of(o.to+e.length),range:S.range(o.anchor+e.length,o.head+e.length)};let l=Al(n.doc,o.head);return!l||/\s/.test(l)||i.indexOf(l)>-1?{changes:{insert:e+t,from:o.head},effects:Ei.of(o.head+e.length),range:S.cursor(o.head+e.length)}:{range:s=o}});return s?null:n.update(r,{scrollIntoView:!0,userEvent:"input.type"})}function YT(n,e,t){let i=null,s=n.changeByRange(r=>r.empty&&Al(n.doc,r.head)==t?{changes:{from:r.head,to:r.head+t.length,insert:t},range:S.cursor(r.head+t.length)}:i={range:r});return i?null:n.update(s,{scrollIntoView:!0,userEvent:"input.type"})}function NT(n,e,t,i){let s=i.stringPrefixes||Wn.stringPrefixes,r=null,o=n.changeByRange(l=>{if(!l.empty)return{changes:[{insert:e,from:l.from},{insert:e,from:l.to}],effects:Ei.of(l.to+e.length),range:S.range(l.anchor+e.length,l.head+e.length)};let a=l.head,h=Al(n.doc,a),c;if(h==e){if(Rp(n,a))return{changes:{insert:e+e,from:a},effects:Ei.of(a+e.length),range:S.cursor(a+e.length)};if(w1(n,a)){let u=t&&n.sliceDoc(a,a+e.length*3)==e+e+e?e+e+e:e;return{changes:{from:a,to:a+u.length,insert:u},range:S.cursor(a+u.length)}}}else{if(t&&n.sliceDoc(a-2*e.length,a)==e+e&&(c=Dp(n,a-2*e.length,s))>-1&&Rp(n,c))return{changes:{insert:e+e+e+e,from:a},effects:Ei.of(a+e.length),range:S.cursor(a+e.length)};if(n.charCategorizer(a)(h)!=oe.Word&&Dp(n,a,s)>-1&&!jT(n,a,e,s))return{changes:{insert:e+e,from:a},effects:Ei.of(a+e.length),range:S.cursor(a+e.length)}}return{range:r=l}});return r?null:n.update(o,{scrollIntoView:!0,userEvent:"input.type"})}function Rp(n,e){let t=fe(n).resolveInner(e+1);return t.parent&&t.from==e}function jT(n,e,t,i){let s=fe(n).resolveInner(e,-1),r=i.reduce((o,l)=>Math.max(o,l.length),0);for(let o=0;o<5;o++){let l=n.sliceDoc(s.from,Math.min(s.to,s.from+t.length+r)),a=l.indexOf(t);if(!a||a>-1&&i.indexOf(l.slice(0,a))>-1){let c=s.firstChild;for(;c&&c.from==s.from&&c.to-c.from>t.length+a;){if(n.sliceDoc(c.to-t.length,c.to)==t)return!1;c=c.firstChild}return!0}let h=s.to==e&&s.parent;if(!h)break;s=h}return!1}function Dp(n,e,t){let i=n.charCategorizer(e);if(i(n.sliceDoc(e-1,e))!=oe.Word)return e;for(let s of t){let r=e-s.length;if(n.sliceDoc(r,e)==s&&i(n.sliceDoc(r-1,r))!=oe.Word)return r}return-1}function GT(n={}){return[vT,Be,Oe.of(n),kT,HT,O1]}const k1=[{key:"Ctrl-Space",run:xa},{mac:"Alt-`",run:xa},{mac:"Alt-i",run:xa},{key:"Escape",run:ST},{key:"ArrowDown",run:Wr(!0)},{key:"ArrowUp",run:Wr(!1)},{key:"PageDown",run:Wr(!0,"page")},{key:"PageUp",run:Wr(!1,"page")},{key:"Enter",run:bT}],HT=xt.highest(_n.computeN([Oe],n=>n.facet(Oe).defaultKeymap?[k1]:[]));class Zp{constructor(e,t,i){this.from=e,this.to=t,this.diagnostic=i}}class Bi{constructor(e,t,i){this.diagnostics=e,this.panel=t,this.selected=i}static init(e,t,i){let s=i.facet(Vn).markerFilter;s&&(e=s(e,i));let r=e.slice().sort((d,p)=>d.from-p.from||d.to-p.to),o=new ai,l=[],a=0,h=i.doc.iter(),c=0,f=i.doc.length;for(let d=0;;){let p=d==r.length?null:r[d];if(!p&&!l.length)break;let g,m;if(l.length)g=a,m=l.reduce((x,v)=>Math.min(x,v.to),p&&p.from>g?p.from:1e8);else{if(g=p.from,g>f)break;m=p.to,l.push(p),d++}for(;dx.from||x.to==g))l.push(x),d++,m=Math.min(x.to,m);else{m=Math.min(x.from,m);break}}m=Math.min(m,f);let O=!1;if(l.some(x=>x.from==g&&(x.to==m||m==f))&&(O=g==m,!O&&m-g<10)){let x=g-(c+h.value.length);x>0&&(h.next(x),c=g);for(let v=g;;){if(v>=m){O=!0;break}if(!h.lineBreak&&c+h.value.length>v)break;v=c+h.value.length,c+=h.value.length,h.next()}}let y=a2(l);if(O)o.add(g,g,E.widget({widget:new n2(y),diagnostics:l.slice()}));else{let x=l.reduce((v,w)=>w.markClass?v+" "+w.markClass:v,"");o.add(g,m,E.mark({class:"cm-lintRange cm-lintRange-"+y+x,diagnostics:l.slice(),inclusiveEnd:l.some(v=>v.to>m)}))}if(a=m,a==f)break;for(let x=0;x{if(!(e&&o.diagnostics.indexOf(e)<0))if(!i)i=new Zp(s,r,e||o.diagnostics[0]);else{if(o.diagnostics.indexOf(i.diagnostic)<0)return!1;i=new Zp(i.from,r,i.diagnostic)}}),i}function FT(n,e){let t=e.pos,i=e.end||t,s=n.state.facet(Vn).hideOn(n,t,i);if(s!=null)return s;let r=n.startState.doc.lineAt(e.pos);return!!(n.effects.some(o=>o.is(Q1))||n.changes.touchesRange(r.from,Math.max(r.to,i)))}function UT(n,e){return n.field(je,!1)?e:e.concat(B.appendConfig.of(h2))}const Q1=B.define(),Tf=B.define(),v1=B.define(),je=Se.define({create(){return new Bi(E.none,null,null)},update(n,e){if(e.docChanged&&n.diagnostics.size){let t=n.diagnostics.map(e.changes),i=null,s=n.panel;if(n.selected){let r=e.changes.mapPos(n.selected.from,1);i=Ds(t,n.selected.diagnostic,r)||Ds(t,null,r)}!t.size&&s&&e.state.facet(Vn).autoPanel&&(s=null),n=new Bi(t,s,i)}for(let t of e.effects)if(t.is(Q1)){let i=e.state.facet(Vn).autoPanel?t.value.length?zn.open:null:n.panel;n=Bi.init(t.value,i,e.state)}else t.is(Tf)?n=new Bi(n.diagnostics,t.value?zn.open:null,n.selected):t.is(v1)&&(n=new Bi(n.diagnostics,n.panel,t.value));return n},provide:n=>[yh.from(n,e=>e.panel),D.decorations.from(n,e=>e.diagnostics)]}),KT=E.mark({class:"cm-lintRange cm-lintRange-active"});function JT(n,e,t){let{diagnostics:i}=n.state.field(je),s,r=-1,o=-1;i.between(e-(t<0?1:0),e+(t>0?1:0),(a,h,{spec:c})=>{if(e>=a&&e<=h&&(a==h||(e>a||t>0)&&(eP1(n,t,!1)))}const t2=n=>{let e=n.state.field(je,!1);(!e||!e.panel)&&n.dispatch({effects:UT(n.state,[Tf.of(!0)])});let t=uw(n,zn.open);return t&&t.dom.querySelector(".cm-panel-lint ul").focus(),!0},Bp=n=>{let e=n.state.field(je,!1);return!e||!e.panel?!1:(n.dispatch({effects:Tf.of(!1)}),!0)},i2=n=>{let e=n.state.field(je,!1);if(!e)return!1;let t=n.state.selection.main,i=e.diagnostics.iter(t.to+1);return!i.value&&(i=e.diagnostics.iter(0),!i.value||i.from==t.from&&i.to==t.to)?!1:(n.dispatch({selection:{anchor:i.from,head:i.to},scrollIntoView:!0}),!0)},s2=[{key:"Mod-Shift-m",run:t2,preventDefault:!0},{key:"F8",run:i2}],Vn=k.define({combine(n){return{sources:n.map(e=>e.source).filter(e=>e!=null),..._t(n.map(e=>e.config),{delay:750,markerFilter:null,tooltipFilter:null,needsRefresh:null,hideOn:()=>null},{delay:Math.max,markerFilter:Xp,tooltipFilter:Xp,needsRefresh:(e,t)=>e?t?i=>e(i)||t(i):e:t,hideOn:(e,t)=>e?t?(i,s,r)=>e(i,s,r)||t(i,s,r):e:t,autoPanel:(e,t)=>e||t})}}});function Xp(n,e){return n?e?(t,i)=>e(n(t,i),i):n:e}function $1(n){let e=[];if(n)e:for(let{name:t}of n){for(let i=0;ir.toLowerCase()==s.toLowerCase())){e.push(s);continue e}}e.push("")}return e}function P1(n,e,t){var i;let s=t?$1(e.actions):[];return H("li",{class:"cm-diagnostic cm-diagnostic-"+e.severity},H("span",{class:"cm-diagnosticText"},e.renderMessage?e.renderMessage(n):e.message),(i=e.actions)===null||i===void 0?void 0:i.map((r,o)=>{let l=!1,a=d=>{if(d.preventDefault(),l)return;l=!0;let p=Ds(n.state.field(je).diagnostics,e);p&&r.apply(n,p.from,p.to)},{name:h}=r,c=s[o]?h.indexOf(s[o]):-1,f=c<0?h:[h.slice(0,c),H("u",h.slice(c,c+1)),h.slice(c+1)],u=r.markClass?" "+r.markClass:"";return H("button",{type:"button",class:"cm-diagnosticAction"+u,onclick:a,onmousedown:a,"aria-label":` Action: ${h}${c<0?"":` (access key "${s[o]})"`}.`},f)}),e.source&&H("div",{class:"cm-diagnosticSource"},e.source))}class n2 extends Yt{constructor(e){super(),this.sev=e}eq(e){return e.sev==this.sev}toDOM(){return H("span",{class:"cm-lintPoint cm-lintPoint-"+this.sev})}}class Lp{constructor(e,t){this.diagnostic=t,this.id="item_"+Math.floor(Math.random()*4294967295).toString(16),this.dom=P1(e,t,!0),this.dom.id=this.id,this.dom.setAttribute("role","option")}}class zn{constructor(e){this.view=e,this.items=[];let t=s=>{if(!(s.ctrlKey||s.altKey||s.metaKey)){if(s.keyCode==27)Bp(this.view),this.view.focus();else if(s.keyCode==38||s.keyCode==33)this.moveSelection((this.selectedIndex-1+this.items.length)%this.items.length);else if(s.keyCode==40||s.keyCode==34)this.moveSelection((this.selectedIndex+1)%this.items.length);else if(s.keyCode==36)this.moveSelection(0);else if(s.keyCode==35)this.moveSelection(this.items.length-1);else if(s.keyCode==13)this.view.focus();else if(s.keyCode>=65&&s.keyCode<=90&&this.selectedIndex>=0){let{diagnostic:r}=this.items[this.selectedIndex],o=$1(r.actions);for(let l=0;l{for(let r=0;rBp(this.view)},"×")),this.update()}get selectedIndex(){let e=this.view.state.field(je).selected;if(!e)return-1;for(let t=0;t{for(let c of h.diagnostics){if(o.has(c))continue;o.add(c);let f=-1,u;for(let d=i;di&&(this.items.splice(i,f-i),s=!0)),t&&u.diagnostic==t.diagnostic?u.dom.hasAttribute("aria-selected")||(u.dom.setAttribute("aria-selected","true"),r=u):u.dom.hasAttribute("aria-selected")&&u.dom.removeAttribute("aria-selected"),i++}});i({sel:r.dom.getBoundingClientRect(),panel:this.list.getBoundingClientRect()}),write:({sel:l,panel:a})=>{let h=a.height/this.list.offsetHeight;l.topa.bottom&&(this.list.scrollTop+=(l.bottom-a.bottom)/h)}})):this.selectedIndex<0&&this.list.removeAttribute("aria-activedescendant"),s&&this.sync()}sync(){let e=this.list.firstChild;function t(){let i=e;e=i.nextSibling,i.remove()}for(let i of this.items)if(i.dom.parentNode==this.list){for(;e!=i.dom;)t();e=i.dom.nextSibling}else this.list.insertBefore(i.dom,e);for(;e;)t()}moveSelection(e){if(this.selectedIndex<0)return;let t=this.view.state.field(je),i=Ds(t.diagnostics,this.items[e].diagnostic);i&&this.view.dispatch({selection:{anchor:i.from,head:i.to},scrollIntoView:!0,effects:v1.of(i)})}static open(e){return new zn(e)}}function r2(n,e='viewBox="0 0 40 40"'){return`url('data:image/svg+xml,${encodeURIComponent(n)}')`}function Vr(n){return r2(``,'width="6" height="3"')}const o2=D.baseTheme({".cm-diagnostic":{padding:"3px 6px 3px 8px",marginLeft:"-1px",display:"block",whiteSpace:"pre-wrap"},".cm-diagnostic-error":{borderLeft:"5px solid #d11"},".cm-diagnostic-warning":{borderLeft:"5px solid orange"},".cm-diagnostic-info":{borderLeft:"5px solid #999"},".cm-diagnostic-hint":{borderLeft:"5px solid #66d"},".cm-diagnosticAction":{font:"inherit",border:"none",padding:"2px 4px",backgroundColor:"#444",color:"white",borderRadius:"3px",marginLeft:"8px",cursor:"pointer"},".cm-diagnosticSource":{fontSize:"70%",opacity:.7},".cm-lintRange":{backgroundPosition:"left bottom",backgroundRepeat:"repeat-x",paddingBottom:"0.7px"},".cm-lintRange-error":{backgroundImage:Vr("#d11")},".cm-lintRange-warning":{backgroundImage:Vr("orange")},".cm-lintRange-info":{backgroundImage:Vr("#999")},".cm-lintRange-hint":{backgroundImage:Vr("#66d")},".cm-lintRange-active":{backgroundColor:"#ffdd9980"},".cm-tooltip-lint":{padding:0,margin:0},".cm-lintPoint":{position:"relative","&:after":{content:'""',position:"absolute",bottom:0,left:"-2px",borderLeft:"3px solid transparent",borderRight:"3px solid transparent",borderBottom:"4px solid #d11"}},".cm-lintPoint-warning":{"&:after":{borderBottomColor:"orange"}},".cm-lintPoint-info":{"&:after":{borderBottomColor:"#999"}},".cm-lintPoint-hint":{"&:after":{borderBottomColor:"#66d"}},".cm-panel.cm-panel-lint":{position:"relative","& ul":{maxHeight:"100px",overflowY:"auto","& [aria-selected]":{backgroundColor:"#ddd","& u":{textDecoration:"underline"}},"&:focus [aria-selected]":{background_fallback:"#bdf",backgroundColor:"Highlight",color_fallback:"white",color:"HighlightText"},"& u":{textDecoration:"none"},padding:0,margin:0},"& [name=close]":{position:"absolute",top:"0",right:"2px",background:"inherit",border:"none",font:"inherit",padding:0,margin:0}}});function l2(n){return n=="error"?4:n=="warning"?3:n=="info"?2:1}function a2(n){let e="hint",t=1;for(let i of n){let s=l2(i.severity);s>t&&(t=s,e=i.severity)}return e}const h2=[je,D.decorations.compute([je],n=>{let{selected:e,panel:t}=n.field(je);return!e||!t||e.from==e.to?E.none:E.set([KT.range(e.from,e.to)])}),cw(JT,{hideOn:FT}),o2],c2=[ww(),vw(),zx(),Mv(),Sk(),Mx(),Bx(),L.allowMultipleSelections.of(!0),ok(),SO(kk,{fallback:!0}),Mk(),LT(),GT(),Jx(),iw(),jx(),TC(),_n.of([...zT,...R$,...FC,...Wv,...gk,...k1,...s2])];var Ep={};class Fo{constructor(e,t,i,s,r,o,l,a,h,c=0,f){this.p=e,this.stack=t,this.state=i,this.reducePos=s,this.pos=r,this.score=o,this.buffer=l,this.bufferBase=a,this.curContext=h,this.lookAhead=c,this.parent=f}toString(){return`[${this.stack.filter((e,t)=>t%3==0).concat(this.state)}]@${this.pos}${this.score?"!"+this.score:""}`}static start(e,t,i=0){let s=e.parser.context;return new Fo(e,[],t,i,i,0,[],0,s?new Wp(s,s.start):null,0,null)}get context(){return this.curContext?this.curContext.context:null}pushState(e,t){this.stack.push(this.state,t,this.bufferBase+this.buffer.length),this.state=e}reduce(e){var t;let i=e>>19,s=e&65535,{parser:r}=this.p,o=this.reducePos=2e3&&!(!((t=this.p.parser.nodeSet.types[s])===null||t===void 0)&&t.isAnonymous)&&(h==this.p.lastBigReductionStart?(this.p.bigReductionCount++,this.p.lastBigReductionSize=c):this.p.lastBigReductionSizea;)this.stack.pop();this.reduceContext(s,h)}storeNode(e,t,i,s=4,r=!1){if(e==0&&(!this.stack.length||this.stack[this.stack.length-1]0&&o.buffer[l-4]==0&&o.buffer[l-1]>-1){if(t==i)return;if(o.buffer[l-2]>=t){o.buffer[l-2]=i;return}}}if(!r||this.pos==i)this.buffer.push(e,t,i,s);else{let o=this.buffer.length;if(o>0&&(this.buffer[o-4]!=0||this.buffer[o-1]<0)){let l=!1;for(let a=o;a>0&&this.buffer[a-2]>i;a-=4)if(this.buffer[a-1]>=0){l=!0;break}if(l)for(;o>0&&this.buffer[o-2]>i;)this.buffer[o]=this.buffer[o-4],this.buffer[o+1]=this.buffer[o-3],this.buffer[o+2]=this.buffer[o-2],this.buffer[o+3]=this.buffer[o-1],o-=4,s>4&&(s-=4)}this.buffer[o]=e,this.buffer[o+1]=t,this.buffer[o+2]=i,this.buffer[o+3]=s}}shift(e,t,i,s){if(e&131072)this.pushState(e&65535,this.pos);else if((e&262144)==0){let r=e,{parser:o}=this.p;this.pos=s;let l=o.stateFlag(r,1);!l&&(s>i||t<=o.maxNode)&&(this.reducePos=s),this.pushState(r,l?i:Math.min(i,this.reducePos)),this.shiftContext(t,i),t<=o.maxNode&&this.buffer.push(t,i,s,4)}else this.pos=s,this.shiftContext(t,i),t<=this.p.parser.maxNode&&this.buffer.push(t,i,s,4)}apply(e,t,i,s){e&65536?this.reduce(e):this.shift(e,t,i,s)}useNode(e,t){let i=this.p.reused.length-1;(i<0||this.p.reused[i]!=e)&&(this.p.reused.push(e),i++);let s=this.pos;this.reducePos=this.pos=s+e.length,this.pushState(t,s),this.buffer.push(i,s,this.reducePos,-1),this.curContext&&this.updateContext(this.curContext.tracker.reuse(this.curContext.context,e,this,this.p.stream.reset(this.pos-e.length)))}split(){let e=this,t=e.buffer.length;for(;t>0&&e.buffer[t-2]>e.reducePos;)t-=4;let i=e.buffer.slice(t),s=e.bufferBase+t;for(;e&&s==e.bufferBase;)e=e.parent;return new Fo(this.p,this.stack.slice(),this.state,this.reducePos,this.pos,this.score,i,s,this.curContext,this.lookAhead,e)}recoverByDelete(e,t){let i=e<=this.p.parser.maxNode;i&&this.storeNode(e,this.pos,t,4),this.storeNode(0,this.pos,t,i?8:4),this.pos=this.reducePos=t,this.score-=190}canShift(e){for(let t=new f2(this);;){let i=this.p.parser.stateSlot(t.state,4)||this.p.parser.hasAction(t.state,e);if(i==0)return!1;if((i&65536)==0)return!0;t.reduce(i)}}recoverByInsert(e){if(this.stack.length>=300)return[];let t=this.p.parser.nextStates(this.state);if(t.length>8||this.stack.length>=120){let s=[];for(let r=0,o;ra&1&&l==o)||s.push(t[r],o)}t=s}let i=[];for(let s=0;s>19,s=t&65535,r=this.stack.length-i*3;if(r<0||e.getGoto(this.stack[r],s,!1)<0){let o=this.findForcedReduction();if(o==null)return!1;t=o}this.storeNode(0,this.pos,this.pos,4,!0),this.score-=100}return this.reducePos=this.pos,this.reduce(t),!0}findForcedReduction(){let{parser:e}=this.p,t=[],i=(s,r)=>{if(!t.includes(s))return t.push(s),e.allActions(s,o=>{if(!(o&393216))if(o&65536){let l=(o>>19)-r;if(l>1){let a=o&65535,h=this.stack.length-l*3;if(h>=0&&e.getGoto(this.stack[h],a,!1)>=0)return l<<19|65536|a}}else{let l=i(o,r+1);if(l!=null)return l}})};return i(this.state,0)}forceAll(){for(;!this.p.parser.stateFlag(this.state,2);)if(!this.forceReduce()){this.storeNode(0,this.pos,this.pos,4,!0);break}return this}get deadEnd(){if(this.stack.length!=3)return!1;let{parser:e}=this.p;return e.data[e.stateSlot(this.state,1)]==65535&&!e.stateSlot(this.state,4)}restart(){this.storeNode(0,this.pos,this.pos,4,!0),this.state=this.stack[0],this.stack.length=0}sameState(e){if(this.state!=e.state||this.stack.length!=e.stack.length)return!1;for(let t=0;t0&&this.emitLookAhead()}}class Wp{constructor(e,t){this.tracker=e,this.context=t,this.hash=e.strict?e.hash(t):0}}class f2{constructor(e){this.start=e,this.state=e.state,this.stack=e.stack,this.base=this.stack.length}reduce(e){let t=e&65535,i=e>>19;i==0?(this.stack==this.start.stack&&(this.stack=this.stack.slice()),this.stack.push(this.state,0,0),this.base+=3):this.base-=(i-1)*3;let s=this.start.p.parser.getGoto(this.stack[this.base-3],t,!0);this.state=s}}class Uo{constructor(e,t,i){this.stack=e,this.pos=t,this.index=i,this.buffer=e.buffer,this.index==0&&this.maybeNext()}static create(e,t=e.bufferBase+e.buffer.length){return new Uo(e,t,t-e.bufferBase)}maybeNext(){let e=this.stack.parent;e!=null&&(this.index=this.stack.bufferBase-e.bufferBase,this.stack=e,this.buffer=e.buffer)}get id(){return this.buffer[this.index-4]}get start(){return this.buffer[this.index-3]}get end(){return this.buffer[this.index-2]}get size(){return this.buffer[this.index-1]}next(){this.index-=4,this.pos-=4,this.index==0&&this.maybeNext()}fork(){return new Uo(this.stack,this.pos,this.index)}}function an(n,e=Uint16Array){if(typeof n!="string")return n;let t=null;for(let i=0,s=0;i=92&&o--,o>=34&&o--;let a=o-32;if(a>=46&&(a-=46,l=!0),r+=a,l)break;r*=46}t?t[s++]=r:t=new e(r)}return t}class io{constructor(){this.start=-1,this.value=-1,this.end=-1,this.extended=-1,this.lookAhead=0,this.mask=0,this.context=0}}const Vp=new io;class u2{constructor(e,t){this.input=e,this.ranges=t,this.chunk="",this.chunkOff=0,this.chunk2="",this.chunk2Pos=0,this.next=-1,this.token=Vp,this.rangeIndex=0,this.pos=this.chunkPos=t[0].from,this.range=t[0],this.end=t[t.length-1].to,this.readNext()}resolveOffset(e,t){let i=this.range,s=this.rangeIndex,r=this.pos+e;for(;ri.to:r>=i.to;){if(s==this.ranges.length-1)return null;let o=this.ranges[++s];r+=o.from-i.to,i=o}return r}clipPos(e){if(e>=this.range.from&&ee)return Math.max(e,t.from);return this.end}peek(e){let t=this.chunkOff+e,i,s;if(t>=0&&t=this.chunk2Pos&&il.to&&(this.chunk2=this.chunk2.slice(0,l.to-i)),s=this.chunk2.charCodeAt(0)}}return i>=this.token.lookAhead&&(this.token.lookAhead=i+1),s}acceptToken(e,t=0){let i=t?this.resolveOffset(t,-1):this.pos;if(i==null||i=this.chunk2Pos&&this.posthis.range.to?e.slice(0,this.range.to-this.pos):e,this.chunkPos=this.pos,this.chunkOff=0}}readNext(){return this.chunkOff>=this.chunk.length&&(this.getChunk(),this.chunkOff==this.chunk.length)?this.next=-1:this.next=this.chunk.charCodeAt(this.chunkOff)}advance(e=1){for(this.chunkOff+=e;this.pos+e>=this.range.to;){if(this.rangeIndex==this.ranges.length-1)return this.setDone();e-=this.range.to-this.pos,this.range=this.ranges[++this.rangeIndex],this.pos=this.range.from}return this.pos+=e,this.pos>=this.token.lookAhead&&(this.token.lookAhead=this.pos+1),this.readNext()}setDone(){return this.pos=this.chunkPos=this.end,this.range=this.ranges[this.rangeIndex=this.ranges.length-1],this.chunk="",this.next=-1}reset(e,t){if(t?(this.token=t,t.start=e,t.lookAhead=e+1,t.value=t.extended=-1):this.token=Vp,this.pos!=e){if(this.pos=e,e==this.end)return this.setDone(),this;for(;e=this.range.to;)this.range=this.ranges[++this.rangeIndex];e>=this.chunkPos&&e=this.chunkPos&&t<=this.chunkPos+this.chunk.length)return this.chunk.slice(e-this.chunkPos,t-this.chunkPos);if(e>=this.chunk2Pos&&t<=this.chunk2Pos+this.chunk2.length)return this.chunk2.slice(e-this.chunk2Pos,t-this.chunk2Pos);if(e>=this.range.from&&t<=this.range.to)return this.input.read(e,t);let i="";for(let s of this.ranges){if(s.from>=t)break;s.to>e&&(i+=this.input.read(Math.max(s.from,e),Math.min(s.to,t)))}return i}}class ms{constructor(e,t){this.data=e,this.id=t}token(e,t){let{parser:i}=t.p;C1(this.data,e,t,this.id,i.data,i.tokenPrecTable)}}ms.prototype.contextual=ms.prototype.fallback=ms.prototype.extend=!1;class Ko{constructor(e,t,i){this.precTable=t,this.elseToken=i,this.data=typeof e=="string"?an(e):e}token(e,t){let i=e.pos,s=0;for(;;){let r=e.next<0,o=e.resolveOffset(1,1);if(C1(this.data,e,t,0,this.data,this.precTable),e.token.value>-1)break;if(this.elseToken==null)return;if(r||s++,o==null)break;e.reset(o,e.token)}s&&(e.reset(i,e.token),e.acceptToken(this.elseToken,s))}}Ko.prototype.contextual=ms.prototype.fallback=ms.prototype.extend=!1;class Ge{constructor(e,t={}){this.token=e,this.contextual=!!t.contextual,this.fallback=!!t.fallback,this.extend=!!t.extend}}function C1(n,e,t,i,s,r){let o=0,l=1<0){let p=n[d];if(a.allows(p)&&(e.token.value==-1||e.token.value==p||d2(p,e.token.value,s,r))){e.acceptToken(p);break}}let c=e.next,f=0,u=n[o+2];if(e.next<0&&u>f&&n[h+u*3-3]==65535){o=n[h+u*3-1];continue e}for(;f>1,p=h+d+(d<<1),g=n[p],m=n[p+1]||65536;if(c=m)f=d+1;else{o=n[p+2],e.advance();continue e}}break}}function zp(n,e,t){for(let i=e,s;(s=n[i])!=65535;i++)if(s==t)return i-e;return-1}function d2(n,e,t,i){let s=zp(t,i,e);return s<0||zp(t,i,n)e)&&!i.type.isError)return t<0?Math.max(0,Math.min(i.to-1,e-25)):Math.min(n.length,Math.max(i.from+1,e+25));if(t<0?i.prevSibling():i.nextSibling())break;if(!i.parent())return t<0?0:n.length}}class p2{constructor(e,t){this.fragments=e,this.nodeSet=t,this.i=0,this.fragment=null,this.safeFrom=-1,this.safeTo=-1,this.trees=[],this.start=[],this.index=[],this.nextFragment()}nextFragment(){let e=this.fragment=this.i==this.fragments.length?null:this.fragments[this.i++];if(e){for(this.safeFrom=e.openStart?qp(e.tree,e.from+e.offset,1)-e.offset:e.from,this.safeTo=e.openEnd?qp(e.tree,e.to+e.offset,-1)-e.offset:e.to;this.trees.length;)this.trees.pop(),this.start.pop(),this.index.pop();this.trees.push(e.tree),this.start.push(-e.offset),this.index.push(0),this.nextStart=this.safeFrom}else this.nextStart=1e9}nodeAt(e){if(ee)return this.nextStart=o,null;if(r instanceof ae){if(o==e){if(o=Math.max(this.safeFrom,e)&&(this.trees.push(r),this.start.push(o),this.index.push(0))}else this.index[t]++,this.nextStart=o+r.length}}}class g2{constructor(e,t){this.stream=t,this.tokens=[],this.mainToken=null,this.actions=[],this.tokens=e.tokenizers.map(i=>new io)}getActions(e){let t=0,i=null,{parser:s}=e.p,{tokenizers:r}=s,o=s.stateSlot(e.state,3),l=e.curContext?e.curContext.hash:0,a=0;for(let h=0;hf.end+25&&(a=Math.max(f.lookAhead,a)),f.value!=0)){let u=t;if(f.extended>-1&&(t=this.addActions(e,f.extended,f.end,t)),t=this.addActions(e,f.value,f.end,t),!c.extend&&(i=f,t>u))break}}for(;this.actions.length>t;)this.actions.pop();return a&&e.setLookAhead(a),!i&&e.pos==this.stream.end&&(i=new io,i.value=e.p.parser.eofTerm,i.start=i.end=e.pos,t=this.addActions(e,i.value,i.end,t)),this.mainToken=i,this.actions}getMainToken(e){if(this.mainToken)return this.mainToken;let t=new io,{pos:i,p:s}=e;return t.start=i,t.end=Math.min(i+1,s.stream.end),t.value=i==s.stream.end?s.parser.eofTerm:0,t}updateCachedToken(e,t,i){let s=this.stream.clipPos(i.pos);if(t.token(this.stream.reset(s,e),i),e.value>-1){let{parser:r}=i.p;for(let o=0;o=0&&i.p.parser.dialect.allows(l>>1)){(l&1)==0?e.value=l>>1:e.extended=l>>1;break}}}else e.value=0,e.end=this.stream.clipPos(s+1)}putAction(e,t,i,s){for(let r=0;re.bufferLength*4?new p2(i,e.nodeSet):null}get parsedPos(){return this.minStackPos}advance(){let e=this.stacks,t=this.minStackPos,i=this.stacks=[],s,r;if(this.bigReductionCount>300&&e.length==1){let[o]=e;for(;o.forceReduce()&&o.stack.length&&o.stack[o.stack.length-2]>=this.lastBigReductionStart;);this.bigReductionCount=this.lastBigReductionSize=0}for(let o=0;ot)i.push(l);else{if(this.advanceStack(l,i,e))continue;{s||(s=[],r=[]),s.push(l);let a=this.tokens.getMainToken(l);r.push(a.value,a.end)}}break}}if(!i.length){let o=s&&b2(s);if(o)return Ie&&console.log("Finish with "+this.stackID(o)),this.stackToTree(o);if(this.parser.strict)throw Ie&&s&&console.log("Stuck with token "+(this.tokens.mainToken?this.parser.getName(this.tokens.mainToken.value):"none")),new SyntaxError("No parse at "+t);this.recovering||(this.recovering=5)}if(this.recovering&&s){let o=this.stoppedAt!=null&&s[0].pos>this.stoppedAt?s[0]:this.runRecovery(s,r,i);if(o)return Ie&&console.log("Force-finish "+this.stackID(o)),this.stackToTree(o.forceAll())}if(this.recovering){let o=this.recovering==1?1:this.recovering*3;if(i.length>o)for(i.sort((l,a)=>a.score-l.score);i.length>o;)i.pop();i.some(l=>l.reducePos>t)&&this.recovering--}else if(i.length>1){e:for(let o=0;o500&&h.buffer.length>500)if((l.score-h.score||l.buffer.length-h.buffer.length)>0)i.splice(a--,1);else{i.splice(o--,1);continue e}}}i.length>12&&(i.sort((o,l)=>l.score-o.score),i.splice(12,i.length-12))}this.minStackPos=i[0].pos;for(let o=1;o ":"";if(this.stoppedAt!=null&&s>this.stoppedAt)return e.forceReduce()?e:null;if(this.fragments){let h=e.curContext&&e.curContext.tracker.strict,c=h?e.curContext.hash:0;for(let f=this.fragments.nodeAt(s);f;){let u=this.parser.nodeSet.types[f.type.id]==f.type?r.getGoto(e.state,f.type.id):-1;if(u>-1&&f.length&&(!h||(f.prop(V.contextHash)||0)==c))return e.useNode(f,u),Ie&&console.log(o+this.stackID(e)+` (via reuse of ${r.getName(f.type.id)})`),!0;if(!(f instanceof ae)||f.children.length==0||f.positions[0]>0)break;let d=f.children[0];if(d instanceof ae&&f.positions[0]==0)f=d;else break}}let l=r.stateSlot(e.state,4);if(l>0)return e.reduce(l),Ie&&console.log(o+this.stackID(e)+` (via always-reduce ${r.getName(l&65535)})`),!0;if(e.stack.length>=8400)for(;e.stack.length>6e3&&e.forceReduce(););let a=this.tokens.getActions(e);for(let h=0;hs?t.push(p):i.push(p)}return!1}advanceFully(e,t){let i=e.pos;for(;;){if(!this.advanceStack(e,null,null))return!1;if(e.pos>i)return Ip(e,t),!0}}runRecovery(e,t,i){let s=null,r=!1;for(let o=0;o ":"";if(l.deadEnd&&(r||(r=!0,l.restart(),Ie&&console.log(c+this.stackID(l)+" (restarted)"),this.advanceFully(l,i))))continue;let f=l.split(),u=c;for(let d=0;d<10&&f.forceReduce()&&(Ie&&console.log(u+this.stackID(f)+" (via force-reduce)"),!this.advanceFully(f,i));d++)Ie&&(u=this.stackID(f)+" -> ");for(let d of l.recoverByInsert(a))Ie&&console.log(c+this.stackID(d)+" (via recover-insert)"),this.advanceFully(d,i);this.stream.end>l.pos?(h==l.pos&&(h++,a=0),l.recoverByDelete(a,h),Ie&&console.log(c+this.stackID(l)+` (via recover-delete ${this.parser.getName(a)})`),Ip(l,i)):(!s||s.scoren;class T1{constructor(e){this.start=e.start,this.shift=e.shift||Qa,this.reduce=e.reduce||Qa,this.reuse=e.reuse||Qa,this.hash=e.hash||(()=>0),this.strict=e.strict!==!1}}class Zs extends sO{constructor(e){if(super(),this.wrappers=[],e.version!=14)throw new RangeError(`Parser version (${e.version}) doesn't match runtime version (14)`);let t=e.nodeNames.split(" ");this.minRepeatTerm=t.length;for(let l=0;le.topRules[l][1]),s=[];for(let l=0;l=0)r(c,a,l[h++]);else{let f=l[h+-c];for(let u=-c;u>0;u--)r(l[h++],a,f);h++}}}this.nodeSet=new Vc(t.map((l,a)=>De.define({name:a>=this.minRepeatTerm?void 0:l,id:a,props:s[a],top:i.indexOf(a)>-1,error:a==0,skipped:e.skippedNodes&&e.skippedNodes.indexOf(a)>-1}))),e.propSources&&(this.nodeSet=this.nodeSet.extend(...e.propSources)),this.strict=!1,this.bufferLength=Km;let o=an(e.tokenData);this.context=e.context,this.specializerSpecs=e.specialized||[],this.specialized=new Uint16Array(this.specializerSpecs.length);for(let l=0;ltypeof l=="number"?new ms(o,l):l),this.topRules=e.topRules,this.dialects=e.dialects||{},this.dynamicPrecedences=e.dynamicPrecedences||null,this.tokenPrecTable=e.tokenPrec,this.termNames=e.termNames||null,this.maxNode=this.nodeSet.types.length-1,this.dialect=this.parseDialect(),this.top=this.topRules[Object.keys(this.topRules)[0]]}createParse(e,t,i){let s=new m2(this,e,t,i);for(let r of this.wrappers)s=r(s,e,t,i);return s}getGoto(e,t,i=!1){let s=this.goto;if(t>=s[0])return-1;for(let r=s[t+1];;){let o=s[r++],l=o&1,a=s[r++];if(l&&i)return a;for(let h=r+(o>>1);r0}validAction(e,t){return!!this.allActions(e,i=>i==t?!0:null)}allActions(e,t){let i=this.stateSlot(e,4),s=i?t(i):void 0;for(let r=this.stateSlot(e,1);s==null;r+=3){if(this.data[r]==65535)if(this.data[r+1]==1)r=Gt(this.data,r+2);else break;s=t(Gt(this.data,r+1))}return s}nextStates(e){let t=[];for(let i=this.stateSlot(e,1);;i+=3){if(this.data[i]==65535)if(this.data[i+1]==1)i=Gt(this.data,i+2);else break;if((this.data[i+2]&1)==0){let s=this.data[i+1];t.some((r,o)=>o&1&&r==s)||t.push(this.data[i],s)}}return t}configure(e){let t=Object.assign(Object.create(Zs.prototype),this);if(e.props&&(t.nodeSet=this.nodeSet.extend(...e.props)),e.top){let i=this.topRules[e.top];if(!i)throw new RangeError(`Invalid top rule name ${e.top}`);t.top=i}return e.tokenizers&&(t.tokenizers=this.tokenizers.map(i=>{let s=e.tokenizers.find(r=>r.from==i);return s?s.to:i})),e.specializers&&(t.specializers=this.specializers.slice(),t.specializerSpecs=this.specializerSpecs.map((i,s)=>{let r=e.specializers.find(l=>l.from==i.external);if(!r)return i;let o=Object.assign(Object.assign({},i),{external:r.to});return t.specializers[s]=_p(o),o})),e.contextTracker&&(t.context=e.contextTracker),e.dialect&&(t.dialect=this.parseDialect(e.dialect)),e.strict!=null&&(t.strict=e.strict),e.wrap&&(t.wrappers=t.wrappers.concat(e.wrap)),e.bufferLength!=null&&(t.bufferLength=e.bufferLength),t}hasWrappers(){return this.wrappers.length>0}getName(e){return this.termNames?this.termNames[e]:String(e<=this.maxNode&&this.nodeSet.types[e].name||e)}get eofTerm(){return this.maxNode+1}get topNode(){return this.nodeSet.types[this.top[1]]}dynamicPrecedence(e){let t=this.dynamicPrecedences;return t==null?0:t[e]||0}parseDialect(e){let t=Object.keys(this.dialects),i=t.map(()=>!1);if(e)for(let r of e.split(" ")){let o=t.indexOf(r);o>=0&&(i[o]=!0)}let s=null;for(let r=0;ri)&&t.p.parser.stateFlag(t.state,2)&&(!e||e.scoren.external(t,i)<<1|e}return n.get}const S2=55,y2=1,x2=56,w2=2,k2=57,Q2=3,Yp=4,v2=5,Mf=6,M1=7,A1=8,R1=9,D1=10,$2=11,P2=12,C2=13,va=58,T2=14,M2=15,Np=59,Z1=21,A2=23,B1=24,R2=25,Oc=27,X1=28,D2=29,Z2=32,B2=35,X2=37,L2=38,E2=0,W2=1,V2={area:!0,base:!0,br:!0,col:!0,command:!0,embed:!0,frame:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0,menuitem:!0},z2={dd:!0,li:!0,optgroup:!0,option:!0,p:!0,rp:!0,rt:!0,tbody:!0,td:!0,tfoot:!0,th:!0,tr:!0},jp={dd:{dd:!0,dt:!0},dt:{dd:!0,dt:!0},li:{li:!0},option:{option:!0,optgroup:!0},optgroup:{optgroup:!0},p:{address:!0,article:!0,aside:!0,blockquote:!0,dir:!0,div:!0,dl:!0,fieldset:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,menu:!0,nav:!0,ol:!0,p:!0,pre:!0,section:!0,table:!0,ul:!0},rp:{rp:!0,rt:!0},rt:{rp:!0,rt:!0},tbody:{tbody:!0,tfoot:!0},td:{td:!0,th:!0},tfoot:{tbody:!0},th:{td:!0,th:!0},thead:{tbody:!0,tfoot:!0},tr:{tr:!0}};function q2(n){return n==45||n==46||n==58||n>=65&&n<=90||n==95||n>=97&&n<=122||n>=161}let Gp=null,Hp=null,Fp=0;function bc(n,e){let t=n.pos+e;if(Fp==t&&Hp==n)return Gp;let i=n.peek(e),s="";for(;q2(i);)s+=String.fromCharCode(i),i=n.peek(++e);return Hp=n,Fp=t,Gp=s?s.toLowerCase():i==I2||i==_2?void 0:null}const L1=60,Jo=62,Af=47,I2=63,_2=33,Y2=45;function Up(n,e){this.name=n,this.parent=e}const N2=[Mf,D1,M1,A1,R1],j2=new T1({start:null,shift(n,e,t,i){return N2.indexOf(e)>-1?new Up(bc(i,1)||"",n):n},reduce(n,e){return e==Z1&&n?n.parent:n},reuse(n,e,t,i){let s=e.type.id;return s==Mf||s==X2?new Up(bc(i,1)||"",n):n},strict:!1}),G2=new Ge((n,e)=>{if(n.next!=L1){n.next<0&&e.context&&n.acceptToken(va);return}n.advance();let t=n.next==Af;t&&n.advance();let i=bc(n,0);if(i===void 0)return;if(!i)return n.acceptToken(t?M2:T2);let s=e.context?e.context.name:null;if(t){if(i==s)return n.acceptToken($2);if(s&&z2[s])return n.acceptToken(va,-2);if(e.dialectEnabled(E2))return n.acceptToken(P2);for(let r=e.context;r;r=r.parent)if(r.name==i)return;n.acceptToken(C2)}else{if(i=="script")return n.acceptToken(M1);if(i=="style")return n.acceptToken(A1);if(i=="textarea")return n.acceptToken(R1);if(V2.hasOwnProperty(i))return n.acceptToken(D1);s&&jp[s]&&jp[s][i]?n.acceptToken(va,-1):n.acceptToken(Mf)}},{contextual:!0}),H2=new Ge(n=>{for(let e=0,t=0;;t++){if(n.next<0){t&&n.acceptToken(Np);break}if(n.next==Y2)e++;else if(n.next==Jo&&e>=2){t>=3&&n.acceptToken(Np,-2);break}else e=0;n.advance()}});function F2(n){for(;n;n=n.parent)if(n.name=="svg"||n.name=="math")return!0;return!1}const U2=new Ge((n,e)=>{if(n.next==Af&&n.peek(1)==Jo){let t=e.dialectEnabled(W2)||F2(e.context);n.acceptToken(t?v2:Yp,2)}else n.next==Jo&&n.acceptToken(Yp,1)});function Rf(n,e,t){let i=2+n.length;return new Ge(s=>{for(let r=0,o=0,l=0;;l++){if(s.next<0){l&&s.acceptToken(e);break}if(r==0&&s.next==L1||r==1&&s.next==Af||r>=2&&ro?s.acceptToken(e,-o):s.acceptToken(t,-(o-2));break}else if((s.next==10||s.next==13)&&l){s.acceptToken(e,1);break}else r=o=0;s.advance()}})}const K2=Rf("script",S2,y2),J2=Rf("style",x2,w2),eM=Rf("textarea",k2,Q2),tM=hl({"Text RawText IncompleteTag IncompleteCloseTag":b.content,"StartTag StartCloseTag SelfClosingEndTag EndTag":b.angleBracket,TagName:b.tagName,"MismatchedCloseTag/TagName":[b.tagName,b.invalid],AttributeName:b.attributeName,"AttributeValue UnquotedAttributeValue":b.attributeValue,Is:b.definitionOperator,"EntityReference CharacterReference":b.character,Comment:b.blockComment,ProcessingInst:b.processingInstruction,DoctypeDecl:b.documentMeta}),iM=Zs.deserialize({version:14,states:",xOVO!rOOO!ZQ#tO'#CrO!`Q#tO'#C{O!eQ#tO'#DOO!jQ#tO'#DRO!oQ#tO'#DTO!tOaO'#CqO#PObO'#CqO#[OdO'#CqO$kO!rO'#CqOOO`'#Cq'#CqO$rO$fO'#DUO$zQ#tO'#DWO%PQ#tO'#DXOOO`'#Dl'#DlOOO`'#DZ'#DZQVO!rOOO%UQ&rO,59^O%aQ&rO,59gO%lQ&rO,59jO%wQ&rO,59mO&SQ&rO,59oOOOa'#D_'#D_O&_OaO'#CyO&jOaO,59]OOOb'#D`'#D`O&rObO'#C|O&}ObO,59]OOOd'#Da'#DaO'VOdO'#DPO'bOdO,59]OOO`'#Db'#DbO'jO!rO,59]O'qQ#tO'#DSOOO`,59],59]OOOp'#Dc'#DcO'vO$fO,59pOOO`,59p,59pO(OQ#|O,59rO(TQ#|O,59sOOO`-E7X-E7XO(YQ&rO'#CtOOQW'#D['#D[O(hQ&rO1G.xOOOa1G.x1G.xOOO`1G/Z1G/ZO(sQ&rO1G/ROOOb1G/R1G/RO)OQ&rO1G/UOOOd1G/U1G/UO)ZQ&rO1G/XOOO`1G/X1G/XO)fQ&rO1G/ZOOOa-E7]-E7]O)qQ#tO'#CzOOO`1G.w1G.wOOOb-E7^-E7^O)vQ#tO'#C}OOOd-E7_-E7_O){Q#tO'#DQOOO`-E7`-E7`O*QQ#|O,59nOOOp-E7a-E7aOOO`1G/[1G/[OOO`1G/^1G/^OOO`1G/_1G/_O*VQ,UO,59`OOQW-E7Y-E7YOOOa7+$d7+$dOOO`7+$u7+$uOOOb7+$m7+$mOOOd7+$p7+$pOOO`7+$s7+$sO*bQ#|O,59fO*gQ#|O,59iO*lQ#|O,59lOOO`1G/Y1G/YO*qO7[O'#CwO+SOMhO'#CwOOQW1G.z1G.zOOO`1G/Q1G/QOOO`1G/T1G/TOOO`1G/W1G/WOOOO'#D]'#D]O+eO7[O,59cOOQW,59c,59cOOOO'#D^'#D^O+vOMhO,59cOOOO-E7Z-E7ZOOQW1G.}1G.}OOOO-E7[-E7[",stateData:",c~O!_OS~OUSOVPOWQOXROYTO[]O][O^^O_^Oa^Ob^Oc^Od^Oy^O|_O!eZO~OgaO~OgbO~OgcO~OgdO~OgeO~O!XfOPmP![mP~O!YiOQpP![pP~O!ZlORsP![sP~OUSOVPOWQOXROYTOZqO[]O][O^^O_^Oa^Ob^Oc^Od^Oy^O!eZO~O![rO~P#gO!]sO!fuO~OgvO~OgwO~OS|OT}OiyO~OS!POT}OiyO~OS!ROT}OiyO~OS!TOT}OiyO~OS}OT}OiyO~O!XfOPmX![mX~OP!WO![!XO~O!YiOQpX![pX~OQ!ZO![!XO~O!ZlORsX![sX~OR!]O![!XO~O![!XO~P#gOg!_O~O!]sO!f!aO~OS!bO~OS!cO~Oj!dOShXThXihX~OS!fOT!gOiyO~OS!hOT!gOiyO~OS!iOT!gOiyO~OS!jOT!gOiyO~OS!gOT!gOiyO~Og!kO~Og!lO~Og!mO~OS!nO~Ol!qO!a!oO!c!pO~OS!rO~OS!sO~OS!tO~Ob!uOc!uOd!uO!a!wO!b!uO~Ob!xOc!xOd!xO!c!wO!d!xO~Ob!uOc!uOd!uO!a!{O!b!uO~Ob!xOc!xOd!xO!c!{O!d!xO~OT~cbd!ey|!e~",goto:"%q!aPPPPPPPPPPPPPPPPPPPPP!b!hP!nPP!zP!}#Q#T#Z#^#a#g#j#m#s#y!bP!b!bP$P$V$m$s$y%P%V%]%cPPPPPPPP%iX^OX`pXUOX`pezabcde{!O!Q!S!UR!q!dRhUR!XhXVOX`pRkVR!XkXWOX`pRnWR!XnXXOX`pQrXR!XpXYOX`pQ`ORx`Q{aQ!ObQ!QcQ!SdQ!UeZ!e{!O!Q!S!UQ!v!oR!z!vQ!y!pR!|!yQgUR!VgQjVR!YjQmWR![mQpXR!^pQtZR!`tS_O`ToXp",nodeNames:"⚠ StartCloseTag StartCloseTag StartCloseTag EndTag SelfClosingEndTag StartTag StartTag StartTag StartTag StartTag StartCloseTag StartCloseTag StartCloseTag IncompleteTag IncompleteCloseTag Document Text EntityReference CharacterReference InvalidEntity Element OpenTag TagName Attribute AttributeName Is AttributeValue UnquotedAttributeValue ScriptText CloseTag OpenTag StyleText CloseTag OpenTag TextareaText CloseTag OpenTag CloseTag SelfClosingTag Comment ProcessingInst MismatchedCloseTag CloseTag DoctypeDecl",maxTerm:68,context:j2,nodeProps:[["closedBy",-10,1,2,3,7,8,9,10,11,12,13,"EndTag",6,"EndTag SelfClosingEndTag",-4,22,31,34,37,"CloseTag"],["openedBy",4,"StartTag StartCloseTag",5,"StartTag",-4,30,33,36,38,"OpenTag"],["group",-10,14,15,18,19,20,21,40,41,42,43,"Entity",17,"Entity TextContent",-3,29,32,35,"TextContent Entity"],["isolate",-11,22,30,31,33,34,36,37,38,39,42,43,"ltr",-3,27,28,40,""]],propSources:[tM],skippedNodes:[0],repeatNodeCount:9,tokenData:"!]tw8twx7Sx!P8t!P!Q5u!Q!]8t!]!^/^!^!a7S!a#S8t#S#T;{#T#s8t#s$f5u$f;'S8t;'S;=`>V<%l?Ah8t?Ah?BY5u?BY?Mn8t?MnO5u!Z5zblWOX5uXZ7SZ[5u[^7S^p5uqr5urs7Sst+Ptw5uwx7Sx!]5u!]!^7w!^!a7S!a#S5u#S#T7S#T;'S5u;'S;=`8n<%lO5u!R7VVOp7Sqs7St!]7S!]!^7l!^;'S7S;'S;=`7q<%lO7S!R7qOb!R!R7tP;=`<%l7S!Z8OYlWb!ROX+PZ[+P^p+Pqr+Psw+Px!^+P!a#S+P#T;'S+P;'S;=`+t<%lO+P!Z8qP;=`<%l5u!_8{iiSlWOX5uXZ7SZ[5u[^7S^p5uqr8trs7Sst/^tw8twx7Sx!P8t!P!Q5u!Q!]8t!]!^:j!^!a7S!a#S8t#S#T;{#T#s8t#s$f5u$f;'S8t;'S;=`>V<%l?Ah8t?Ah?BY5u?BY?Mn8t?MnO5u!_:sbiSlWb!ROX+PZ[+P^p+Pqr/^sw/^x!P/^!P!Q+P!Q!^/^!a#S/^#S#T0m#T#s/^#s$f+P$f;'S/^;'S;=`1e<%l?Ah/^?Ah?BY+P?BY?Mn/^?MnO+P!VP<%l?Ah;{?Ah?BY7S?BY?Mn;{?MnO7S!V=dXiSb!Rqr0msw0mx!P0m!Q!^0m!a#s0m$f;'S0m;'S;=`1_<%l?Ah0m?BY?Mn0m!V>SP;=`<%l;{!_>YP;=`<%l8t!_>dhiSlWOX@OXZAYZ[@O[^AY^p@OqrBwrsAYswBwwxAYx!PBw!P!Q@O!Q!]Bw!]!^/^!^!aAY!a#SBw#S#TE{#T#sBw#s$f@O$f;'SBw;'S;=`HS<%l?AhBw?Ah?BY@O?BY?MnBw?MnO@O!Z@TalWOX@OXZAYZ[@O[^AY^p@Oqr@OrsAYsw@OwxAYx!]@O!]!^Az!^!aAY!a#S@O#S#TAY#T;'S@O;'S;=`Bq<%lO@O!RA]UOpAYq!]AY!]!^Ao!^;'SAY;'S;=`At<%lOAY!RAtOc!R!RAwP;=`<%lAY!ZBRYlWc!ROX+PZ[+P^p+Pqr+Psw+Px!^+P!a#S+P#T;'S+P;'S;=`+t<%lO+P!ZBtP;=`<%l@O!_COhiSlWOX@OXZAYZ[@O[^AY^p@OqrBwrsAYswBwwxAYx!PBw!P!Q@O!Q!]Bw!]!^Dj!^!aAY!a#SBw#S#TE{#T#sBw#s$f@O$f;'SBw;'S;=`HS<%l?AhBw?Ah?BY@O?BY?MnBw?MnO@O!_DsbiSlWc!ROX+PZ[+P^p+Pqr/^sw/^x!P/^!P!Q+P!Q!^/^!a#S/^#S#T0m#T#s/^#s$f+P$f;'S/^;'S;=`1e<%l?Ah/^?Ah?BY+P?BY?Mn/^?MnO+P!VFQbiSOpAYqrE{rsAYswE{wxAYx!PE{!P!QAY!Q!]E{!]!^GY!^!aAY!a#sE{#s$fAY$f;'SE{;'S;=`G|<%l?AhE{?Ah?BYAY?BY?MnE{?MnOAY!VGaXiSc!Rqr0msw0mx!P0m!Q!^0m!a#s0m$f;'S0m;'S;=`1_<%l?Ah0m?BY?Mn0m!VHPP;=`<%lE{!_HVP;=`<%lBw!ZHcW!cxaP!b`Or(trs'ksv(tw!^(t!^!_)e!_;'S(t;'S;=`*P<%lO(t!aIYliSaPlW!b`!dpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx}-_}!OKQ!O!P-_!P!Q$q!Q!^-_!^!_*V!_!a&X!a#S-_#S#T1k#T#s-_#s$f$q$f;'S-_;'S;=`3X<%l?Ah-_?Ah?BY$q?BY?Mn-_?MnO$q!aK_kiSaPlW!b`!dpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx!P-_!P!Q$q!Q!^-_!^!_*V!_!`&X!`!aMS!a#S-_#S#T1k#T#s-_#s$f$q$f;'S-_;'S;=`3X<%l?Ah-_?Ah?BY$q?BY?Mn-_?MnO$q!TM_XaP!b`!dp!fQOr&Xrs&}sv&Xwx(tx!^&X!^!_*V!_;'S&X;'S;=`*y<%lO&X!aNZ!ZiSgQaPlW!b`!dpOX$qXZ&XZ[$q[^&X^p$qpq&Xqr-_rs&}sv-_vw/^wx(tx}-_}!OMz!O!PMz!P!Q$q!Q![Mz![!]Mz!]!^-_!^!_*V!_!a&X!a!c-_!c!}Mz!}#R-_#R#SMz#S#T1k#T#oMz#o#s-_#s$f$q$f$}-_$}%OMz%O%W-_%W%oMz%o%p-_%p&aMz&a&b-_&b1pMz1p4UMz4U4dMz4d4e-_4e$ISMz$IS$I`-_$I`$IbMz$Ib$Je-_$Je$JgMz$Jg$Kh-_$Kh%#tMz%#t&/x-_&/x&EtMz&Et&FV-_&FV;'SMz;'S;:j!#|;:j;=`3X<%l?&r-_?&r?AhMz?Ah?BY$q?BY?MnMz?MnO$q!a!$PP;=`<%lMz!R!$ZY!b`!dpOq*Vqr!$yrs(Vsv*Vwx)ex!a*V!a!b!4t!b;'S*V;'S;=`*s<%lO*V!R!%Q]!b`!dpOr*Vrs(Vsv*Vwx)ex}*V}!O!%y!O!f*V!f!g!']!g#W*V#W#X!0`#X;'S*V;'S;=`*s<%lO*V!R!&QX!b`!dpOr*Vrs(Vsv*Vwx)ex}*V}!O!&m!O;'S*V;'S;=`*s<%lO*V!R!&vV!b`!dp!ePOr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!'dX!b`!dpOr*Vrs(Vsv*Vwx)ex!q*V!q!r!(P!r;'S*V;'S;=`*s<%lO*V!R!(WX!b`!dpOr*Vrs(Vsv*Vwx)ex!e*V!e!f!(s!f;'S*V;'S;=`*s<%lO*V!R!(zX!b`!dpOr*Vrs(Vsv*Vwx)ex!v*V!v!w!)g!w;'S*V;'S;=`*s<%lO*V!R!)nX!b`!dpOr*Vrs(Vsv*Vwx)ex!{*V!{!|!*Z!|;'S*V;'S;=`*s<%lO*V!R!*bX!b`!dpOr*Vrs(Vsv*Vwx)ex!r*V!r!s!*}!s;'S*V;'S;=`*s<%lO*V!R!+UX!b`!dpOr*Vrs(Vsv*Vwx)ex!g*V!g!h!+q!h;'S*V;'S;=`*s<%lO*V!R!+xY!b`!dpOr!+qrs!,hsv!+qvw!-Swx!.[x!`!+q!`!a!/j!a;'S!+q;'S;=`!0Y<%lO!+qq!,mV!dpOv!,hvx!-Sx!`!,h!`!a!-q!a;'S!,h;'S;=`!.U<%lO!,hP!-VTO!`!-S!`!a!-f!a;'S!-S;'S;=`!-k<%lO!-SP!-kO|PP!-nP;=`<%l!-Sq!-xS!dp|POv(Vx;'S(V;'S;=`(h<%lO(Vq!.XP;=`<%l!,ha!.aX!b`Or!.[rs!-Ssv!.[vw!-Sw!`!.[!`!a!.|!a;'S!.[;'S;=`!/d<%lO!.[a!/TT!b`|POr)esv)ew;'S)e;'S;=`)y<%lO)ea!/gP;=`<%l!.[!R!/sV!b`!dp|POr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!0]P;=`<%l!+q!R!0gX!b`!dpOr*Vrs(Vsv*Vwx)ex#c*V#c#d!1S#d;'S*V;'S;=`*s<%lO*V!R!1ZX!b`!dpOr*Vrs(Vsv*Vwx)ex#V*V#V#W!1v#W;'S*V;'S;=`*s<%lO*V!R!1}X!b`!dpOr*Vrs(Vsv*Vwx)ex#h*V#h#i!2j#i;'S*V;'S;=`*s<%lO*V!R!2qX!b`!dpOr*Vrs(Vsv*Vwx)ex#m*V#m#n!3^#n;'S*V;'S;=`*s<%lO*V!R!3eX!b`!dpOr*Vrs(Vsv*Vwx)ex#d*V#d#e!4Q#e;'S*V;'S;=`*s<%lO*V!R!4XX!b`!dpOr*Vrs(Vsv*Vwx)ex#X*V#X#Y!+q#Y;'S*V;'S;=`*s<%lO*V!R!4{Y!b`!dpOr!4trs!5ksv!4tvw!6Vwx!8]x!a!4t!a!b!:]!b;'S!4t;'S;=`!;r<%lO!4tq!5pV!dpOv!5kvx!6Vx!a!5k!a!b!7W!b;'S!5k;'S;=`!8V<%lO!5kP!6YTO!a!6V!a!b!6i!b;'S!6V;'S;=`!7Q<%lO!6VP!6lTO!`!6V!`!a!6{!a;'S!6V;'S;=`!7Q<%lO!6VP!7QOyPP!7TP;=`<%l!6Vq!7]V!dpOv!5kvx!6Vx!`!5k!`!a!7r!a;'S!5k;'S;=`!8V<%lO!5kq!7yS!dpyPOv(Vx;'S(V;'S;=`(h<%lO(Vq!8YP;=`<%l!5ka!8bX!b`Or!8]rs!6Vsv!8]vw!6Vw!a!8]!a!b!8}!b;'S!8];'S;=`!:V<%lO!8]a!9SX!b`Or!8]rs!6Vsv!8]vw!6Vw!`!8]!`!a!9o!a;'S!8];'S;=`!:V<%lO!8]a!9vT!b`yPOr)esv)ew;'S)e;'S;=`)y<%lO)ea!:YP;=`<%l!8]!R!:dY!b`!dpOr!4trs!5ksv!4tvw!6Vwx!8]x!`!4t!`!a!;S!a;'S!4t;'S;=`!;r<%lO!4t!R!;]V!b`!dpyPOr*Vrs(Vsv*Vwx)ex;'S*V;'S;=`*s<%lO*V!R!;uP;=`<%l!4t!V!{let h=l.type.id;if(h==D2)return $a(l,a,t);if(h==Z2)return $a(l,a,i);if(h==B2)return $a(l,a,s);if(h==Z1&&r.length){let c=l.node,f=c.firstChild,u=f&&Kp(f,a),d;if(u){for(let p of r)if(p.tag==u&&(!p.attrs||p.attrs(d||(d=E1(f,a))))){let g=c.lastChild,m=g.type.id==L2?g.from:c.to;if(m>f.to)return{parser:p.parser,overlay:[{from:f.to,to:m}]}}}}if(o&&h==B1){let c=l.node,f;if(f=c.firstChild){let u=o[a.read(f.from,f.to)];if(u)for(let d of u){if(d.tagName&&d.tagName!=Kp(c.parent,a))continue;let p=c.lastChild;if(p.type.id==Oc){let g=p.from+1,m=p.lastChild,O=p.to-(m&&m.isError?0:1);if(O>g)return{parser:d.parser,overlay:[{from:g,to:O}],bracketed:!0}}else if(p.type.id==X1)return{parser:d.parser,overlay:[{from:p.from,to:p.to}]}}}}return null})}const sM=122,Jp=1,nM=123,rM=124,V1=2,oM=125,lM=3,aM=4,z1=[9,10,11,12,13,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288],hM=58,cM=40,q1=95,fM=91,so=45,uM=46,dM=35,pM=37,gM=38,mM=92,OM=10,bM=42;function qn(n){return n>=65&&n<=90||n>=97&&n<=122||n>=161}function Df(n){return n>=48&&n<=57}function eg(n){return Df(n)||n>=97&&n<=102||n>=65&&n<=70}const I1=(n,e,t)=>(i,s)=>{for(let r=!1,o=0,l=0;;l++){let{next:a}=i;if(qn(a)||a==so||a==q1||r&&Df(a))!r&&(a!=so||l>0)&&(r=!0),o===l&&a==so&&o++,i.advance();else if(a==mM&&i.peek(1)!=OM){if(i.advance(),eg(i.next)){do i.advance();while(eg(i.next));i.next==32&&i.advance()}else i.next>-1&&i.advance();r=!0}else{r&&i.acceptToken(o==2&&s.canShift(V1)?e:a==cM?t:n);break}}},SM=new Ge(I1(nM,V1,rM)),yM=new Ge(I1(oM,lM,aM)),xM=new Ge(n=>{if(z1.includes(n.peek(-1))){let{next:e}=n;(qn(e)||e==q1||e==dM||e==uM||e==bM||e==fM||e==hM&&qn(n.peek(1))||e==so||e==gM)&&n.acceptToken(sM)}}),wM=new Ge(n=>{if(!z1.includes(n.peek(-1))){let{next:e}=n;if(e==pM&&(n.advance(),n.acceptToken(Jp)),qn(e)){do n.advance();while(qn(n.next)||Df(n.next));n.acceptToken(Jp)}}}),kM=hl({"AtKeyword import charset namespace keyframes media supports":b.definitionKeyword,"from to selector":b.keyword,NamespaceName:b.namespace,KeyframeName:b.labelName,KeyframeRangeName:b.operatorKeyword,TagName:b.tagName,ClassName:b.className,PseudoClassName:b.constant(b.className),IdName:b.labelName,"FeatureName PropertyName":b.propertyName,AttributeName:b.attributeName,NumberLiteral:b.number,KeywordQuery:b.keyword,UnaryQueryOp:b.operatorKeyword,"CallTag ValueName":b.atom,VariableName:b.variableName,Callee:b.operatorKeyword,Unit:b.unit,"UniversalSelector NestingSelector":b.definitionOperator,"MatchOp CompareOp":b.compareOperator,"ChildOp SiblingOp, LogicOp":b.logicOperator,BinOp:b.arithmeticOperator,Important:b.modifier,Comment:b.blockComment,ColorLiteral:b.color,"ParenthesizedContent StringLiteral":b.string,":":b.punctuation,"PseudoOp #":b.derefOperator,"; ,":b.separator,"( )":b.paren,"[ ]":b.squareBracket,"{ }":b.brace}),QM={__proto__:null,lang:38,"nth-child":38,"nth-last-child":38,"nth-of-type":38,"nth-last-of-type":38,dir:38,"host-context":38,if:84,url:124,"url-prefix":124,domain:124,regexp:124},vM={__proto__:null,or:98,and:98,not:106,only:106,layer:170},$M={__proto__:null,selector:112,layer:166},PM={__proto__:null,"@import":162,"@media":174,"@charset":178,"@namespace":182,"@keyframes":188,"@supports":200,"@scope":204},CM={__proto__:null,to:207},TM=Zs.deserialize({version:14,states:"EbQYQdOOO#qQdOOP#xO`OOOOQP'#Cf'#CfOOQP'#Ce'#CeO#}QdO'#ChO$nQaO'#CcO$xQdO'#CkO%TQdO'#DpO%YQdO'#DrO%_QdO'#DuO%_QdO'#DxOOQP'#FV'#FVO&eQhO'#EhOOQS'#FU'#FUOOQS'#Ek'#EkQYQdOOO&lQdO'#EOO&PQhO'#EUO&lQdO'#EWO'aQdO'#EYO'lQdO'#E]O'tQhO'#EcO(VQdO'#EeO(bQaO'#CfO)VQ`O'#D{O)[Q`O'#F`O)gQdO'#F`QOQ`OOP)qO&jO'#CaPOOO)C@t)C@tOOQP'#Cj'#CjOOQP,59S,59SO#}QdO,59SO)|QdO,59VO%TQdO,5:[O%YQdO,5:^O%_QdO,5:aO%_QdO,5:cO%_QdO,5:dO%_QdO'#ErO*XQ`O,58}O*aQdO'#DzOOQS,58},58}OOQP'#Cn'#CnOOQO'#Dn'#DnOOQP,59V,59VO*hQ`O,59VO*mQ`O,59VOOQP'#Dq'#DqOOQP,5:[,5:[OOQO'#Ds'#DsO*rQpO,5:^O+]QaO,5:aO+sQaO,5:dOOQW'#DZ'#DZO,ZQhO'#DdO,xQhO'#FaO'tQhO'#DbO-WQ`O'#DhOOQW'#F['#F[O-]Q`O,5;SO-eQ`O'#DeOOQS-E8i-E8iOOQ['#Cs'#CsO-jQdO'#CtO.QQdO'#CzO.hQdO'#C}O/OQ!pO'#DPO1RQ!jO,5:jOOQO'#DU'#DUO*mQ`O'#DTO1cQ!nO'#FXO3`Q`O'#DVO3eQ`O'#DkOOQ['#FX'#FXO-`Q`O,5:pO3jQ!bO,5:rOOQS'#E['#E[O3rQ`O,5:tO3wQdO,5:tOOQO'#E_'#E_O4PQ`O,5:wO4UQhO,5:}O%_QdO'#DgOOQS,5;P,5;PO-eQ`O,5;PO4^QdO,5;PO4fQdO,5:gO4vQdO'#EtO5TQ`O,5;zO5TQ`O,5;zPOOO'#Ej'#EjP5`O&jO,58{POOO,58{,58{OOQP1G.n1G.nOOQP1G.q1G.qO*hQ`O1G.qO*mQ`O1G.qOOQP1G/v1G/vO5kQpO1G/xO5sQaO1G/{O6ZQaO1G/}O6qQaO1G0OO7XQaO,5;^OOQO-E8p-E8pOOQS1G.i1G.iO7cQ`O,5:fO7hQdO'#DoO7oQdO'#CrOOQP1G/x1G/xO&lQdO1G/xO7vQ!jO'#DZO8UQ!bO,59vO8^QhO,5:OOOQO'#F]'#F]O8XQ!bO,59zO'tQhO,59xO8fQhO'#EvO8sQ`O,5;{O9OQhO,59|O9uQhO'#DiOOQW,5:S,5:SOOQS1G0n1G0nOOQW,5:P,5:PO9|Q!fO'#FYOOQS'#FY'#FYOOQS'#Em'#EmO;^QdO,59`OOQ[,59`,59`O;tQdO,59fOOQ[,59f,59fO<[QdO,59iOOQ[,59i,59iOOQ[,59k,59kO&lQdO,59mOPQ!fO1G0ROOQO1G0R1G0ROOQO,5;`,5;`O>gQdO,5;`OOQO-E8r-E8rO>tQ`O1G1fPOOO-E8h-E8hPOOO1G.g1G.gOOQP7+$]7+$]OOQP7+%d7+%dO&lQdO7+%dOOQS1G0Q1G0QO?PQaO'#F_O?ZQ`O,5:ZO?`Q!fO'#ElO@^QdO'#FWO@hQ`O,59^O@mQ!bO7+%dO&lQdO1G/bO@uQhO1G/fOOQW1G/j1G/jOOQW1G/d1G/dOAWQhO,5;bOOQO-E8t-E8tOAfQhO'#DZOAtQhO'#F^OBPQ`O'#F^OBUQ`O,5:TOOQS-E8k-E8kOOQ[1G.z1G.zOOQ[1G/Q1G/QOOQ[1G/T1G/TOOQ[1G/X1G/XOBZQdO,5:lOOQS7+%p7+%pOB`Q`O7+%pOBeQhO'#DYOBmQ`O,59sO'tQhO,59sOOQ[1G/q1G/qOBuQ`O1G/qOOQS7+%z7+%zOBzQbO'#DPOOQO'#Eb'#EbOCYQ`O'#EaOOQO'#Ea'#EaOCeQ`O'#EwOCmQdO,5:zOOQS,5:z,5:zOOQ[1G/m1G/mOOQS7+&V7+&VO-`Q`O7+&VOCxQ!fO'#EsO&lQdO'#EsOEPQdO7+%mOOQO7+%m7+%mOOQO1G0z1G0zOEdQ!bO<jAN>jOIUQaO,5;]OOQO-E8o-E8oOI`QdO,5;[OOQO-E8n-E8nOOQW<WO&lQdO1G0uOK]Q`O7+'OOOQO,5;a,5;aOOQO-E8s-E8sOOQW<t}!O?V!O!P?t!P!Q@]!Q![AU![!]BP!]!^B{!^!_C^!_!`DY!`!aDm!a!b$q!b!cEn!c!}$q!}#OG{#O#P$q#P#QH^#Q#R6W#R#o$q#o#pHo#p#q6W#q#rIQ#r#sIc#s#y$q#y#z%i#z$f$q$f$g%i$g#BY$q#BY#BZ%i#BZ$IS$q$IS$I_%i$I_$I|$q$I|$JO%i$JO$JT$q$JT$JU%i$JU$KV$q$KV$KW%i$KW&FU$q&FU&FV%i&FV;'S$q;'S;=`Iz<%lO$q`$tSOy%Qz;'S%Q;'S;=`%c<%lO%Q`%VS!a`Oy%Qz;'S%Q;'S;=`%c<%lO%Q`%fP;=`<%l%Q~%nh#s~OX%QX^'Y^p%Qpq'Yqy%Qz#y%Q#y#z'Y#z$f%Q$f$g'Y$g#BY%Q#BY#BZ'Y#BZ$IS%Q$IS$I_'Y$I_$I|%Q$I|$JO'Y$JO$JT%Q$JT$JU'Y$JU$KV%Q$KV$KW'Y$KW&FU%Q&FU&FV'Y&FV;'S%Q;'S;=`%c<%lO%Q~'ah#s~!a`OX%QX^'Y^p%Qpq'Yqy%Qz#y%Q#y#z'Y#z$f%Q$f$g'Y$g#BY%Q#BY#BZ'Y#BZ$IS%Q$IS$I_'Y$I_$I|%Q$I|$JO'Y$JO$JT%Q$JT$JU'Y$JU$KV%Q$KV$KW'Y$KW&FU%Q&FU&FV'Y&FV;'S%Q;'S;=`%c<%lO%Qj)OUOy%Qz#]%Q#]#^)b#^;'S%Q;'S;=`%c<%lO%Qj)gU!a`Oy%Qz#a%Q#a#b)y#b;'S%Q;'S;=`%c<%lO%Qj*OU!a`Oy%Qz#d%Q#d#e*b#e;'S%Q;'S;=`%c<%lO%Qj*gU!a`Oy%Qz#c%Q#c#d*y#d;'S%Q;'S;=`%c<%lO%Qj+OU!a`Oy%Qz#f%Q#f#g+b#g;'S%Q;'S;=`%c<%lO%Qj+gU!a`Oy%Qz#h%Q#h#i+y#i;'S%Q;'S;=`%c<%lO%Qj,OU!a`Oy%Qz#T%Q#T#U,b#U;'S%Q;'S;=`%c<%lO%Qj,gU!a`Oy%Qz#b%Q#b#c,y#c;'S%Q;'S;=`%c<%lO%Qj-OU!a`Oy%Qz#h%Q#h#i-b#i;'S%Q;'S;=`%c<%lO%Qj-iS!qY!a`Oy%Qz;'S%Q;'S;=`%c<%lO%Q~-xWOY-uZr-urs.bs#O-u#O#P.g#P;'S-u;'S;=`/c<%lO-u~.gOt~~.jRO;'S-u;'S;=`.s;=`O-u~.vXOY-uZr-urs.bs#O-u#O#P.g#P;'S-u;'S;=`/c;=`<%l-u<%lO-u~/fP;=`<%l-uj/nYjYOy%Qz!Q%Q!Q![0^![!c%Q!c!i0^!i#T%Q#T#Z0^#Z;'S%Q;'S;=`%c<%lO%Qj0cY!a`Oy%Qz!Q%Q!Q![1R![!c%Q!c!i1R!i#T%Q#T#Z1R#Z;'S%Q;'S;=`%c<%lO%Qj1WY!a`Oy%Qz!Q%Q!Q![1v![!c%Q!c!i1v!i#T%Q#T#Z1v#Z;'S%Q;'S;=`%c<%lO%Qj1}YrY!a`Oy%Qz!Q%Q!Q![2m![!c%Q!c!i2m!i#T%Q#T#Z2m#Z;'S%Q;'S;=`%c<%lO%Qj2tYrY!a`Oy%Qz!Q%Q!Q![3d![!c%Q!c!i3d!i#T%Q#T#Z3d#Z;'S%Q;'S;=`%c<%lO%Qj3iY!a`Oy%Qz!Q%Q!Q![4X![!c%Q!c!i4X!i#T%Q#T#Z4X#Z;'S%Q;'S;=`%c<%lO%Qj4`YrY!a`Oy%Qz!Q%Q!Q![5O![!c%Q!c!i5O!i#T%Q#T#Z5O#Z;'S%Q;'S;=`%c<%lO%Qj5TY!a`Oy%Qz!Q%Q!Q![5s![!c%Q!c!i5s!i#T%Q#T#Z5s#Z;'S%Q;'S;=`%c<%lO%Qj5zSrY!a`Oy%Qz;'S%Q;'S;=`%c<%lO%Qd6ZUOy%Qz!_%Q!_!`6m!`;'S%Q;'S;=`%c<%lO%Qd6tS!hS!a`Oy%Qz;'S%Q;'S;=`%c<%lO%Qb7VSZQOy%Qz;'S%Q;'S;=`%c<%lO%Q~7fWOY7cZw7cwx.bx#O7c#O#P8O#P;'S7c;'S;=`8z<%lO7c~8RRO;'S7c;'S;=`8[;=`O7c~8_XOY7cZw7cwx.bx#O7c#O#P8O#P;'S7c;'S;=`8z;=`<%l7c<%lO7c~8}P;=`<%l7cj9VSeYOy%Qz;'S%Q;'S;=`%c<%lO%Q~9hOd~n9oUWQvWOy%Qz!_%Q!_!`6m!`;'S%Q;'S;=`%c<%lO%Qj:YWvW!mQOy%Qz!O%Q!O!P:r!P!Q%Q!Q![=w![;'S%Q;'S;=`%c<%lO%Qj:wU!a`Oy%Qz!Q%Q!Q![;Z![;'S%Q;'S;=`%c<%lO%Qj;bY!a`#}YOy%Qz!Q%Q!Q![;Z![!g%Q!g!hO[!a`#}YOy%Qz!O%Q!O!P;Z!P!Q%Q!Q![=w![!g%Q!g!hyS!^YOy%Qz;'S%Q;'S;=`%c<%lO%Qj?[WvWOy%Qz!O%Q!O!P:r!P!Q%Q!Q![=w![;'S%Q;'S;=`%c<%lO%Qj?yU]YOy%Qz!Q%Q!Q![;Z![;'S%Q;'S;=`%c<%lO%Q~@bTvWOy%Qz{@q{;'S%Q;'S;=`%c<%lO%Q~@xS!a`#t~Oy%Qz;'S%Q;'S;=`%c<%lO%QjAZ[#}YOy%Qz!O%Q!O!P;Z!P!Q%Q!Q![=w![!g%Q!g!hQM[n]||-1},{term:125,get:n=>vM[n]||-1},{term:4,get:n=>$M[n]||-1},{term:25,get:n=>PM[n]||-1},{term:123,get:n=>CM[n]||-1}],tokenPrec:1963});let Pa=null;function Ca(){if(!Pa&&typeof document=="object"&&document.body){let{style:n}=document.body,e=[],t=new Set;for(let i in n)i!="cssText"&&i!="cssFloat"&&typeof n[i]=="string"&&(/[A-Z]/.test(i)&&(i=i.replace(/[A-Z]/g,s=>"-"+s.toLowerCase())),t.has(i)||(e.push(i),t.add(i)));Pa=e.sort().map(i=>({type:"property",label:i,apply:i+": "}))}return Pa||[]}const tg=["active","after","any-link","autofill","backdrop","before","checked","cue","default","defined","disabled","empty","enabled","file-selector-button","first","first-child","first-letter","first-line","first-of-type","focus","focus-visible","focus-within","fullscreen","has","host","host-context","hover","in-range","indeterminate","invalid","is","lang","last-child","last-of-type","left","link","marker","modal","not","nth-child","nth-last-child","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","part","placeholder","placeholder-shown","read-only","read-write","required","right","root","scope","selection","slotted","target","target-text","valid","visited","where"].map(n=>({type:"class",label:n})),ig=["above","absolute","activeborder","additive","activecaption","after-white-space","ahead","alias","all","all-scroll","alphabetic","alternate","always","antialiased","appworkspace","asterisks","attr","auto","auto-flow","avoid","avoid-column","avoid-page","avoid-region","axis-pan","background","backwards","baseline","below","bidi-override","blink","block","block-axis","bold","bolder","border","border-box","both","bottom","break","break-all","break-word","bullets","button","button-bevel","buttonface","buttonhighlight","buttonshadow","buttontext","calc","capitalize","caps-lock-indicator","caption","captiontext","caret","cell","center","checkbox","circle","cjk-decimal","clear","clip","close-quote","col-resize","collapse","color","color-burn","color-dodge","column","column-reverse","compact","condensed","contain","content","contents","content-box","context-menu","continuous","copy","counter","counters","cover","crop","cross","crosshair","currentcolor","cursive","cyclic","darken","dashed","decimal","decimal-leading-zero","default","default-button","dense","destination-atop","destination-in","destination-out","destination-over","difference","disc","discard","disclosure-closed","disclosure-open","document","dot-dash","dot-dot-dash","dotted","double","down","e-resize","ease","ease-in","ease-in-out","ease-out","element","ellipse","ellipsis","embed","end","ethiopic-abegede-gez","ethiopic-halehame-aa-er","ethiopic-halehame-gez","ew-resize","exclusion","expanded","extends","extra-condensed","extra-expanded","fantasy","fast","fill","fill-box","fixed","flat","flex","flex-end","flex-start","footnotes","forwards","from","geometricPrecision","graytext","grid","groove","hand","hard-light","help","hidden","hide","higher","highlight","highlighttext","horizontal","hsl","hsla","hue","icon","ignore","inactiveborder","inactivecaption","inactivecaptiontext","infinite","infobackground","infotext","inherit","initial","inline","inline-axis","inline-block","inline-flex","inline-grid","inline-table","inset","inside","intrinsic","invert","italic","justify","keep-all","landscape","large","larger","left","level","lighter","lighten","line-through","linear","linear-gradient","lines","list-item","listbox","listitem","local","logical","loud","lower","lower-hexadecimal","lower-latin","lower-norwegian","lowercase","ltr","luminosity","manipulation","match","matrix","matrix3d","medium","menu","menutext","message-box","middle","min-intrinsic","mix","monospace","move","multiple","multiple_mask_images","multiply","n-resize","narrower","ne-resize","nesw-resize","no-close-quote","no-drop","no-open-quote","no-repeat","none","normal","not-allowed","nowrap","ns-resize","numbers","numeric","nw-resize","nwse-resize","oblique","opacity","open-quote","optimizeLegibility","optimizeSpeed","outset","outside","outside-shape","overlay","overline","padding","padding-box","painted","page","paused","perspective","pinch-zoom","plus-darker","plus-lighter","pointer","polygon","portrait","pre","pre-line","pre-wrap","preserve-3d","progress","push-button","radial-gradient","radio","read-only","read-write","read-write-plaintext-only","rectangle","region","relative","repeat","repeating-linear-gradient","repeating-radial-gradient","repeat-x","repeat-y","reset","reverse","rgb","rgba","ridge","right","rotate","rotate3d","rotateX","rotateY","rotateZ","round","row","row-resize","row-reverse","rtl","run-in","running","s-resize","sans-serif","saturation","scale","scale3d","scaleX","scaleY","scaleZ","screen","scroll","scrollbar","scroll-position","se-resize","self-start","self-end","semi-condensed","semi-expanded","separate","serif","show","single","skew","skewX","skewY","skip-white-space","slide","slider-horizontal","slider-vertical","sliderthumb-horizontal","sliderthumb-vertical","slow","small","small-caps","small-caption","smaller","soft-light","solid","source-atop","source-in","source-out","source-over","space","space-around","space-between","space-evenly","spell-out","square","start","static","status-bar","stretch","stroke","stroke-box","sub","subpixel-antialiased","svg_masks","super","sw-resize","symbolic","symbols","system-ui","table","table-caption","table-cell","table-column","table-column-group","table-footer-group","table-header-group","table-row","table-row-group","text","text-bottom","text-top","textarea","textfield","thick","thin","threeddarkshadow","threedface","threedhighlight","threedlightshadow","threedshadow","to","top","transform","translate","translate3d","translateX","translateY","translateZ","transparent","ultra-condensed","ultra-expanded","underline","unidirectional-pan","unset","up","upper-latin","uppercase","url","var","vertical","vertical-text","view-box","visible","visibleFill","visiblePainted","visibleStroke","visual","w-resize","wait","wave","wider","window","windowframe","windowtext","words","wrap","wrap-reverse","x-large","x-small","xor","xx-large","xx-small"].map(n=>({type:"keyword",label:n})).concat(["aliceblue","antiquewhite","aqua","aquamarine","azure","beige","bisque","black","blanchedalmond","blue","blueviolet","brown","burlywood","cadetblue","chartreuse","chocolate","coral","cornflowerblue","cornsilk","crimson","cyan","darkblue","darkcyan","darkgoldenrod","darkgray","darkgreen","darkkhaki","darkmagenta","darkolivegreen","darkorange","darkorchid","darkred","darksalmon","darkseagreen","darkslateblue","darkslategray","darkturquoise","darkviolet","deeppink","deepskyblue","dimgray","dodgerblue","firebrick","floralwhite","forestgreen","fuchsia","gainsboro","ghostwhite","gold","goldenrod","gray","grey","green","greenyellow","honeydew","hotpink","indianred","indigo","ivory","khaki","lavender","lavenderblush","lawngreen","lemonchiffon","lightblue","lightcoral","lightcyan","lightgoldenrodyellow","lightgray","lightgreen","lightpink","lightsalmon","lightseagreen","lightskyblue","lightslategray","lightsteelblue","lightyellow","lime","limegreen","linen","magenta","maroon","mediumaquamarine","mediumblue","mediumorchid","mediumpurple","mediumseagreen","mediumslateblue","mediumspringgreen","mediumturquoise","mediumvioletred","midnightblue","mintcream","mistyrose","moccasin","navajowhite","navy","oldlace","olive","olivedrab","orange","orangered","orchid","palegoldenrod","palegreen","paleturquoise","palevioletred","papayawhip","peachpuff","peru","pink","plum","powderblue","purple","rebeccapurple","red","rosybrown","royalblue","saddlebrown","salmon","sandybrown","seagreen","seashell","sienna","silver","skyblue","slateblue","slategray","snow","springgreen","steelblue","tan","teal","thistle","tomato","turquoise","violet","wheat","white","whitesmoke","yellow","yellowgreen"].map(n=>({type:"constant",label:n}))),MM=["a","abbr","address","article","aside","b","bdi","bdo","blockquote","body","br","button","canvas","caption","cite","code","col","colgroup","dd","del","details","dfn","dialog","div","dl","dt","em","figcaption","figure","footer","form","header","hgroup","h1","h2","h3","h4","h5","h6","hr","html","i","iframe","img","input","ins","kbd","label","legend","li","main","meter","nav","ol","output","p","pre","ruby","section","select","small","source","span","strong","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","tr","u","ul"].map(n=>({type:"type",label:n})),AM=["@charset","@color-profile","@container","@counter-style","@font-face","@font-feature-values","@font-palette-values","@import","@keyframes","@layer","@media","@namespace","@page","@position-try","@property","@scope","@starting-style","@supports","@view-transition"].map(n=>({type:"keyword",label:n})),jt=/^(\w[\w-]*|-\w[\w-]*|)$/,RM=/^-(-[\w-]*)?$/;function DM(n,e){var t;if((n.name=="("||n.type.isError)&&(n=n.parent||n),n.name!="ArgList")return!1;let i=(t=n.parent)===null||t===void 0?void 0:t.firstChild;return i?.name!="Callee"?!1:e.sliceString(i.from,i.to)=="var"}const sg=new iO,ZM=["Declaration"];function BM(n){for(let e=n;;){if(e.type.isTop)return e;if(!(e=e.parent))return n}}function _1(n,e,t){if(e.to-e.from>4096){let i=sg.get(e);if(i)return i;let s=[],r=new Set,o=e.cursor(j.IncludeAnonymous);if(o.firstChild())do for(let l of _1(n,o.node,t))r.has(l.label)||(r.add(l.label),s.push(l));while(o.nextSibling());return sg.set(e,s),s}else{let i=[],s=new Set;return e.cursor().iterate(r=>{var o;if(t(r)&&r.matchContext(ZM)&&((o=r.node.nextSibling)===null||o===void 0?void 0:o.name)==":"){let l=n.sliceString(r.from,r.to);s.has(l)||(s.add(l),i.push({label:l,type:"variable"}))}}),i}}const XM=n=>e=>{let{state:t,pos:i}=e,s=fe(t).resolveInner(i,-1),r=s.type.isError&&s.from==s.to-1&&t.doc.sliceString(s.from,s.to)=="-";if(s.name=="PropertyName"||(r||s.name=="TagName")&&/^(Block|Styles)$/.test(s.resolve(s.to).name))return{from:s.from,options:Ca(),validFor:jt};if(s.name=="ValueName")return{from:s.from,options:ig,validFor:jt};if(s.name=="PseudoClassName")return{from:s.from,options:tg,validFor:jt};if(n(s)||(e.explicit||r)&&DM(s,t.doc))return{from:n(s)||r?s.from:i,options:_1(t.doc,BM(s),n),validFor:RM};if(s.name=="TagName"){for(let{parent:a}=s;a;a=a.parent)if(a.name=="Block")return{from:s.from,options:Ca(),validFor:jt};return{from:s.from,options:MM,validFor:jt}}if(s.name=="AtKeyword")return{from:s.from,options:AM,validFor:jt};if(!e.explicit)return null;let o=s.resolve(i),l=o.childBefore(i);return l&&l.name==":"&&o.name=="PseudoClassSelector"?{from:i,options:tg,validFor:jt}:l&&l.name==":"&&o.name=="Declaration"||o.name=="ArgList"?{from:i,options:ig,validFor:jt}:o.name=="Block"||o.name=="Styles"?{from:i,options:Ca(),validFor:jt}:null},LM=XM(n=>n.name=="VariableName"),el=xs.define({name:"css",parser:TM.configure({props:[ul.add({Declaration:Fr()}),dl.add({"Block KeyframeList":cO})]}),languageData:{commentTokens:{block:{open:"/*",close:"*/"}},indentOnInput:/^\s*\}$/,wordChars:"-"}});function EM(){return new Yc(el,el.data.of({autocomplete:LM}))}const WM=316,VM=317,ng=1,zM=2,qM=3,IM=4,_M=318,YM=320,NM=321,jM=5,GM=6,HM=0,Sc=[9,10,11,12,13,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288],Y1=125,FM=59,yc=47,UM=42,KM=43,JM=45,eA=60,tA=44,iA=63,sA=46,nA=91,rA=new T1({start:!1,shift(n,e){return e==jM||e==GM||e==YM?n:e==NM},strict:!1}),oA=new Ge((n,e)=>{let{next:t}=n;(t==Y1||t==-1||e.context)&&n.acceptToken(_M)},{contextual:!0,fallback:!0}),lA=new Ge((n,e)=>{let{next:t}=n,i;Sc.indexOf(t)>-1||t==yc&&((i=n.peek(1))==yc||i==UM)||t!=Y1&&t!=FM&&t!=-1&&!e.context&&n.acceptToken(WM)},{contextual:!0}),aA=new Ge((n,e)=>{n.next==nA&&!e.context&&n.acceptToken(VM)},{contextual:!0}),hA=new Ge((n,e)=>{let{next:t}=n;if(t==KM||t==JM){if(n.advance(),t==n.next){n.advance();let i=!e.context&&e.canShift(ng);n.acceptToken(i?ng:zM)}}else t==iA&&n.peek(1)==sA&&(n.advance(),n.advance(),(n.next<48||n.next>57)&&n.acceptToken(qM))},{contextual:!0});function Ta(n,e){return n>=65&&n<=90||n>=97&&n<=122||n==95||n>=192||!e&&n>=48&&n<=57}const cA=new Ge((n,e)=>{if(n.next!=eA||!e.dialectEnabled(HM)||(n.advance(),n.next==yc))return;let t=0;for(;Sc.indexOf(n.next)>-1;)n.advance(),t++;if(Ta(n.next,!0)){for(n.advance(),t++;Ta(n.next,!1);)n.advance(),t++;for(;Sc.indexOf(n.next)>-1;)n.advance(),t++;if(n.next==tA)return;for(let i=0;;i++){if(i==7){if(!Ta(n.next,!0))return;break}if(n.next!="extends".charCodeAt(i))break;n.advance(),t++}}n.acceptToken(IM,-t)}),fA=hl({"get set async static":b.modifier,"for while do if else switch try catch finally return throw break continue default case defer":b.controlKeyword,"in of await yield void typeof delete instanceof as satisfies":b.operatorKeyword,"let var const using function class extends":b.definitionKeyword,"import export from":b.moduleKeyword,"with debugger new":b.keyword,TemplateString:b.special(b.string),super:b.atom,BooleanLiteral:b.bool,this:b.self,null:b.null,Star:b.modifier,VariableName:b.variableName,"CallExpression/VariableName TaggedTemplateExpression/VariableName":b.function(b.variableName),VariableDefinition:b.definition(b.variableName),Label:b.labelName,PropertyName:b.propertyName,PrivatePropertyName:b.special(b.propertyName),"CallExpression/MemberExpression/PropertyName":b.function(b.propertyName),"FunctionDeclaration/VariableDefinition":b.function(b.definition(b.variableName)),"ClassDeclaration/VariableDefinition":b.definition(b.className),"NewExpression/VariableName":b.className,PropertyDefinition:b.definition(b.propertyName),PrivatePropertyDefinition:b.definition(b.special(b.propertyName)),UpdateOp:b.updateOperator,"LineComment Hashbang":b.lineComment,BlockComment:b.blockComment,Number:b.number,String:b.string,Escape:b.escape,ArithOp:b.arithmeticOperator,LogicOp:b.logicOperator,BitOp:b.bitwiseOperator,CompareOp:b.compareOperator,RegExp:b.regexp,Equals:b.definitionOperator,Arrow:b.function(b.punctuation),": Spread":b.punctuation,"( )":b.paren,"[ ]":b.squareBracket,"{ }":b.brace,"InterpolationStart InterpolationEnd":b.special(b.brace),".":b.derefOperator,", ;":b.separator,"@":b.meta,TypeName:b.typeName,TypeDefinition:b.definition(b.typeName),"type enum interface implements namespace module declare":b.definitionKeyword,"abstract global Privacy readonly override":b.modifier,"is keyof unique infer asserts":b.operatorKeyword,JSXAttributeValue:b.attributeValue,JSXText:b.content,"JSXStartTag JSXStartCloseTag JSXSelfCloseEndTag JSXEndTag":b.angleBracket,"JSXIdentifier JSXNameSpacedName":b.tagName,"JSXAttribute/JSXIdentifier JSXAttribute/JSXNameSpacedName":b.attributeName,"JSXBuiltin/JSXIdentifier":b.standard(b.tagName)}),uA={__proto__:null,export:20,as:25,from:33,default:36,async:41,function:42,in:52,out:55,const:56,extends:60,this:64,true:72,false:72,null:84,void:88,typeof:92,super:108,new:142,delete:154,yield:163,await:167,class:172,public:235,private:235,protected:235,readonly:237,instanceof:256,satisfies:259,import:292,keyof:349,unique:353,infer:359,asserts:395,is:397,abstract:417,implements:419,type:421,let:424,var:426,using:429,interface:435,enum:439,namespace:445,module:447,declare:451,global:455,defer:471,for:476,of:485,while:488,with:492,do:496,if:500,else:502,switch:506,case:512,try:518,catch:522,finally:526,return:530,throw:534,break:538,continue:542,debugger:546},dA={__proto__:null,async:129,get:131,set:133,declare:195,public:197,private:197,protected:197,static:199,abstract:201,override:203,readonly:209,accessor:211,new:401},pA={__proto__:null,"<":193},gA=Zs.deserialize({version:14,states:"$F|Q%TQlOOO%[QlOOO'_QpOOP(lO`OOO*zQ!0MxO'#CiO+RO#tO'#CjO+aO&jO'#CjO+oO#@ItO'#DaO.QQlO'#DgO.bQlO'#DrO%[QlO'#DzO0fQlO'#ESOOQ!0Lf'#E['#E[O1PQ`O'#EXOOQO'#Ep'#EpOOQO'#Il'#IlO1XQ`O'#GsO1dQ`O'#EoO1iQ`O'#EoO3hQ!0MxO'#JrO6[Q!0MxO'#JsO6uQ`O'#F]O6zQ,UO'#FtOOQ!0Lf'#Ff'#FfO7VO7dO'#FfO9XQMhO'#F|O9`Q`O'#F{OOQ!0Lf'#Js'#JsOOQ!0Lb'#Jr'#JrO9eQ`O'#GwOOQ['#K_'#K_O9pQ`O'#IYO9uQ!0LrO'#IZOOQ['#J`'#J`OOQ['#I_'#I_Q`QlOOQ`QlOOO9}Q!L^O'#DvO:UQlO'#EOO:]QlO'#EQO9kQ`O'#GsO:dQMhO'#CoO:rQ`O'#EnO:}Q`O'#EyO;hQMhO'#FeO;xQ`O'#GsOOQO'#K`'#K`O;}Q`O'#K`O<]Q`O'#G{O<]Q`O'#G|O<]Q`O'#HOO9kQ`O'#HRO=SQ`O'#HUO>kQ`O'#CeO>{Q`O'#HcO?TQ`O'#HiO?TQ`O'#HkO`QlO'#HmO?TQ`O'#HoO?TQ`O'#HrO?YQ`O'#HxO?_Q!0LsO'#IOO%[QlO'#IQO?jQ!0LsO'#ISO?uQ!0LsO'#IUO9uQ!0LrO'#IWO@QQ!0MxO'#CiOASQpO'#DlQOQ`OOO%[QlO'#EQOAjQ`O'#ETO:dQMhO'#EnOAuQ`O'#EnOBQQ!bO'#FeOOQ['#Cg'#CgOOQ!0Lb'#Dq'#DqOOQ!0Lb'#Jv'#JvO%[QlO'#JvOOQO'#Jy'#JyOOQO'#Ih'#IhOCQQpO'#EgOOQ!0Lb'#Ef'#EfOOQ!0Lb'#J}'#J}OC|Q!0MSO'#EgODWQpO'#EWOOQO'#Jx'#JxODlQpO'#JyOEyQpO'#EWODWQpO'#EgPFWO&2DjO'#CbPOOO)CD})CD}OOOO'#I`'#I`OFcO#tO,59UOOQ!0Lh,59U,59UOOOO'#Ia'#IaOFqO&jO,59UOGPQ!L^O'#DcOOOO'#Ic'#IcOGWO#@ItO,59{OOQ!0Lf,59{,59{OGfQlO'#IdOGyQ`O'#JtOIxQ!fO'#JtO+}QlO'#JtOJPQ`O,5:ROJgQ`O'#EpOJtQ`O'#KTOKPQ`O'#KSOKPQ`O'#KSOKXQ`O,5;^OK^Q`O'#KROOQ!0Ln,5:^,5:^OKeQlO,5:^OMcQ!0MxO,5:fONSQ`O,5:nONmQ!0LrO'#KQONtQ`O'#KPO9eQ`O'#KPO! YQ`O'#KPO! bQ`O,5;]O! gQ`O'#KPO!#lQ!fO'#JsOOQ!0Lh'#Ci'#CiO%[QlO'#ESO!$[Q!fO,5:sOOQS'#Jz'#JzOOQO-EtOOQ['#Jh'#JhOOQ[,5>u,5>uOOQ[-E<]-E<]O!TO`QlO,5>VO!LOQ`O,5>XO`QlO,5>ZO!LTQ`O,5>^O!LYQlO,5>dOOQ[,5>j,5>jO%[QlO,5>jO9uQ!0LrO,5>lOOQ[,5>n,5>nO#!dQ`O,5>nOOQ[,5>p,5>pO#!dQ`O,5>pOOQ[,5>r,5>rO##QQpO'#D_O%[QlO'#JvO##sQpO'#JvO##}QpO'#DmO#$`QpO'#DmO#&qQlO'#DmO#&xQ`O'#JuO#'QQ`O,5:WO#'VQ`O'#EtO#'eQ`O'#KUO#'mQ`O,5;_O#'rQpO'#DmO#(PQpO'#EVOOQ!0Lf,5:o,5:oO%[QlO,5:oO#(WQ`O,5:oO?YQ`O,5;YO!CUQpO,5;YO!C^QMhO,5;YO:dQMhO,5;YO#(`Q`O,5@bO#(eQ07dO,5:sOOQO-EPO$6^Q`O,5>POOQ[1G3i1G3iO`QlO1G3iOOQ[1G3o1G3oOOQ[1G3q1G3qO?TQ`O1G3sO$6cQlO1G3uO$:gQlO'#HtOOQ[1G3x1G3xO$:tQ`O'#HzO?YQ`O'#H|OOQ[1G4O1G4OO$:|QlO1G4OO9uQ!0LrO1G4UOOQ[1G4W1G4WOOQ!0Lb'#G_'#G_O9uQ!0LrO1G4YO9uQ!0LrO1G4[O$?TQ`O,5@bO!)[QlO,5;`O9eQ`O,5;`O?YQ`O,5:XO!)[QlO,5:XO!CUQpO,5:XO$?YQ?MtO,5:XOOQO,5;`,5;`O$?dQpO'#IeO$?zQ`O,5@aOOQ!0Lf1G/r1G/rO$@SQpO'#IkO$@^Q`O,5@pOOQ!0Lb1G0y1G0yO#$`QpO,5:XOOQO'#Ig'#IgO$@fQpO,5:qOOQ!0Ln,5:q,5:qO#(ZQ`O1G0ZOOQ!0Lf1G0Z1G0ZO%[QlO1G0ZOOQ!0Lf1G0t1G0tO?YQ`O1G0tO!CUQpO1G0tO!C^QMhO1G0tOOQ!0Lb1G5|1G5|O!ByQ!0LrO1G0^OOQO1G0m1G0mO%[QlO1G0mO$@mQ!0LrO1G0mO$@xQ!0LrO1G0mO!CUQpO1G0^ODWQpO1G0^O$AWQ!0LrO1G0mOOQO1G0^1G0^O$AlQ!0MxO1G0mPOOO-E<[-E<[POOO1G.h1G.hOOOO1G/i1G/iO$AvQ!bO,5QQpO,5@}OOQ!0Lb1G3c1G3cOOQ[7+$V7+$VO@zQ`O7+$VO9uQ!0LrO7+$VO%>]Q`O7+$VO%[QlO1G6lO%[QlO1G6mO%>bQ!0LrO1G6lO%>lQlO1G3kO%>sQ`O1G3kO%>xQlO1G3kOOQ[7+)T7+)TO9uQ!0LrO7+)_O`QlO7+)aOOQ['#Kh'#KhOOQ['#JS'#JSO%?PQlO,5>`OOQ[,5>`,5>`O%[QlO'#HuO%?^Q`O'#HwOOQ[,5>f,5>fO9eQ`O,5>fOOQ[,5>h,5>hOOQ[7+)j7+)jOOQ[7+)p7+)pOOQ[7+)t7+)tOOQ[7+)v7+)vO%?cQpO1G5|O%?}Q?MtO1G0zO%@XQ`O1G0zOOQO1G/s1G/sO%@dQ?MtO1G/sO?YQ`O1G/sO!)[QlO'#DmOOQO,5?P,5?POOQO-ERQ`O7+,WO&>WQ`O7+,XO%[QlO7+,WO%[QlO7+,XOOQ[7+)V7+)VO&>]Q`O7+)VO&>bQlO7+)VO&>iQ`O7+)VOOQ[<nQ`O,5>aOOQ[,5>c,5>cO&>sQ`O1G4QO9eQ`O7+&fO!)[QlO7+&fOOQO7+%_7+%_O&>xQ?MtO1G6ZO?YQ`O7+%_OOQ!0Lf<yQ?MvO,5?aO'@|Q?MvO,5?cO'CPQ?MvO7+'|O'DuQMjOG27TOOQO<VO!l$xO#jROe!iOpkOrPO(T)]O(VTO(YUO(aVO(o[O~O!]$_Oa$qa'z$qa'w$qa!k$qa!Y$qa!_$qa%i$qa!g$qa~Ol)dO~P!&zOh%VOp%WOr%XOs$tOt$tOz%YO|%ZO!O%]O!S${O!_$|O!i%bO!l$xO#j%cO$W%`O$t%^O$v%_O$y%aO(T(vO(VTO(YUO(a$uO(y$}O(z%PO~Og(pP~P!,TO!Q)iO!g)hO!_$^X$Z$^X$]$^X$_$^X$f$^X~O!g)hO!_({X$Z({X$]({X$_({X$f({X~O!Q)iO~P!.^O!Q)iO!_({X$Z({X$]({X$_({X$f({X~O!_)kO$Z)oO$])jO$_)jO$f)pO~O![)sO~P!)[O$]$hO$_$gO$f)wO~On$zX!Q$zX#S$zX'y$zX(y$zX(z$zX~OgmXg$zXnmX!]mX#`mX~P!0SOx)yO(b)zO(c)|O~On*VO!Q*OO'y*PO(y$}O(z%PO~Og)}O~P!1WOg*WO~Oh%VOr%XOs$tOt$tOz%YO|%ZO!OVO!l$xO#jVO!l$xO#jROe!iOpkOrPO(VTO(YUO(aVO(o[O~O(T=QO~P#$qO!]-]O!^(iX~O!^-_O~O!g-VO#`-UO!]#hX!^#hX~O!]-`O!^(xX~O!^-bO~O!c-cO!d-cO(U!lO~P#$`O!^-fO~P'_On-iO!_'`O~O!Y-nO~Os!{a!b!{a!c!{a!d!{a#T!{a#U!{a#V!{a#W!{a#X!{a#[!{a#]!{a(U!{a(V!{a(Y!{a(e!{a(o!{a~P!#vO!p-sO#`-qO~PChO!c-uO!d-uO(U!lO~PDWOa%nO#`-qO'z%nO~Oa%nO!g#vO#`-qO'z%nO~Oa%nO!g#vO!p-sO#`-qO'z%nO(r'pO~O(P'xO(Q'xO(R-zO~Ov-{O~O!Y'Wa!]'Wa~P!:tO![.PO!Y'WX!]'WX~P%[O!](VO!Y(ha~O!Y(ha~PHRO!](^O!Y(va~O!S%hO![.TO!_%iO(T%gO!Y'^X!]'^X~O#`.VO!](ta!k(taa(ta'z(ta~O!g#vO~P#,wO!](jO!k(sa~O!S%hO!_%iO#j.ZO(T%gO~Op.`O!S%hO![.]O!_%iO!|]O#i._O#j.]O(T%gO!]'aX!k'aX~OR.dO!l#xO~Oh%VOn.gO!_'`O%i.fO~Oa#ci!]#ci'z#ci'w#ci!Y#ci!k#civ#ci!_#ci%i#ci!g#ci~P!:tOn>]O!Q*OO'y*PO(y$}O(z%PO~O#k#_aa#_a#`#_a'z#_a!]#_a!k#_a!_#_a!Y#_a~P#/sO#k(`XP(`XR(`X[(`Xa(`Xj(`Xr(`X!S(`X!l(`X!p(`X#R(`X#n(`X#o(`X#p(`X#q(`X#r(`X#s(`X#t(`X#u(`X#v(`X#x(`X#z(`X#{(`X'z(`X(a(`X(r(`X!k(`X!Y(`X'w(`Xv(`X!_(`X%i(`X!g(`X~P!6kO!].tO!k(kX~P!:tO!k.wO~O!Y.yO~OP$[OR#zO!Q#yO!S#{O!l#xO!p$[O(aVO[#mia#mij#mir#mi!]#mi#R#mi#o#mi#p#mi#q#mi#r#mi#s#mi#t#mi#u#mi#v#mi#x#mi#z#mi#{#mi'z#mi(r#mi(y#mi(z#mi'w#mi!Y#mi!k#miv#mi!_#mi%i#mi!g#mi~O#n#mi~P#3cO#n$OO~P#3cOP$[OR#zOr$aO!Q#yO!S#{O!l#xO!p$[O#n$OO#o$PO#p$PO#q$PO(aVO[#mia#mij#mi!]#mi#R#mi#s#mi#t#mi#u#mi#v#mi#x#mi#z#mi#{#mi'z#mi(r#mi(y#mi(z#mi'w#mi!Y#mi!k#miv#mi!_#mi%i#mi!g#mi~O#r#mi~P#6QO#r$QO~P#6QOP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!l#xO!p$[O#R$RO#n$OO#o$PO#p$PO#q$PO#r$QO#s$RO#t$RO#u$bO(aVOa#mi!]#mi#x#mi#z#mi#{#mi'z#mi(r#mi(y#mi(z#mi'w#mi!Y#mi!k#miv#mi!_#mi%i#mi!g#mi~O#v#mi~P#8oOP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!l#xO!p$[O#R$RO#n$OO#o$PO#p$PO#q$PO#r$QO#s$RO#t$RO#u$bO#v$SO(aVO(z#}Oa#mi!]#mi#z#mi#{#mi'z#mi(r#mi(y#mi'w#mi!Y#mi!k#miv#mi!_#mi%i#mi!g#mi~O#x$UO~P#;VO#x#mi~P#;VO#v$SO~P#8oOP$[OR#zO[$cOj$ROr$aO!Q#yO!S#{O!l#xO!p$[O#R$RO#n$OO#o$PO#p$PO#q$PO#r$QO#s$RO#t$RO#u$bO#v$SO#x$UO(aVO(y#|O(z#}Oa#mi!]#mi#{#mi'z#mi(r#mi'w#mi!Y#mi!k#miv#mi!_#mi%i#mi!g#mi~O#z#mi~P#={O#z$WO~P#={OP]XR]X[]Xj]Xr]X!Q]X!S]X!l]X!p]X#R]X#S]X#`]X#kfX#n]X#o]X#p]X#q]X#r]X#s]X#t]X#u]X#v]X#x]X#z]X#{]X$Q]X(a]X(r]X(y]X(z]X!]]X!^]X~O$O]X~P#@jOP$[OR#zO[]O!Q*OO'y*PO(y$}O(z%POP#miR#mi!S#mi!l#mi!p#mi#n#mi#o#mi#p#mi#q#mi(a#mi~P#EyO!]/POg(pX~P!1WOg/RO~Oa$Pi!]$Pi'z$Pi'w$Pi!Y$Pi!k$Piv$Pi!_$Pi%i$Pi!g$Pi~P!:tO$]/SO$_/SO~O$]/TO$_/TO~O!g)hO#`/UO!_$cX$Z$cX$]$cX$_$cX$f$cX~O![/VO~O!_)kO$Z/XO$])jO$_)jO$f/YO~O!]VO!l$xO#j^O!Q*OO'y*PO(y$}O(z%POP#miR#mi!S#mi!l#mi!p#mi#n#mi#o#mi#p#mi#q#mi(a#mi~P&,QO#S$dOP(`XR(`X[(`Xj(`Xn(`Xr(`X!Q(`X!S(`X!l(`X!p(`X#R(`X#n(`X#o(`X#p(`X#q(`X#r(`X#s(`X#t(`X#u(`X#v(`X#x(`X#z(`X#{(`X$O(`X'y(`X(a(`X(r(`X(y(`X(z(`X!](`X!^(`X~O$O$Pi!]$Pi!^$Pi~P#BwO$O!ri!^!ri~P$+oOg']a!]']a~P!1WO!^7nO~O!]'da!^'da~P#BwO!Y7oO~P#/sO!g#vO(r'pO!]'ea!k'ea~O!]/pO!k)Oi~O!]/pO!g#vO!k)Oi~Og$|q!]$|q#`$|q$O$|q~P!1WO!Y'ga!]'ga~P#/sO!g7vO~O!]/yO!Y)Pi~P#/sO!]/yO!Y)Pi~O!Y7yO~Oh%VOr8OO!l%eO(r'pO~Oj8QO!g#vO~Or8TO!g#vO(r'pO~O!Q*OO'y*PO(z%POn'ja(y'ja!]'ja#`'ja~Og'ja$O'ja~P&5RO!Q*OO'y*POn'la(y'la(z'la!]'la#`'la~Og'la$O'la~P&5tOg(_q!](_q~P!1WO#`8VOg(_q!](_q~P!1WO!Y8WO~Og%Oq!]%Oq#`%Oq$O%Oq~P!1WOa$oy!]$oy'z$oy'w$oy!Y$oy!k$oyv$oy!_$oy%i$oy!g$oy~P!:tO!g6rO~O!]5[O!_)Qa~O!_'`OP$TaR$Ta[$Taj$Tar$Ta!Q$Ta!S$Ta!]$Ta!l$Ta!p$Ta#R$Ta#n$Ta#o$Ta#p$Ta#q$Ta#r$Ta#s$Ta#t$Ta#u$Ta#v$Ta#x$Ta#z$Ta#{$Ta(a$Ta(r$Ta(y$Ta(z$Ta~O%i7WO~P&8fO%^8[Oa%[i!_%[i'z%[i!]%[i~Oa#cy!]#cy'z#cy'w#cy!Y#cy!k#cyv#cy!_#cy%i#cy!g#cy~P!:tO[8^O~Ob8`O(T+qO(VTO(YUO~O!]1TO!^)Xi~O`8dO~O(e(|O!]'pX!^'pX~O!]5uO!^)Ua~O!^8nO~P%;eO(o!sO~P$&YO#[8oO~O!_1oO~O!_1oO%i8qO~On8tO!_1oO%i8qO~O[8yO!]'sa!^'sa~O!]1zO!^)Vi~O!k8}O~O!k9OO~O!k9RO~O!k9RO~P%[Oa9TO~O!g9UO~O!k9VO~O!](wi!^(wi~P#BwOa%nO#`9_O'z%nO~O!](ty!k(tya(ty'z(ty~P!:tO!](jO!k(sy~O%i9bO~P&8fO!_'`O%i9bO~O#k$|qP$|qR$|q[$|qa$|qj$|qr$|q!S$|q!]$|q!l$|q!p$|q#R$|q#n$|q#o$|q#p$|q#q$|q#r$|q#s$|q#t$|q#u$|q#v$|q#x$|q#z$|q#{$|q'z$|q(a$|q(r$|q!k$|q!Y$|q'w$|q#`$|qv$|q!_$|q%i$|q!g$|q~P#/sO#k'jaP'jaR'ja['jaa'jaj'jar'ja!S'ja!l'ja!p'ja#R'ja#n'ja#o'ja#p'ja#q'ja#r'ja#s'ja#t'ja#u'ja#v'ja#x'ja#z'ja#{'ja'z'ja(a'ja(r'ja!k'ja!Y'ja'w'jav'ja!_'ja%i'ja!g'ja~P&5RO#k'laP'laR'la['laa'laj'lar'la!S'la!l'la!p'la#R'la#n'la#o'la#p'la#q'la#r'la#s'la#t'la#u'la#v'la#x'la#z'la#{'la'z'la(a'la(r'la!k'la!Y'la'w'lav'la!_'la%i'la!g'la~P&5tO#k%OqP%OqR%Oq[%Oqa%Oqj%Oqr%Oq!S%Oq!]%Oq!l%Oq!p%Oq#R%Oq#n%Oq#o%Oq#p%Oq#q%Oq#r%Oq#s%Oq#t%Oq#u%Oq#v%Oq#x%Oq#z%Oq#{%Oq'z%Oq(a%Oq(r%Oq!k%Oq!Y%Oq'w%Oq#`%Oqv%Oq!_%Oq%i%Oq!g%Oq~P#/sO!]'Yi!k'Yi~P!:tO$O#cq!]#cq!^#cq~P#BwO(y$}OP%aaR%aa[%aaj%aar%aa!S%aa!l%aa!p%aa#R%aa#n%aa#o%aa#p%aa#q%aa#r%aa#s%aa#t%aa#u%aa#v%aa#x%aa#z%aa#{%aa$O%aa(a%aa(r%aa!]%aa!^%aa~On%aa!Q%aa'y%aa(z%aa~P&IyO(z%POP%caR%ca[%caj%car%ca!S%ca!l%ca!p%ca#R%ca#n%ca#o%ca#p%ca#q%ca#r%ca#s%ca#t%ca#u%ca#v%ca#x%ca#z%ca#{%ca$O%ca(a%ca(r%ca!]%ca!^%ca~On%ca!Q%ca'y%ca(y%ca~P&LQOn>^O!Q*OO'y*PO(z%PO~P&IyOn>^O!Q*OO'y*PO(y$}O~P&LQOR0kO!Q0kO!S0lO#S$dOP}a[}aj}an}ar}a!l}a!p}a#R}a#n}a#o}a#p}a#q}a#r}a#s}a#t}a#u}a#v}a#x}a#z}a#{}a$O}a'y}a(a}a(r}a(y}a(z}a!]}a!^}a~O!Q*OO'y*POP$saR$sa[$saj$san$sar$sa!S$sa!l$sa!p$sa#R$sa#n$sa#o$sa#p$sa#q$sa#r$sa#s$sa#t$sa#u$sa#v$sa#x$sa#z$sa#{$sa$O$sa(a$sa(r$sa(y$sa(z$sa!]$sa!^$sa~O!Q*OO'y*POP$uaR$ua[$uaj$uan$uar$ua!S$ua!l$ua!p$ua#R$ua#n$ua#o$ua#p$ua#q$ua#r$ua#s$ua#t$ua#u$ua#v$ua#x$ua#z$ua#{$ua$O$ua(a$ua(r$ua(y$ua(z$ua!]$ua!^$ua~On>^O!Q*OO'y*PO(y$}O(z%PO~OP%TaR%Ta[%Taj%Tar%Ta!S%Ta!l%Ta!p%Ta#R%Ta#n%Ta#o%Ta#p%Ta#q%Ta#r%Ta#s%Ta#t%Ta#u%Ta#v%Ta#x%Ta#z%Ta#{%Ta$O%Ta(a%Ta(r%Ta!]%Ta!^%Ta~P''VO$O$mq!]$mq!^$mq~P#BwO$O$oq!]$oq!^$oq~P#BwO!^9oO~O$O9pO~P!1WO!g#vO!]'ei!k'ei~O!g#vO(r'pO!]'ei!k'ei~O!]/pO!k)Oq~O!Y'gi!]'gi~P#/sO!]/yO!Y)Pq~Or9wO!g#vO(r'pO~O[9yO!Y9xO~P#/sO!Y9xO~Oj:PO!g#vO~Og(_y!](_y~P!1WO!]'na!_'na~P#/sOa%[q!_%[q'z%[q!]%[q~P#/sO[:UO~O!]1TO!^)Xq~O`:YO~O#`:ZO!]'pa!^'pa~O!]5uO!^)Ui~P#BwO!S:]O~O!_1oO%i:`O~O(VTO(YUO(e:eO~O!]1zO!^)Vq~O!k:hO~O!k:iO~O!k:jO~O!k:jO~P%[O#`:mO!]#hy!^#hy~O!]#hy!^#hy~P#BwO%i:rO~P&8fO!_'`O%i:rO~O$O#|y!]#|y!^#|y~P#BwOP$|iR$|i[$|ij$|ir$|i!S$|i!l$|i!p$|i#R$|i#n$|i#o$|i#p$|i#q$|i#r$|i#s$|i#t$|i#u$|i#v$|i#x$|i#z$|i#{$|i$O$|i(a$|i(r$|i!]$|i!^$|i~P''VO!Q*OO'y*PO(z%POP'iaR'ia['iaj'ian'iar'ia!S'ia!l'ia!p'ia#R'ia#n'ia#o'ia#p'ia#q'ia#r'ia#s'ia#t'ia#u'ia#v'ia#x'ia#z'ia#{'ia$O'ia(a'ia(r'ia(y'ia!]'ia!^'ia~O!Q*OO'y*POP'kaR'ka['kaj'kan'kar'ka!S'ka!l'ka!p'ka#R'ka#n'ka#o'ka#p'ka#q'ka#r'ka#s'ka#t'ka#u'ka#v'ka#x'ka#z'ka#{'ka$O'ka(a'ka(r'ka(y'ka(z'ka!]'ka!^'ka~O(y$}OP%aiR%ai[%aij%ain%air%ai!Q%ai!S%ai!l%ai!p%ai#R%ai#n%ai#o%ai#p%ai#q%ai#r%ai#s%ai#t%ai#u%ai#v%ai#x%ai#z%ai#{%ai$O%ai'y%ai(a%ai(r%ai(z%ai!]%ai!^%ai~O(z%POP%ciR%ci[%cij%cin%cir%ci!Q%ci!S%ci!l%ci!p%ci#R%ci#n%ci#o%ci#p%ci#q%ci#r%ci#s%ci#t%ci#u%ci#v%ci#x%ci#z%ci#{%ci$O%ci'y%ci(a%ci(r%ci(y%ci!]%ci!^%ci~O$O$oy!]$oy!^$oy~P#BwO$O#cy!]#cy!^#cy~P#BwO!g#vO!]'eq!k'eq~O!]/pO!k)Oy~O!Y'gq!]'gq~P#/sOr:|O!g#vO(r'pO~O[;QO!Y;PO~P#/sO!Y;PO~Og(_!R!](_!R~P!1WOa%[y!_%[y'z%[y!]%[y~P#/sO!]1TO!^)Xy~O!]5uO!^)Uq~O(T;XO~O!_1oO%i;[O~O!k;_O~O%i;dO~P&8fOP$|qR$|q[$|qj$|qr$|q!S$|q!l$|q!p$|q#R$|q#n$|q#o$|q#p$|q#q$|q#r$|q#s$|q#t$|q#u$|q#v$|q#x$|q#z$|q#{$|q$O$|q(a$|q(r$|q!]$|q!^$|q~P''VO!Q*OO'y*PO(z%POP'jaR'ja['jaj'jan'jar'ja!S'ja!l'ja!p'ja#R'ja#n'ja#o'ja#p'ja#q'ja#r'ja#s'ja#t'ja#u'ja#v'ja#x'ja#z'ja#{'ja$O'ja(a'ja(r'ja(y'ja!]'ja!^'ja~O!Q*OO'y*POP'laR'la['laj'lan'lar'la!S'la!l'la!p'la#R'la#n'la#o'la#p'la#q'la#r'la#s'la#t'la#u'la#v'la#x'la#z'la#{'la$O'la(a'la(r'la(y'la(z'la!]'la!^'la~OP%OqR%Oq[%Oqj%Oqr%Oq!S%Oq!l%Oq!p%Oq#R%Oq#n%Oq#o%Oq#p%Oq#q%Oq#r%Oq#s%Oq#t%Oq#u%Oq#v%Oq#x%Oq#z%Oq#{%Oq$O%Oq(a%Oq(r%Oq!]%Oq!^%Oq~P''VOg%e!Z!]%e!Z#`%e!Z$O%e!Z~P!1WO!Y;hO~P#/sOr;iO!g#vO(r'pO~O[;kO!Y;hO~P#/sO!]'pq!^'pq~P#BwO!]#h!Z!^#h!Z~P#BwO#k%e!ZP%e!ZR%e!Z[%e!Za%e!Zj%e!Zr%e!Z!S%e!Z!]%e!Z!l%e!Z!p%e!Z#R%e!Z#n%e!Z#o%e!Z#p%e!Z#q%e!Z#r%e!Z#s%e!Z#t%e!Z#u%e!Z#v%e!Z#x%e!Z#z%e!Z#{%e!Z'z%e!Z(a%e!Z(r%e!Z!k%e!Z!Y%e!Z'w%e!Z#`%e!Zv%e!Z!_%e!Z%i%e!Z!g%e!Z~P#/sOr;tO!g#vO(r'pO~O!Y;uO~P#/sOr;|O!g#vO(r'pO~O!Y;}O~P#/sOP%e!ZR%e!Z[%e!Zj%e!Zr%e!Z!S%e!Z!l%e!Z!p%e!Z#R%e!Z#n%e!Z#o%e!Z#p%e!Z#q%e!Z#r%e!Z#s%e!Z#t%e!Z#u%e!Z#v%e!Z#x%e!Z#z%e!Z#{%e!Z$O%e!Z(a%e!Z(r%e!Z!]%e!Z!^%e!Z~P''VOrROe!iOpkOrPO(T)]O(VTO(YUO(aVO(o[O~O!]WO!l$xO#jgPPP!>oI[PPPPPPPPP!BOP!C]PPI[!DnPI[PI[I[I[I[I[PI[!FQP!I[P!LbP!Lf!Lp!Lt!LtP!IXP!Lx!LxP#!OP#!SI[PI[#!Y#%_CjA^PA^PA^A^P#&lA^A^#)OA^#+vA^#.SA^A^#.r#1W#1W#1]#1f#1W#1qPP#1WPA^#2ZA^#6YA^A^6mPPP#:_PPP#:x#:xP#:xP#;`#:xPP#;fP#;]P#;]#;y#;]#P#>V#>]#>k#>q#>{#?R#?]#?c#?s#?y#@k#@}#AT#AZ#Ai#BO#Cs#DR#DY#Et#FS#Gt#HS#HY#H`#Hf#Hp#Hv#H|#IW#Ij#IpPPPPPPPPPPP#IvPPPPPPP#Jk#Mx$ b$ i$ qPPP$']P$'f$*_$0x$0{$1O$1}$2Q$2X$2aP$2g$2jP$3W$3[$4S$5b$5g$5}PP$6S$6Y$6^$6a$6e$6i$7e$7|$8e$8i$8l$8o$8y$8|$9Q$9UR!|RoqOXst!Z#d%m&r&t&u&w,s,x2[2_Y!vQ'`-e1o5{Q%tvQ%|yQ&T|Q&j!VS'W!e-]Q'f!iS'l!r!yU*k$|*Z*oQ+o%}S+|&V&WQ,d&dQ-c'_Q-m'gQ-u'mQ0[*qQ1b,OQ1y,eR<{SU+P%]S!S!nQ!r!v!y!z$|'W'_'`'l'm'n*k*o*q*r-]-c-e-u0[0_1o5{5}%[$ti#v$b$c$d$x${%O%Q%^%_%c)y*R*T*V*Y*a*g*w*x+f+i,S,V.f/P/d/m/x/y/{0`0b0i0j0o1f1i1q3c4^4_4j4o5Q5[5_6S7W7v8Q8V8[8q9b9p9y:P:`:r;Q;[;d;kP>X>Y>]>^Q&X|Q'U!eS'[%i-`Q+t&PQ,P&WQ,f&gQ0n+SQ1Y+uQ1_+{Q2Q,jQ2R,kQ5f1TQ5o1aQ6[1zQ6_1|Q6`2PQ8`5gQ8c5lQ8|6bQ:X8dQ:f8yQ;V:YR<}*ZrnOXst!V!Z#d%m&i&r&t&u&w,s,x2[2_R,h&k&z^OPXYstuvwz!Z!`!g!j!o#S#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n%m%t&R&k&n&o&r&t&u&w&{'T'b'r(V(](d(x(z)O)s)}*i+X+]+g,p,s,x-U-X-i-q.P.V.g.t.{/V/n0]0l0r1S1r2S2T2V2X2[2_2a2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:mR>S[#]WZ#W#Z'X(T!b%jm#h#i#l$x%e%h(^(h(i(j*Y*^*b+Z+[+^,o-V.T.Z.[.]._/m/p2d3[3]4a6r7TQ%wxQ%{yW&Q|&V&W,OQ&_!TQ'c!hQ'e!iQ(q#sS+n%|%}Q+r&PQ,_&bQ,c&dS-l'f'gQ.i(rQ1R+oQ1X+uQ1Z+vQ1^+zQ1t,`S1x,d,eQ2|-mQ5e1TQ5i1WQ5n1`Q6Z1yQ8_5gQ8b5kQ8f5pQ:T8^R;T:U!U$zi$d%O%Q%^%_%c*R*T*a*w*x/P/x0`0b0i0j0o4_5Q8V9p>P>X>Y!^%yy!i!u%{%|%}'V'e'f'g'k'u*j+n+o-Y-l-m-t0R0U1R2u2|3T4r4s4v7}9{Q+h%wQ,T&[Q,W&]Q,b&dQ.h(qQ1s,_U1w,c,d,eQ3e.iQ6U1tS6Y1x1yQ8x6Z#f>T#v$b$c$x${)y*V*Y*g+f+i,S,V.f/d/m/y/{1f1i1q3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k]>^o>UPS&[!Q&iQ&]!RQ&^!SU*}%[%d=sR,R&Y%]%Si#v$b$c$d$x${%O%Q%^%_%c)y*R*T*V*Y*a*g*w*x+f+i,S,V.f/P/d/m/x/y/{0`0b0i0j0o1f1i1q3c4^4_4j4o5Q5[5_6S7W7v8Q8V8[8q9b9p9y:P:`:r;Q;[;d;kP>X>Y>]>^T)z$u){V+P%]S$i$^c#Y#e%q%s%u(S(Y(t(y)R)S)T)U)V)W)X)Y)Z)[)^)`)b)g)q+d+x-Z-x-}.S.U.s.v.z.|.}/O/b0p2k2n3O3V3k3p3q3r3s3t3u3v3w3x3y3z3{3|4P4Q4X5X5c6u6{7Q7a7b7k7l8k9X9]9g9m9n:o;W;`SQ'Y!eR2q-]!W!nQ!e!r!v!y!z$|'W'_'`'l'm'n*Z*k*o*q*r-]-c-e-u0[0_1o5{5}R1l,ZnqOXst!Z#d%m&r&t&u&w,s,x2[2_Q&y!^Q'v!xS(s#u<^Q+l%zQ,]&_Q,^&aQ-j'dQ-w'oS.r(x=PS0q+X=ZQ1P+mQ1n,[Q2c,zQ2e,{Q2m-WQ2z-kQ2}-oS5Y0r=eQ5a1QS5d1S=fQ6t2oQ6x2{Q6}3SQ8]5bQ9Y6vQ9Z6yQ9^7OR:l9V$d$]c#Y#e%s%u(S(Y(t(y)R)S)T)U)V)W)X)Y)Z)[)^)`)b)g)q+d+x-Z-x-}.S.U.s.v.z.}/O/b0p2k2n3O3V3k3p3q3r3s3t3u3v3w3x3y3z3{3|4P4Q4X5X5c6u6{7Q7a7b7k7l8k9X9]9g9m9n:o;W;`SS#q]SU$fd)_,mS(p#p'iU*v%R(w4OU0m+O.n7gQ5^0xQ7V3`Q9d7YR:s9em!tQ!r!v!y!z'`'l'm'n-e-u1o5{5}Q't!uS(f#g2US-s'k'wQ/s*]Q0R*jQ3U-vQ4f/tQ4r0TQ4s0UQ4x0^Q7r4`S7}4t4vS8R4y4{Q9r7sQ9v7yQ9{8OQ:Q8TS:{9w9xS;g:|;PS;s;h;iS;{;t;uSSR=o>R%^bOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&o&r&t&u&w&{'T'b'r(T(V(](d(x(z)O)}*i+X+]+g,p,s,x-i-q.P.V.g.t.{/n0]0l0r1S1r2S2T2V2X2[2_2a3Q3W3d3l4z6T6e6f6i6|7[8t9T9_Q%fj!^%xy!i!u%{%|%}'V'e'f'g'k'u*j+n+o-Y-l-m-t0R0U1R2u2|3T4r4s4v7}9{S&Oz!jQ+k%yQ,a&dW1v,b,c,d,eU6X1w1x1yS8w6Y6ZQ:d8x!r=j$Z$n'X)s-U-X/V2p4T5w6s:Z:mSQ=t>QR=u>R%QeOPXYstuvw!Z!`!g!o#S#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&r&t&u&w&{'T'b'r(V(](d(x(z)O)}*i+X+]+g,p,s,x-i-q.P.V.g.t.{/n0]0l0r1S1r2S2T2V2X2[2_2a3Q3W3d3l4z6T6e6f6i6|7[8t9T9_Y#bWZ#W#Z(T!b%jm#h#i#l$x%e%h(^(h(i(j*Y*^*b+Z+[+^,o-V.T.Z.[.]._/m/p2d3[3]4a6r7TQ,n&o!p=k$Z$n)s-U-X/V2p4T5w6s:Z:mSR=n'XU']!e%i*ZR2s-`%SdOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&r&t&u&w&{'T'b'r(T(V(](d(x(z)O)}*i+X+],p,s,x-i-q.P.V.t.{/n0]0l0r1S1r2S2T2V2X2[2_2a3Q3W3l4z6T6e6f6i6|8t9T9_!r)_$Z$n'X)s-U-X/V2p4T5w6s:Z:mSQ,m&oQ0x+gQ3`.gQ7Y3dR9e7[!b$Tc#Y%q(S(Y(t(y)Z)[)`)g+x-x-}.S.U.s.v/b0p3O3V3k3{5X5c6{7Q7a9]:oS)^)q-Z.|2k2n3p4P4X6u7b7k7l8k9X9g9m9n;W;`=vQ>X>ZR>Y>['QkOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n%m%t&R&k&n&o&r&t&u&w&{'T'X'b'r(T(V(](d(x(z)O)s)}*i+X+]+g,p,s,x-U-X-i-q.P.V.g.t.{/V/n0]0l0r1S1r2S2T2V2X2[2_2a2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:mSS$oh$pR4U/U'XgOPWXYZhstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n$p%m%t&R&k&n&o&r&t&u&w&{'T'X'b'r(T(V(](d(x(z)O)s)}*i+X+]+g,p,s,x-U-X-i-q.P.V.g.t.{/U/V/n0]0l0r1S1r2S2T2V2X2[2_2a2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:mST$kf$qQ$ifS)j$l)nR)v$qT$jf$qT)l$l)n'XhOPWXYZhstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$Z$_$a$e$n$p%m%t&R&k&n&o&r&t&u&w&{'T'X'b'r(T(V(](d(x(z)O)s)}*i+X+]+g,p,s,x-U-X-i-q.P.V.g.t.{/U/V/n0]0l0r1S1r2S2T2V2X2[2_2a2p3Q3W3d3l4T4z5w6T6e6f6i6s6|7[8t9T9_:Z:mST$oh$pQ$rhR)u$p%^jOPWXYZstuvw!Z!`!g!o#S#W#Z#d#o#u#x#{$O$P$Q$R$S$T$U$V$W$X$_$a$e%m%t&R&k&n&o&r&t&u&w&{'T'b'r(T(V(](d(x(z)O)}*i+X+]+g,p,s,x-i-q.P.V.g.t.{/n0]0l0r1S1r2S2T2V2X2[2_2a3Q3W3d3l4z6T6e6f6i6|7[8t9T9_!s>Q$Z$n'X)s-U-X/V2p4T5w6s:Z:mS#glOPXZst!Z!`!o#S#d#o#{$n%m&k&n&o&r&t&u&w&{'T'b)O)s*i+]+g,p,s,x-i.g/V/n0]0l1r2S2T2V2X2[2_2a3d4T4z6T6e6f6i7[8t9T!U%Ri$d%O%Q%^%_%c*R*T*a*w*x/P/x0`0b0i0j0o4_5Q8V9p>P>X>Y#f(w#v$b$c$x${)y*V*Y*g+f+i,S,V.f/d/m/y/{1f1i1q3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k]>^Q+T%aQ/c*Oo4OP>X>YQ*c$zU*l$|*Z*oQ+U%bQ0W*m#f=q#v$b$c$x${)y*V*Y*g+f+i,S,V.f/d/m/y/{1f1i1q3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k]>^n=rTQ=x>UQ=y>VR=z>W!U%Ri$d%O%Q%^%_%c*R*T*a*w*x/P/x0`0b0i0j0o4_5Q8V9p>P>X>Y#f(w#v$b$c$x${)y*V*Y*g+f+i,S,V.f/d/m/y/{1f1i1q3c4^4j4o5[5_6S7W7v8Q8[8q9b9y:P:`:r;Q;[;d;k]>^o4OP>X>Y>]>^Q,U&]Q1h,WQ5s1gR8h5tV*n$|*Z*oU*n$|*Z*oT5z1o5{S0P*i/nQ4w0]T8S4z:]Q+j%xQ0V*lQ1O+kQ1u,aQ6W1vQ8v6XQ:c8wR;^:d!U%Oi$d%O%Q%^%_%c*R*T*a*w*x/P/x0`0b0i0j0o4_5Q8V9p>P>X>Yx*R$v)e*S*u+V/v0d0e4R4g5R5S5W7p8U:R:x=p=}>OS0`*t0a#f]>^nZ>[`=T3}7c7f7j9h:t:w;yS=_.l3iT=`7e9k!U%Qi$d%O%Q%^%_%c*R*T*a*w*x/P/x0`0b0i0j0o4_5Q8V9p>P>X>Y|*T$v)e*U*t+V/g/v0d0e4R4g4|5R5S5W7p8U:R:x=p=}>OS0b*u0c#f]>^nZ>[d=V3}7d7e7j9h9i:t:u:w;yS=a.m3jT=b7f9lrnOXst!V!Z#d%m&i&r&t&u&w,s,x2[2_Q&f!UR,p&ornOXst!V!Z#d%m&i&r&t&u&w,s,x2[2_R&f!UQ,Y&^R1d,RsnOXst!V!Z#d%m&i&r&t&u&w,s,x2[2_Q1p,_S6R1s1tU8p6P6Q6US:_8r8sS;Y:^:aQ;m;ZR;w;nQ&m!VR,i&iR6_1|R:f8yW&Q|&V&W,OR1Z+vQ&r!WR,s&sR,y&xT2],x2_R,}&yQ,|&yR2f,}Q'y!{R-y'ySsOtQ#dXT%ps#dQ#OTR'{#OQ#RUR'}#RQ){$uR/`){Q#UVR(Q#UQ#XWU(W#X(X.QQ(X#YR.Q(YQ-^'YR2r-^Q.u(yS3m.u3nR3n.vQ-e'`R2v-eY!rQ'`-e1o5{R'j!rQ/Q)eR4S/QU#_W%h*YU(_#_(`.RQ(`#`R.R(ZQ-a']R2t-at`OXst!V!Z#d%m&i&k&r&t&u&w,s,x2[2_S#hZ%eU#r`#h.[R.[(jQ(k#jQ.X(gW.a(k.X3X7RQ3X.YR7R3YQ)n$lR/W)nQ$phR)t$pQ$`cU)a$`-|O>Z>[Q/z*eU4k/z4m7xQ4m/|R7x4lS*o$|*ZR0Y*ox*S$v)e*t*u+V/v0d0e4R4g5R5S5W7p8U:R:x=p=}>O!d.j(u)c*[*e.l.m.q/_/k/|0v1e3h4[4h4l5r7]7`7w7z8X8Z9t9|:S:};R;e;j;v>Z>[U/h*S.j7ca7c3}7e7f7j9h:t:w;yQ0a*tQ3i.lU4}0a3i9kR9k7e|*U$v)e*t*u+V/g/v0d0e4R4g4|5R5S5W7p8U:R:x=p=}>O!h.k(u)c*[*e.l.m.q/_/k/|0v1e3f3h4[4h4l5r7]7^7`7w7z8X8Z9t9|:S:};R;e;j;v>Z>[U/j*U.k7de7d3}7e7f7j9h9i:t:u:w;yQ0c*uQ3j.mU5P0c3j9lR9l7fQ*z%UR0g*zQ5]0vR8Y5]Q+_%kR0u+_Q5v1jS8j5v:[R:[8kQ,[&_R1m,[Q5{1oR8m5{Q1{,fS6]1{8zR8z6_Q1U+rW5h1U5j8a:VQ5j1XQ8a5iR:V8bQ+w&QR1[+wQ2_,xR6m2_YrOXst#dQ&v!ZQ+a%mQ,r&rQ,t&tQ,u&uQ,w&wQ2Y,sS2],x2_R6l2[Q%opQ&z!_Q&}!aQ'P!bQ'R!cQ'q!uQ+`%lQ+l%zQ,Q&XQ,h&mQ-P&|W-p'k's't'wQ-w'oQ0X*nQ1P+mQ1c,PS2O,i,lQ2g-OQ2h-RQ2i-SQ2}-oW3P-r-s-v-xQ5a1QQ5m1_Q5q1eQ6V1uQ6a2QQ6k2ZU6z3O3R3UQ6}3SQ8]5bQ8e5oQ8g5rQ8l5zQ8u6WQ8{6`S9[6{7PQ9^7OQ:W8cQ:b8vQ:g8|Q:n9]Q;U:XQ;]:cQ;a:oQ;l;VR;o;^Q%zyQ'd!iQ'o!uU+m%{%|%}Q-W'VU-k'e'f'gS-o'k'uQ0Q*jS1Q+n+oQ2o-YS2{-l-mQ3S-tS4p0R0UQ5b1RQ6v2uQ6y2|Q7O3TU7{4r4s4vQ9z7}R;O9{S$wi>PR*{%VU%Ui%V>PR0f*yQ$viS(u#v+iS)c$b$cQ)e$dQ*[$xS*e${*YQ*t%OQ*u%QQ+Q%^Q+R%_Q+V%cQ.lPQ=}>XQ>O>YQ>Z>]R>[>^Q+O%]Q.nSR#[WR'Z!el!tQ!r!v!y!z'`'l'm'n-e-u1o5{5}S'V!e-]U*j$|*Z*oS-Y'W'_S0U*k*qQ0^*rQ2u-cQ4v0[R4{0_R({#xQ!fQT-d'`-e]!qQ!r'`-e1o5{Q#p]R'i < TypeParamList in out const TypeDefinition extends ThisType this LiteralType ArithOp Number BooleanLiteral TemplateType InterpolationEnd Interpolation InterpolationStart NullType null VoidType void TypeofType typeof MemberExpression . PropertyName [ TemplateString Escape Interpolation super RegExp ] ArrayExpression Spread , } { ObjectExpression Property async get set PropertyDefinition Block : NewTarget new NewExpression ) ( ArgList UnaryExpression delete LogicOp BitOp YieldExpression yield AwaitExpression await ParenthesizedExpression ClassExpression class ClassBody MethodDeclaration Decorator @ MemberExpression PrivatePropertyName CallExpression TypeArgList CompareOp < declare Privacy static abstract override PrivatePropertyDefinition PropertyDeclaration readonly accessor Optional TypeAnnotation Equals StaticBlock FunctionExpression ArrowFunction ParamList ParamList ArrayPattern ObjectPattern PatternProperty Privacy readonly Arrow MemberExpression BinaryExpression ArithOp ArithOp ArithOp ArithOp BitOp CompareOp instanceof satisfies CompareOp BitOp BitOp BitOp LogicOp LogicOp ConditionalExpression LogicOp LogicOp AssignmentExpression UpdateOp PostfixExpression CallExpression InstantiationExpression TaggedTemplateExpression DynamicImport import ImportMeta JSXElement JSXSelfCloseEndTag JSXSelfClosingTag JSXIdentifier JSXBuiltin JSXIdentifier JSXNamespacedName JSXMemberExpression JSXSpreadAttribute JSXAttribute JSXAttributeValue JSXEscape JSXEndTag JSXOpenTag JSXFragmentTag JSXText JSXEscape JSXStartCloseTag JSXCloseTag PrefixCast < ArrowFunction TypeParamList SequenceExpression InstantiationExpression KeyofType keyof UniqueType unique ImportType InferredType infer TypeName ParenthesizedType FunctionSignature ParamList NewSignature IndexedType TupleType Label ArrayType ReadonlyType ObjectType MethodType PropertyType IndexSignature PropertyDefinition CallSignature TypePredicate asserts is NewSignature new UnionType LogicOp IntersectionType LogicOp ConditionalType ParameterizedType ClassDeclaration abstract implements type VariableDeclaration let var using TypeAliasDeclaration InterfaceDeclaration interface EnumDeclaration enum EnumBody NamespaceDeclaration namespace module AmbientDeclaration declare GlobalDeclaration global ClassDeclaration ClassBody AmbientFunctionDeclaration ExportGroup VariableName VariableName ImportDeclaration defer ImportGroup ForStatement for ForSpec ForInSpec ForOfSpec of WhileStatement while WithStatement with DoStatement do IfStatement if else SwitchStatement switch SwitchBody CaseLabel case DefaultLabel TryStatement try CatchClause catch FinallyClause finally ReturnStatement return ThrowStatement throw BreakStatement break ContinueStatement continue DebuggerStatement debugger LabeledStatement ExpressionStatement SingleExpression SingleClassItem",maxTerm:380,context:rA,nodeProps:[["isolate",-8,5,6,14,37,39,51,53,55,""],["group",-26,9,17,19,68,207,211,215,216,218,221,224,234,237,243,245,247,249,252,258,264,266,268,270,272,274,275,"Statement",-34,13,14,32,35,36,42,51,54,55,57,62,70,72,76,80,82,84,85,110,111,120,121,136,139,141,142,143,144,145,147,148,167,169,171,"Expression",-23,31,33,37,41,43,45,173,175,177,178,180,181,182,184,185,186,188,189,190,201,203,205,206,"Type",-3,88,103,109,"ClassItem"],["openedBy",23,"<",38,"InterpolationStart",56,"[",60,"{",73,"(",160,"JSXStartCloseTag"],["closedBy",-2,24,168,">",40,"InterpolationEnd",50,"]",61,"}",74,")",165,"JSXEndTag"]],propSources:[fA],skippedNodes:[0,5,6,278],repeatNodeCount:37,tokenData:"$Fq07[R!bOX%ZXY+gYZ-yZ[+g[]%Z]^.c^p%Zpq+gqr/mrs3cst:_tuEruvJSvwLkwx! Yxy!'iyz!(sz{!)}{|!,q|}!.O}!O!,q!O!P!/Y!P!Q!9j!Q!R#:O!R![#<_![!]#I_!]!^#Jk!^!_#Ku!_!`$![!`!a$$v!a!b$*T!b!c$,r!c!}Er!}#O$-|#O#P$/W#P#Q$4o#Q#R$5y#R#SEr#S#T$7W#T#o$8b#o#p$x#r#s$@U#s$f%Z$f$g+g$g#BYEr#BY#BZ$A`#BZ$ISEr$IS$I_$A`$I_$I|Er$I|$I}$Dk$I}$JO$Dk$JO$JTEr$JT$JU$A`$JU$KVEr$KV$KW$A`$KW&FUEr&FU&FV$A`&FV;'SEr;'S;=`I|<%l?HTEr?HT?HU$A`?HUOEr(n%d_$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z&j&hT$i&jO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c&j&zP;=`<%l&c'|'U]$i&j(Z!bOY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}!b(SU(Z!bOY'}Zw'}x#O'}#P;'S'};'S;=`(f<%lO'}!b(iP;=`<%l'}'|(oP;=`<%l&}'[(y]$i&j(WpOY(rYZ&cZr(rrs&cs!^(r!^!_)r!_#O(r#O#P&c#P#o(r#o#p)r#p;'S(r;'S;=`*a<%lO(rp)wU(WpOY)rZr)rs#O)r#P;'S)r;'S;=`*Z<%lO)rp*^P;=`<%l)r'[*dP;=`<%l(r#S*nX(Wp(Z!bOY*gZr*grs'}sw*gwx)rx#O*g#P;'S*g;'S;=`+Z<%lO*g#S+^P;=`<%l*g(n+dP;=`<%l%Z07[+rq$i&j(Wp(Z!b'|0/lOX%ZXY+gYZ&cZ[+g[p%Zpq+gqr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p$f%Z$f$g+g$g#BY%Z#BY#BZ+g#BZ$IS%Z$IS$I_+g$I_$JT%Z$JT$JU+g$JU$KV%Z$KV$KW+g$KW&FU%Z&FU&FV+g&FV;'S%Z;'S;=`+a<%l?HT%Z?HT?HU+g?HUO%Z07[.ST(X#S$i&j'}0/lO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c07[.n_$i&j(Wp(Z!b'}0/lOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z)3p/x`$i&j!p),Q(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`0z!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW1V`#v(Ch$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_!`2X!`#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z(KW2d_#v(Ch$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'At3l_(V':f$i&j(Z!bOY4kYZ5qZr4krs7nsw4kwx5qx!^4k!^!_8p!_#O4k#O#P5q#P#o4k#o#p8p#p;'S4k;'S;=`:X<%lO4k(^4r_$i&j(Z!bOY4kYZ5qZr4krs7nsw4kwx5qx!^4k!^!_8p!_#O4k#O#P5q#P#o4k#o#p8p#p;'S4k;'S;=`:X<%lO4k&z5vX$i&jOr5qrs6cs!^5q!^!_6y!_#o5q#o#p6y#p;'S5q;'S;=`7h<%lO5q&z6jT$d`$i&jO!^&c!_#o&c#p;'S&c;'S;=`&w<%lO&c`6|TOr6yrs7]s;'S6y;'S;=`7b<%lO6y`7bO$d``7eP;=`<%l6y&z7kP;=`<%l5q(^7w]$d`$i&j(Z!bOY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}!r8uZ(Z!bOY8pYZ6yZr8prs9hsw8pwx6yx#O8p#O#P6y#P;'S8p;'S;=`:R<%lO8p!r9oU$d`(Z!bOY'}Zw'}x#O'}#P;'S'};'S;=`(f<%lO'}!r:UP;=`<%l8p(^:[P;=`<%l4k%9[:hh$i&j(Wp(Z!bOY%ZYZ&cZq%Zqr`#P#o`x!^=^!^!_?q!_#O=^#O#P>`#P#o=^#o#p?q#p;'S=^;'S;=`@h<%lO=^&n>gXWS$i&jOY>`YZ&cZ!^>`!^!_?S!_#o>`#o#p?S#p;'S>`;'S;=`?k<%lO>`S?XSWSOY?SZ;'S?S;'S;=`?e<%lO?SS?hP;=`<%l?S&n?nP;=`<%l>`!f?xWWS(Z!bOY?qZw?qwx?Sx#O?q#O#P?S#P;'S?q;'S;=`@b<%lO?q!f@eP;=`<%l?q(Q@kP;=`<%l=^'`@w]WS$i&j(WpOY@nYZ&cZr@nrs>`s!^@n!^!_Ap!_#O@n#O#P>`#P#o@n#o#pAp#p;'S@n;'S;=`Bg<%lO@ntAwWWS(WpOYApZrAprs?Ss#OAp#O#P?S#P;'SAp;'S;=`Ba<%lOAptBdP;=`<%lAp'`BjP;=`<%l@n#WBvYWS(Wp(Z!bOYBmZrBmrs?qswBmwxApx#OBm#O#P?S#P;'SBm;'S;=`Cf<%lOBm#WCiP;=`<%lBm(rCoP;=`<%l^!Q^$i&j!X7`OY!=yYZ&cZ!P!=y!P!Q!>|!Q!^!=y!^!_!@c!_!}!=y!}#O!CW#O#P!Dy#P#o!=y#o#p!@c#p;'S!=y;'S;=`!Ek<%lO!=y|#X#Z&c#Z#[!>|#[#]&c#]#^!>|#^#a&c#a#b!>|#b#g&c#g#h!>|#h#i&c#i#j!>|#j#k!>|#k#m&c#m#n!>|#n#o&c#p;'S&c;'S;=`&w<%lO&c7`!@hX!X7`OY!@cZ!P!@c!P!Q!AT!Q!}!@c!}#O!Ar#O#P!Bq#P;'S!@c;'S;=`!CQ<%lO!@c7`!AYW!X7`#W#X!AT#Z#[!AT#]#^!AT#a#b!AT#g#h!AT#i#j!AT#j#k!AT#m#n!AT7`!AuVOY!ArZ#O!Ar#O#P!B[#P#Q!@c#Q;'S!Ar;'S;=`!Bk<%lO!Ar7`!B_SOY!ArZ;'S!Ar;'S;=`!Bk<%lO!Ar7`!BnP;=`<%l!Ar7`!BtSOY!@cZ;'S!@c;'S;=`!CQ<%lO!@c7`!CTP;=`<%l!@c^!Ezl$i&j(Z!b!X7`OY&}YZ&cZw&}wx&cx!^&}!^!_'}!_#O&}#O#P&c#P#W&}#W#X!Eq#X#Z&}#Z#[!Eq#[#]&}#]#^!Eq#^#a&}#a#b!Eq#b#g&}#g#h!Eq#h#i&}#i#j!Eq#j#k!Eq#k#m&}#m#n!Eq#n#o&}#o#p'}#p;'S&};'S;=`(l<%lO&}8r!GyZ(Z!b!X7`OY!GrZw!Grwx!@cx!P!Gr!P!Q!Hl!Q!}!Gr!}#O!JU#O#P!Bq#P;'S!Gr;'S;=`!J|<%lO!Gr8r!Hse(Z!b!X7`OY'}Zw'}x#O'}#P#W'}#W#X!Hl#X#Z'}#Z#[!Hl#[#]'}#]#^!Hl#^#a'}#a#b!Hl#b#g'}#g#h!Hl#h#i'}#i#j!Hl#j#k!Hl#k#m'}#m#n!Hl#n;'S'};'S;=`(f<%lO'}8r!JZX(Z!bOY!JUZw!JUwx!Arx#O!JU#O#P!B[#P#Q!Gr#Q;'S!JU;'S;=`!Jv<%lO!JU8r!JyP;=`<%l!JU8r!KPP;=`<%l!Gr>^!KZ^$i&j(Z!bOY!KSYZ&cZw!KSwx!CWx!^!KS!^!_!JU!_#O!KS#O#P!DR#P#Q!^!LYP;=`<%l!KS>^!L`P;=`<%l!_#c#d#Bq#d#l%Z#l#m#Es#m#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#_#c#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#>j_$i&j(Wp(Z!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#?rd$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!R#AQ!R!S#AQ!S!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#AQ#S#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#A]f$i&j(Wp(Z!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!R#AQ!R!S#AQ!S!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#AQ#S#b%Z#b#c#>_#c#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#Bzc$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!Y#DV!Y!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#DV#S#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#Dbe$i&j(Wp(Z!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q!Y#DV!Y!^%Z!^!_*g!_#O%Z#O#P&c#P#R%Z#R#S#DV#S#b%Z#b#c#>_#c#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#E|g$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q![#Ge![!^%Z!^!_*g!_!c%Z!c!i#Ge!i#O%Z#O#P&c#P#R%Z#R#S#Ge#S#T%Z#T#Z#Ge#Z#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z'Ad#Gpi$i&j(Wp(Z!bs'9tOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!Q%Z!Q![#Ge![!^%Z!^!_*g!_!c%Z!c!i#Ge!i#O%Z#O#P&c#P#R%Z#R#S#Ge#S#T%Z#T#Z#Ge#Z#b%Z#b#c#>_#c#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z*)x#Il_!g$b$i&j$O)Lv(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z)[#Jv_al$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z04f#LS^h#)`#R-v$?V_!^(CdvBr$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z?O$@a_!q7`$i&j(Wp(Z!bOY%ZYZ&cZr%Zrs&}sw%Zwx(rx!^%Z!^!_*g!_#O%Z#O#P&c#P#o%Z#o#p*g#p;'S%Z;'S;=`+a<%lO%Z07[$Aq|$i&j(Wp(Z!b'|0/l$]#t(T,2j(e$I[OX%ZXY+gYZ&cZ[+g[p%Zpq+gqr%Zrs&}st%ZtuEruw%Zwx(rx}%Z}!OGv!O!Q%Z!Q![Er![!^%Z!^!_*g!_!c%Z!c!}Er!}#O%Z#O#P&c#P#R%Z#R#SEr#S#T%Z#T#oEr#o#p*g#p$f%Z$f$g+g$g#BYEr#BY#BZ$A`#BZ$ISEr$IS$I_$A`$I_$JTEr$JT$JU$A`$JU$KVEr$KV$KW$A`$KW&FUEr&FU&FV$A`&FV;'SEr;'S;=`I|<%l?HTEr?HT?HU$A`?HUOEr07[$D|k$i&j(Wp(Z!b'}0/l$]#t(T,2j(e$I[OY%ZYZ&cZr%Zrs&}st%ZtuEruw%Zwx(rx}%Z}!OGv!O!Q%Z!Q![Er![!^%Z!^!_*g!_!c%Z!c!}Er!}#O%Z#O#P&c#P#R%Z#R#SEr#S#T%Z#T#oEr#o#p*g#p$g%Z$g;'SEr;'S;=`I|<%lOEr",tokenizers:[lA,aA,hA,cA,2,3,4,5,6,7,8,9,10,11,12,13,14,oA,new Ko("$S~RRtu[#O#Pg#S#T#|~_P#o#pb~gOx~~jVO#i!P#i#j!U#j#l!P#l#m!q#m;'S!P;'S;=`#v<%lO!P~!UO!U~~!XS!Q![!e!c!i!e#T#Z!e#o#p#Z~!hR!Q![!q!c!i!q#T#Z!q~!tR!Q![!}!c!i!}#T#Z!}~#QR!Q![!P!c!i!P#T#Z!P~#^R!Q![#g!c!i#g#T#Z#g~#jS!Q![#g!c!i#g#T#Z#g#q#r!P~#yP;=`<%l!P~$RO(c~~",141,340),new Ko("j~RQYZXz{^~^O(Q~~aP!P!Qd~iO(R~~",25,323)],topRules:{Script:[0,7],SingleExpression:[1,276],SingleClassItem:[2,277]},dialects:{jsx:0,ts:15175},dynamicPrecedences:{80:1,82:1,94:1,169:1,199:1},specialized:[{term:327,get:n=>uA[n]||-1},{term:343,get:n=>dA[n]||-1},{term:95,get:n=>pA[n]||-1}],tokenPrec:15201}),N1=[Ze("function ${name}(${params}) {\n ${}\n}",{label:"function",detail:"definition",type:"keyword"}),Ze("for (let ${index} = 0; ${index} < ${bound}; ${index}++) {\n ${}\n}",{label:"for",detail:"loop",type:"keyword"}),Ze("for (let ${name} of ${collection}) {\n ${}\n}",{label:"for",detail:"of loop",type:"keyword"}),Ze("do {\n ${}\n} while (${})",{label:"do",detail:"loop",type:"keyword"}),Ze("while (${}) {\n ${}\n}",{label:"while",detail:"loop",type:"keyword"}),Ze(`try { \${} } catch (\${error}) { \${} }`,{label:"try",detail:"/ catch block",type:"keyword"}),Ze("if (${}) {\n ${}\n}",{label:"if",detail:"block",type:"keyword"}),Ze(`if (\${}) { \${} } else { \${} }`,{label:"if",detail:"/ else block",type:"keyword"}),Ze(`class \${name} { constructor(\${params}) { \${} } }`,{label:"class",detail:"definition",type:"keyword"}),Ze('import {${names}} from "${module}"\n${}',{label:"import",detail:"named",type:"keyword"}),Ze('import ${name} from "${module}"\n${}',{label:"import",detail:"default",type:"keyword"})],mA=N1.concat([Ze("interface ${name} {\n ${}\n}",{label:"interface",detail:"definition",type:"keyword"}),Ze("type ${name} = ${type}",{label:"type",detail:"definition",type:"keyword"}),Ze("enum ${name} {\n ${}\n}",{label:"enum",detail:"definition",type:"keyword"})]),rg=new iO,j1=new Set(["Script","Block","FunctionExpression","FunctionDeclaration","ArrowFunction","MethodDeclaration","ForStatement"]);function js(n){return(e,t)=>{let i=e.node.getChild("VariableDefinition");return i&&t(i,n),!0}}const OA=["FunctionDeclaration"],bA={FunctionDeclaration:js("function"),ClassDeclaration:js("class"),ClassExpression:()=>!0,EnumDeclaration:js("constant"),TypeAliasDeclaration:js("type"),NamespaceDeclaration:js("namespace"),VariableDefinition(n,e){n.matchContext(OA)||e(n,"variable")},TypeDefinition(n,e){e(n,"type")},__proto__:null};function G1(n,e){let t=rg.get(e);if(t)return t;let i=[],s=!0;function r(o,l){let a=n.sliceString(o.from,o.to);i.push({label:a,type:l})}return e.cursor(j.IncludeAnonymous).iterate(o=>{if(s)s=!1;else if(o.name){let l=bA[o.name];if(l&&l(o,r)||j1.has(o.name))return!1}else if(o.to-o.from>8192){for(let l of G1(n,o.node))i.push(l);return!1}}),rg.set(e,i),i}const og=/^[\w$\xa1-\uffff][\w$\d\xa1-\uffff]*$/,H1=["TemplateString","String","RegExp","LineComment","BlockComment","VariableDefinition","TypeDefinition","Label","PropertyDefinition","PropertyName","PrivatePropertyDefinition","PrivatePropertyName","JSXText","JSXAttributeValue","JSXOpenTag","JSXCloseTag","JSXSelfClosingTag",".","?."];function SA(n){let e=fe(n.state).resolveInner(n.pos,-1);if(H1.indexOf(e.name)>-1)return null;let t=e.name=="VariableName"||e.to-e.from<20&&og.test(n.state.sliceDoc(e.from,e.to));if(!t&&!n.explicit)return null;let i=[];for(let s=e;s;s=s.parent)j1.has(s.name)&&(i=i.concat(G1(n.state.doc,s)));return{options:i,from:t?e.from:n.pos,validFor:og}}const It=xs.define({name:"javascript",parser:gA.configure({props:[ul.add({IfStatement:Fr({except:/^\s*({|else\b)/}),TryStatement:Fr({except:/^\s*({|catch\b|finally\b)/}),LabeledStatement:nk,SwitchBody:n=>{let e=n.textAfter,t=/^\s*\}/.test(e),i=/^\s*(case|default)\b/.test(e);return n.baseIndent+(t?0:i?1:2)*n.unit},Block:sk({closing:"}"}),ArrowFunction:n=>n.baseIndent+n.unit,"TemplateString BlockComment":()=>null,"Statement Property":Fr({except:/^\s*{/}),JSXElement(n){let e=/^\s*<\//.test(n.textAfter);return n.lineIndent(n.node.from)+(e?0:n.unit)},JSXEscape(n){let e=/\s*\}/.test(n.textAfter);return n.lineIndent(n.node.from)+(e?0:n.unit)},"JSXOpenTag JSXSelfClosingTag"(n){return n.column(n.node.from)+n.unit}}),dl.add({"Block ClassBody SwitchBody EnumBody ObjectExpression ArrayExpression ObjectType":cO,BlockComment(n){return{from:n.from+2,to:n.to-2}}})]}),languageData:{closeBrackets:{brackets:["(","[","{","'",'"',"`"]},commentTokens:{line:"//",block:{open:"/*",close:"*/"}},indentOnInput:/^\s*(?:case |default:|\{|\}|<\/)$/,wordChars:"$"}}),F1={test:n=>/^JSX/.test(n.name),facet:oO({commentTokens:{block:{open:"{/*",close:"*/}"}}})},U1=It.configure({dialect:"ts"},"typescript"),K1=It.configure({dialect:"jsx",props:[_c.add(n=>n.isTop?[F1]:void 0)]}),J1=It.configure({dialect:"jsx ts",props:[_c.add(n=>n.isTop?[F1]:void 0)]},"typescript");let eS=n=>({label:n,type:"keyword"});const tS="break case const continue default delete export extends false finally in instanceof let new return static super switch this throw true typeof var yield".split(" ").map(eS),yA=tS.concat(["declare","implements","private","protected","public"].map(eS));function xA(n={}){let e=n.jsx?n.typescript?J1:K1:n.typescript?U1:It,t=n.typescript?mA.concat(yA):N1.concat(tS);return new Yc(e,[It.data.of({autocomplete:tT(H1,d1(t))}),It.data.of({autocomplete:SA}),n.jsx?QA:[]])}function wA(n){for(;;){if(n.name=="JSXOpenTag"||n.name=="JSXSelfClosingTag"||n.name=="JSXFragmentTag")return n;if(n.name=="JSXEscape"||!n.parent)return null;n=n.parent}}function lg(n,e,t=n.length){for(let i=e?.firstChild;i;i=i.nextSibling)if(i.name=="JSXIdentifier"||i.name=="JSXBuiltin"||i.name=="JSXNamespacedName"||i.name=="JSXMemberExpression")return n.sliceString(i.from,Math.min(i.to,t));return""}const kA=typeof navigator=="object"&&/Android\b/.test(navigator.userAgent),QA=D.inputHandler.of((n,e,t,i,s)=>{if((kA?n.composing:n.compositionStarted)||n.state.readOnly||e!=t||i!=">"&&i!="/"||!It.isActiveAt(n.state,e,-1))return!1;let r=s(),{state:o}=r,l=o.changeByRange(a=>{var h;let{head:c}=a,f=fe(o).resolveInner(c-1,-1),u;if(f.name=="JSXStartTag"&&(f=f.parent),!(o.doc.sliceString(c-1,c)!=i||f.name=="JSXAttributeValue"&&f.to>c)){if(i==">"&&f.name=="JSXFragmentTag")return{range:a,changes:{from:c,insert:""}};if(i=="/"&&f.name=="JSXStartCloseTag"){let d=f.parent,p=d.parent;if(p&&d.from==c-2&&((u=lg(o.doc,p.firstChild,c))||((h=p.firstChild)===null||h===void 0?void 0:h.name)=="JSXFragmentTag")){let g=`${u}>`;return{range:S.cursor(c+g.length,-1),changes:{from:c,insert:g}}}}else if(i==">"){let d=wA(f);if(d&&d.name=="JSXOpenTag"&&!/^\/?>|^<\//.test(o.doc.sliceString(c,c+2))&&(u=lg(o.doc,d,c)))return{range:a,changes:{from:c,insert:``}}}}return{range:a}});return l.changes.empty?!1:(n.dispatch([r,o.update(l,{userEvent:"input.complete",scrollIntoView:!0})]),!0)}),Gs=["_blank","_self","_top","_parent"],Ma=["ascii","utf-8","utf-16","latin1","latin1"],Aa=["get","post","put","delete"],Ra=["application/x-www-form-urlencoded","multipart/form-data","text/plain"],_e=["true","false"],R={},vA={a:{attrs:{href:null,ping:null,type:null,media:null,target:Gs,hreflang:null}},abbr:R,address:R,area:{attrs:{alt:null,coords:null,href:null,target:null,ping:null,media:null,hreflang:null,type:null,shape:["default","rect","circle","poly"]}},article:R,aside:R,audio:{attrs:{src:null,mediagroup:null,crossorigin:["anonymous","use-credentials"],preload:["none","metadata","auto"],autoplay:["autoplay"],loop:["loop"],controls:["controls"]}},b:R,base:{attrs:{href:null,target:Gs}},bdi:R,bdo:R,blockquote:{attrs:{cite:null}},body:R,br:R,button:{attrs:{form:null,formaction:null,name:null,value:null,autofocus:["autofocus"],disabled:["autofocus"],formenctype:Ra,formmethod:Aa,formnovalidate:["novalidate"],formtarget:Gs,type:["submit","reset","button"]}},canvas:{attrs:{width:null,height:null}},caption:R,center:R,cite:R,code:R,col:{attrs:{span:null}},colgroup:{attrs:{span:null}},command:{attrs:{type:["command","checkbox","radio"],label:null,icon:null,radiogroup:null,command:null,title:null,disabled:["disabled"],checked:["checked"]}},data:{attrs:{value:null}},datagrid:{attrs:{disabled:["disabled"],multiple:["multiple"]}},datalist:{attrs:{data:null}},dd:R,del:{attrs:{cite:null,datetime:null}},details:{attrs:{open:["open"]}},dfn:R,div:R,dl:R,dt:R,em:R,embed:{attrs:{src:null,type:null,width:null,height:null}},eventsource:{attrs:{src:null}},fieldset:{attrs:{disabled:["disabled"],form:null,name:null}},figcaption:R,figure:R,footer:R,form:{attrs:{action:null,name:null,"accept-charset":Ma,autocomplete:["on","off"],enctype:Ra,method:Aa,novalidate:["novalidate"],target:Gs}},h1:R,h2:R,h3:R,h4:R,h5:R,h6:R,head:{children:["title","base","link","style","meta","script","noscript","command"]},header:R,hgroup:R,hr:R,html:{attrs:{manifest:null}},i:R,iframe:{attrs:{src:null,srcdoc:null,name:null,width:null,height:null,sandbox:["allow-top-navigation","allow-same-origin","allow-forms","allow-scripts"],seamless:["seamless"]}},img:{attrs:{alt:null,src:null,ismap:null,usemap:null,width:null,height:null,crossorigin:["anonymous","use-credentials"]}},input:{attrs:{alt:null,dirname:null,form:null,formaction:null,height:null,list:null,max:null,maxlength:null,min:null,name:null,pattern:null,placeholder:null,size:null,src:null,step:null,value:null,width:null,accept:["audio/*","video/*","image/*"],autocomplete:["on","off"],autofocus:["autofocus"],checked:["checked"],disabled:["disabled"],formenctype:Ra,formmethod:Aa,formnovalidate:["novalidate"],formtarget:Gs,multiple:["multiple"],readonly:["readonly"],required:["required"],type:["hidden","text","search","tel","url","email","password","datetime","date","month","week","time","datetime-local","number","range","color","checkbox","radio","file","submit","image","reset","button"]}},ins:{attrs:{cite:null,datetime:null}},kbd:R,keygen:{attrs:{challenge:null,form:null,name:null,autofocus:["autofocus"],disabled:["disabled"],keytype:["RSA"]}},label:{attrs:{for:null,form:null}},legend:R,li:{attrs:{value:null}},link:{attrs:{href:null,type:null,hreflang:null,media:null,sizes:["all","16x16","16x16 32x32","16x16 32x32 64x64"]}},map:{attrs:{name:null}},mark:R,menu:{attrs:{label:null,type:["list","context","toolbar"]}},meta:{attrs:{content:null,charset:Ma,name:["viewport","application-name","author","description","generator","keywords"],"http-equiv":["content-language","content-type","default-style","refresh"]}},meter:{attrs:{value:null,min:null,low:null,high:null,max:null,optimum:null}},nav:R,noscript:R,object:{attrs:{data:null,type:null,name:null,usemap:null,form:null,width:null,height:null,typemustmatch:["typemustmatch"]}},ol:{attrs:{reversed:["reversed"],start:null,type:["1","a","A","i","I"]},children:["li","script","template","ul","ol"]},optgroup:{attrs:{disabled:["disabled"],label:null}},option:{attrs:{disabled:["disabled"],label:null,selected:["selected"],value:null}},output:{attrs:{for:null,form:null,name:null}},p:R,param:{attrs:{name:null,value:null}},pre:R,progress:{attrs:{value:null,max:null}},q:{attrs:{cite:null}},rp:R,rt:R,ruby:R,samp:R,script:{attrs:{type:["text/javascript"],src:null,async:["async"],defer:["defer"],charset:Ma}},section:R,select:{attrs:{form:null,name:null,size:null,autofocus:["autofocus"],disabled:["disabled"],multiple:["multiple"]}},slot:{attrs:{name:null}},small:R,source:{attrs:{src:null,type:null,media:null}},span:R,strong:R,style:{attrs:{type:["text/css"],media:null,scoped:null}},sub:R,summary:R,sup:R,table:R,tbody:R,td:{attrs:{colspan:null,rowspan:null,headers:null}},template:R,textarea:{attrs:{dirname:null,form:null,maxlength:null,name:null,placeholder:null,rows:null,cols:null,autofocus:["autofocus"],disabled:["disabled"],readonly:["readonly"],required:["required"],wrap:["soft","hard"]}},tfoot:R,th:{attrs:{colspan:null,rowspan:null,headers:null,scope:["row","col","rowgroup","colgroup"]}},thead:R,time:{attrs:{datetime:null}},title:R,tr:R,track:{attrs:{src:null,label:null,default:null,kind:["subtitles","captions","descriptions","chapters","metadata"],srclang:null}},ul:{children:["li","script","template","ul","ol"]},var:R,video:{attrs:{src:null,poster:null,width:null,height:null,crossorigin:["anonymous","use-credentials"],preload:["auto","metadata","none"],autoplay:["autoplay"],mediagroup:["movie"],muted:["muted"],controls:["controls"]}},wbr:R},iS={accesskey:null,class:null,contenteditable:_e,contextmenu:null,dir:["ltr","rtl","auto"],draggable:["true","false","auto"],dropzone:["copy","move","link","string:","file:"],hidden:["hidden"],id:null,inert:["inert"],itemid:null,itemprop:null,itemref:null,itemscope:["itemscope"],itemtype:null,lang:["ar","bn","de","en-GB","en-US","es","fr","hi","id","ja","pa","pt","ru","tr","zh"],spellcheck:_e,autocorrect:_e,autocapitalize:_e,style:null,tabindex:null,title:null,translate:["yes","no"],rel:["stylesheet","alternate","author","bookmark","help","license","next","nofollow","noreferrer","prefetch","prev","search","tag"],role:"alert application article banner button cell checkbox complementary contentinfo dialog document feed figure form grid gridcell heading img list listbox listitem main navigation region row rowgroup search switch tab table tabpanel textbox timer".split(" "),"aria-activedescendant":null,"aria-atomic":_e,"aria-autocomplete":["inline","list","both","none"],"aria-busy":_e,"aria-checked":["true","false","mixed","undefined"],"aria-controls":null,"aria-describedby":null,"aria-disabled":_e,"aria-dropeffect":null,"aria-expanded":["true","false","undefined"],"aria-flowto":null,"aria-grabbed":["true","false","undefined"],"aria-haspopup":_e,"aria-hidden":_e,"aria-invalid":["true","false","grammar","spelling"],"aria-label":null,"aria-labelledby":null,"aria-level":null,"aria-live":["off","polite","assertive"],"aria-multiline":_e,"aria-multiselectable":_e,"aria-owns":null,"aria-posinset":null,"aria-pressed":["true","false","mixed","undefined"],"aria-readonly":_e,"aria-relevant":null,"aria-required":_e,"aria-selected":["true","false","undefined"],"aria-setsize":null,"aria-sort":["ascending","descending","none","other"],"aria-valuemax":null,"aria-valuemin":null,"aria-valuenow":null,"aria-valuetext":null},sS="beforeunload copy cut dragstart dragover dragleave dragenter dragend drag paste focus blur change click load mousedown mouseenter mouseleave mouseup keydown keyup resize scroll unload".split(" ").map(n=>"on"+n);for(let n of sS)iS[n]=null;class tl{constructor(e,t){this.tags={...vA,...e},this.globalAttrs={...iS,...t},this.allTags=Object.keys(this.tags),this.globalAttrNames=Object.keys(this.globalAttrs)}}tl.default=new tl;function Bs(n,e,t=n.length){if(!e)return"";let i=e.firstChild,s=i&&i.getChild("TagName");return s?n.sliceString(s.from,Math.min(s.to,t)):""}function Xs(n,e=!1){for(;n;n=n.parent)if(n.name=="Element")if(e)e=!1;else return n;return null}function nS(n,e,t){let i=t.tags[Bs(n,Xs(e))];return i?.children||t.allTags}function Zf(n,e){let t=[];for(let i=Xs(e);i&&!i.type.isTop;i=Xs(i.parent)){let s=Bs(n,i);if(s&&i.lastChild.name=="CloseTag")break;s&&t.indexOf(s)<0&&(e.name=="EndTag"||e.from>=i.firstChild.to)&&t.push(s)}return t}const rS=/^[:\-\.\w\u00b7-\uffff]*$/;function ag(n,e,t,i,s){let r=/\s*>/.test(n.sliceDoc(s,s+5))?"":">",o=Xs(t,t.name=="StartTag"||t.name=="TagName");return{from:i,to:s,options:nS(n.doc,o,e).map(l=>({label:l,type:"type"})).concat(Zf(n.doc,t).map((l,a)=>({label:"/"+l,apply:"/"+l+r,type:"type",boost:99-a}))),validFor:/^\/?[:\-\.\w\u00b7-\uffff]*$/}}function hg(n,e,t,i){let s=/\s*>/.test(n.sliceDoc(i,i+5))?"":">";return{from:t,to:i,options:Zf(n.doc,e).map((r,o)=>({label:r,apply:r+s,type:"type",boost:99-o})),validFor:rS}}function $A(n,e,t,i){let s=[],r=0;for(let o of nS(n.doc,t,e))s.push({label:"<"+o,type:"type"});for(let o of Zf(n.doc,t))s.push({label:"",type:"type",boost:99-r++});return{from:i,to:i,options:s,validFor:/^<\/?[:\-\.\w\u00b7-\uffff]*$/}}function PA(n,e,t,i,s){let r=Xs(t),o=r?e.tags[Bs(n.doc,r)]:null,l=o&&o.attrs?Object.keys(o.attrs):[],a=o&&o.globalAttrs===!1?l:l.length?l.concat(e.globalAttrNames):e.globalAttrNames;return{from:i,to:s,options:a.map(h=>({label:h,type:"property"})),validFor:rS}}function CA(n,e,t,i,s){var r;let o=(r=t.parent)===null||r===void 0?void 0:r.getChild("AttributeName"),l=[],a;if(o){let h=n.sliceDoc(o.from,o.to),c=e.globalAttrs[h];if(!c){let f=Xs(t),u=f?e.tags[Bs(n.doc,f)]:null;c=u?.attrs&&u.attrs[h]}if(c){let f=n.sliceDoc(i,s).toLowerCase(),u='"',d='"';/^['"]/.test(f)?(a=f[0]=='"'?/^[^"]*$/:/^[^']*$/,u="",d=n.sliceDoc(s,s+1)==f[0]?"":f[0],f=f.slice(1),i++):a=/^[^\s<>='"]*$/;for(let p of c)l.push({label:p,apply:u+p+d,type:"constant"})}}return{from:i,to:s,options:l,validFor:a}}function TA(n,e){let{state:t,pos:i}=e,s=fe(t).resolveInner(i,-1),r=s.resolve(i);for(let o=i,l;r==s&&(l=s.childBefore(o));){let a=l.lastChild;if(!a||!a.type.isError||a.fromTA(i,s)}const AA=It.parser.configure({top:"SingleExpression"}),oS=[{tag:"script",attrs:n=>n.type=="text/typescript"||n.lang=="ts",parser:U1.parser},{tag:"script",attrs:n=>n.type=="text/babel"||n.type=="text/jsx",parser:K1.parser},{tag:"script",attrs:n=>n.type=="text/typescript-jsx",parser:J1.parser},{tag:"script",attrs(n){return/^(importmap|speculationrules|application\/(.+\+)?json)$/i.test(n.type)},parser:AA},{tag:"script",attrs(n){return!n.type||/^(?:text|application)\/(?:x-)?(?:java|ecma)script$|^module$|^$/i.test(n.type)},parser:It.parser},{tag:"style",attrs(n){return(!n.lang||n.lang=="css")&&(!n.type||/^(text\/)?(x-)?(stylesheet|css)$/i.test(n.type))},parser:el.parser}],lS=[{name:"style",parser:el.parser.configure({top:"Styles"})}].concat(sS.map(n=>({name:n,parser:It.parser}))),aS=xs.define({name:"html",parser:iM.configure({props:[ul.add({Element(n){let e=/^(\s*)(<\/)?/.exec(n.textAfter);return n.node.to<=n.pos+e[0].length?n.continue():n.lineIndent(n.node.from)+(e[2]?0:n.unit)},"OpenTag CloseTag SelfClosingTag"(n){return n.column(n.node.from)+n.unit},Document(n){if(n.pos+/\s*/.exec(n.textAfter)[0].lengthn.getChild("TagName")})]}),languageData:{commentTokens:{block:{open:""}},indentOnInput:/^\s*<\/\w+\W$/,wordChars:"-_"}}),no=aS.configure({wrap:W1(oS,lS)});function RA(n={}){let e="",t;n.matchClosingTags===!1&&(e="noMatch"),n.selfClosingTags===!0&&(e=(e?e+" ":"")+"selfClosing"),(n.nestedLanguages&&n.nestedLanguages.length||n.nestedAttributes&&n.nestedAttributes.length)&&(t=W1((n.nestedLanguages||[]).concat(oS),(n.nestedAttributes||[]).concat(lS)));let i=t?aS.configure({wrap:t,dialect:e}):e?no.configure({dialect:e}):no;return new Yc(i,[no.data.of({autocomplete:MA(n)}),n.autoCloseTags!==!1?DA:[],xA().support,EM().support])}const cg=new Set("area base br col command embed frame hr img input keygen link meta param source track wbr menuitem".split(" ")),DA=D.inputHandler.of((n,e,t,i,s)=>{if(n.composing||n.state.readOnly||e!=t||i!=">"&&i!="/"||!no.isActiveAt(n.state,e,-1))return!1;let r=s(),{state:o}=r,l=o.changeByRange(a=>{var h,c,f;let u=o.doc.sliceString(a.from-1,a.to)==i,{head:d}=a,p=fe(o).resolveInner(d,-1),g;if(u&&i==">"&&p.name=="EndTag"){let m=p.parent;if(((c=(h=m.parent)===null||h===void 0?void 0:h.lastChild)===null||c===void 0?void 0:c.name)!="CloseTag"&&(g=Bs(o.doc,m.parent,d))&&!cg.has(g)){let O=d+(o.doc.sliceString(d,d+1)===">"?1:0),y=``;return{range:a,changes:{from:d,to:O,insert:y}}}}else if(u&&i=="/"&&p.name=="IncompleteCloseTag"){let m=p.parent;if(p.from==d-2&&((f=m.lastChild)===null||f===void 0?void 0:f.name)!="CloseTag"&&(g=Bs(o.doc,m,d))&&!cg.has(g)){let O=d+(o.doc.sliceString(d,d+1)===">"?1:0),y=`${g}>`;return{range:S.cursor(d+y.length,-1),changes:{from:d,to:O,insert:y}}}}return{range:a}});return l.changes.empty?!1:(n.dispatch([r,o.update(l,{userEvent:"input.complete",scrollIntoView:!0})]),!0)}),ZA="#e5c07b",fg="#e06c75",BA="#56b6c2",XA="#ffffff",ro="#abb2bf",xc="#7d8799",LA="#61afef",EA="#98c379",ug="#d19a66",WA="#c678dd",VA="#21252b",dg="#2c313a",pg="#282c34",Da="#353a42",zA="#3E4451",gg="#528bff",qA=D.theme({"&":{color:ro,backgroundColor:pg},".cm-content":{caretColor:gg},".cm-cursor, .cm-dropCursor":{borderLeftColor:gg},"&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":{backgroundColor:zA},".cm-panels":{backgroundColor:VA,color:ro},".cm-panels.cm-panels-top":{borderBottom:"2px solid black"},".cm-panels.cm-panels-bottom":{borderTop:"2px solid black"},".cm-searchMatch":{backgroundColor:"#72a1ff59",outline:"1px solid #457dff"},".cm-searchMatch.cm-searchMatch-selected":{backgroundColor:"#6199ff2f"},".cm-activeLine":{backgroundColor:"#6699ff0b"},".cm-selectionMatch":{backgroundColor:"#aafe661a"},"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{backgroundColor:"#bad0f847"},".cm-gutters":{backgroundColor:pg,color:xc,border:"none"},".cm-activeLineGutter":{backgroundColor:dg},".cm-foldPlaceholder":{backgroundColor:"transparent",border:"none",color:"#ddd"},".cm-tooltip":{border:"none",backgroundColor:Da},".cm-tooltip .cm-tooltip-arrow:before":{borderTopColor:"transparent",borderBottomColor:"transparent"},".cm-tooltip .cm-tooltip-arrow:after":{borderTopColor:Da,borderBottomColor:Da},".cm-tooltip-autocomplete":{"& > ul > li[aria-selected]":{backgroundColor:dg,color:ro}}},{dark:!0}),IA=jn.define([{tag:b.keyword,color:WA},{tag:[b.name,b.deleted,b.character,b.propertyName,b.macroName],color:fg},{tag:[b.function(b.variableName),b.labelName],color:LA},{tag:[b.color,b.constant(b.name),b.standard(b.name)],color:ug},{tag:[b.definition(b.name),b.separator],color:ro},{tag:[b.typeName,b.className,b.number,b.changed,b.annotation,b.modifier,b.self,b.namespace],color:ZA},{tag:[b.operator,b.operatorKeyword,b.url,b.escape,b.regexp,b.link,b.special(b.string)],color:BA},{tag:[b.meta,b.comment],color:xc},{tag:b.strong,fontWeight:"bold"},{tag:b.emphasis,fontStyle:"italic"},{tag:b.strikethrough,textDecoration:"line-through"},{tag:b.link,color:xc,textDecoration:"underline"},{tag:b.heading,fontWeight:"bold",color:fg},{tag:[b.atom,b.bool,b.special(b.variableName)],color:ug},{tag:[b.processingInstruction,b.string,b.inserted],color:EA},{tag:b.invalid,color:XA}]),_A=[qA,SO(IA)],YA={class:"source-editor"},NA=hS({__name:"SourceEditor",props:{modelValue:{}},emits:["update:modelValue","exit"],setup(n,{emit:e}){const t=n,i=e,s=mS(null);let r=null;const o=()=>document.documentElement.getAttribute("data-theme")?.includes("dark")||window.matchMedia("(prefers-color-scheme: dark)").matches,l=c=>{const u=new DOMParser().parseFromString(c,"text/html");return u.querySelectorAll('a[data-type="mention"]').forEach(p=>{const g=p.getAttribute("data-mention");if(g){const m=u.createTextNode(g);p.parentNode?.replaceChild(m,p)}}),u.body.innerHTML},a=c=>{const f=" ";let u="",d=0;const p=["a","span","strong","em","b","i","u","s","mark","small","sub","sup","code","br","img"],g=["br","hr","img","input","meta","link","area","base","col","embed","param","source","track","wbr"];c=c.replace(/>\s+<").trim();const m=c.split(/(<[^>]+>)/g).filter(O=>O.trim());for(const O of m)if(O.startsWith("");y&&!p.includes(y)&&u&&(u+=` `+f.repeat(d)),u+=O,!x&&y&&!p.includes(y)&&d++}else u+=O;return u.trim()};cS(()=>{if(!s.value)return;const c=l(t.modelValue),f=a(c),u=[c2,RA(),_n.of([D$]),D.updateListener.of(p=>{p.docChanged&&i("update:modelValue",p.state.doc.toString())}),D.lineWrapping];o()&&u.push(_A);const d=L.create({doc:f,extensions:u});r=new D({state:d,parent:s.value}),i("update:modelValue",f)}),fS(()=>t.modelValue,c=>{r&&c!==r.state.doc.toString()&&r.dispatch({changes:{from:0,to:r.state.doc.length,insert:c}})}),uS(()=>{r?.destroy()});const h=()=>{i("exit")};return(c,f)=>(dS(),pS("div",YA,[zs("div",{class:"source-editor-toolbar"},[f[1]||(f[1]=zs("span",{class:"text-xs text-neutral-content"},"Source Mode",-1)),zs("button",{type:"button",onClick:h,class:"btn btn-xs btn-ghost",title:"Exit source mode"},[...f[0]||(f[0]=[zs("i",{class:"fa-regular fa-eye","aria-hidden":"true"},null,-1),gS(" Visual Editor ",-1)])])]),zs("div",{ref_key:"editorContainer",ref:s,class:"source-editor-content"},null,512)]))}}),xD=OS(NA,[["__scopeId","data-v-d2900713"]]);export{xD as default}; ================================================ FILE: public/build/assets/Tiptap-B5LGKvdR.css ================================================ .mention-list[data-v-c99f4ece]{min-width:200px}.mention-item[data-v-c99f4ece],.slash-command-item[data-v-aa19753b]{transition:background-color .1s}.bubble-menu[data-v-539a3ede]{z-index:845;position:relative}[data-v-539a3ede] .ProseMirror{min-height:200px;max-height:70vh;overflow-y:auto;border:1px solid hsl(var(--bc)/.1);border-radius:var(--rounded-btn);background-color:hsl(var(--b1)/1);padding:.6rem .8rem;margin-bottom:1rem}[data-v-539a3ede] .ProseMirror:focus{outline-style:solid;outline-width:2px;outline-offset:2px;outline-color:hsl(var(--p)/.3);border-color:transparent}[data-v-539a3ede] .ProseMirror p.is-editor-empty:first-child:before{content:attr(data-placeholder);--tw-text-opacity: .4;color:hsl(var(--bc)/var(--tw-text-opacity));pointer-events:none;float:left;height:0}[data-v-539a3ede] .iframe-wrapper{margin:1rem 0}[data-v-539a3ede] .iframe-wrapper iframe{max-width:100%;border:0}[data-v-539a3ede] .details{flex-direction:row}[data-v-539a3ede] .details summary{display:inline}.tiptap-editor table td,.tiptap-editor table th{box-sizing:border-box}.tiptap-editor table .selectedCell{background:hsl(var(--p)/1);color:hsl(var(--pc)/1)}.tiptap-editor table .selectedCell:after{content:"";inset:0;pointer-events:none;position:absolute;z-index:2}.tiptap-editor table .column-resize-handle{background:hsl(var(--p)/1);bottom:-2px;margin:0;pointer-events:none;position:absolute;right:-2px;top:0;width:4px}.tiptap-editor .tableWrapper{margin:1.5rem 0;overflow-x:auto}.tiptap-editor.resize-cursor{cursor:ew-resize;cursor:col-resize}.tiptap-editor .ProseMirror-selectednode img{outline:2px solid hsl(var(--p)/1)}.tiptap-editor [data-resize-handle]{position:absolute;background:hsl(var(--pc)/1);border:1px solid hsl(var(--pc)/1);border-radius:2px;z-index:10}.tiptap-editor [data-resize-handle]:hover{background:hsl(var(--p)/1)}.tiptap-editor [data-resize-handle][data-resize-handle=top-left],.tiptap-editor [data-resize-handle][data-resize-handle=top-right],.tiptap-editor [data-resize-handle][data-resize-handle=bottom-left],.tiptap-editor [data-resize-handle][data-resize-handle=bottom-right]{width:8px;height:8px}.tiptap-editor [data-resize-handle][data-resize-handle=top-left]{top:-4px;left:-4px;cursor:nwse-resize}.tiptap-editor [data-resize-handle][data-resize-handle=top-right]{top:-4px;right:-4px;cursor:nesw-resize}.tiptap-editor [data-resize-handle][data-resize-handle=bottom-left]{bottom:-4px;left:-4px;cursor:nesw-resize}.tiptap-editor [data-resize-handle][data-resize-handle=bottom-right]{bottom:-4px;right:-4px;cursor:nwse-resize}.tiptap-editor [data-resize-handle][data-resize-handle=top],.tiptap-editor [data-resize-handle][data-resize-handle=bottom]{height:6px;left:8px;right:8px}.tiptap-editor [data-resize-handle][data-resize-handle=top]{top:-3px;cursor:ns-resize}.tiptap-editor [data-resize-handle][data-resize-handle=bottom]{bottom:-3px;cursor:ns-resize}.tiptap-editor [data-resize-handle][data-resize-handle=left],.tiptap-editor [data-resize-handle][data-resize-handle=right]{width:6px;top:8px;bottom:8px}.tiptap-editor [data-resize-handle][data-resize-handle=left]{left:-3px;cursor:ew-resize}.tiptap-editor [data-resize-handle][data-resize-handle=right]{right:-3px;cursor:ew-resize}.tiptap-editor [data-resize-state=true] [data-resize-wrapper]{outline:2px solid hsl(var(--p)/1);border-radius:.125rem} ================================================ FILE: public/build/assets/Tiptap-Bd5ZGSft.js ================================================ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/GalleryDialog-B3Id-RLo.js","assets/vendor-tiptap-D5xFoo7B.js","assets/index-D5GkNzM3.js","assets/_plugin-vue_export-helper-DlAUqK2U.js","assets/GalleryDialog-SGgOgWve.css","assets/SourceEditor-CaEGRaAo.js","assets/SourceEditor-BKF0zshA.css"])))=>i.map(i=>d[i]); import{_ as de}from"./preload-helper-I4rgV-VL.js";import{U as Le,V as Te,W as Me,X as te,Y as re,Z as ve,_ as ae,f as O,E as Y,o as p,c as b,F as j,r as Q,a as n,n as x,d as D,t as _,b as S,i as R,z as B,$ as ye,a0 as xe,a1 as we,a2 as ke,a3 as Ce,a4 as oe,a5 as $e,a6 as He,a7 as Ee,a8 as Ie,w as q,p as ee,s as y,u as h,g as Ae,G as Fe,h as se,C as le,m as Se,q as U,J as Re,a9 as De,aa as Be,ab as Pe,ac as Ue,ad as je,ae as Ke,af as ze,ag as Oe,ah as Ne,ai as Ve,aj as qe,ak as We,al as Ge,j as ce,x as Z,am as J,an as fe,ao as Ze,M as ge}from"./vendor-tiptap-D5xFoo7B.js";import{_ as ue}from"./_plugin-vue_export-helper-DlAUqK2U.js";import{a as Je}from"./index-D5GkNzM3.js";const Qe=Le.extend({addAttributes(){return{...this.parent?.(),colwidth:{default:null,parseHTML:e=>{const s=e.getAttribute("colwidth");if(s)return s.split(",").map(l=>parseInt(l,10));const t=e.style.width;return t&&t.endsWith("px")?[parseInt(t,10)]:null},renderHTML:e=>e.colwidth?{colwidth:e.colwidth.join(","),style:`width: ${e.colwidth[0]}px`}:{}}}}}),Xe=Te.extend({addAttributes(){return{...this.parent?.(),colwidth:{default:null,parseHTML:e=>{const s=e.getAttribute("colwidth");if(s)return s.split(",").map(l=>parseInt(l,10));const t=e.style.width;return t&&t.endsWith("px")?[parseInt(t,10)]:null},renderHTML:e=>e.colwidth?{colwidth:e.colwidth.join(","),style:`width: ${e.colwidth[0]}px`}:{}}}}}),Ye=Me.extend({addAttributes(){return{...this.parent?.(),class:{default:null,parseHTML:e=>e.getAttribute("class"),renderHTML:e=>e.class?{class:e.class}:{}}}},renderHTML({HTMLAttributes:e}){return["table",te(this.options.HTMLAttributes,e),["tbody",0]]}}),_e=new ae("mention"),et=re.create({name:"mention",addOptions(){return{HTMLAttributes:{},renderLabel({options:e,node:s}){return s.attrs.label||s.attrs.name||s.attrs.id},suggestion:{char:"@",pluginKey:_e,command:({editor:e,range:s,props:t})=>{e.view.state.selection.$to.nodeAfter?.text?.startsWith(" ")&&(s.to+=1);const o=t.section==="entities"?t.mention:t.inject;e.chain().focus().insertContentAt(s,[{type:"text",text:o},{type:"text",text:" "}]).run(),window.getSelection()?.collapseToEnd()},allow:({state:e,range:s})=>{const t=e.doc.resolve(s.from),l=e.schema.nodes[this.name];return!!t.parent.type.contentMatch.matchType(l)}}}},group:"inline",inline:!0,selectable:!0,atom:!0,draggable:!0,addAttributes(){return{id:{default:null,parseHTML:e=>e.getAttribute("data-id"),renderHTML:e=>e.id?{"data-id":e.id}:{}},label:{default:null,parseHTML:e=>e.getAttribute("data-label"),renderHTML:e=>({})},name:{default:null,parseHTML:e=>e.getAttribute("data-name"),renderHTML:e=>e.name?{"data-name":e.name}:{}},mention:{default:null,parseHTML:e=>{const s=e.getAttribute("data-mention");if(!s)return null;const t=s.match(/\[?([a-zA-Z_]+:\d+)/);return t?t[1]:s},renderHTML:e=>e.mention?{"data-mention":`[${e.mention}]`}:{}},image:{default:null,parseHTML:e=>e.getAttribute("data-image"),renderHTML:e=>({})},entity:{default:null,parseHTML:e=>e.getAttribute("data-entity"),renderHTML:e=>({})},url:{default:null,parseHTML:e=>e.getAttribute("data-url"),renderHTML:e=>({})},config:{default:null,parseHTML:e=>e.getAttribute("data-config"),renderHTML:e=>e.config?{"data-config":e.config}:{"data-config":""}}}},parseHTML(){return[{tag:"span[data-mention]"}]},renderHTML({node:e,HTMLAttributes:s}){const t=this.options.renderLabel({options:this.options,node:e}),l=[];return e.attrs.image&&l.push(["img",{src:e.attrs.image,class:"inline-block w-4 h-4 rounded-full object-cover mr-1 align-middle",alt:t}]),l.push(t),e.attrs.config&&e.attrs.config.split("|").find(o=>o.startsWith("alias:"))&&l.push(["i",{class:"fa-regular fa-masks-theater"}]),["a",te({"data-type":"mention"},this.options.HTMLAttributes,s),["span",{class:"rounded-xl bg-base-200 hover:bg-base-300 text-base-content px-2 py-0.5 inline-flex items-center gap-1 cursor-pointer"},...l]]},renderText({node:e}){return e.attrs.mention||`[${e.attrs.label}]`},addKeyboardShortcuts(){return{Backspace:()=>this.editor.commands.command(({tr:e,state:s})=>{let t=!1;const{selection:l}=s,{empty:d,anchor:o}=l;return d?(s.doc.nodesBetween(o-1,o,(c,f)=>{if(c.type.name===this.name)return t=!0,e.insertText(this.options.suggestion.char||"",f,f+c.nodeSize),!1}),t):!1})}},addProseMirrorPlugins(){return[ve({editor:this.editor,...this.options.suggestion})]}}),tt={class:"mention-list bg-base-100 shadow-lg rounded-lg z-50 max-h-[300px] overflow-y-auto"},nt={class:"section-header px-3 py-1 text-xs font-semibold text-neutral-content/70 bg-base-200/50 flex items-center gap-2"},st=["onClick"],it={class:"flex gap-2 items-center"},lt={key:0,class:"fa-regular fa-plus text-success","aria-hidden":"true"},rt=["src","alt"],at={class:"mention-name flex gap-1"},ot=["innerHTML"],ut=["innerHTML"],dt=["innerHTML"],ct={key:1,class:"px-3 py-2 text-neutral-content text-xs"},ft={key:0},gt={key:1},mt={key:2},pt=O({__name:"MentionList",props:{items:{},command:{type:Function},loading:{type:Boolean},query:{}},setup(e,{expose:s}){const t=e,l=R(0);Y(()=>t.items,()=>{l.value=0});const d={entities:{label:"Entries",icon:"fa-regular fa-bookmark"},posts:{label:"Articles",icon:"fa-regular fa-newspaper"},attributes:{label:"Properties",icon:"fa-regular fa-heart"},new:{label:"Create New",icon:"fa-regular fa-plus"}},o=B(()=>["entities","posts","attributes","new"].map(m=>({key:m,label:d[m].label,icon:d[m].icon,items:t.items.filter(L=>L.section===m)})).filter(m=>m.items.length>0)),c=B(()=>t.query.length<3),f=B(()=>!c.value&&t.loading),a=B(()=>!c.value&&!t.loading&&t.items.length===0),w=({event:M})=>M.key==="ArrowUp"?(E(),!0):M.key==="ArrowDown"?(F(),!0):M.key==="Enter"?(A(),!0):!1,E=()=>{l.value=(l.value+t.items.length-1)%t.items.length},F=()=>{l.value=(l.value+1)%t.items.length},A=()=>{C(l.value)},C=M=>{const m=t.items[M];m&&t.command(m)},I=(M,m)=>{let L=0;for(const T of o.value){if(T.key===M)return L+m;L+=T.items.length}return L};return s({onKeyDown:w}),(M,m)=>(p(),b("div",tt,[e.items.length?(p(!0),b(j,{key:0},Q(o.value,L=>(p(),b("div",{key:L.key,class:"mention-section"},[n("div",nt,[n("i",{class:x(L.icon),"aria-hidden":"true"},null,2),D(" "+_(L.label),1)]),(p(!0),b(j,null,Q(L.items,(T,g)=>(p(),b("button",{key:T.id??`${L.key}-${g}`,onClick:v=>C(I(L.key,g)),class:x(["mention-item flex items-center gap-2 w-full text-left px-3 py-2 hover:bg-base-200 text-xs justify-between cursor-pointer",{"bg-base-200":I(L.key,g)===l.value}])},[n("div",it,[L.key==="new"?(p(),b("i",lt)):T.image?(p(),b("img",{key:1,src:T.image,alt:T.name,loading:"lazy",class:"w-6 h-6 rounded-full object-cover"},null,8,rt)):S("",!0),n("span",at,[n("span",{innerHTML:T.name},null,8,ot),T.alias_name?(p(),b(j,{key:0},[m[0]||(m[0]=n("i",{class:"fa-regular fa-masks-theater","aria-hidden":"true"},null,-1)),D(" ("+_(T.alias_name)+") ",1)],64)):S("",!0)])]),T.type?(p(),b("span",{key:0,class:"mention-type text-neutral-content",innerHTML:T.type},null,8,ut)):S("",!0),T.value?(p(),b("span",{key:1,class:"text-neutral-content",innerHTML:T.value},null,8,dt)):S("",!0)],10,st))),128))]))),128)):(p(),b("div",ct,[c.value?(p(),b("span",ft," Type at least 3 characters ")):f.value?(p(),b("span",gt,[...m[1]||(m[1]=[n("i",{class:"fa-solid fa-spinner fa-spin","aria-hidden":"true"},null,-1),D(" Loading... ",-1)])])):a.value?(p(),b("span",mt," No results ")):S("",!0)]))]))}}),ht=ue(pt,[["__scopeId","data-v-c99f4ece"]]),me=(e,s)=>{xe({getBoundingClientRect:()=>Ce(e.view,e.state.selection.from,e.state.selection.to)},s,{placement:"bottom-start",strategy:"absolute",middleware:[we(),ke()]}).then(({x:l,y:d,strategy:o})=>{s.style.width="max-content",s.style.position=o,s.style.left=`${l}px`,s.style.top=`${d}px`})},bt=(e,s)=>{let t=null;return{char:"@",items:async({query:l})=>{if(l.length<3)return[];t&&t.abort(),t=new AbortController;try{const d=new URL(e);d.searchParams.set("q",l);const o=await fetch(d.toString(),{signal:t.signal});if(!o.ok)return[];const c=await o.json(),f=[];return c.entities?.length&&c.entities.forEach(a=>{f.push({id:a.id,name:a.name,image:a.image,url:a.url,aliases:a.aliases,alias_name:a.alias_name,alias_id:a.alias_id,mention:a.mention,type:a.type,section:"entities"})}),c.posts?.length&&c.posts.forEach(a=>{f.push({id:a.id,name:a.name,inject:a.inject,section:"posts"})}),c.attributes?.length&&c.attributes.forEach(a=>{f.push({id:a.id,name:a.name,value:a.value,inject:a.inject,section:"attributes"})}),c.new?.length&&c.new.forEach(a=>{f.push({name:a.name,type:a.type,inject:a.inject,section:"new"})}),f}catch(d){return d.name==="AbortError"?[]:(console.error("Error fetching mentions:",d),[])}},render:()=>{let l;return{onStart:d=>{l=new ye(ht,{props:{items:d.items,command:o=>{s&&o.section==="entities"&&s({id:parseInt(o.id),name:o.name,type:o.type,image:o.image,url:o.url,aliases:o.aliases}),d.command(o)},loading:d.query&&d.query.length>=3,query:d.query||""},editor:d.editor}),d.clientRect&&(l.element.style.position="absolute",document.body.appendChild(l.element),me(d.editor,l.element))},onUpdate(d){const o=d.query&&d.query.length>=3&&d.items.length===0,c=f=>{s&&f.section==="entities"&&s({id:parseInt(f.id),name:f.name,type:f.type,image:f.image,url:f.url,aliases:f.aliases}),d.command(f)};l.updateProps({items:d.items,command:c,loading:o,query:d.query||""}),d.clientRect&&me(d.editor,l.element)},onKeyDown(d){return d.event.key==="Escape"?(l.destroy(),!0):l.ref?.onKeyDown(d)},onExit(){l.destroy()}}}}},vt=new ae("mentionParser"),yt=oe.create({name:"mentionParser",addOptions(){return{entities:[]}},addProseMirrorPlugins(){return[new $e({key:vt,appendTransaction:(e,s,t)=>{if(!e.some(a=>a.docChanged))return null;const d="value"in this.options.entities?this.options.entities.value:this.options.entities||[],o=t.tr;let c=!1;const f=/\[([a-zA-Z_]+):(\d+)(?:\|[^\]]+)?\]/g;return t.doc.descendants((a,w)=>{if(a.isText&&a.text){const E=a.text;let F;const A=[];for(;(F=f.exec(E))!==null;){const C=F[0],I=F[1],M=F[2];if(I==="post")continue;const m=`${I}:${M}`;let L,T;const g=C.indexOf("|");if(g!==-1){const v=C.substring(g+1,C.length-1).split("|"),u=[];for(const r of v)r.includes(":")?u.push(r):L||(L=r);u.length>0&&(T=u.join("|"))}A.push({start:w+F.index,end:w+F.index+F[0].length,mention:m,module:I,id:M,customLabel:L,config:T})}for(let C=A.length-1;C>=0;C--){const{start:I,end:M,mention:m,module:L,id:T,customLabel:g,config:v}=A[C],u=o.mapping.map(I),r=o.mapping.map(M);if(o.doc.resolve(u),o.doc.nodeAt(u)?.type.name!=="mention"){const k=t.schema.nodes.mention;if(k){const $=d.find(z=>z.id===parseInt(T));$?$.name:`${L}${T}`;const N=g||"",P=$?.image||null,G=$?.url||null;let W=$?$.name:`${L}:${T}`;if(v&&$?.aliases){const z=v.match(/alias:(\d+)/);if(z){const V=$.aliases.find(X=>X.id===parseInt(z[1]));V&&(W=V.name)}}const K=k.create({id:T,name:W,label:N,mention:m,image:P,url:G,config:v||null,entity:$});o.replaceWith(u,r,K),c=!0}}}}}),c?o:null}})]}}),xt=new ae("slashCommand"),wt=oe.create({name:"slashCommand",addOptions(){return{suggestion:{char:"/",pluginKey:xt,command:({editor:e,range:s,props:t})=>{e.chain().focus().deleteRange(s).run(),t.command(e)}}}},addProseMirrorPlugins(){return[ve({editor:this.editor,...this.options.suggestion})]}}),kt={class:"slash-command-list bg-base-100 shadow-lg rounded-lg z-50 max-h-[300px] overflow-y-auto min-w-[200px]"},Ct=["onClick"],At={class:"w-6 h-6 rounded bg-base-200 flex items-center justify-center"},Ft={class:"flex flex-col"},Lt={class:"font-medium text-sm"},Tt={class:"text-xs text-neutral-content"},Mt={key:1,class:"px-3 py-2 text-neutral-content text-sm"},$t=O({__name:"SlashCommandList",props:{items:{},command:{type:Function}},setup(e,{expose:s}){const t=e,l=R(0);Y(()=>t.items,()=>{l.value=0});const d=({event:w})=>w.key==="ArrowUp"?(o(),!0):w.key==="ArrowDown"?(c(),!0):w.key==="Enter"?(f(),!0):!1,o=()=>{l.value=(l.value+t.items.length-1)%t.items.length},c=()=>{l.value=(l.value+1)%t.items.length},f=()=>{a(l.value)},a=w=>{const E=t.items[w];E&&t.command(E)};return s({onKeyDown:d}),(w,E)=>(p(),b("div",kt,[e.items.length?(p(!0),b(j,{key:0},Q(e.items,(F,A)=>(p(),b("button",{key:F.title,onClick:C=>a(A),class:x(["slash-command-item flex items-center gap-2 w-full text-left px-3 py-2 hover:bg-base-300 text-sm cursor-pointer",{"bg-base-300":A===l.value}])},[n("div",At,[n("i",{class:x([F.icon,"text-xs"]),"aria-hidden":"true"},null,2)]),n("div",Ft,[n("span",Lt,_(F.title),1),n("span",Tt,_(F.description),1)])],10,Ct))),128)):(p(),b("div",Mt," No commands found "))]))}}),Ht=ue($t,[["__scopeId","data-v-aa19753b"]]),pe=(e,s)=>{xe({getBoundingClientRect:()=>Ce(e.view,e.state.selection.from,e.state.selection.to)},s,{placement:"bottom-start",strategy:"absolute",middleware:[we(),ke()]}).then(({x:l,y:d,strategy:o})=>{s.style.width="max-content",s.style.position=o,s.style.left=`${l}px`,s.style.top=`${d}px`})},Et=`

    `,It=[{title:"Source",description:"Edit raw HTML source",icon:"fa-regular fa-code",command:e=>{window.dispatchEvent(new CustomEvent("tiptap:source-mode"))}},{title:"Gallery",description:"Insert an image from gallery",icon:"fa-regular fa-images",searchTerms:["image","photo","picture","media"],command:e=>{e.commands.openGallery()}},{title:"Insert Media",description:"Upload an image from your device",icon:"fa-regular fa-upload",searchTerms:["image","photo","picture","media","upload","file"],command:e=>{e.commands.uploadMedia()}},{title:"Table",description:"Insert a table",icon:"fa-regular fa-table",command:e=>{e.chain().focus().insertContent(Et,{parseOptions:{preserveWhitespace:!0}}).run()}},{title:"Heading 1",description:"Huge heading",icon:"fa-regular fa-heading",command:e=>{e.chain().focus().toggleHeading({level:1}).run()}},{title:"Heading 2",description:"Large heading",icon:"fa-regular fa-heading",command:e=>{e.chain().focus().toggleHeading({level:2}).run()}},{title:"Heading 3",description:"Medium heading",icon:"fa-regular fa-heading",command:e=>{e.chain().focus().toggleHeading({level:3}).run()}},{title:"Bullet List",description:"Create a bullet list",icon:"fa-regular fa-list-ul",command:e=>{e.chain().focus().toggleBulletList().run()}},{title:"Numbered List",description:"Create a numbered list",icon:"fa-regular fa-list-ol",command:e=>{e.chain().focus().toggleOrderedList().run()}},{title:"Task List",description:"Create a checklist",icon:"fa-regular fa-square-check",searchTerms:["checkbox","checklist","todo","task"],command:e=>{e.chain().focus().toggleTaskList().run()}},{title:"Quote",description:"Insert a quote block",icon:"fa-regular fa-quote-right",command:e=>{e.chain().focus().toggleBlockquote().run()}},{title:"Code Block",description:"Insert a code block",icon:"fa-regular fa-code",command:e=>{e.chain().focus().toggleCodeBlock().run()}},{title:"Horizontal Rule",description:"Insert a divider",icon:"fa-regular fa-minus",command:e=>{e.chain().focus().setHorizontalRule().run()}}],St=()=>({items:({query:e})=>{const s=e.toLowerCase();return It.filter(t=>t.title.toLowerCase().includes(s)||t.searchTerms?.some(l=>l.includes(s)))},render:()=>{let e;return{onStart:s=>{e=new ye(Ht,{props:{items:s.items,command:s.command},editor:s.editor}),s.clientRect&&(e.element.style.position="absolute",document.body.appendChild(e.element),pe(s.editor,e.element))},onUpdate(s){e.updateProps({items:s.items,command:s.command}),s.clientRect&&pe(s.editor,e.element)},onKeyDown(s){return s.event.key==="Escape"?(e.destroy(),!0):e.ref?.onKeyDown(s)},onExit(){e.destroy()}}}}),Rt=oe.create({name:"gallery",addOptions(){return{galleryUrl:"",galleryId:""}},addCommands(){return{openGallery:()=>({editor:e})=>{const s=new CustomEvent("tiptap:open-gallery",{detail:{editor:e,galleryUrl:this.options.galleryUrl,galleryId:this.options.galleryId}});return window.dispatchEvent(s),!0},uploadMedia:()=>({editor:e})=>{const s=this.options.galleryUrl,t=document.createElement("input");return t.type="file",t.accept="image/png,image/jpg,image/jpeg,image/gif,image/webp",t.onchange=async()=>{const l=t.files?.[0];if(!l)return;const d=new FormData;d.append("file",l);try{const o=await Je.post(s,d);o.status===200&&o.data&&e.chain().focus().setImage({src:o.data.src,"data-uuid":o.data.uuid}).run()}catch(o){if(o.response?.status===204||o.response?.data){const c=o.response?.data?.errors||o.response?.data||"Upload failed";window.showToast(c,"error")}else window.showToast("Upload failed","error")}},t.click(),!0}}}}),Dt=He.extend({name:"image",addAttributes(){return{src:{default:null},alt:{default:null},title:{default:null},"data-gallery-id":{default:null},class:{default:null,parseHTML:e=>(e.getAttribute("class")||"").split(/\s+/).filter(l=>!["note-float-left","note-float-right","float-left","float-right"].includes(l)).join(" ").trim()||null},width:{default:null,parseHTML:e=>{const t=(e.getAttribute("style")||"").match(/width:\s*(\d+(?:\.\d+)?)px/);if(t)return parseFloat(t[1]);const l=e.getAttribute("width");return l?parseFloat(l):null},renderHTML:()=>({})},height:{default:null,parseHTML:e=>{const t=(e.getAttribute("style")||"").match(/height:\s*(\d+(?:\.\d+)?)px/);if(t)return parseFloat(t[1]);const l=e.getAttribute("height");return l?parseFloat(l):null},renderHTML:()=>({})},widthStyle:{default:null,parseHTML:e=>{const t=(e.getAttribute("style")||"").match(/width:\s*(\d+%)/);return t?t[1]:null},renderHTML:()=>({})},floatStyle:{default:null,parseHTML:e=>{const t=(e.getAttribute("style")||"").match(/float:\s*(left|right)/);if(t)return t[1];const l=e.getAttribute("class")||"";return l.includes("note-float-left")||l.includes("float-left")?"left":l.includes("note-float-right")||l.includes("float-right")?"right":null},renderHTML:()=>({})}}},renderHTML({node:e,HTMLAttributes:s}){const{width:t,height:l,widthStyle:d,floatStyle:o}=e.attrs,c=[];d?c.push(`width: ${d}`):t!=null&&c.push(`width: ${typeof t=="number"?`${t}px`:t}`),!d&&l!=null&&c.push(`height: ${typeof l=="number"?`${l}px`:l}`),o&&(c.push(`float: ${o}`),o==="left"&&c.push("margin-right: 0.5rem"),o==="right"&&c.push("margin-left: 0.5rem"));const f=c.length>0?{style:c.join("; ")}:{};return["img",te(this.options.HTMLAttributes,s,f)]},addCommands(){return{...this.parent?.(),setImageWidth:e=>({commands:s})=>s.updateAttributes("image",{widthStyle:e,width:null,height:null}),setImageFloat:e=>({commands:s})=>s.updateAttributes("image",{floatStyle:e})}},addNodeView(){if(!this.options.resize?.enabled||typeof document>"u")return null;const{directions:e,minWidth:s,minHeight:t,alwaysPreserveAspectRatio:l}=this.options.resize;return({node:d,getPos:o,HTMLAttributes:c,editor:f})=>{const a=document.createElement("img");Object.entries(c).forEach(([A,C])=>{if(C!=null)switch(A){case"width":case"height":break;default:a.setAttribute(A,C);break}}),a.src=c.src;const w=(A,C)=>{const{widthStyle:I,floatStyle:M}=A;I?(C.style.width=I,a.style.width="100%",a.style.height="auto"):C.style.width="",C.style.float=M||"",C.style.marginRight=M==="left"?"0.5rem":"",C.style.marginLeft=M==="right"?"0.5rem":""},E=new Ee({element:a,editor:f,node:d,getPos:o,onResize:(A,C)=>{a.style.width=`${A}px`,a.style.height=`${C}px`},onCommit:(A,C)=>{const I=o();I!==void 0&&this.editor.chain().setNodeSelection(I).updateAttributes(this.name,{width:A,height:C,widthStyle:null}).run()},onUpdate:A=>A.type!==d.type?!1:(w(A.attrs,E.dom),!0),options:{directions:e,min:{width:s,height:t},preserveAspectRatio:l===!0}}),F=E.dom;return F.style.visibility="hidden",F.style.pointerEvents="none",a.onload=()=>{F.style.visibility="",F.style.pointerEvents=""},w(d.attrs,F),E}}}),Bt=re.create({name:"iframe",group:"block",atom:!0,addOptions(){return{allowFullscreen:!0,HTMLAttributes:{class:"iframe-wrapper relative overflow-hidden max-w-full h-fit"}}},addAttributes(){return{src:{default:null,parseHTML:e=>e.getAttribute("src")},frameborder:{default:0,parseHTML:e=>e.getAttribute("frameborder")||0},allowfullscreen:{default:this.options.allowFullscreen,parseHTML:e=>e.hasAttribute("allowfullscreen")},width:{default:"100%",parseHTML:e=>e.getAttribute("width")||"100%"},height:{default:315,parseHTML:e=>e.getAttribute("height")||315},allow:{default:"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share",parseHTML:e=>e.getAttribute("allow")},title:{default:null,parseHTML:e=>e.getAttribute("title")},style:{default:null,parseHTML:e=>e.getAttribute("style")}}},parseHTML(){return[{tag:"iframe"}]},renderHTML({HTMLAttributes:e}){return["div",this.options.HTMLAttributes,["iframe",e]]},addCommands(){return{setIframe:e=>({tr:s,dispatch:t})=>{const{selection:l}=s,d=this.type.create(e);return t&&s.replaceRangeWith(l.from,l.to,d),!0}}}}),Pt=re.create({name:"div",group:"block",content:"block+",defining:!0,addAttributes(){return{class:{default:null,parseHTML:e=>e.getAttribute("class")},style:{default:null,parseHTML:e=>e.getAttribute("style")}}},parseHTML(){return[{tag:"div",getAttrs:e=>e.parentElement?.getAttribute("data-type")==="taskItem"?!1:{}}]},renderHTML({HTMLAttributes:e}){return["div",te(e),0]}}),Ut=Ie.extend({addAttributes(){return{...this.parent?.(),style:{default:null,parseHTML:e=>e.getAttribute("style")||null,renderHTML:e=>e.style?{style:e.style}:{}}}}}),jt={class:"flex items-center gap-2 text-xs text-neutral-content px-2"},Kt=["placeholder"],zt=["href"],Ot={key:1,class:"text-neutral-content flex items-center gap-1"},Nt=O({__name:"MentionBubbleMenu",props:{editor:{},mentions:{}},setup(e,{expose:s}){const t=e,l=R(null),d=R(""),o=R(null),c=R(""),f=R(!1);s({syncLabel:()=>{const g=t.editor.getAttributes("mention");g?.entity&&(d.value=g?.label||"")}});const w=()=>{t.editor.chain().focus().deleteSelection().run()},E=()=>{},F=()=>{let g=d.value.trim();const u=t.editor.getAttributes("mention")?.id,r=t.mentions.find(i=>i.id===parseInt(u));g===r?.name&&(g=""),t.editor.chain().focus().updateAttributes("mention",{label:g}).run(),d.value=""},A=g=>{const v=g.relatedTarget;v&&v.closest(".bubble-menu")||F()},C=g=>{g.key==="Enter"?(g.preventDefault(),F()):g.key==="Escape"&&(g.preventDefault(),d.value="",t.editor.commands.focus())},I=()=>{const g=t.editor.getAttributes("mention");c.value=g?.config||"",f.value=!0,setTimeout(()=>{o.value?.focus(),o.value?.select()},10)},M=()=>{const g=c.value.trim();t.editor.chain().focus().updateAttributes("mention",{config:g||null}).run(),f.value=!1,c.value=""},m=g=>{const v=g.relatedTarget;v&&v.closest(".bubble-menu")||M()},L=()=>{t.editor.chain().focus().updateAttributes("mention",{config:null}).run(),f.value=!1,c.value=""},T=g=>{g.key==="Enter"?(g.preventDefault(),M()):g.key==="Escape"&&(g.preventDefault(),f.value=!1,c.value="",t.editor.commands.focus())};return(g,v)=>(p(),b("div",jt,[f.value?(p(),b(j,{key:0},[q(n("input",{ref_key:"mentionConfigInput",ref:o,"onUpdate:modelValue":v[0]||(v[0]=u=>c.value=u),type:"text",placeholder:"page:abilities|anchor:#ability-1",class:"p-0 px-1 rounded text-xs outline-none focus:ring-1 focus:ring-primary min-w-[250px]",onBlur:m,onKeydown:T},null,544),[[ee,c.value]]),e.editor.getAttributes("mention").config?(p(),b("button",{key:0,onClick:y(L,["prevent"]),class:"hover:text-warning",title:"Clear config"},[...v[2]||(v[2]=[n("i",{class:"fa-regular fa-times"},null,-1)])])):S("",!0)],64)):(p(),b(j,{key:1},[e.editor.getAttributes("mention").entity?(p(),b(j,{key:0},[q(n("input",{ref_key:"mentionLabelInput",ref:l,"onUpdate:modelValue":v[1]||(v[1]=u=>d.value=u),type:"text",placeholder:e.editor.getAttributes("mention").entity.name,class:"p-0 px-1 rounded text-xs outline-none focus:ring-1 focus:ring-primary min-w-[150px]",onFocus:E,onBlur:A,onKeydown:C},null,40,Kt),[[ee,d.value]]),e.editor.getAttributes("mention").url?(p(),b("a",{key:0,class:"text-link",href:e.editor.getAttributes("mention").url,title:"Go to entity"},[...v[3]||(v[3]=[n("i",{class:"fa-regular fa-external-link-alt","aria-hidden":"true"},null,-1)])],8,zt)):S("",!0)],64)):(p(),b("span",Ot,[...v[4]||(v[4]=[n("i",{class:"fa-regular fa-exclamation-triangle","aria-hidden":"true"},null,-1),D(" Unknown entity ",-1)])])),n("button",{onClick:y(I,["prevent"]),class:x(["hover:text-primary",{"text-primary":e.editor.getAttributes("mention").config}]),title:"Customize mention"},[...v[5]||(v[5]=[n("i",{class:"fa-regular fa-cog","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:y(w,["prevent"]),class:"hover:text-error-content",title:"Remove mention"},[...v[6]||(v[6]=[n("i",{class:"fa-regular fa-trash","aria-hidden":"true"},null,-1)])])],64))]))}}),Vt={class:"flex gap-2 items-center text-xs text-neutral-content px-2"},qt=["href"],Wt=O({__name:"LinkBubbleMenu",props:{editor:{}},setup(e){const s=e,t=R(null),l=R("");Y(()=>s.editor.getAttributes("link").href,a=>{l.value=a||""},{immediate:!0}),Y(()=>s.editor.isActive("link")&&s.editor.state.selection.empty,a=>{a&&setTimeout(()=>t.value?.focus(),10)});const d=()=>{if(!l.value){s.editor.chain().focus().extendMarkRange("link").unsetLink().run();return}let a=l.value;/^https?:\/\//i.test(a)||(a="https://"+a),s.editor.chain().focus().extendMarkRange("link").setLink({href:a}).run()},o=()=>{s.editor.chain().focus().extendMarkRange("link").unsetLink().run()},c=()=>{s.editor.getAttributes("link").href?s.editor.commands.focus():s.editor.chain().focus().extendMarkRange("link").unsetLink().run()},f=a=>{a.key==="Enter"?(a.preventDefault(),d()):a.key==="Escape"&&(a.preventDefault(),c())};return(a,w)=>(p(),b("div",Vt,[q(n("input",{ref_key:"linkInputRef",ref:t,"onUpdate:modelValue":w[0]||(w[0]=E=>l.value=E),type:"text",placeholder:"Enter URL...",class:"p-0 px-1 rounded text-xs outline-none focus:ring-1 focus:ring-primary min-w-[200px]",onKeydown:f},null,544),[[ee,l.value]]),e.editor.isActive("link")?(p(),b("a",{key:0,href:e.editor.getAttributes("link").href,target:"_blank",class:"hover:text-base-content",title:"Open in a new window"},[...w[1]||(w[1]=[n("i",{class:"fa-regular fa-external-link-alt"},null,-1)])],8,qt)):S("",!0),e.editor.isActive("link")?(p(),b("button",{key:1,onClick:y(o,["prevent"]),class:"hover:text-error-content",title:"Remove link"},[...w[2]||(w[2]=[n("i",{class:"fa-regular fa-unlink","aria-label":"Removal icon"},null,-1)])])):S("",!0)]))}}),H=e=>{const s="px-2 py-1 rounded-lg hover:bg-base-200 block hover:text-base-content ",t=e?"bg-base-300 border-primary text-base-content":"text-neutral-content ";return s+" "+t},Gt={class:"flex gap-1 items-center text-xs text-neutral-content px-2"},Zt=O({__name:"TableBubbleMenu",props:{editor:{}},setup(e){const s=e,t=()=>{const{selection:u}=s.editor.state,{$from:r}=u;for(let i=r.depth;i>0;i--){const k=r.node(i);if(k.type.name==="table")return{node:k,pos:r.before(i)}}return null},l=()=>(t()?.node.attrs.class||"").split(" ").filter(Boolean),d=u=>l().includes(u),o=B(()=>d("table-bordered")),c=B(()=>d("table-striped")),f=B(()=>d("table-left")),a=B(()=>d("table-right")),w=u=>{const r=t();if(!r)return;u.some(k=>k.startsWith("table-"))&&!u.includes("table")&&u.unshift("table");const{tr:i}=s.editor.state;i.setNodeMarkup(r.pos,void 0,{...r.node.attrs,class:u.join(" ")||null}),s.editor.view.dispatch(i)},E=(u,r=[])=>{const i=l(),k=i.filter($=>!r.includes($));i.includes(u)?w(k.filter($=>$!==u)):w([...k,u])},F=()=>E("table-bordered"),A=()=>E("table-striped"),C=()=>E("table-left",["table-right"]),I=()=>E("table-right",["table-left"]),M=()=>{s.editor.chain().focus().addColumnAfter().run()},m=()=>{s.editor.chain().focus().addRowAfter().run()},L=()=>{s.editor.chain().focus().deleteTable().run()},T=()=>{s.editor.chain().focus().deleteRow().run()},g=()=>{s.editor.chain().focus().deleteColumn().run()},v=()=>{s.editor.chain().focus().toggleHeaderRow().run()};return(u,r)=>(p(),b("div",Gt,[n("button",{onClick:y(M,["prevent"]),class:x(h(H)(!1)),title:"Add column"},[...r[0]||(r[0]=[n("i",{class:"fa-regular fa-table-columns","aria-hidden":"true"},null,-1),n("i",{class:"fa-regular fa-plus text-[8px]","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:y(m,["prevent"]),class:x(h(H)(!1)),title:"Add row"},[...r[1]||(r[1]=[n("i",{class:"fa-regular fa-table-rows","aria-hidden":"true"},null,-1),n("i",{class:"fa-regular fa-plus text-[8px]","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:y(g,["prevent"]),class:x(h(H)(!1)),title:"Delete column"},[...r[2]||(r[2]=[n("i",{class:"fa-regular fa-table-columns","aria-hidden":"true"},null,-1),n("i",{class:"fa-regular fa-minus text-[8px]","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:y(T,["prevent"]),class:x(h(H)(!1)),title:"Delete row"},[...r[3]||(r[3]=[n("i",{class:"fa-regular fa-table-rows","aria-hidden":"true"},null,-1),n("i",{class:"fa-regular fa-minus text-[8px]","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:y(v,["prevent"]),class:x(h(H)(!1)),title:"Toggle header row"},[...r[4]||(r[4]=[n("i",{class:"fa-regular fa-heading","aria-hidden":"true"},null,-1)])],2),r[10]||(r[10]=n("span",{class:"w-px h-4 bg-base-content/20 mx-1"},null,-1)),n("button",{onClick:y(F,["prevent"]),class:x(h(H)(o.value)),title:"Toggle bordered"},[...r[5]||(r[5]=[n("i",{class:"fa-regular fa-border-all","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:y(A,["prevent"]),class:x(h(H)(c.value)),title:"Toggle striped"},[...r[6]||(r[6]=[n("i",{class:"fa-regular fa-bars","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:y(C,["prevent"]),class:x(h(H)(f.value)),title:"Align left"},[...r[7]||(r[7]=[n("i",{class:"fa-regular fa-align-left","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:y(I,["prevent"]),class:x(h(H)(a.value)),title:"Align right"},[...r[8]||(r[8]=[n("i",{class:"fa-regular fa-align-right","aria-hidden":"true"},null,-1)])],2),r[11]||(r[11]=n("span",{class:"w-px h-4 bg-base-content/20 mx-1"},null,-1)),n("button",{onClick:y(L,["prevent"]),class:"hover:text-error-content px-2 py-1",title:"Delete table"},[...r[9]||(r[9]=[n("i",{class:"fa-regular fa-trash","aria-hidden":"true"},null,-1)])])]))}}),Jt={class:"flex gap-1 items-center text-xs text-neutral-content px-2"},Qt={class:"flex items-center gap-0.5 border-r border-base-300 pr-2 mr-1"},Xt={class:"flex items-center gap-0.5 border-r border-base-300 pr-2 mr-1"},Yt=O({__name:"ImageBubbleMenu",props:{editor:{}},setup(e){const s=e,t=f=>{s.editor.commands.setImageWidth(f)},l=f=>{s.editor.commands.setImageFloat(f)},d=()=>{s.editor.chain().focus().deleteSelection().run()},o=()=>s.editor.getAttributes("image").widthStyle||null,c=()=>s.editor.getAttributes("image").floatStyle||null;return(f,a)=>(p(),b("div",Jt,[n("div",Qt,[n("button",{onClick:a[0]||(a[0]=y(w=>t("25%"),["prevent"])),class:x(h(H)(o()==="25%")),title:"25% width"}," 25% ",2),n("button",{onClick:a[1]||(a[1]=y(w=>t("50%"),["prevent"])),class:x(h(H)(o()==="50%")),title:"50% width"}," 50% ",2),n("button",{onClick:a[2]||(a[2]=y(w=>t("100%"),["prevent"])),class:x(h(H)(o()==="100%")),title:"100% width"}," 100% ",2),n("button",{onClick:a[3]||(a[3]=y(w=>t(null),["prevent"])),class:x(h(H)(o()===null)),title:"Reset width"},[...a[7]||(a[7]=[n("i",{class:"fa-regular fa-undo","aria-hidden":"true"},null,-1)])],2)]),n("div",Xt,[n("button",{onClick:a[4]||(a[4]=y(w=>l("left"),["prevent"])),class:x(h(H)(c()==="left")),title:"Float left"},[...a[8]||(a[8]=[n("i",{class:"fa-regular fa-align-left","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:a[5]||(a[5]=y(w=>l("right"),["prevent"])),class:x(h(H)(c()==="right")),title:"Float right"},[...a[9]||(a[9]=[n("i",{class:"fa-regular fa-align-right","aria-hidden":"true"},null,-1)])],2),n("button",{onClick:a[6]||(a[6]=y(w=>l(null),["prevent"])),class:x(h(H)(c()===null)),title:"No float"},[...a[10]||(a[10]=[n("i",{class:"fa-regular fa-align-justify","aria-hidden":"true"},null,-1)])],2)]),n("button",{onClick:y(d,["prevent"]),class:"hover:text-error-content px-2 py-1",title:"Delete image"},[...a[11]||(a[11]=[n("i",{class:"fa-regular fa-trash","aria-hidden":"true"},null,-1)])])]))}}),_t={class:"relative"},en=["title"],tn={class:"absolute top-full left-0 mt-1 bg-base-100 shadow-lg rounded-lg p-3 z-50 w-[220px]"},nn={key:0,class:"mb-2"},sn={class:"flex flex-wrap gap-1"},ln=["onMousedown","title"],rn={class:"mb-2"},an={class:"grid grid-cols-7 gap-1"},on=["onMousedown","title"],un={class:"mb-2"},dn=["onKeydown"],ie="recent_colors",he=10,be=O({__name:"ColorPicker",props:{currentColor:{},icon:{},title:{}},emits:["select"],setup(e,{emit:s}){const t=e,l=s,d=Math.random().toString(36).substring(7),o=R(!1),c=R(""),f=R([]),a=["#000000","#434343","#666666","#999999","#cccccc","#efefef","#ffffff","#FB0300","#FF9900","#FFFF01","#00FF00","#00FFFF","#0000FF","#9900FF","#FB3533","#FFAD33","#FFFF34","#33FF33","#33FFFF","#3333FF","#AD33FF","#FC6866","#FFC266","#FFFF67","#66FF66","#66FFFF","#6666FF","#C266FF","#FD9B99","#FFD699","#FFFF9A","#99FF99","#99FFFF","#9999FF","#D699FF","#FECECC","#FFEBCC","#FFFFCD","#CCFFCC","#CCFFFF","#CCCCFF","#EBCCFF","#FEE1E0","#FFF3E0","#FFFFE1","#E0FFE0","#E0FFFF","#E0E0FF","#F3E0FF","#FFF5F4","#FFF9F4","#FFFFF5","#F4FFF4","#F4FFFF","#F4F4FF","#F9F4FF"],w=()=>document.cookie.split(";").reduce((g,v)=>{const[u,r]=v.split("=").map(i=>i.trim());return u&&r&&(g[u]=decodeURIComponent(r)),g},{}),E=(g,v,u=30)=>{const r=new Date;r.setTime(r.getTime()+u*24*60*60*1e3),document.cookie=`${g}=${encodeURIComponent(v)}; expires=${r.toUTCString()}; path=/`},F=()=>{const g=w();f.value=g[ie]?JSON.parse(g[ie]):[]},A=g=>{f.value=[g,...f.value.filter(v=>v!==g)],f.value.length>he&&(f.value=f.value.slice(0,he)),E(ie,JSON.stringify(f.value))},C=g=>{A(g),l("select",g),o.value=!1},I=()=>{c.value&&/^#[0-9A-Fa-f]{6}$/.test(c.value)&&(C(c.value),c.value="")},M=()=>{l("select",null),o.value=!1},m=g=>{g.detail!==d&&(o.value=!1)},L=()=>{o.value||(window.dispatchEvent(new CustomEvent("colorpicker:close",{detail:d})),F()),o.value=!o.value},T=B(()=>!!t.currentColor);return Ae(()=>{F(),window.addEventListener("colorpicker:close",m)}),Fe(()=>{window.removeEventListener("colorpicker:close",m)}),(g,v)=>(p(),b("div",_t,[n("button",{onClick:y(L,["prevent"]),class:x([h(H)(T.value),"flex items-center gap-0.5"]),title:e.title},[n("i",{class:x(e.icon),"aria-hidden":"true"},null,2),e.currentColor?(p(),b("span",{key:0,class:"w-2 h-2 rounded-full border border-base-300",style:se({backgroundColor:e.currentColor})},null,4)):S("",!0)],10,en),q(n("div",tn,[f.value.length>0?(p(),b("div",nn,[v[1]||(v[1]=n("div",{class:"text-xs text-neutral-content mb-1"},"Recent",-1)),n("div",sn,[(p(!0),b(j,null,Q(f.value,u=>(p(),b("button",{key:u,onMousedown:y(r=>C(u),["prevent"]),class:"w-5 h-5 rounded border border-base-300 hover:scale-110 transition-transform",style:se({backgroundColor:u}),title:u},null,44,ln))),128))])])):S("",!0),n("div",rn,[v[2]||(v[2]=n("div",{class:"text-xs text-neutral-content mb-1"},"Colors",-1)),n("div",an,[(p(),b(j,null,Q(a,u=>n("button",{key:u,onMousedown:y(r=>C(u),["prevent"]),class:"w-5 h-5 rounded border border-base-300 hover:scale-110 transition-transform",style:se({backgroundColor:u}),title:u},null,44,on)),64))])]),n("div",un,[q(n("input",{"onUpdate:modelValue":v[0]||(v[0]=u=>c.value=u),type:"text",placeholder:"#000000",maxlength:"7",class:"w-full p-1 text-xs rounded border border-base-300 outline-none focus:ring-1 focus:ring-primary",onKeydown:Se(y(I,["prevent"]),["enter"]),onBlur:I},null,40,dn),[[ee,c.value]])]),n("button",{onMousedown:y(M,["prevent"]),class:"w-full text-xs text-neutral-content hover:text-error-content py-1 flex items-center justify-center gap-1 cursor-pointer"},[...v[3]||(v[3]=[n("i",{class:"fa-regular fa-eraser","aria-hidden":"true"},null,-1),D(" Remove color ",-1)])],32)],512),[[le,o.value]])]))}}),cn={class:"relative"},fn={key:0,class:"text-xs"},gn=["innerHTML"],mn={key:0,class:"fa-regular fa-check"},pn={key:0,class:"fa-regular fa-check"},hn={key:0,class:"fa-regular fa-check"},bn={key:0,class:"fa-regular fa-check"},vn={key:0,class:"fa-regular fa-check"},yn={key:0,class:"fa-regular fa-check"},xn={class:"flex items-center gap-0.5 border-r border-base-300 pr-2 mr-1"},wn={class:"flex items-center gap-0.5 border-r border-base-300 pr-2 mr-1"},kn={class:"relative"},Cn={key:0,class:"fa-regular fa-check"},An={key:0,class:"fa-regular fa-check"},Fn=O({__name:"TextBubbleMenu",props:{editor:{}},emits:["openLink"],setup(e,{emit:s}){const t=e,l=s,d=R(!1),o=R(!1),c=B(()=>t.editor.getAttributes("textStyle").color||null),f=B(()=>t.editor.getAttributes("highlight").color||null),a=r=>{r?t.editor.chain().focus().setColor(r).run():t.editor.chain().focus().unsetColor().run()},w=r=>{r?t.editor.chain().focus().setHighlight({color:r}).run():t.editor.chain().focus().unsetHighlight().run()},E=B(()=>!t.editor||t.editor.isActive("paragraph")?"fa-regular fa-paragraph":"fa-regular fa-heading"),F=()=>t.editor.isActive("heading",{level:1})?"1":t.editor.isActive("heading",{level:2})?"2":t.editor.isActive("heading",{level:3})?"3":t.editor.isActive("heading",{level:4})?"4":t.editor.isActive("heading",{level:5})?"5":0,A=r=>{r===null?t.editor.chain().focus().setParagraph().run():t.editor.chain().focus().toggleHeading({level:r}).run(),d.value=!1},C=()=>{d.value=!d.value},I=()=>{d.value=!1},M=B(()=>t.editor?t.editor.isActive("bulletList")?"fa-regular fa-list-ul":t.editor.isActive("orderedList")?"fa-regular fa-list-ol":"fa-regular fa-list-ul":"fa-regular fa-list-ol"),m=r=>{r==="bullet"?t.editor.chain().focus().toggleBulletList().run():t.editor.chain().focus().toggleOrderedList().run(),o.value=!1},L=()=>{o.value=!o.value},T=()=>{o.value=!1},g=B(()=>t.editor.isActive({textAlign:"center"})?"fa-regular fa-align-center":t.editor.isActive({textAlign:"right"})?"fa-regular fa-align-right":"fa-regular fa-align-left"),v=()=>{t.editor.isActive({textAlign:"center"})?t.editor.chain().focus().setTextAlign("right").run():t.editor.isActive({textAlign:"right"})?t.editor.chain().focus().setTextAlign("left").run():t.editor.chain().focus().setTextAlign("center").run()},u=()=>{t.editor.chain().focus().unsetAllMarks().clearNodes().run()};return(r,i)=>(p(),b(j,null,[n("div",cn,[n("button",{onClick:y(C,["prevent"]),class:x([h(H)(!1),"flex items-center gap-0.5"]),onBlur:I},[n("i",{class:x(E.value)},null,2),e.editor.isActive("heading")?(p(),b("sub",fn,[n("span",{innerHTML:F()},null,8,gn)])):S("",!0),i[16]||(i[16]=n("i",{class:"fa-regular fa-chevron-down","aria-label":"Toggle paragraph styles"},null,-1))],34),q(n("div",{class:"absolute top-full left-0 mt-1 bg-base-100 shadow-lg rounded-lg py-1 z-50 min-w-[200px]",onMousedown:i[6]||(i[6]=y(()=>{},["prevent"]))},[n("button",{onClick:i[0]||(i[0]=y(k=>A(null),["prevent"])),class:x(["block w-full text-left px-3 py-2 hover:bg-base-200 text-neutral-content text-xs flex items-center justify-between gap-1",{"text-semibold text-base-content":e.editor.isActive("paragraph")}])},[i[17]||(i[17]=D(" Paragraph ",-1)),e.editor.isActive("paragraph")?(p(),b("i",mn)):S("",!0)],2),n("button",{onClick:i[1]||(i[1]=y(k=>A(1),["prevent"])),class:x(["block w-full text-left px-3 py-2 hover:bg-base-200 text-neutral-content flex items-center justify-between gap-1 text-[1.4rem]",{"font-semibold text-base-content":e.editor.isActive("heading",{level:1})}])},[i[18]||(i[18]=D(" Heading 1 ",-1)),e.editor.isActive("heading",{level:1})?(p(),b("i",pn)):S("",!0)],2),n("button",{onClick:i[2]||(i[2]=y(k=>A(2),["prevent"])),class:x(["block w-full text-left px-3 py-2 hover:bg-base-200 text-neutral-content flex items-center justify-between gap-1 text-[1.3rem]",{"font-semibold text-base-content":e.editor.isActive("heading",{level:2})}])},[i[19]||(i[19]=D(" Heading 2 ",-1)),e.editor.isActive("heading",{level:2})?(p(),b("i",hn)):S("",!0)],2),n("button",{onClick:i[3]||(i[3]=y(k=>A(3),["prevent"])),class:x(["block w-full text-left px-3 py-2 hover:bg-base-200 text-neutral-content flex items-center justify-between gap-1 text-[1.2rem]",{"font-semibold text-base-content":e.editor.isActive("heading",{level:3})}])},[i[20]||(i[20]=D(" Heading 3 ",-1)),e.editor.isActive("heading",{level:3})?(p(),b("i",bn)):S("",!0)],2),n("button",{onClick:i[4]||(i[4]=y(k=>A(4),["prevent"])),class:x(["w-full text-left px-3 py-2 hover:bg-base-200 text-neutral-content text-xs flex items-center justify-between gap-1 text[1.1rem]",{"font-semibold text-base-content":e.editor.isActive("heading",{level:4})}])},[i[21]||(i[21]=D(" Heading 4 ",-1)),e.editor.isActive("heading",{level:4})?(p(),b("i",vn)):S("",!0)],2),n("button",{onClick:i[5]||(i[5]=y(k=>A(5),["prevent"])),class:x(["w-full text-left px-3 py-2 hover:bg-base-200 text-neutral-content text-xs flex items-center justify-between gap-1",{"font-semibold text-base-content":e.editor.isActive("heading",{level:5})}])},[i[22]||(i[22]=D(" Heading 5 ",-1)),e.editor.isActive("heading",{level:5})?(p(),b("i",yn)):S("",!0)],2)],544),[[le,d.value]])]),n("button",{onClick:i[7]||(i[7]=y(k=>e.editor.chain().focus().toggleBold().run(),["prevent"])),class:x(h(H)(e.editor.isActive("bold")))},[...i[23]||(i[23]=[n("i",{class:"fa-regular fa-bold","aria-label":"Bold"},null,-1)])],2),n("button",{onClick:i[8]||(i[8]=y(k=>e.editor.chain().focus().toggleItalic().run(),["prevent"])),class:x(h(H)(e.editor.isActive("italic")))},[...i[24]||(i[24]=[n("i",{class:"fa-regular fa-italic","aria-label":"Italic"},null,-1)])],2),n("button",{onClick:i[9]||(i[9]=y(k=>e.editor.chain().focus().toggleStrike().run(),["prevent"])),class:x(h(H)(e.editor.isActive("strike")))},[...i[25]||(i[25]=[n("i",{class:"fa-regular fa-strikethrough","aria-label":"Strikethrough"},null,-1)])],2),n("button",{onClick:i[10]||(i[10]=y(k=>e.editor.chain().focus().toggleUnderline().run(),["prevent"])),class:x(h(H)(e.editor.isActive("underline")))},[...i[26]||(i[26]=[n("i",{class:"fa-regular fa-underline","aria-label":"Underline"},null,-1)])],2),n("div",xn,[U(be,{"current-color":c.value,icon:"fa-regular fa-font",title:"Text color",onSelect:a},null,8,["current-color"]),U(be,{"current-color":f.value,icon:"fa-regular fa-fill",title:"Highlight color",onSelect:w},null,8,["current-color"])]),n("div",wn,[n("button",{onClick:i[11]||(i[11]=y(k=>l("openLink"),["prevent"])),class:x(h(H)(e.editor.isActive("link")))},[...i[27]||(i[27]=[n("i",{class:"fa-regular fa-link","aria-label":"Link"},null,-1)])],2),n("button",{onClick:i[12]||(i[12]=y(k=>e.editor.chain().focus().toggleBlockquote().run(),["prevent"])),class:x(h(H)(e.editor.isActive("blockquote")))},[...i[28]||(i[28]=[n("i",{class:"fa-regular fa-quote-right","aria-label":"Quote"},null,-1)])],2),n("button",{onClick:y(v,["prevent"]),class:x(h(H)(e.editor.isActive({textAlign:"center"})||e.editor.isActive({textAlign:"right"}))),title:"Text alignment"},[n("i",{class:x(g.value),"aria-label":"Text alignment"},null,2)],2),n("div",kn,[n("button",{onClick:y(L,["prevent"]),class:x([h(H)(!1),"flex items-center gap-0.5"]),onBlur:T},[n("i",{class:x(M.value)},null,2),i[29]||(i[29]=n("i",{class:"fa-regular fa-chevron-down","aria-label":"Toggle paragraph styles"},null,-1))],34),q(n("div",{class:"absolute top-full left-0 mt-1 bg-base-100 shadow-lg rounded-lg py-1 z-50 min-w-[200px]",onMousedown:i[15]||(i[15]=y(()=>{},["prevent"]))},[n("button",{onClick:i[13]||(i[13]=y(k=>m("bullet"),["prevent"])),class:x(["block w-full text-left px-3 py-2 hover:bg-base-200 text-neutral-content text-xs flex items-center justify-between gap-1",{"text-semibold text-base-content":e.editor.isActive("bulletList")}])},[i[30]||(i[30]=n("div",{class:"flex gap-1 items-center"},[n("i",{class:"fa-regular fa-list-ul","aria-hidden":"true"}),D(" List ")],-1)),e.editor.isActive("bulletList")?(p(),b("i",Cn)):S("",!0)],2),n("button",{onClick:i[14]||(i[14]=y(k=>m("ordered"),["prevent"])),class:x(["block w-full text-left px-3 py-2 hover:bg-base-200 text-neutral-content text-xs flex items-center justify-between gap-1",{"text-semibold text-base-content":e.editor.isActive("orderedList")}])},[i[31]||(i[31]=n("div",{class:"flex gap-1 items-center"},[n("i",{class:"fa-regular fa-list-ol","aria-hidden":"true"}),D(" Numbered list ")],-1)),e.editor.isActive("orderedList")?(p(),b("i",An)):S("",!0)],2)],544),[[le,o.value]])])]),n("button",{onClick:y(u,["prevent"]),class:x(h(H)(!1)),title:"Clear formatting"},[...i[32]||(i[32]=[n("i",{class:"fa-regular fa-paint-roller","aria-label":"Clear formatting"},null,-1)])],2)],64))}}),Ln={class:"bubble-menu bg-base-100 shadow rounded-2xl flex gap-0.5 items-center px-2 py-2"},Tn={class:"bubble-menu bg-base-100 shadow rounded-2xl flex gap-0.5 items-center px-2 py-2"},Mn={class:"bubble-menu bg-base-100 shadow rounded-2xl flex gap-0.5 items-center px-2 py-2"},$n={class:"bubble-menu bg-base-100 shadow rounded-2xl flex gap-0.5 items-center px-2 py-2"},Hn={class:"bubble-menu bg-base-100 shadow rounded-2xl flex gap-0.5 items-center px-2 py-2"},En={key:1,class:"text-neutral-content text-xs mt-2 flex items-center gap-5"},In=["name","value"],Sn=O({__name:"Tiptap",props:{modelValue:{},content:{},gallery:{},mentions:{},galleryUpload:{},fieldName:{default:"entry"}},setup(e){const s=ge(()=>de(()=>import("./GalleryDialog-B3Id-RLo.js"),__vite__mapDeps([0,1,2,3,4]))),t=ge(()=>de(()=>import("./SourceEditor-CaEGRaAo.js"),__vite__mapDeps([5,1,3,6]))),l=e,d=Re().uid.toString(),o=R(l.content??l.modelValue??""),c=R([]),f=R(!1),a=R(!1),w=R(!1),E=B(()=>f.value&&!a.value&&m.value?.isEmpty),F=()=>{w.value=!0},A=()=>{m.value?.commands.setContent(o.value),w.value=!1,setTimeout(()=>{m.value?.commands.focus()},50)},C=R(null),I=u=>{c.value.find(i=>i.id===u.id)||c.value.push(u)},M=[Ke.configure({link:!1,bulletList:!1,orderedList:!1,listItem:!1,listKeymap:!1,heading:!1}),Ut,ze.configure({placeholder:"Start writing..."}),Oe.configure({openOnClick:!1,defaultProtocol:"https",HTMLAttributes:{class:"text-link"}}),Ne.configure({taskItem:{nested:!0}}),Ye.configure({resizable:!0,allowTableNodeSelection:!0}),De,Qe.configure({}),Xe.configure({}),wt.configure({suggestion:St()}),Dt.configure({inline:!0,allowBase64:!1,resize:{enabled:!0,minWidth:20,minHeight:20,alwaysPreserveAspectRatio:!0}}),Bt,Pt,Ve.configure({persist:!0,HTMLAttributes:{class:"details"}}),Be,Pe,Ue,je,qe.configure({multicolor:!0}),We.configure({types:["heading","paragraph"]}),window.tiptapCustomExtensions??[]];l.gallery&&M.push(Rt.configure({galleryUrl:l.gallery,galleryId:d})),l.mentions&&M.push(et.configure({HTMLAttributes:{class:"mention"},suggestion:bt(l.mentions,I),renderText({node:u}){const r=u.attrs.mention,i=u.attrs.label,k=u.attrs.id,$=u.attrs.config,N=r?.match(/\[([^:]+):(\d+)/),P=N?N[1]:null;if(P&&k){const G=c.value.find(z=>z.id===parseInt(k)),W=G?G.name:null,K=[`${P}:${k}`];return i&&W&&i!==W&&K.push(i),$&&K.push($),`[${K.join("|")}]`}return r||`[${i}]`}}),yt.configure({entities:c}));const m=Ge({content:o.value,extensions:M,onFocus:()=>{f.value=!0},onBlur:()=>{f.value=!1},onUpdate:({editor:u})=>{o.value=u.getHTML().replace(/]*) data-table-class="([^"]+)"([^>]*)>/g,''),!a.value&&!u.isEmpty&&(a.value=!0)},onSelectionUpdate:({editor:u})=>{u.isActive("mention")&&C.value?.syncLabel()},editorProps:{clipboardTextSerializer:u=>{let r="";return u.content.forEach(i=>{r+=L(i)}),r},handlePaste:(u,r,i)=>{if(l.galleryUpload){const z=Array.from(r.clipboardData?.items||[]).find(V=>V.type.startsWith("image/"));if(z){const V=z.getAsFile();if(V){const X=new FormData;return X.append("file[]",V),axios.post(l.galleryUpload,X).then(ne=>{ne.data?.url&&m.value?.chain().focus().setImage({src:ne.data.url,"data-gallery-id":String(ne.data.id)}).run()}).catch(()=>{window.showToast("Image upload failed","error")}),!0}}}const k=r.clipboardData?.getData("text/plain")||"",$=r.clipboardData?.getData("text/html")||"";if(($||k).match(/]*src=["']([^"']+)["'][^>]*>/i)||k.includes("(r.ctrlKey||r.metaKey)&&r.key==="k"&&!u.state.selection.empty?(r.preventDefault(),g(),!0):!1}}),L=u=>{if(u.type.name==="mention")return T(u);if(u.isText)return u.text||"";let r="";return u.content&&u.content.forEach(i=>{r+=L(i)}),u.isBlock&&r&&(r+=` `),r},T=u=>{const r=u.attrs.config,i=u.attrs.id,$=[`${u.attrs.type}:${i}`];return r&&$.push(r),`[${$.join("|")}]`},g=()=>{if(!m.value)return;const u=m.value.getAttributes("link").href||"",{to:r}=m.value.state.selection;m.value.chain().focus().setLink({href:u}).setTextSelection(r).run()},v=u=>{const r=[],i=[],k=/\[([a-zA-Z_]+):(\d+)(?:\|[^\]]+)?\]/g;let $;for(;($=k.exec(u))!==null;){const N=$[1],P=parseInt($[2],10);N==="post"?i.includes(P)||i.push(P):r.includes(P)||r.push(P)}return{entityIds:r,postIds:i}};return Ae(()=>{if(window.addEventListener("tiptap:source-mode",F),l.mentions&&l.content){const{entityIds:u,postIds:r}=v(l.content);(u.length>0||r.length>0)&&axios.post(l.mentions,{entities:u,posts:r}).then(i=>{c.value=i.data,m.value?.commands.setContent(l.content)})}}),Fe(()=>{window.removeEventListener("tiptap:source-mode",F),m?.value.destroy()}),(u,r)=>(p(),b(j,null,[w.value?(p(),ce(h(t),{key:0,modelValue:o.value,"onUpdate:modelValue":r[0]||(r[0]=i=>o.value=i),onExit:A},null,8,["modelValue"])):(p(),b(j,{key:1},[h(m)?(p(),b(j,{key:0},[U(h(J),{editor:h(m),"plugin-key":"mentionBubbleMenu","should-show":({editor:i})=>i.isActive("mention")},{default:Z(()=>[n("div",Ln,[U(Nt,{ref_key:"mentionBubbleRef",ref:C,editor:h(m),mentions:c.value},null,8,["editor","mentions"])])]),_:1},8,["editor","should-show"]),U(h(J),{editor:h(m),"plugin-key":"linkBubbleMenu","should-show":({editor:i})=>i.isActive("link")&&i.state.selection.empty},{default:Z(()=>[n("div",Tn,[U(Wt,{editor:h(m)},null,8,["editor"])])]),_:1},8,["editor","should-show"]),U(h(J),{editor:h(m),"plugin-key":"tableBubbleMenu","should-show":({editor:i})=>i.state.selection instanceof h(fe)},{default:Z(()=>[n("div",Mn,[U(Zt,{editor:h(m)},null,8,["editor"])])]),_:1},8,["editor","should-show"]),U(h(J),{editor:h(m),"plugin-key":"imageBubbleMenu","should-show":({editor:i})=>i.isActive("image")},{default:Z(()=>[n("div",$n,[U(Yt,{editor:h(m)},null,8,["editor"])])]),_:1},8,["editor","should-show"]),U(h(J),{editor:h(m),"plugin-key":"textBubbleMenu","should-show":({editor:i})=>!(i.state.selection.empty||i.isActive("mention")||i.state.selection instanceof h(fe)||i.isActive("image"))},{default:Z(()=>[n("div",Hn,[U(Fn,{editor:h(m),onOpenLink:g},null,8,["editor"])])]),_:1},8,["editor","should-show"])],64)):S("",!0),U(h(Ze),{editor:h(m),class:""},null,8,["editor"]),E.value?(p(),b("p",En,[...r[1]||(r[1]=[n("span",null,[D(" Use "),n("kbd",null,"@"),D(" to reference entities ")],-1),n("span",null,[n("kbd",null,"/"),D(" for commands ")],-1)])])):S("",!0)],64)),n("input",{type:"hidden",name:l.fieldName,value:o.value},null,8,In),e.gallery?(p(),ce(h(s),{key:2,"gallery-id":h(d)},null,8,["gallery-id"])):S("",!0)],64))}}),Un=ue(Sn,[["__scopeId","data-v-539a3ede"]]);export{Un as default}; ================================================ FILE: public/build/assets/_commonjsHelpers-Cpj98o6Y.js ================================================ var o=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function l(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}export{o as c,l as g}; ================================================ FILE: public/build/assets/_plugin-vue_export-helper-DlAUqK2U.js ================================================ const s=(t,r)=>{const o=t.__vccOpts||t;for(const[c,e]of r)o[c]=e;return o};export{s as _}; ================================================ FILE: public/build/assets/abilities-_1tR-hmN.js ================================================ import{f as k,o as t,c as i,a as s,h as x,b as n,F as f,r as v,n as T,j as p,i as b,g as M,B as w,e as H}from"./vendor-tiptap-D5xFoo7B.js";const _=["data-tags"],$={class:"ability-box p-4 rounded-lg bg-box shadow-xs flex flex-col md:flex-row items-center md:items-start gap-2 md:gap-4"},C={key:0,class:""},A=["href"],B={class:"flex flex-col gap-4 w-full"},j={class:"flex gap-2 md:gap-4 items-center w-full"},I={class:"flex gap-2 items-center text-lg grow"},N=["href","innerHTML"],z=["title"],D=["title"],E=["title"],F=["title"],V=["title"],P=["innerHTML"],S={key:1,class:""},U=["title"],q=["innerHTML"],G={key:0,class:"visible md:hidden"},J=["innerHTML"],K=["innerHTML"],O={key:2,class:"flex gap-2 items-center ability-tags"},Q=["href","data-url","innerHTML"],R=["innerHTML"],W={key:4,class:"flex gap-2 md:gap-4 ability-charges w-full items-end"},X={class:"flex gap-1 flex-wrap grow"},Y=["onClick"],Z=["innerHTML"],ee={class:"flex-none"},te=["innerHTML"],ie=["innerHTML"],se=k({__name:"Ability",props:{ability:{},permission:{}},setup(e){const o=e,r=()=>o.permission.value,u=()=>o.ability.images.thumb?{backgroundImage:"url("+o.ability.images.thumb+")"}:{},g=a=>{window.openDialog("abilities-dialog",a.actions.edit)},h=()=>o.ability.charges-o.ability.used_charges,y=()=>o.ability.i18n.left.replace(/:amount/,""),c=a=>{let d="rounded-xl bg-base-200 text-xs py-1 px-3 text-base-content";return d+=" "+a.class},m=(a,d)=>{d>a.used_charges?a.used_charges+=1:a.used_charges-=1,axios.post(a.actions.use,{used:a.used_charges}).then(l=>{l.data.success||(a.used_charges-=1)}).catch(()=>{a.used_charges-=1})};return(a,d)=>(t(),i("div",{class:"ability","data-tags":e.ability.class},[s("div",$,[e.ability.images.has?(t(),i("div",C,[s("a",{class:"ability-image rounded-lg block w-40 h-40 cover-background",href:e.ability.images.url,style:x(u())},null,12,A)])):n("",!0),s("div",B,[s("div",j,[s("div",I,[s("a",{href:e.ability.actions.view,class:"ability-name text-lg text-link",innerHTML:e.ability.name},null,8,N),e.ability.visibility_id===2?(t(),i("i",{key:0,class:"fa-regular fa-lock",title:e.ability.visibility},null,8,z)):n("",!0),e.ability.visibility_id===3?(t(),i("i",{key:1,class:"fa-regular fa-user-lock",title:e.ability.visibility},null,8,D)):n("",!0),e.ability.visibility_id===5?(t(),i("i",{key:2,class:"fa-regular fa-users",title:e.ability.visibility},null,8,E)):n("",!0),e.ability.visibility_id===4?(t(),i("i",{key:3,class:"fa-regular fa-user-secret",title:e.ability.visibility},null,8,F)):n("",!0),e.ability.visibility_id===1?(t(),i("i",{key:4,class:"fa-regular fa-eye",title:e.ability.visibility},null,8,V)):n("",!0)]),e.ability.type?(t(),i("div",{key:0,class:"hidden md:inline bg-base-200 p-2 px-3 rounded-2xl flex-none",innerHTML:e.ability.type},null,8,P)):n("",!0),e.permission?(t(),i("div",S,[r?(t(),i("a",{key:0,role:"button",onClick:d[0]||(d[0]=l=>g(e.ability)),class:"btn2 btn-ghost btn-sm",title:e.ability.i18n.edit},[d[1]||(d[1]=s("i",{class:"fa-regular fa-pencil","aria-hidden":"true"},null,-1)),s("span",{class:"sr-only",innerHTML:e.ability.i18n.edit},null,8,q)],8,U)):n("",!0)])):n("",!0)]),e.ability.type?(t(),i("div",G,[s("div",{class:"inline-block bg-base-200 p-2 rounded-xl",innerHTML:e.ability.type},null,8,J)])):n("",!0),e.ability.entry?(t(),i("div",{key:1,class:"entity-content",innerHTML:e.ability.entry},null,8,K)):n("",!0),e.ability.tags?(t(),i("div",O,[(t(!0),i(f,null,v(e.ability.tags,l=>(t(),i("a",{class:T(c(l)),style:x(l.style||""),href:l.url,"data-toggle":"tooltip-ajax","data-url":l.tooltip,innerHTML:l.name},null,14,Q))),256))])):n("",!0),e.ability.note?(t(),i("div",{key:3,class:"entity-content text-sm text-neutral-content",innerHTML:e.ability.note},null,8,R)):n("",!0),e.ability.charges&&e.permission?(t(),i("div",W,[s("div",X,[(t(!0),i(f,null,v(e.ability.charges,l=>(t(),i("div",{class:T(["charge cursor-pointer rounded-full p-2 hover:bg-accent hover:text-accent-content w-8 h-8 flex items-center justify-center",{"bg-base-200 charge-used":e.ability.used_charges>=l}]),onClick:pe=>m(e.ability,l)},[s("span",{innerHTML:l},null,8,Z)],10,Y))),256))]),s("div",ee,[s("span",{class:"text-lg",innerHTML:h()},null,8,te),s("span",{innerHTML:y()},null,8,ie)])])):n("",!0)])])],8,_))}}),ne={class:"ability-parent flex flex-col gap-5 w-full"},ae={class:"parent-head flex gap-2 md:gap-5 items-center"},le={class:"flex flex-col gap-1 grow overflow-hidden"},oe={key:0},re=["href","innerHTML"],ce=["innerHTML"],de=["innerHTML","data-title"],ue={class:"flex-none self-end"},ge={key:0,"aria-hidden":"true",class:"fa-thin fa-chevron-circle-up fa-2x"},ye={key:1,"aria-hidden":"true",class:"fa-thin fa-chevron-circle-down fa-2x"},he={key:2,class:"sr-only"},be={key:3,class:"sr-only"},me={key:0,class:"parent-abilities flex flex-col gap-5"},fe=k({__name:"Parent",props:{group:{},permission:{},meta:{}},setup(e){const o=e,r=b(!1),u=()=>o.group.has_image?{backgroundImage:"url("+o.group.image+")"}:{},g=h=>{r.value=!r.value};return(h,y)=>(t(),i("div",ne,[s("div",ae,[e.group.has_image?(t(),i("div",{key:0,class:"parent-image rounded-full w-12 h-12 md:w-16 md:h-16 cover-background flex-none",style:x(u())},null,4)):n("",!0),s("div",le,[e.group.url?(t(),i("div",oe,[s("a",{href:e.group.url,innerHTML:e.group.name,class:"parent-name text-xl"},null,8,re)])):(t(),i("span",{key:1,class:"parent-name text-xl",innerHTML:e.group.name},null,8,ce)),s("p",{class:"truncate",innerHTML:e.group.type,"data-toggle":"tooltip","data-title":e.group.type},null,8,de)]),s("div",ue,[s("span",{role:"button",onClick:y[0]||(y[0]=c=>g(e.group)),class:"cursor-pointer inline-block"},[r.value?(t(),i("i",ye)):(t(),i("i",ge)),r.value?(t(),i("span",be,"Collapse section")):(t(),i("span",he,"Expand section"))])])]),r.value?n("",!0):(t(),i("div",me,[(t(!0),i(f,null,v(e.group.abilities,c=>(t(),p(se,{key:c.id,ability:c,permission:e.permission,meta:e.meta},null,8,["ability","permission","meta"]))),128))]))]))}}),ve={class:"viewport box-abilities relative flex flex-col gap-5"},xe={key:0,class:"load more text-center text-2xl"},ke={class:"flex gap-5 flex-wrap"},Te=k({__name:"Abilities",props:{id:{},api:{},permission:{}},setup(e){const o=e,r=b([]),u=b([]),g=b(!0),h=b(!0),y=()=>{axios.get(o.api).then(c=>{r.value=c.data.data.groups,u.value=c.data.data.meta,g.value=!1,h.value=!1})};return M(()=>{y()}),w(()=>{window.ajaxTooltip(),window.initTooltips()}),(c,m)=>(t(),i("div",ve,[g.value?(t(),i("div",xe,[...m[0]||(m[0]=[s("i",{class:"fa-solid fa-spin fa-spinner","aria-hidden":"true"},null,-1)])])):n("",!0),s("div",ke,[(t(!0),i(f,null,v(r.value,a=>(t(),p(fe,{key:a.id,group:a,permission:e.permission,meta:u.value},null,8,["group","permission","meta"]))),128))])]))}}),L=H({});L.component("abilities",Te);L.mount("#abilities"); ================================================ FILE: public/build/assets/app-7Sr4fLAQ.css ================================================ .clr-picker{display:none;flex-wrap:wrap;position:absolute;width:200px;z-index:1000;border-radius:10px;background-color:#fff;justify-content:flex-end;direction:ltr;box-shadow:0 0 5px #0000000d,0 5px 20px #0000001a;-moz-user-select:none;-webkit-user-select:none;user-select:none}.clr-picker.clr-open,.clr-picker[data-inline=true]{display:flex}.clr-picker[data-inline=true]{position:relative}.clr-gradient{position:relative;width:100%;height:100px;margin-bottom:15px;border-radius:3px 3px 0 0;background-image:linear-gradient(#0000,#000),linear-gradient(90deg,#fff,currentColor);cursor:pointer}.clr-marker{position:absolute;width:12px;height:12px;margin:-6px 0 0 -6px;border:1px solid #fff;border-radius:50%;background-color:currentColor;cursor:pointer}.clr-picker input[type=range]::-webkit-slider-runnable-track{width:100%;height:16px}.clr-picker input[type=range]::-webkit-slider-thumb{width:16px;height:16px;-webkit-appearance:none}.clr-picker input[type=range]::-moz-range-track{width:100%;height:16px;border:0}.clr-picker input[type=range]::-moz-range-thumb{width:16px;height:16px;border:0}.clr-hue{background-image:linear-gradient(to right,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.clr-hue,.clr-alpha{position:relative;width:calc(100% - 40px);height:8px;margin:5px 20px;border-radius:4px}.clr-alpha span{display:block;height:100%;width:100%;border-radius:inherit;background-image:linear-gradient(90deg,rgba(0,0,0,0),currentColor)}.clr-hue input,.clr-alpha input{position:absolute;width:calc(100% + 32px);height:16px;left:-16px;top:-4px;margin:0;background-color:transparent;opacity:0;cursor:pointer;appearance:none;-webkit-appearance:none}.clr-hue div,.clr-alpha div{position:absolute;width:16px;height:16px;left:0;top:50%;margin-left:-8px;transform:translateY(-50%);border:2px solid #fff;border-radius:50%;background-color:currentColor;box-shadow:0 0 1px #888;pointer-events:none}.clr-alpha div:before{content:"";position:absolute;height:100%;width:100%;left:0;top:0;border-radius:50%;background-color:currentColor}.clr-format{display:none;order:1;width:calc(100% - 40px);margin:0 20px 20px}.clr-segmented{display:flex;position:relative;width:100%;margin:0;padding:0;border:1px solid #ddd;border-radius:15px;box-sizing:border-box;color:#999;font-size:12px}.clr-segmented input,.clr-segmented legend{position:absolute;width:100%;height:100%;margin:0;padding:0;border:0;left:0;top:0;opacity:0;pointer-events:none}.clr-segmented label{flex-grow:1;margin:0;padding:4px 0;font-size:inherit;font-weight:400;line-height:initial;text-align:center;cursor:pointer}.clr-segmented label:first-of-type{border-radius:10px 0 0 10px}.clr-segmented label:last-of-type{border-radius:0 10px 10px 0}.clr-segmented input:checked+label{color:#fff;background-color:#666}.clr-swatches{order:2;width:calc(100% - 32px);margin:0 16px}.clr-swatches div{display:flex;flex-wrap:wrap;padding-bottom:12px;justify-content:center}.clr-swatches button{position:relative;width:20px;height:20px;margin:0 4px 6px;padding:0;border:0;border-radius:50%;color:inherit;text-indent:-1000px;white-space:nowrap;overflow:hidden;cursor:pointer}.clr-swatches button:after{content:"";display:block;position:absolute;width:100%;height:100%;left:0;top:0;border-radius:inherit;background-color:currentColor;box-shadow:inset 0 0 0 1px #0000001a}input.clr-color{order:1;width:calc(100% - 80px);height:32px;margin:15px 20px 20px auto;padding:0 10px;border:1px solid #ddd;border-radius:16px;color:#444;background-color:#fff;font-family:sans-serif;font-size:14px;text-align:center;box-shadow:none}input.clr-color:focus{outline:none;border:1px solid #1e90ff}.clr-close,.clr-clear{display:none;order:2;height:24px;margin:0 20px 20px;padding:0 20px;border:0;border-radius:12px;color:#fff;background-color:#666;font-family:inherit;font-size:12px;font-weight:400;cursor:pointer}.clr-close{display:block;margin:0 20px 20px auto}.clr-preview{position:relative;width:32px;height:32px;margin:15px 0 20px 20px;border-radius:50%;overflow:hidden}.clr-preview:before,.clr-preview:after{content:"";position:absolute;height:100%;width:100%;left:0;top:0;border:1px solid #fff;border-radius:50%}.clr-preview:after{border:0;background-color:currentColor;box-shadow:inset 0 0 0 1px #0000001a}.clr-preview button{position:absolute;width:100%;height:100%;z-index:1;margin:0;padding:0;border:0;border-radius:50%;outline-offset:-2px;background-color:transparent;text-indent:-9999px;cursor:pointer;overflow:hidden}.clr-marker,.clr-hue div,.clr-alpha div,.clr-color{box-sizing:border-box}.clr-field{display:inline-block;position:relative;color:transparent}.clr-field input{margin:0;direction:ltr}.clr-field.clr-rtl input{text-align:right}.clr-field button{position:absolute;width:30px;height:100%;right:0;top:50%;transform:translateY(-50%);margin:0;padding:0;border:0;color:inherit;text-indent:-1000px;white-space:nowrap;overflow:hidden;pointer-events:none}.clr-field.clr-rtl button{right:auto;left:0}.clr-field button:after{content:"";display:block;position:absolute;width:100%;height:100%;left:0;top:0;border-radius:inherit;background-color:currentColor;box-shadow:inset 0 0 1px #00000080}.clr-alpha,.clr-alpha div,.clr-swatches button,.clr-preview:before,.clr-field button{background-image:repeating-linear-gradient(45deg,#aaa 25%,transparent 25%,transparent 75%,#aaa 75%,#aaa),repeating-linear-gradient(45deg,#aaa 25%,#fff 25% 75%,#aaa 75%,#aaa);background-position:0 0,4px 4px;background-size:8px 8px}.clr-marker:focus{outline:none}.clr-keyboard-nav .clr-marker:focus,.clr-keyboard-nav .clr-hue input:focus+div,.clr-keyboard-nav .clr-alpha input:focus+div,.clr-keyboard-nav .clr-segmented input:focus+label{outline:none;box-shadow:0 0 0 2px #1e90ff,0 0 2px 2px #fff}.clr-picker[data-alpha=false] .clr-alpha{display:none}.clr-picker[data-minimal=true]{padding-top:16px}.clr-picker[data-minimal=true] .clr-gradient,.clr-picker[data-minimal=true] .clr-hue,.clr-picker[data-minimal=true] .clr-alpha,.clr-picker[data-minimal=true] .clr-color,.clr-picker[data-minimal=true] .clr-preview{display:none}.clr-dark{background-color:#444}.clr-dark .clr-segmented{border-color:#777}.clr-dark .clr-swatches button:after{box-shadow:inset 0 0 0 1px #ffffff4d}.clr-dark input.clr-color{color:#fff;border-color:#777;background-color:#555}.clr-dark input.clr-color:focus{border-color:#1e90ff}.clr-dark .clr-preview:after{box-shadow:inset 0 0 0 1px #ffffff80}.clr-dark .clr-alpha,.clr-dark .clr-alpha div,.clr-dark .clr-swatches button,.clr-dark .clr-preview:before{background-image:repeating-linear-gradient(45deg,#666 25%,transparent 25%,transparent 75%,#888 75%,#888),repeating-linear-gradient(45deg,#888 25%,#444 25% 75%,#888 75%,#888)}.clr-picker.clr-polaroid{border-radius:6px;box-shadow:0 0 5px #0000001a,0 5px 30px #0003}.clr-picker.clr-polaroid:before{content:"";display:block;position:absolute;width:16px;height:10px;left:20px;top:-10px;border:solid transparent;border-width:0 8px 10px 8px;border-bottom-color:currentColor;box-sizing:border-box;color:#fff;filter:drop-shadow(0 -4px 3px rgba(0,0,0,.1));pointer-events:none}.clr-picker.clr-polaroid.clr-dark:before{color:#444}.clr-picker.clr-polaroid.clr-left:before{left:auto;right:20px}.clr-picker.clr-polaroid.clr-top:before{top:auto;bottom:-10px;transform:rotate(180deg)}.clr-polaroid .clr-gradient{width:calc(100% - 20px);height:120px;margin:10px;border-radius:3px}.clr-polaroid .clr-hue,.clr-polaroid .clr-alpha{width:calc(100% - 30px);height:10px;margin:6px 15px;border-radius:5px}.clr-polaroid .clr-hue div,.clr-polaroid .clr-alpha div{box-shadow:0 0 5px #0003}.clr-polaroid .clr-format{width:calc(100% - 20px);margin:0 10px 15px}.clr-polaroid .clr-swatches{width:calc(100% - 12px);margin:0 6px}.clr-polaroid .clr-swatches div{padding-bottom:10px}.clr-polaroid .clr-swatches button{width:22px;height:22px}.clr-polaroid input.clr-color{width:calc(100% - 60px);margin:10px 10px 15px auto}.clr-polaroid .clr-clear{margin:0 10px 15px}.clr-polaroid .clr-close{margin:0 10px 15px auto}.clr-polaroid .clr-preview{margin:10px 0 15px 10px}.clr-picker.clr-large{width:275px}.clr-large .clr-gradient{height:150px}.clr-large .clr-swatches button{width:22px;height:22px}.clr-picker.clr-pill{width:380px;padding-left:180px;box-sizing:border-box}.clr-pill .clr-gradient{position:absolute;width:180px;height:100%;left:0;top:0;margin-bottom:0;border-radius:3px 0 0 3px}.clr-pill .clr-hue{margin-top:20px} ================================================ FILE: public/build/assets/app-B8BXQiap.js ================================================ import{S as Wt}from"./sortable.esm-DdTU3J9A.js";import"./dialog-DkrH_pRQ.js";import{C as Oe}from"./coloris-DsKOFKq5.js";import{c as p,a as l,t as v,b,F as P,w as ee,v as yt,n as D,d as z,o as f,e as we,f as J,g as Fe,h as oe,r as j,i as k,j as re,k as Ve,l as Gt,m as me,p as ke,q as ge,s as Le,u as Yt,x as bt,y as Qt,z as fe,A as zn}from"./vendor-tiptap-D5xFoo7B.js";import{a as Jt}from"./index-D5GkNzM3.js";import{_ as ot}from"./_plugin-vue_export-helper-DlAUqK2U.js";import{t as ye}from"./tippy.esm-CnBRltuW.js";import{v as Xt}from"./v-click-outside.umd-Cl-Y_A58.js";import{_ as Wn}from"./Browser.vue_vue_type_script_setup_true_lang-DjY0tfEc.js";import{T as Gn,p as Yn}from"./vue-tippy.esm-browser-B_r0Ygiv.js";import"./_commonjsHelpers-Cpj98o6Y.js";const Zt="kanka.default";window.triggerEvent=function(t){t=t||Zt;const e=new Event(t);document.dispatchEvent(e)};window.onEvent=function(t,e){e=e||Zt,document.addEventListener(e,t)};window.onReady=function(t){document.readyState==="complete"||document.readyState==="interactive"?setTimeout(t,1):document.addEventListener("DOMContentLoaded",t)};function je(t,e){t.split(/\s+/).forEach(n=>{e(n)})}class Qn{constructor(){this._events={}}on(e,n){je(e,s=>{const i=this._events[s]||[];i.push(n),this._events[s]=i})}off(e,n){var s=arguments.length;if(s===0){this._events={};return}je(e,i=>{if(s===1){delete this._events[i];return}const r=this._events[i];r!==void 0&&(r.splice(r.indexOf(n),1),this._events[i]=r)})}trigger(e,...n){var s=this;je(e,i=>{const r=s._events[i];r!==void 0&&r.forEach(o=>{o.apply(s,n)})})}}function Jn(t){return t.plugins={},class extends t{constructor(){super(...arguments),this.plugins={names:[],settings:{},requested:{},loaded:{}}}static define(e,n){t.plugins[e]={name:e,fn:n}}initializePlugins(e){var n,s;const i=this,r=[];if(Array.isArray(e))e.forEach(o=>{typeof o=="string"?r.push(o):(i.plugins.settings[o.name]=o.options,r.push(o.name))});else if(e)for(n in e)e.hasOwnProperty(n)&&(i.plugins.settings[n]=e[n],r.push(n));for(;s=r.shift();)i.require(s)}loadPlugin(e){var n=this,s=n.plugins,i=t.plugins[e];if(!t.plugins.hasOwnProperty(e))throw new Error('Unable to find "'+e+'" plugin');s.requested[e]=!0,s.loaded[e]=i.fn.apply(n,[n.plugins.settings[e]||{}]),s.names.push(e)}require(e){var n=this,s=n.plugins;if(!n.plugins.loaded.hasOwnProperty(e)){if(s.requested[e])throw new Error('Plugin has circular dependency ("'+e+'")');n.loadPlugin(e)}return s.loaded[e]}}}const Ne=t=>(t=t.filter(Boolean),t.length<2?t[0]||"":Zn(t)==1?"["+t.join("")+"]":"(?:"+t.join("|")+")"),en=t=>{if(!Xn(t))return t.join("");let e="",n=0;const s=()=>{n>1&&(e+="{"+n+"}")};return t.forEach((i,r)=>{if(i===t[r-1]){n++;return}s(),e+=i,n=1}),s(),e},tn=t=>{let e=Array.from(t);return Ne(e)},Xn=t=>new Set(t).size!==t.length,Se=t=>(t+"").replace(/([\$\(\)\*\+\.\?\[\]\^\{\|\}\\])/gu,"\\$1"),Zn=t=>t.reduce((e,n)=>Math.max(e,es(n)),0),es=t=>Array.from(t).length,nn=t=>{if(t.length===1)return[[t]];let e=[];const n=t.substring(1);return nn(n).forEach(function(i){let r=i.slice(0);r[0]=t.charAt(0)+r[0],e.push(r),r=i.slice(0),r.unshift(t.charAt(0)),e.push(r)}),e},ts=[[0,65535]],ns="[̀-ͯ·ʾʼ]";let Ie,sn;const ss=3,lt={},_t={"/":"⁄∕",0:"߀",a:"ⱥɐɑ",aa:"ꜳ",ae:"æǽǣ",ao:"ꜵ",au:"ꜷ",av:"ꜹꜻ",ay:"ꜽ",b:"ƀɓƃ",c:"ꜿƈȼↄ",d:"đɗɖᴅƌꮷԁɦ",e:"ɛǝᴇɇ",f:"ꝼƒ",g:"ǥɠꞡᵹꝿɢ",h:"ħⱨⱶɥ",i:"ɨı",j:"ɉȷ",k:"ƙⱪꝁꝃꝅꞣ",l:"łƚɫⱡꝉꝇꞁɭ",m:"ɱɯϻ",n:"ꞥƞɲꞑᴎлԉ",o:"øǿɔɵꝋꝍᴑ",oe:"œ",oi:"ƣ",oo:"ꝏ",ou:"ȣ",p:"ƥᵽꝑꝓꝕρ",q:"ꝗꝙɋ",r:"ɍɽꝛꞧꞃ",s:"ßȿꞩꞅʂ",t:"ŧƭʈⱦꞇ",th:"þ",tz:"ꜩ",u:"ʉ",v:"ʋꝟʌ",vy:"ꝡ",w:"ⱳ",y:"ƴɏỿ",z:"ƶȥɀⱬꝣ",hv:"ƕ"};for(let t in _t){let e=_t[t]||"";for(let n=0;n{Ie===void 0&&(Ie=cs(ts))},xt=(t,e="NFKD")=>t.normalize(e),Me=t=>Array.from(t).reduce((e,n)=>e+os(n),""),os=t=>(t=xt(t).toLowerCase().replace(is,e=>lt[e]||""),xt(t,"NFC"));function*ls(t){for(const[e,n]of t)for(let s=e;s<=n;s++){let i=String.fromCharCode(s),r=Me(i);r!=i.toLowerCase()&&(r.length>ss||r.length!=0&&(yield{folded:r,composed:i,code_point:s}))}}const as=t=>{const e={},n=(s,i)=>{const r=e[s]||new Set,o=new RegExp("^"+tn(r)+"$","iu");i.match(o)||(r.add(Se(i)),e[s]=r)};for(let s of ls(t))n(s.folded,s.folded),n(s.folded,s.composed);return e},cs=t=>{const e=as(t),n={};let s=[];for(let r in e){let o=e[r];o&&(n[r]=tn(o)),r.length>1&&s.push(Se(r))}s.sort((r,o)=>o.length-r.length);const i=Ne(s);return sn=new RegExp("^"+i,"u"),n},ds=(t,e=1)=>{let n=0;return t=t.map(s=>(Ie[s]&&(n+=s.length),Ie[s]||s)),n>=e?en(t):""},us=(t,e=1)=>(e=Math.max(e,t.length-1),Ne(nn(t).map(n=>ds(n,e)))),wt=(t,e=!0)=>{let n=t.length>1?1:0;return Ne(t.map(s=>{let i=[];const r=e?s.length():s.length()-1;for(let o=0;o{for(const n of e){if(n.start!=t.start||n.end!=t.end||n.substrs.join("")!==t.substrs.join(""))continue;let s=t.parts;const i=o=>{for(const a of s){if(a.start===o.start&&a.substr===o.substr)return!1;if(!(o.length==1||a.length==1)&&(o.starta.start||a.starto.start))return!0}return!1};if(!(n.parts.filter(i).length>0))return!0}return!1};class He{parts;substrs;start;end;constructor(){this.parts=[],this.substrs=[],this.start=0,this.end=0}add(e){e&&(this.parts.push(e),this.substrs.push(e.substr),this.start=Math.min(e.start,this.start),this.end=Math.max(e.end,this.end))}last(){return this.parts[this.parts.length-1]}length(){return this.parts.length}clone(e,n){let s=new He,i=JSON.parse(JSON.stringify(this.parts)),r=i.pop();for(const d of i)s.add(d);let o=n.substr.substring(0,e-r.start),a=o.length;return s.add({start:r.start,end:r.start+a,length:a,substr:o}),s}}const ps=t=>{rs(),t=Me(t);let e="",n=[new He];for(let s=0;s0){d=d.sort((u,m)=>u.length()-m.length());for(let u of d)fs(u,n)||n.push(u);continue}if(s>0&&c.size==1&&!c.has("3")){e+=wt(n,!1);let u=new He;const m=n[0];m&&u.add(m.last()),n=[u]}}return e+=wt(n,!0),e},ms=(t,e)=>{if(t)return t[e]},hs=(t,e)=>{if(t){for(var n,s=e.split(".");(n=s.shift())&&(t=t[n]););return t}},Re=(t,e,n)=>{var s,i;return!t||(t=t+"",e.regex==null)||(i=t.search(e.regex),i===-1)?0:(s=e.string.length/t.length,i===0&&(s+=.5),s*n)},Ue=(t,e)=>{var n=t[e];if(typeof n=="function")return n;n&&!Array.isArray(n)&&(t[e]=[n])},Ce=(t,e)=>{if(Array.isArray(t))t.forEach(e);else for(var n in t)t.hasOwnProperty(n)&&e(t[n],n)},gs=(t,e)=>typeof t=="number"&&typeof e=="number"?t>e?1:te?1:e>t?-1:0);class vs{items;settings;constructor(e,n){this.items=e,this.settings=n||{diacritics:!0}}tokenize(e,n,s){if(!e||!e.length)return[];const i=[],r=e.split(/\s+/);var o;return s&&(o=new RegExp("^("+Object.keys(s).map(Se).join("|")+"):(.*)$")),r.forEach(a=>{let d,c=null,u=null;o&&(d=a.match(o))&&(c=d[1],a=d[2]),a.length>0&&(this.settings.diacritics?u=ps(a)||null:u=Se(a),u&&n&&(u="\\b"+u)),i.push({string:a,regex:u?new RegExp(u,"iu"):null,field:c})}),i}getScoreFunction(e,n){var s=this.prepareSearch(e,n);return this._getScoreFunction(s)}_getScoreFunction(e){const n=e.tokens,s=n.length;if(!s)return function(){return 0};const i=e.options.fields,r=e.weights,o=i.length,a=e.getAttrFn;if(!o)return function(){return 1};const d=(function(){return o===1?function(c,u){const m=i[0].field;return Re(a(u,m),c,r[m]||1)}:function(c,u){var m=0;if(c.field){const h=a(u,c.field);!c.regex&&h?m+=1/o:m+=Re(h,c,1)}else Ce(r,(h,y)=>{m+=Re(a(u,y),c,h)});return m/o}})();return s===1?function(c){return d(n[0],c)}:e.options.conjunction==="and"?function(c){var u,m=0;for(let h of n){if(u=d(h,c),u<=0)return 0;m+=u}return m/s}:function(c){var u=0;return Ce(n,m=>{u+=d(m,c)}),u/s}}getSortFunction(e,n){var s=this.prepareSearch(e,n);return this._getSortFunction(s)}_getSortFunction(e){var n,s=[];const i=this,r=e.options,o=!e.query&&r.sort_empty?r.sort_empty:r.sort;if(typeof o=="function")return o.bind(this);const a=function(c,u){return c==="$score"?u.score:e.getAttrFn(i.items[u.id],c)};if(o)for(let c of o)(e.query||c.field!=="$score")&&s.push(c);if(e.query){n=!0;for(let c of s)if(c.field==="$score"){n=!1;break}n&&s.unshift({field:"$score",direction:"desc"})}else s=s.filter(c=>c.field!=="$score");return s.length?function(c,u){var m,h;for(let y of s)if(h=y.field,m=(y.direction==="desc"?-1:1)*gs(a(h,c),a(h,u)),m)return m;return 0}:null}prepareSearch(e,n){const s={};var i=Object.assign({},n);if(Ue(i,"sort"),Ue(i,"sort_empty"),i.fields){Ue(i,"fields");const r=[];i.fields.forEach(o=>{typeof o=="string"&&(o={field:o,weight:1}),r.push(o),s[o.field]="weight"in o?o.weight:1}),i.fields=r}return{options:i,query:e.toLowerCase().trim(),tokens:this.tokenize(e,i.respect_word_boundaries,s),total:0,items:[],weights:s,getAttrFn:i.nesting?hs:ms}}search(e,n){var s=this,i,r;r=this.prepareSearch(e,n),n=r.options,e=r.query;const o=n.score||s._getScoreFunction(r);e.length?Ce(s.items,(d,c)=>{i=o(d),(n.filter===!1||i>0)&&r.items.push({score:i,id:c})}):Ce(s.items,(d,c)=>{r.items.push({score:1,id:c})});const a=s._getSortFunction(r);return a&&r.items.sort(a),r.total=r.items.length,typeof n.limit=="number"&&(r.items=r.items.slice(0,n.limit)),r}}const Q=t=>typeof t>"u"||t===null?null:$e(t),$e=t=>typeof t=="boolean"?t?"1":"0":t+"",Ke=t=>(t+"").replace(/&/g,"&").replace(//g,">").replace(/"/g,"""),ys=(t,e)=>e>0?window.setTimeout(t,e):(t.call(null),null),bs=(t,e)=>{var n;return function(s,i){var r=this;n&&(r.loading=Math.max(r.loading-1,0),clearTimeout(n)),n=setTimeout(function(){n=null,r.loadedSearches[s]=!0,t.call(r,s,i)},e)}},kt=(t,e,n)=>{var s,i=t.trigger,r={};t.trigger=function(){var o=arguments[0];if(e.indexOf(o)!==-1)r[o]=arguments;else return i.apply(t,arguments)},n.apply(t,[]),t.trigger=i;for(s of e)s in r&&i.apply(t,r[s])},_s=t=>({start:t.selectionStart||0,length:(t.selectionEnd||0)-(t.selectionStart||0)}),B=(t,e=!1)=>{t&&(t.preventDefault(),e&&t.stopPropagation())},W=(t,e,n,s)=>{t.addEventListener(e,n,s)},de=(t,e)=>{if(!e||!e[t])return!1;var n=(e.altKey?1:0)+(e.ctrlKey?1:0)+(e.shiftKey?1:0)+(e.metaKey?1:0);return n===1},ze=(t,e)=>{const n=t.getAttribute("id");return n||(t.setAttribute("id",e),e)},Lt=t=>t.replace(/[\\"']/g,"\\$&"),ue=(t,e)=>{e&&t.append(e)},U=(t,e)=>{if(Array.isArray(t))t.forEach(e);else for(var n in t)t.hasOwnProperty(n)&&e(t[n],n)},ie=t=>{if(t.jquery)return t[0];if(t instanceof HTMLElement)return t;if(rn(t)){var e=document.createElement("template");return e.innerHTML=t.trim(),e.content.firstChild}return document.querySelector(t)},rn=t=>typeof t=="string"&&t.indexOf("<")>-1,xs=t=>t.replace(/['"\\]/g,"\\$&"),We=(t,e)=>{var n=document.createEvent("HTMLEvents");n.initEvent(e,!0,!1),t.dispatchEvent(n)},Ae=(t,e)=>{Object.assign(t.style,e)},Y=(t,...e)=>{var n=on(e);t=ln(t),t.map(s=>{n.map(i=>{s.classList.add(i)})})},le=(t,...e)=>{var n=on(e);t=ln(t),t.map(s=>{n.map(i=>{s.classList.remove(i)})})},on=t=>{var e=[];return U(t,n=>{typeof n=="string"&&(n=n.trim().split(/[\t\n\f\r\s]/)),Array.isArray(n)&&(e=e.concat(n))}),e.filter(Boolean)},ln=t=>(Array.isArray(t)||(t=[t]),t),Ge=(t,e,n)=>{if(!(n&&!n.contains(t)))for(;t&&t.matches;){if(t.matches(e))return t;t=t.parentNode}},St=(t,e=0)=>e>0?t[t.length-1]:t[0],ws=t=>Object.keys(t).length===0,Et=(t,e)=>{if(!t)return-1;e=e||t.nodeName;for(var n=0;t=t.previousElementSibling;)t.matches(e)&&n++;return n},N=(t,e)=>{U(e,(n,s)=>{n==null?t.removeAttribute(s):t.setAttribute(s,""+n)})},tt=(t,e)=>{t.parentNode&&t.parentNode.replaceChild(e,t)},ks=(t,e)=>{if(e===null)return;if(typeof e=="string"){if(!e.length)return;e=new RegExp(e,"i")}const n=r=>{var o=r.data.match(e);if(o&&r.data.length>0){var a=document.createElement("span");a.className="highlight";var d=r.splitText(o.index);d.splitText(o[0].length);var c=d.cloneNode(!0);return a.appendChild(c),tt(d,a),1}return 0},s=r=>{r.nodeType===1&&r.childNodes&&!/(script|style)/i.test(r.tagName)&&(r.className!=="highlight"||r.tagName!=="SPAN")&&Array.from(r.childNodes).forEach(o=>{i(o)})},i=r=>r.nodeType===3?n(r):(s(r),0);i(t)},Ls=t=>{var e=t.querySelectorAll("span.highlight");Array.prototype.forEach.call(e,function(n){var s=n.parentNode;s.replaceChild(n.firstChild,n),s.normalize()})},Ss=65,Es=13,Cs=27,As=37,Ts=38,qs=39,Os=40,Ct=8,$s=46,At=9,Is=typeof navigator>"u"?!1:/Mac/.test(navigator.userAgent),Te=Is?"metaKey":"ctrlKey",Tt={options:[],optgroups:[],plugins:[],delimiter:",",splitOn:null,persist:!0,diacritics:!0,create:null,createOnBlur:!1,createFilter:null,clearAfterSelect:!1,highlight:!0,openOnFocus:!0,shouldOpen:null,maxOptions:50,maxItems:null,hideSelected:null,duplicates:!1,addPrecedence:!1,selectOnTab:!1,preload:null,allowEmptyOption:!1,refreshThrottle:300,loadThrottle:300,loadingClass:"loading",dataAttr:null,optgroupField:"optgroup",valueField:"value",labelField:"text",disabledField:"disabled",optgroupLabelField:"label",optgroupValueField:"value",lockOptgroupOrder:!1,sortField:"$order",searchField:["text"],searchConjunction:"and",mode:null,wrapperClass:"ts-wrapper",controlClass:"ts-control",dropdownClass:"ts-dropdown",dropdownContentClass:"ts-dropdown-content",itemClass:"item",optionClass:"option",dropdownParent:null,controlInput:'',copyClassesToDropdown:!1,placeholder:null,hidePlaceholder:null,shouldLoad:function(t){return t.length>0},render:{}};function qt(t,e){var n=Object.assign({},Tt,e),s=n.dataAttr,i=n.labelField,r=n.valueField,o=n.disabledField,a=n.optgroupField,d=n.optgroupLabelField,c=n.optgroupValueField,u=t.tagName.toLowerCase(),m=t.getAttribute("placeholder")||t.getAttribute("data-placeholder");if(!m&&!n.allowEmptyOption){let _=t.querySelector('option[value=""]');_&&(m=_.textContent)}var h={placeholder:m,options:[],optgroups:[],items:[],maxItems:null},y=()=>{var _,C=h.options,E={},$=1;let w=0;var H=M=>{var q=Object.assign({},M.dataset),L=s&&q[s];return typeof L=="string"&&L.length&&(q=Object.assign(q,JSON.parse(L))),q},te=(M,q)=>{var L=Q(M.value);if(L!=null&&!(!L&&!n.allowEmptyOption)){if(E.hasOwnProperty(L)){if(q){var F=E[L][a];F?Array.isArray(F)?F.push(q):E[L][a]=[F,q]:E[L][a]=q}}else{var S=H(M);S[i]=S[i]||M.textContent,S[r]=S[r]||L,S[o]=S[o]||M.disabled,S[a]=S[a]||q,S.$option=M,S.$order=S.$order||++w,E[L]=S,C.push(S)}M.selected&&h.items.push(L)}},ne=M=>{var q,L;L=H(M),L[d]=L[d]||M.getAttribute("label")||"",L[c]=L[c]||$++,L[o]=L[o]||M.disabled,L.$order=L.$order||++w,h.optgroups.push(L),q=L[c],U(M.children,F=>{te(F,q)})};h.maxItems=t.hasAttribute("multiple")?null:1,U(t.children,M=>{_=M.tagName.toLowerCase(),_==="optgroup"?ne(M):_==="option"&&te(M)})},g=()=>{const _=t.getAttribute(s);if(_)h.options=JSON.parse(_),U(h.options,E=>{h.items.push(E[r])});else{var C=t.value.trim()||"";if(!n.allowEmptyOption&&!C.length)return;const E=C.split(n.delimiter);U(E,$=>{const w={};w[i]=$,w[r]=$,h.options.push(w)}),h.items=E}};return u==="select"?y():g(),Object.assign({},Tt,h,e)}var Ot=0;class V extends Jn(Qn){constructor(e,n){super(),this.order=0,this.isOpen=!1,this.isDisabled=!1,this.isReadOnly=!1,this.isInvalid=!1,this.isValid=!0,this.isLocked=!1,this.isFocused=!1,this.isInputHidden=!1,this.isSetup=!1,this.ignoreFocus=!1,this.ignoreHover=!1,this.hasOptions=!1,this.lastValue="",this.caretPos=0,this.loading=0,this.loadedSearches={},this.activeOption=null,this.activeItems=[],this.optgroups={},this.options={},this.userOptions={},this.items=[],this.refreshTimeout=null,Ot++;var s,i=ie(e);if(i.tomselect)throw new Error("Tom Select already initialized on this element");i.tomselect=this;var r=window.getComputedStyle&&window.getComputedStyle(i,null);s=r.getPropertyValue("direction");const o=qt(i,n);this.settings=o,this.input=i,this.tabIndex=i.tabIndex||0,this.is_select_tag=i.tagName.toLowerCase()==="select",this.rtl=/rtl/i.test(s),this.inputId=ze(i,"tomselect-"+Ot),this.isRequired=i.required,this.sifter=new vs(this.options,{diacritics:o.diacritics}),o.mode=o.mode||(o.maxItems===1?"single":"multi"),typeof o.hideSelected!="boolean"&&(o.hideSelected=o.mode==="multi"),typeof o.hidePlaceholder!="boolean"&&(o.hidePlaceholder=o.mode!=="multi");var a=o.createFilter;typeof a!="function"&&(typeof a=="string"&&(a=new RegExp(a)),a instanceof RegExp?o.createFilter=C=>a.test(C):o.createFilter=C=>this.settings.duplicates||!this.options[C]),this.initializePlugins(o.plugins),this.setupCallbacks(),this.setupTemplates();const d=ie("
    "),c=ie("
    "),u=this._render("dropdown"),m=ie('
    '),h=this.input.getAttribute("class")||"",y=o.mode;var g;if(Y(d,o.wrapperClass,h,y),Y(c,o.controlClass),ue(d,c),Y(u,o.dropdownClass,y),o.copyClassesToDropdown&&Y(u,h),Y(m,o.dropdownContentClass),ue(u,m),ie(o.dropdownParent||d).appendChild(u),rn(o.controlInput)){g=ie(o.controlInput);var _=["autocorrect","autocapitalize","autocomplete","spellcheck","aria-label"];U(_,C=>{i.getAttribute(C)&&N(g,{[C]:i.getAttribute(C)})}),g.tabIndex=-1,c.appendChild(g),this.focus_node=g}else o.controlInput?(g=ie(o.controlInput),this.focus_node=g):(g=ie(""),this.focus_node=c);this.wrapper=d,this.dropdown=u,this.dropdown_content=m,this.control=c,this.control_input=g,this.setup()}setup(){const e=this,n=e.settings,s=e.control_input,i=e.dropdown,r=e.dropdown_content,o=e.wrapper,a=e.control,d=e.input,c=e.focus_node,u={passive:!0},m=e.inputId+"-ts-dropdown";N(r,{id:m}),N(c,{role:"combobox","aria-haspopup":"listbox","aria-expanded":"false","aria-controls":m});const h=ze(c,e.inputId+"-ts-control"),y="label[for='"+xs(e.inputId)+"']",g=document.querySelector(y),_=e.focus.bind(e);if(g){W(g,"click",_),N(g,{for:h});const w=ze(g,e.inputId+"-ts-label");N(c,{"aria-labelledby":w}),N(r,{"aria-labelledby":w})}if(o.style.width=d.style.width,o.style.minWidth=d.style.minWidth,o.style.maxWidth=d.style.maxWidth,e.plugins.names.length){const w="plugin-"+e.plugins.names.join(" plugin-");Y([o,i],w)}(n.maxItems===null||n.maxItems>1)&&e.is_select_tag&&N(d,{multiple:"multiple"}),n.placeholder&&N(s,{placeholder:n.placeholder}),!n.splitOn&&n.delimiter&&(n.splitOn=new RegExp("\\s*"+Se(n.delimiter)+"+\\s*")),n.load&&n.loadThrottle&&(n.load=bs(n.load,n.loadThrottle)),W(i,"mousemove",()=>{e.ignoreHover=!1}),W(i,"mouseenter",w=>{var H=Ge(w.target,"[data-selectable]",i);H&&e.onOptionHover(w,H)},{capture:!0}),W(i,"click",w=>{const H=Ge(w.target,"[data-selectable]");H&&(e.onOptionSelect(w,H),B(w,!0))}),W(a,"click",w=>{var H=Ge(w.target,"[data-ts-item]",a);if(H&&e.onItemSelect(w,H)){B(w,!0);return}s.value==""&&(e.onClick(),B(w,!0))}),W(c,"keydown",w=>e.onKeyDown(w)),W(s,"keypress",w=>e.onKeyPress(w)),W(s,"input",w=>e.onInput(w)),W(c,"blur",w=>e.onBlur(w)),W(c,"focus",w=>e.onFocus(w)),W(s,"paste",w=>e.onPaste(w));const C=w=>{const H=w.composedPath()[0];if(!o.contains(H)&&!i.contains(H)){e.isFocused&&e.blur(),e.inputState();return}H==s&&e.isOpen?w.stopPropagation():B(w,!0)},E=()=>{e.isOpen&&e.positionDropdown()},$=()=>{e.isValid&&(e.isValid=!1,e.isInvalid=!0,e.refreshState())};W(d,"invalid",$),W(document,"mousedown",C),W(window,"scroll",E,u),W(window,"resize",E,u),this._destroy=()=>{d.removeEventListener("invalid",$),document.removeEventListener("mousedown",C),window.removeEventListener("scroll",E),window.removeEventListener("resize",E),g&&g.removeEventListener("click",_)},this.revertSettings={innerHTML:d.innerHTML,tabIndex:d.tabIndex},d.tabIndex=-1,d.insertAdjacentElement("afterend",e.wrapper),e.sync(!1),n.items=[],delete n.optgroups,delete n.options,e.refreshItems(),e.close(!1),e.inputState(),e.isSetup=!0,d.disabled?e.disable():d.readOnly?e.setReadOnly(!0):e.enable(),e.on("change",this.onChange),Y(d,"tomselected","ts-hidden-accessible"),e.trigger("initialize"),n.preload===!0&&e.preload()}setupOptions(e=[],n=[]){this.addOptions(e),U(n,s=>{this.registerOptionGroup(s)})}setupTemplates(){var e=this,n=e.settings.labelField,s=e.settings.optgroupLabelField,i={optgroup:r=>{let o=document.createElement("div");return o.className="optgroup",o.appendChild(r.options),o},optgroup_header:(r,o)=>'
    '+o(r[s])+"
    ",option:(r,o)=>"
    "+o(r[n])+"
    ",item:(r,o)=>"
    "+o(r[n])+"
    ",option_create:(r,o)=>'
    Add '+o(r.input)+"
    ",no_results:()=>'
    No results found
    ',loading:()=>'
    ',not_loading:()=>{},dropdown:()=>"
    "};e.settings.render=Object.assign({},i,e.settings.render)}setupCallbacks(){var e,n,s={initialize:"onInitialize",change:"onChange",item_add:"onItemAdd",item_remove:"onItemRemove",item_select:"onItemSelect",clear:"onClear",option_add:"onOptionAdd",option_remove:"onOptionRemove",option_clear:"onOptionClear",optgroup_add:"onOptionGroupAdd",optgroup_remove:"onOptionGroupRemove",optgroup_clear:"onOptionGroupClear",dropdown_open:"onDropdownOpen",dropdown_close:"onDropdownClose",type:"onType",load:"onLoad",focus:"onFocus",blur:"onBlur"};for(e in s)n=this.settings[s[e]],n&&this.on(e,n)}sync(e=!0){const n=this,s=e?qt(n.input,{delimiter:n.settings.delimiter,allowEmptyOption:n.settings.allowEmptyOption}):n.settings;n.setupOptions(s.options,s.optgroups),n.setValue(s.items||[],!0),n.lastQuery=null}onClick(){var e=this;if(e.activeItems.length>0){e.clearActiveItems(),e.focus();return}e.isFocused&&e.isOpen?e.blur():e.focus()}onMouseDown(){}onChange(){We(this.input,"input"),We(this.input,"change")}onPaste(e){var n=this;if(n.isInputHidden||n.isLocked){B(e);return}n.settings.splitOn&&setTimeout(()=>{var s=n.inputValue();if(s.match(n.settings.splitOn)){var i=s.trim().split(n.settings.splitOn);U(i,r=>{Q(r)&&(this.options[r]?n.addItem(r):n.createItem(r))})}},0)}onKeyPress(e){var n=this;if(n.isLocked){B(e);return}var s=String.fromCharCode(e.keyCode||e.which);if(n.settings.create&&n.settings.mode==="multi"&&s===n.settings.delimiter){n.createItem(),B(e);return}}onKeyDown(e){var n=this;if(n.ignoreHover=!0,n.isLocked){e.keyCode!==At&&B(e);return}switch(e.keyCode){case Ss:if(de(Te,e)&&n.control_input.value==""){B(e),n.selectAll();return}break;case Cs:n.isOpen&&(B(e,!0),n.close()),n.clearActiveItems();return;case Os:if(!n.isOpen&&n.hasOptions)n.open();else if(n.activeOption){let s=n.getAdjacent(n.activeOption,1);s&&n.setActiveOption(s)}B(e);return;case Ts:if(n.activeOption){let s=n.getAdjacent(n.activeOption,-1);s&&n.setActiveOption(s)}B(e);return;case Es:n.canSelect(n.activeOption)?(n.onOptionSelect(e,n.activeOption),B(e)):(n.settings.create&&n.createItem()||document.activeElement==n.control_input&&n.isOpen)&&B(e);return;case As:n.advanceSelection(-1,e);return;case qs:n.advanceSelection(1,e);return;case At:n.settings.selectOnTab&&(n.canSelect(n.activeOption)?(n.onOptionSelect(e,n.activeOption),B(e)):n.settings.create&&n.createItem()&&B(e));return;case Ct:case $s:n.deleteSelection(e);return}n.isInputHidden&&!de(Te,e)&&B(e)}onInput(e){if(this.isLocked)return;const n=this.inputValue();if(this.lastValue!==n){if(this.lastValue=n,n==""){this._onInput();return}this.refreshTimeout&&window.clearTimeout(this.refreshTimeout),this.refreshTimeout=ys(()=>{this.refreshTimeout=null,this._onInput()},this.settings.refreshThrottle)}}_onInput(){const e=this.lastValue;this.settings.shouldLoad.call(this,e)&&this.load(e),this.refreshOptions(),this.trigger("type",e)}onOptionHover(e,n){this.ignoreHover||this.setActiveOption(n,!1)}onFocus(e){var n=this,s=n.isFocused;if(n.isDisabled||n.isReadOnly){n.blur(),B(e);return}n.ignoreFocus||(n.isFocused=!0,n.settings.preload==="focus"&&n.preload(),s||n.trigger("focus"),n.activeItems.length||(n.inputState(),n.refreshOptions(!!n.settings.openOnFocus)),n.refreshState())}onBlur(e){if(document.hasFocus()!==!1){var n=this;if(n.isFocused){n.isFocused=!1,n.ignoreFocus=!1;var s=()=>{n.close(),n.setActiveItem(),n.setCaret(n.items.length),n.trigger("blur")};n.settings.create&&n.settings.createOnBlur?n.createItem(null,s):s()}}}onOptionSelect(e,n){var s,i=this;n.parentElement&&n.parentElement.matches("[data-disabled]")||(n.classList.contains("create")?i.createItem(null,()=>{i.settings.closeAfterSelect?i.close():i.settings.clearAfterSelect&&i.setTextboxValue()}):(s=n.dataset.value,typeof s<"u"&&(i.lastQuery=null,i.addItem(s),i.settings.closeAfterSelect?i.close():i.settings.clearAfterSelect&&i.setTextboxValue(),!i.settings.hideSelected&&e.type&&/click/.test(e.type)&&i.setActiveOption(n))))}canSelect(e){return!!(this.isOpen&&e&&this.dropdown_content.contains(e))}onItemSelect(e,n){var s=this;return!s.isLocked&&s.settings.mode==="multi"?(B(e),s.setActiveItem(n,e),!0):!1}canLoad(e){return!(!this.settings.load||this.loadedSearches.hasOwnProperty(e))}load(e){const n=this;if(!n.canLoad(e))return;Y(n.wrapper,n.settings.loadingClass),n.loading++;const s=n.loadCallback.bind(n);n.settings.load.call(n,e,s)}loadCallback(e,n){const s=this;s.loading=Math.max(s.loading-1,0),s.lastQuery=null,s.clearActiveOption(),s.setupOptions(e,n),s.refreshOptions(s.isFocused&&!s.isInputHidden),s.loading||le(s.wrapper,s.settings.loadingClass),s.trigger("load",e,n)}preload(){var e=this.wrapper.classList;e.contains("preloaded")||(e.add("preloaded"),this.load(""))}setTextboxValue(e=""){var n=this.control_input,s=n.value!==e;s&&(n.value=e,We(n,"update"),this.lastValue=e)}getValue(){return this.is_select_tag&&this.input.hasAttribute("multiple")?this.items:this.items.join(this.settings.delimiter)}setValue(e,n){var s=n?[]:["change"];kt(this,s,()=>{this.clear(n),this.addItems(e,n)})}setMaxItems(e){e===0&&(e=null),this.settings.maxItems=e,this.refreshState()}setActiveItem(e,n){var s=this,i,r,o,a,d,c;if(s.settings.mode!=="single"){if(!e){s.clearActiveItems(),s.isFocused&&s.inputState();return}if(i=n&&n.type.toLowerCase(),i==="click"&&de("shiftKey",n)&&s.activeItems.length){for(c=s.getLastActive(),o=Array.prototype.indexOf.call(s.control.children,c),a=Array.prototype.indexOf.call(s.control.children,e),o>a&&(d=o,o=a,a=d),r=o;r<=a;r++)e=s.control.children[r],s.activeItems.indexOf(e)===-1&&s.setActiveItemClass(e);B(n)}else i==="click"&&de(Te,n)||i==="keydown"&&de("shiftKey",n)?e.classList.contains("active")?s.removeActiveItem(e):s.setActiveItemClass(e):(s.clearActiveItems(),s.setActiveItemClass(e));s.inputState(),s.isFocused||s.focus()}}setActiveItemClass(e){const n=this,s=n.control.querySelector(".last-active");s&&le(s,"last-active"),Y(e,"active last-active"),n.trigger("item_select",e),n.activeItems.indexOf(e)==-1&&n.activeItems.push(e)}removeActiveItem(e){var n=this.activeItems.indexOf(e);this.activeItems.splice(n,1),le(e,"active")}clearActiveItems(){le(this.activeItems,"active"),this.activeItems=[]}setActiveOption(e,n=!0){e!==this.activeOption&&(this.clearActiveOption(),e&&(this.activeOption=e,N(this.focus_node,{"aria-activedescendant":e.getAttribute("id")}),N(e,{"aria-selected":"true"}),Y(e,"active"),n&&this.scrollToOption(e)))}scrollToOption(e,n){if(!e)return;const s=this.dropdown_content,i=s.clientHeight,r=s.scrollTop||0,o=e.offsetHeight,a=e.getBoundingClientRect().top-s.getBoundingClientRect().top+r;a+o>i+r?this.scroll(a-i+o,n):a{e.setActiveItemClass(s)}))}inputState(){var e=this;e.control.contains(e.control_input)&&(N(e.control_input,{placeholder:e.settings.placeholder}),e.activeItems.length>0||!e.isFocused&&e.settings.hidePlaceholder&&e.items.length>0?(e.setTextboxValue(),e.isInputHidden=!0):(e.settings.hidePlaceholder&&e.items.length>0&&N(e.control_input,{placeholder:""}),e.isInputHidden=!1),e.wrapper.classList.toggle("input-hidden",e.isInputHidden))}inputValue(){return this.control_input.value.trim()}focus(){var e=this;e.isDisabled||e.isReadOnly||(e.ignoreFocus=!0,e.control_input.offsetWidth?e.control_input.focus():e.focus_node.focus(),setTimeout(()=>{e.ignoreFocus=!1,e.onFocus()},0))}blur(){this.focus_node.blur(),this.onBlur()}getScoreFunction(e){return this.sifter.getScoreFunction(e,this.getSearchOptions())}getSearchOptions(){var e=this.settings,n=e.sortField;return typeof e.sortField=="string"&&(n=[{field:e.sortField}]),{fields:e.searchField,conjunction:e.searchConjunction,sort:n,nesting:e.nesting}}search(e){var n,s,i=this,r=this.getSearchOptions();if(i.settings.score&&(s=i.settings.score.call(i,e),typeof s!="function"))throw new Error('Tom Select "score" setting must be a function that returns a function');return e!==i.lastQuery?(i.lastQuery=e,/(.)\1{15,}/.test(e)&&(e=""),n=i.sifter.search(e,Object.assign(r,{score:s})),i.currentResults=n):n=Object.assign({},i.currentResults),i.settings.hideSelected&&(n.items=n.items.filter(o=>{let a=Q(o.id);return!(a!==null&&i.items.indexOf(a)!==-1)})),n}refreshOptions(e=!0){var n,s,i,r,o,a,d,c,u,m;const h={},y=[];var g=this,_=g.inputValue();const C=_===g.lastQuery||_==""&&g.lastQuery==null;var E=g.search(_),$=null,w=g.settings.shouldOpen||!1,H=g.dropdown_content;C&&($=g.activeOption,$&&(u=$.closest("[data-group]"))),r=E.items.length,typeof g.settings.maxOptions=="number"&&(r=Math.min(r,g.settings.maxOptions)),r>0&&(w=!0);const te=(q,L)=>{let F=h[q];if(F!==void 0){let T=y[F];if(T!==void 0)return[F,T.fragment]}let S=document.createDocumentFragment();return F=y.length,y.push({fragment:S,order:L,optgroup:q}),[F,S]};for(n=0;n0&&(T=T.cloneNode(!0),N(T,{id:F.$id+"-clone-"+s,"aria-selected":null}),T.classList.add("ts-cloned"),le(T,"active"),g.activeOption&&g.activeOption.dataset.value==L&&u&&u.dataset.group===o.toString()&&($=T)),R.appendChild(T),o!=""&&(h[o]=X)}}g.settings.lockOptgroupOrder&&y.sort((q,L)=>q.order-L.order),d=document.createDocumentFragment(),U(y,q=>{let L=q.fragment,F=q.optgroup;if(!L||!L.children.length)return;let S=g.optgroups[F];if(S!==void 0){let T=document.createDocumentFragment(),O=g.render("optgroup_header",S);ue(T,O),ue(T,L);let x=g.render("optgroup",{group:S,options:T});ue(d,x)}else ue(d,L)}),H.innerHTML="",ue(H,d),g.settings.highlight&&(Ls(H),E.query.length&&E.tokens.length&&U(E.tokens,q=>{ks(H,q.regex)}));var M=q=>{let L=g.render(q,{input:_});return L&&(w=!0,H.insertBefore(L,H.firstChild)),L};if(g.loading?M("loading"):g.settings.shouldLoad.call(g,_)?E.items.length===0&&M("no_results"):M("not_loading"),c=g.canCreate(_),c&&(m=M("option_create")),g.hasOptions=E.items.length>0||c,w){if(E.items.length>0){if(!$&&g.settings.mode==="single"&&g.items[0]!=null&&($=g.getOption(g.items[0])),!H.contains($)){let q=0;m&&!g.settings.addPrecedence&&(q=1),$=g.selectable()[q]}}else m&&($=m);e&&!g.isOpen&&(g.open(),g.scrollToOption($,"auto")),g.setActiveOption($)}else g.clearActiveOption(),e&&g.isOpen&&g.close(!1)}selectable(){return this.dropdown_content.querySelectorAll("[data-selectable]")}addOption(e,n=!1){const s=this;if(Array.isArray(e))return s.addOptions(e,n),!1;const i=Q(e[s.settings.valueField]);return i===null||s.options.hasOwnProperty(i)?!1:(e.$order=e.$order||++s.order,e.$id=s.inputId+"-opt-"+e.$order,s.options[i]=e,s.lastQuery=null,n&&(s.userOptions[i]=n,s.trigger("option_add",i,e)),i)}addOptions(e,n=!1){U(e,s=>{this.addOption(s,n)})}registerOption(e){return this.addOption(e)}registerOptionGroup(e){var n=Q(e[this.settings.optgroupValueField]);return n===null?!1:(e.$order=e.$order||++this.order,this.optgroups[n]=e,n)}addOptionGroup(e,n){var s;n[this.settings.optgroupValueField]=e,(s=this.registerOptionGroup(n))&&this.trigger("optgroup_add",s,n)}removeOptionGroup(e){this.optgroups.hasOwnProperty(e)&&(delete this.optgroups[e],this.clearCache(),this.trigger("optgroup_remove",e))}clearOptionGroups(){this.optgroups={},this.clearCache(),this.trigger("optgroup_clear")}updateOption(e,n){const s=this;var i,r;const o=Q(e),a=Q(n[s.settings.valueField]);if(o===null)return;const d=s.options[o];if(d==null)return;if(typeof a!="string")throw new Error("Value must be set in option data");const c=s.getOption(o),u=s.getItem(o);if(n.$order=n.$order||d.$order,delete s.options[o],s.uncacheValue(a),s.options[a]=n,c){if(s.dropdown_content.contains(c)){const m=s._render("option",n);tt(c,m),s.activeOption===c&&s.setActiveOption(m)}c.remove()}u&&(r=s.items.indexOf(o),r!==-1&&s.items.splice(r,1,a),i=s._render("item",n),u.classList.contains("active")&&Y(i,"active"),tt(u,i)),s.lastQuery=null}removeOption(e,n){const s=this;e=$e(e),s.uncacheValue(e),delete s.userOptions[e],delete s.options[e],s.lastQuery=null,s.trigger("option_remove",e),s.removeItem(e,n)}clearOptions(e){const n=(e||this.clearFilter).bind(this);this.loadedSearches={},this.userOptions={},this.clearCache();const s={};U(this.options,(i,r)=>{n(i,r)&&(s[r]=i)}),this.options=this.sifter.items=s,this.lastQuery=null,this.trigger("option_clear")}clearFilter(e,n){return this.items.indexOf(n)>=0}getOption(e,n=!1){const s=Q(e);if(s===null)return null;const i=this.options[s];if(i!=null){if(i.$div)return i.$div;if(n)return this._render("option",i)}return null}getAdjacent(e,n,s="option"){var i=this,r;if(!e)return null;s=="item"?r=i.controlChildren():r=i.dropdown_content.querySelectorAll("[data-selectable]");for(let o=0;o0?r[o+1]:r[o-1];return null}getItem(e){if(typeof e=="object")return e;var n=Q(e);return n!==null?this.control.querySelector(`[data-value="${Lt(n)}"]`):null}addItems(e,n){var s=this,i=Array.isArray(e)?e:[e];i=i.filter(o=>s.items.indexOf(o)===-1);const r=i[i.length-1];i.forEach(o=>{s.isPending=o!==r,s.addItem(o,n)})}addItem(e,n){var s=n?[]:["change","dropdown_close"];kt(this,s,()=>{var i,r;const o=this,a=o.settings.mode,d=Q(e);if(!(d&&o.items.indexOf(d)!==-1&&(a==="single"&&o.close(),a==="single"||!o.settings.duplicates))&&!(d===null||!o.options.hasOwnProperty(d))&&(a==="single"&&o.clear(n),!(a==="multi"&&o.isFull()))){if(i=o._render("item",o.options[d]),o.control.contains(i)&&(i=i.cloneNode(!0)),r=o.isFull(),o.items.splice(o.caretPos,0,d),o.insertAtCaret(i),o.isSetup){if(!o.isPending&&o.settings.hideSelected){let c=o.getOption(d),u=o.getAdjacent(c,1);u&&o.setActiveOption(u)}o.settings.clearAfterSelect&&o.setTextboxValue(),!o.isPending&&!o.settings.closeAfterSelect&&o.refreshOptions(o.isFocused&&a!=="single"),o.settings.closeAfterSelect!=!1&&o.isFull()?o.close():o.isPending||o.positionDropdown(),o.trigger("item_add",d,i),o.isPending||o.updateOriginalInput({silent:n})}(!o.isPending||!r&&o.isFull())&&(o.inputState(),o.refreshState())}})}removeItem(e=null,n){const s=this;if(e=s.getItem(e),!e)return;var i,r;const o=e.dataset.value;i=Et(e),e.remove(),e.classList.contains("active")&&(r=s.activeItems.indexOf(e),s.activeItems.splice(r,1),le(e,"active")),s.items.splice(i,1),s.lastQuery=null,!s.settings.persist&&s.userOptions.hasOwnProperty(o)&&s.removeOption(o,n),i{}){arguments.length===3&&(n=arguments[2]),typeof n!="function"&&(n=()=>{});var s=this,i=s.caretPos,r;if(e=e||s.inputValue(),!s.canCreate(e))return Q(e)&&this.options[e]&&s.addItem(e),n(),!1;s.lock();var o=!1,a=d=>{if(s.unlock(),!d||typeof d!="object")return n();var c=Q(d[s.settings.valueField]);if(typeof c!="string")return n();s.setTextboxValue(),s.addOption(d,!0),s.setCaret(i),s.addItem(c),n(d),o=!0};return typeof s.settings.create=="function"?r=s.settings.create.call(this,e,a):r={[s.settings.labelField]:e,[s.settings.valueField]:e},o||a(r),!0}refreshItems(){var e=this;e.lastQuery=null,e.isSetup&&e.addItems(e.items),e.updateOriginalInput(),e.refreshState()}refreshState(){const e=this;e.refreshValidityState();const n=e.isFull(),s=e.isLocked;e.wrapper.classList.toggle("rtl",e.rtl);const i=e.wrapper.classList;i.toggle("focus",e.isFocused),i.toggle("disabled",e.isDisabled),i.toggle("readonly",e.isReadOnly),i.toggle("required",e.isRequired),i.toggle("invalid",!e.isValid),i.toggle("locked",s),i.toggle("full",n),i.toggle("input-active",e.isFocused&&!e.isInputHidden),i.toggle("dropdown-active",e.isOpen),i.toggle("has-options",ws(e.options)),i.toggle("has-items",e.items.length>0)}refreshValidityState(){var e=this;e.input.validity&&(e.isValid=e.input.validity.valid,e.isInvalid=!e.isValid)}isFull(){return this.settings.maxItems!==null&&this.items.length>=this.settings.maxItems}updateOriginalInput(e={}){const n=this;var s,i;const r=n.input.querySelector('option[value=""]');if(n.is_select_tag){let d=function(c,u,m){return c||(c=ie('")),c!=r&&n.input.append(c),o.push(c),(c!=r||a>0)&&(c.selected=!0),c};const o=[],a=n.input.querySelectorAll("option:checked").length;n.input.querySelectorAll("option:checked").forEach(c=>{c.selected=!1}),n.items.length==0&&n.settings.mode=="single"?d(r,"",""):n.items.forEach(c=>{if(s=n.options[c],i=s[n.settings.labelField]||"",o.includes(s.$option)){const u=n.input.querySelector(`option[value="${Lt(c)}"]:not(:checked)`);d(u,c,i)}else s.$option=d(s.$option,c,i)})}else n.input.value=n.getValue();n.isSetup&&(e.silent||n.trigger("change",n.getValue()))}open(){var e=this;e.isLocked||e.isOpen||e.settings.mode==="multi"&&e.isFull()||(e.isOpen=!0,N(e.focus_node,{"aria-expanded":"true"}),e.refreshState(),Ae(e.dropdown,{visibility:"hidden",display:"block"}),e.positionDropdown(),Ae(e.dropdown,{visibility:"visible",display:"block"}),e.focus(),e.trigger("dropdown_open",e.dropdown))}close(e=!0){var n=this,s=n.isOpen;e&&(n.setTextboxValue(),n.settings.mode==="single"&&n.items.length&&n.inputState()),n.isOpen=!1,N(n.focus_node,{"aria-expanded":"false"}),Ae(n.dropdown,{display:"none"}),n.settings.hideSelected&&n.clearActiveOption(),n.refreshState(),s&&n.trigger("dropdown_close",n.dropdown)}positionDropdown(){if(this.settings.dropdownParent==="body"){var e=this.control,n=e.getBoundingClientRect(),s=e.offsetHeight+n.top+window.scrollY,i=n.left+window.scrollX;Ae(this.dropdown,{width:n.width+"px",top:s+"px",left:i+"px"})}}clear(e){var n=this;if(n.items.length){var s=n.controlChildren();U(s,i=>{n.removeItem(i,!0)}),n.inputState(),e||n.updateOriginalInput(),n.trigger("clear")}}insertAtCaret(e){const n=this,s=n.caretPos,i=n.control;i.insertBefore(e,i.children[s]||null),n.setCaret(s+1)}deleteSelection(e){var n,s,i,r,o=this;n=e&&e.keyCode===Ct?-1:1,s=_s(o.control_input);const a=[];if(o.activeItems.length)r=St(o.activeItems,n),i=Et(r),n>0&&i++,U(o.activeItems,d=>a.push(d));else if((o.isFocused||o.settings.mode==="single")&&o.items.length){const d=o.controlChildren();let c;n<0&&s.start===0&&s.length===0?c=d[o.caretPos-1]:n>0&&s.start===o.inputValue().length&&(c=d[o.caretPos]),c!==void 0&&a.push(c)}if(!o.shouldDelete(a,e))return!1;for(B(e,!0),typeof i<"u"&&o.setCaret(i);a.length;)o.removeItem(a.pop());return o.inputState(),o.positionDropdown(),o.refreshOptions(!1),!0}shouldDelete(e,n){const s=e.map(i=>i.dataset.value);return!(!s.length||typeof this.settings.onDelete=="function"&&this.settings.onDelete.call(this,s,n)===!1)}advanceSelection(e,n){var s,i,r=this;r.rtl&&(e*=-1),!r.inputValue().length&&(de(Te,n)||de("shiftKey",n)?(s=r.getLastActive(e),s?s.classList.contains("active")?i=r.getAdjacent(s,e,"item"):i=s:e>0?i=r.control_input.nextElementSibling:i=r.control_input.previousElementSibling,i&&(i.classList.contains("active")&&r.removeActiveItem(s),r.setActiveItemClass(i))):r.moveCaret(e))}moveCaret(e){}getLastActive(e){let n=this.control.querySelector(".last-active");if(n)return n;var s=this.control.querySelectorAll(".active");if(s)return St(s,e)}setCaret(e){this.caretPos=this.items.length}controlChildren(){return Array.from(this.control.querySelectorAll("[data-ts-item]"))}lock(){this.setLocked(!0)}unlock(){this.setLocked(!1)}setLocked(e=this.isReadOnly||this.isDisabled){this.isLocked=e,this.refreshState()}disable(){this.setDisabled(!0),this.close()}enable(){this.setDisabled(!1)}setDisabled(e){this.focus_node.tabIndex=e?-1:this.tabIndex,this.isDisabled=e,this.input.disabled=e,this.control_input.disabled=e,this.setLocked()}setReadOnly(e){this.isReadOnly=e,this.input.readOnly=e,this.control_input.readOnly=e,this.setLocked()}destroy(){var e=this,n=e.revertSettings;e.trigger("destroy"),e.off(),e.wrapper.remove(),e.dropdown.remove(),e.input.innerHTML=n.innerHTML,e.input.tabIndex=n.tabIndex,le(e.input,"tomselected","ts-hidden-accessible"),e._destroy(),delete e.input.tomselect}render(e,n){var s,i;const r=this;if(typeof this.settings.render[e]!="function"||(i=r.settings.render[e].call(this,n,Ke),!i))return null;if(i=ie(i),e==="option"||e==="option_create"?n[r.settings.disabledField]?N(i,{"aria-disabled":"true"}):N(i,{"data-selectable":""}):e==="optgroup"&&(s=n.group[r.settings.optgroupValueField],N(i,{"data-group":s}),n.group[r.settings.disabledField]&&N(i,{"data-disabled":""})),e==="option"||e==="item"){const o=$e(n[r.settings.valueField]);N(i,{"data-value":o}),e==="item"?(Y(i,r.settings.itemClass),N(i,{"data-ts-item":""})):(Y(i,r.settings.optionClass),N(i,{role:"option",id:n.$id}),n.$div=i,r.options[o]=n)}return i}_render(e,n){const s=this.render(e,n);if(s==null)throw"HTMLElement expected";return s}clearCache(){U(this.options,e=>{e.$div&&(e.$div.remove(),delete e.$div)})}uncacheValue(e){const n=this.getOption(e);n&&n.remove()}canCreate(e){return this.settings.create&&e.length>0&&this.settings.createFilter.call(this,e)}hook(e,n,s){var i=this,r=i[n];i[n]=function(){var o,a;return e==="after"&&(o=r.apply(i,arguments)),a=s.apply(i,arguments),e==="instead"?a:(e==="before"&&(o=r.apply(i,arguments)),o)}}}const Ms=(t,e,n,s)=>{t.addEventListener(e,n,s)};function Hs(){Ms(this.input,"change",()=>{this.sync()})}const Ds=t=>typeof t>"u"||t===null?null:Ps(t),Ps=t=>typeof t=="boolean"?t?"1":"0":t+"",$t=(t,e=!1)=>{t&&(t.preventDefault(),e&&t.stopPropagation())},Fs=t=>{if(t.jquery)return t[0];if(t instanceof HTMLElement)return t;if(Ns(t)){var e=document.createElement("template");return e.innerHTML=t.trim(),e.content.firstChild}return document.querySelector(t)},Ns=t=>typeof t=="string"&&t.indexOf("<")>-1;function Bs(t){var e=this,n=e.onOptionSelect;e.settings.hideSelected=!1;const s=Object.assign({className:"tomselect-checkbox",checkedClassNames:void 0,uncheckedClassNames:void 0},t);var i=function(a,d){d?(a.checked=!0,s.uncheckedClassNames&&a.classList.remove(...s.uncheckedClassNames),s.checkedClassNames&&a.classList.add(...s.checkedClassNames)):(a.checked=!1,s.checkedClassNames&&a.classList.remove(...s.checkedClassNames),s.uncheckedClassNames&&a.classList.add(...s.uncheckedClassNames))},r=function(a){setTimeout(()=>{var d=a.querySelector("input."+s.className);d instanceof HTMLInputElement&&i(d,a.classList.contains("selected"))},1)};e.hook("after","setupTemplates",()=>{var o=e.settings.render.option;e.settings.render.option=(a,d)=>{var c=Fs(o.call(e,a,d)),u=document.createElement("input");s.className&&u.classList.add(s.className),u.addEventListener("click",function(h){$t(h)}),u.type="checkbox";const m=Ds(a[e.settings.valueField]);return i(u,!!(m&&e.items.indexOf(m)>-1)),c.prepend(u),c}}),e.on("item_remove",o=>{var a=e.getOption(o);a&&(a.classList.remove("selected"),r(a))}),e.on("item_add",o=>{var a=e.getOption(o);a&&r(a)}),e.hook("instead","onOptionSelect",(o,a)=>{if(a.classList.contains("selected")){a.classList.remove("selected"),e.removeItem(a.dataset.value),e.refreshOptions(),$t(o,!0);return}n.call(e,o,a),r(a)})}const Vs=t=>{if(t.jquery)return t[0];if(t instanceof HTMLElement)return t;if(js(t)){var e=document.createElement("template");return e.innerHTML=t.trim(),e.content.firstChild}return document.querySelector(t)},js=t=>typeof t=="string"&&t.indexOf("<")>-1;function Rs(t){const e=this,n=Object.assign({className:"clear-button",title:"Clear All",role:"button",tabindex:0,html:s=>`
    ×
    `},t);e.on("initialize",()=>{var s=Vs(n.html(n));s.addEventListener("click",i=>{e.isLocked||(e.clear(),e.settings.mode==="single"&&e.settings.allowEmptyOption&&e.addItem(""),e.refreshOptions(!1),i.preventDefault(),i.stopPropagation())}),e.control.appendChild(s)})}const Us=(t,e=!1)=>{t&&(t.preventDefault(),e&&t.stopPropagation())},he=(t,e,n,s)=>{t.addEventListener(e,n,s)},Ks=(t,e)=>{if(Array.isArray(t))t.forEach(e);else for(var n in t)t.hasOwnProperty(n)&&e(t[n],n)},zs=t=>{if(t.jquery)return t[0];if(t instanceof HTMLElement)return t;if(Ws(t)){var e=document.createElement("template");return e.innerHTML=t.trim(),e.content.firstChild}return document.querySelector(t)},Ws=t=>typeof t=="string"&&t.indexOf("<")>-1,Gs=(t,e)=>{Ks(e,(n,s)=>{n==null?t.removeAttribute(s):t.setAttribute(s,""+n)})},Ys=(t,e)=>{var n;(n=t.parentNode)==null||n.insertBefore(e,t.nextSibling)},Qs=(t,e)=>{var n;(n=t.parentNode)==null||n.insertBefore(e,t)},Js=(t,e)=>{do{var n;if(e=(n=e)==null?void 0:n.previousElementSibling,t==e)return!0}while(e&&e.previousElementSibling);return!1};function Xs(){var t=this;if(t.settings.mode!=="multi")return;var e=t.lock,n=t.unlock;let s=!0,i;t.hook("after","setupTemplates",()=>{var r=t.settings.render.item;t.settings.render.item=(o,a)=>{const d=zs(r.call(t,o,a));Gs(d,{draggable:"true"});const c=_=>{s||Us(_),_.stopPropagation()},u=_=>{i=d,setTimeout(()=>{d.classList.add("ts-dragging")},0)},m=_=>{_.preventDefault(),d.classList.add("ts-drag-over"),y(d,i)},h=()=>{d.classList.remove("ts-drag-over")},y=(_,C)=>{C!==void 0&&(Js(C,d)?Ys(_,C):Qs(_,C))},g=()=>{var _;document.querySelectorAll(".ts-drag-over").forEach(E=>E.classList.remove("ts-drag-over")),(_=i)==null||_.classList.remove("ts-dragging"),i=void 0;var C=[];t.control.querySelectorAll("[data-value]").forEach(E=>{if(E.dataset.value){let $=E.dataset.value;$&&C.push($)}}),t.setValue(C)};return he(d,"mousedown",c),he(d,"dragstart",u),he(d,"dragenter",m),he(d,"dragover",m),he(d,"dragleave",h),he(d,"dragend",g),d}}),t.hook("instead","lock",()=>(s=!1,e.call(t))),t.hook("instead","unlock",()=>(s=!0,n.call(t)))}const Zs=(t,e=!1)=>{t&&(t.preventDefault(),e&&t.stopPropagation())},ei=t=>{if(t.jquery)return t[0];if(t instanceof HTMLElement)return t;if(ti(t)){var e=document.createElement("template");return e.innerHTML=t.trim(),e.content.firstChild}return document.querySelector(t)},ti=t=>typeof t=="string"&&t.indexOf("<")>-1;function ni(t){const e=this,n=Object.assign({title:"Untitled",headerClass:"dropdown-header",titleRowClass:"dropdown-header-title",labelClass:"dropdown-header-label",closeClass:"dropdown-header-close",html:s=>'
    '+s.title+'×
    '},t);e.on("initialize",()=>{var s=ei(n.html(n)),i=s.querySelector("."+n.closeClass);i&&i.addEventListener("click",r=>{Zs(r,!0),e.close()}),e.dropdown.insertBefore(s,e.dropdown.firstChild)})}const si=(t,e)=>{if(Array.isArray(t))t.forEach(e);else for(var n in t)t.hasOwnProperty(n)&&e(t[n],n)},ii=(t,...e)=>{var n=ri(e);t=oi(t),t.map(s=>{n.map(i=>{s.classList.remove(i)})})},ri=t=>{var e=[];return si(t,n=>{typeof n=="string"&&(n=n.trim().split(/[\t\n\f\r\s]/)),Array.isArray(n)&&(e=e.concat(n))}),e.filter(Boolean)},oi=t=>(Array.isArray(t)||(t=[t]),t),li=(t,e)=>{if(!t)return-1;e=e||t.nodeName;for(var n=0;t=t.previousElementSibling;)t.matches(e)&&n++;return n};function ai(){var t=this;t.hook("instead","setCaret",e=>{t.settings.mode==="single"||!t.control.contains(t.control_input)?e=t.items.length:(e=Math.max(0,Math.min(t.items.length,e)),e!=t.caretPos&&!t.isPending&&t.controlChildren().forEach((n,s)=>{s{if(!t.isFocused)return;const n=t.getLastActive(e);if(n){const s=li(n);t.setCaret(e>0?s+1:s),t.setActiveItem(),ii(n,"last-active")}else t.setCaret(t.caretPos+e)})}const ci=27,di=9,ui=(t,e=!1)=>{t&&(t.preventDefault(),e&&t.stopPropagation())},fi=(t,e,n,s)=>{t.addEventListener(e,n,s)},pi=(t,e)=>{if(Array.isArray(t))t.forEach(e);else for(var n in t)t.hasOwnProperty(n)&&e(t[n],n)},It=t=>{if(t.jquery)return t[0];if(t instanceof HTMLElement)return t;if(mi(t)){var e=document.createElement("template");return e.innerHTML=t.trim(),e.content.firstChild}return document.querySelector(t)},mi=t=>typeof t=="string"&&t.indexOf("<")>-1,hi=(t,...e)=>{var n=gi(e);t=vi(t),t.map(s=>{n.map(i=>{s.classList.add(i)})})},gi=t=>{var e=[];return pi(t,n=>{typeof n=="string"&&(n=n.trim().split(/[\t\n\f\r\s]/)),Array.isArray(n)&&(e=e.concat(n))}),e.filter(Boolean)},vi=t=>(Array.isArray(t)||(t=[t]),t);function yi(){const t=this;t.settings.shouldOpen=!0,t.hook("before","setup",()=>{var e;t.focus_node=t.control,hi(t.control_input,"dropdown-input");const n=It('
    ' + dom.blank + '
    ' + trHTML + '
    '); if (options && options.tableClassName) { $table.addClass(options.tableClassName); } return $table[0]; } /** * Delete current table * * @param {WrappedRange} rng * @return {Node} */ }, { key: "deleteTable", value: function deleteTable(rng) { var cell = dom.ancestor(rng.commonAncestor(), dom.isCell); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(cell).closest('table').remove(); } }]); return Table; }(); // CONCATENATED MODULE: ./src/js/base/module/Editor.js function Editor_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Editor_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Editor_createClass(Constructor, protoProps, staticProps) { if (protoProps) Editor_defineProperties(Constructor.prototype, protoProps); if (staticProps) Editor_defineProperties(Constructor, staticProps); return Constructor; } var KEY_BOGUS = 'bogus'; /** * @class Editor */ var Editor_Editor = /*#__PURE__*/function () { function Editor(context) { var _this = this; Editor_classCallCheck(this, Editor); this.context = context; this.$note = context.layoutInfo.note; this.$editor = context.layoutInfo.editor; this.$editable = context.layoutInfo.editable; this.options = context.options; this.lang = this.options.langInfo; this.editable = this.$editable[0]; this.lastRange = null; this.snapshot = null; this.style = new Style_Style(); this.table = new Table_Table(); this.typing = new Typing_Typing(context); this.bullet = new Bullet_Bullet(); this.history = new History_History(context); this.context.memo('help.escape', this.lang.help.escape); this.context.memo('help.undo', this.lang.help.undo); this.context.memo('help.redo', this.lang.help.redo); this.context.memo('help.tab', this.lang.help.tab); this.context.memo('help.untab', this.lang.help.untab); this.context.memo('help.insertParagraph', this.lang.help.insertParagraph); this.context.memo('help.insertOrderedList', this.lang.help.insertOrderedList); this.context.memo('help.insertUnorderedList', this.lang.help.insertUnorderedList); this.context.memo('help.indent', this.lang.help.indent); this.context.memo('help.outdent', this.lang.help.outdent); this.context.memo('help.formatPara', this.lang.help.formatPara); this.context.memo('help.insertHorizontalRule', this.lang.help.insertHorizontalRule); this.context.memo('help.fontName', this.lang.help.fontName); // native commands(with execCommand), generate function for execCommand var commands = ['bold', 'italic', 'underline', 'strikethrough', 'superscript', 'subscript', 'justifyLeft', 'justifyCenter', 'justifyRight', 'justifyFull', 'formatBlock', 'removeFormat', 'backColor']; for (var idx = 0, len = commands.length; idx < len; idx++) { this[commands[idx]] = function (sCmd) { return function (value) { _this.beforeCommand(); document.execCommand(sCmd, false, value); _this.afterCommand(true); }; }(commands[idx]); this.context.memo('help.' + commands[idx], this.lang.help[commands[idx]]); } this.fontName = this.wrapCommand(function (value) { return _this.fontStyling('font-family', env.validFontName(value)); }); this.fontSize = this.wrapCommand(function (value) { var unit = _this.currentStyle()['font-size-unit']; return _this.fontStyling('font-size', value + unit); }); this.fontSizeUnit = this.wrapCommand(function (value) { var size = _this.currentStyle()['font-size']; return _this.fontStyling('font-size', size + value); }); for (var _idx = 1; _idx <= 6; _idx++) { this['formatH' + _idx] = function (idx) { return function () { _this.formatBlock('H' + idx); }; }(_idx); this.context.memo('help.formatH' + _idx, this.lang.help['formatH' + _idx]); } this.insertParagraph = this.wrapCommand(function () { _this.typing.insertParagraph(_this.editable); }); this.insertOrderedList = this.wrapCommand(function () { _this.bullet.insertOrderedList(_this.editable); }); this.insertUnorderedList = this.wrapCommand(function () { _this.bullet.insertUnorderedList(_this.editable); }); this.indent = this.wrapCommand(function () { _this.bullet.indent(_this.editable); }); this.outdent = this.wrapCommand(function () { _this.bullet.outdent(_this.editable); }); /** * insertNode * insert node * @param {Node} node */ this.insertNode = this.wrapCommand(function (node) { if (_this.isLimited(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(node).text().length)) { return; } var rng = _this.getLastRange(); rng.insertNode(node); _this.setLastRange(range.createFromNodeAfter(node).select()); }); /** * insert text * @param {String} text */ this.insertText = this.wrapCommand(function (text) { if (_this.isLimited(text.length)) { return; } var rng = _this.getLastRange(); var textNode = rng.insertNode(dom.createText(text)); _this.setLastRange(range.create(textNode, dom.nodeLength(textNode)).select()); }); /** * paste HTML * @param {String} markup */ this.pasteHTML = this.wrapCommand(function (markup) { if (_this.isLimited(markup.length)) { return; } markup = _this.context.invoke('codeview.purify', markup); var contents = _this.getLastRange().pasteHTML(markup); _this.setLastRange(range.createFromNodeAfter(lists.last(contents)).select()); }); /** * formatBlock * * @param {String} tagName */ this.formatBlock = this.wrapCommand(function (tagName, $target) { var onApplyCustomStyle = _this.options.callbacks.onApplyCustomStyle; if (onApplyCustomStyle) { onApplyCustomStyle.call(_this, $target, _this.context, _this.onFormatBlock); } else { _this.onFormatBlock(tagName, $target); } }); /** * insert horizontal rule */ this.insertHorizontalRule = this.wrapCommand(function () { var hrNode = _this.getLastRange().insertNode(dom.create('HR')); if (hrNode.nextSibling) { _this.setLastRange(range.create(hrNode.nextSibling, 0).normalize().select()); } }); /** * lineHeight * @param {String} value */ this.lineHeight = this.wrapCommand(function (value) { _this.style.stylePara(_this.getLastRange(), { lineHeight: value }); }); /** * create link (command) * * @param {Object} linkInfo */ this.createLink = this.wrapCommand(function (linkInfo) { var linkUrl = linkInfo.url; var linkText = linkInfo.text; var isNewWindow = linkInfo.isNewWindow; var checkProtocol = linkInfo.checkProtocol; var rng = linkInfo.range || _this.getLastRange(); var additionalTextLength = linkText.length - rng.toString().length; if (additionalTextLength > 0 && _this.isLimited(additionalTextLength)) { return; } var isTextChanged = rng.toString() !== linkText; // handle spaced urls from input if (typeof linkUrl === 'string') { linkUrl = linkUrl.trim(); } if (_this.options.onCreateLink) { linkUrl = _this.options.onCreateLink(linkUrl); } else if (checkProtocol) { // if url doesn't have any protocol and not even a relative or a label, use http:// as default linkUrl = /^([A-Za-z][A-Za-z0-9+-.]*\:|#|\/)/.test(linkUrl) ? linkUrl : _this.options.defaultProtocol + linkUrl; } var anchors = []; if (isTextChanged) { rng = rng.deleteContents(); var anchor = rng.insertNode(external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('' + linkText + '')[0]); anchors.push(anchor); } else { anchors = _this.style.styleNodes(rng, { nodeName: 'A', expandClosestSibling: true, onlyPartialContains: true }); } external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(anchors, function (idx, anchor) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(anchor).attr('href', linkUrl); if (isNewWindow) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(anchor).attr('target', '_blank'); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(anchor).removeAttr('target'); } }); _this.setLastRange(_this.createRangeFromList(anchors).select()); }); /** * setting color * * @param {Object} sObjColor color code * @param {String} sObjColor.foreColor foreground color * @param {String} sObjColor.backColor background color */ this.color = this.wrapCommand(function (colorInfo) { var foreColor = colorInfo.foreColor; var backColor = colorInfo.backColor; if (foreColor) { document.execCommand('foreColor', false, foreColor); } if (backColor) { document.execCommand('backColor', false, backColor); } }); /** * Set foreground color * * @param {String} colorCode foreground color code */ this.foreColor = this.wrapCommand(function (colorInfo) { document.execCommand('foreColor', false, colorInfo); }); /** * insert Table * * @param {String} dimension of table (ex : "5x5") */ this.insertTable = this.wrapCommand(function (dim) { var dimension = dim.split('x'); var rng = _this.getLastRange().deleteContents(); rng.insertNode(_this.table.createTable(dimension[0], dimension[1], _this.options)); }); /** * remove media object and Figure Elements if media object is img with Figure. */ this.removeMedia = this.wrapCommand(function () { var $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(_this.restoreTarget()).parent(); if ($target.closest('figure').length) { $target.closest('figure').remove(); } else { $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(_this.restoreTarget()).detach(); } _this.context.triggerEvent('media.delete', $target, _this.$editable); }); /** * float me * * @param {String} value */ this.floatMe = this.wrapCommand(function (value) { var $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(_this.restoreTarget()); $target.toggleClass('note-float-left', value === 'left'); $target.toggleClass('note-float-right', value === 'right'); $target.css('float', value === 'none' ? '' : value); }); /** * resize overlay element * @param {String} value */ this.resize = this.wrapCommand(function (value) { var $target = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(_this.restoreTarget()); value = parseFloat(value); if (value === 0) { $target.css('width', ''); } else { $target.css({ width: value * 100 + '%', height: '' }); } }); } Editor_createClass(Editor, [{ key: "initialize", value: function initialize() { var _this2 = this; // bind custom events this.$editable.on('keydown', function (event) { if (event.keyCode === core_key.code.ENTER) { _this2.context.triggerEvent('enter', event); } _this2.context.triggerEvent('keydown', event); // keep a snapshot to limit text on input event _this2.snapshot = _this2.history.makeSnapshot(); _this2.hasKeyShortCut = false; if (!event.isDefaultPrevented()) { if (_this2.options.shortcuts) { _this2.hasKeyShortCut = _this2.handleKeyMap(event); } else { _this2.preventDefaultEditableShortCuts(event); } } if (_this2.isLimited(1, event)) { var lastRange = _this2.getLastRange(); if (lastRange.eo - lastRange.so === 0) { return false; } } _this2.setLastRange(); // record undo in the key event except keyMap. if (_this2.options.recordEveryKeystroke) { if (_this2.hasKeyShortCut === false) { _this2.history.recordUndo(); } } }).on('keyup', function (event) { _this2.setLastRange(); _this2.context.triggerEvent('keyup', event); }).on('focus', function (event) { _this2.setLastRange(); _this2.context.triggerEvent('focus', event); }).on('blur', function (event) { _this2.context.triggerEvent('blur', event); }).on('mousedown', function (event) { _this2.context.triggerEvent('mousedown', event); }).on('mouseup', function (event) { _this2.setLastRange(); _this2.history.recordUndo(); _this2.context.triggerEvent('mouseup', event); }).on('scroll', function (event) { _this2.context.triggerEvent('scroll', event); }).on('paste', function (event) { _this2.setLastRange(); _this2.context.triggerEvent('paste', event); }).on('input', function () { // To limit composition characters (e.g. Korean) if (_this2.isLimited(0) && _this2.snapshot) { _this2.history.applySnapshot(_this2.snapshot); } }); this.$editable.attr('spellcheck', this.options.spellCheck); this.$editable.attr('autocorrect', this.options.spellCheck); if (this.options.disableGrammar) { this.$editable.attr('data-gramm', false); } // init content before set event this.$editable.html(dom.html(this.$note) || dom.emptyPara); this.$editable.on(env.inputEventName, func.debounce(function () { _this2.context.triggerEvent('change', _this2.$editable.html(), _this2.$editable); }, 10)); this.$editable.on('focusin', function (event) { _this2.context.triggerEvent('focusin', event); }).on('focusout', function (event) { _this2.context.triggerEvent('focusout', event); }); if (this.options.airMode) { if (this.options.overrideContextMenu) { this.$editor.on('contextmenu', function (event) { _this2.context.triggerEvent('contextmenu', event); return false; }); } } else { if (this.options.width) { this.$editor.outerWidth(this.options.width); } if (this.options.height) { this.$editable.outerHeight(this.options.height); } if (this.options.maxHeight) { this.$editable.css('max-height', this.options.maxHeight); } if (this.options.minHeight) { this.$editable.css('min-height', this.options.minHeight); } } this.history.recordUndo(); this.setLastRange(); } }, { key: "destroy", value: function destroy() { this.$editable.off(); } }, { key: "handleKeyMap", value: function handleKeyMap(event) { var keyMap = this.options.keyMap[env.isMac ? 'mac' : 'pc']; var keys = []; if (event.metaKey) { keys.push('CMD'); } if (event.ctrlKey && !event.altKey) { keys.push('CTRL'); } if (event.shiftKey) { keys.push('SHIFT'); } var keyName = core_key.nameFromCode[event.keyCode]; if (keyName) { keys.push(keyName); } var eventName = keyMap[keys.join('+')]; if (keyName === 'TAB' && !this.options.tabDisable) { this.afterCommand(); } else if (eventName) { if (this.context.invoke(eventName) !== false) { event.preventDefault(); // if keyMap action was invoked return true; } } else if (core_key.isEdit(event.keyCode)) { this.afterCommand(); } return false; } }, { key: "preventDefaultEditableShortCuts", value: function preventDefaultEditableShortCuts(event) { // B(Bold, 66) / I(Italic, 73) / U(Underline, 85) if ((event.ctrlKey || event.metaKey) && lists.contains([66, 73, 85], event.keyCode)) { event.preventDefault(); } } }, { key: "isLimited", value: function isLimited(pad, event) { pad = pad || 0; if (typeof event !== 'undefined') { if (core_key.isMove(event.keyCode) || core_key.isNavigation(event.keyCode) || event.ctrlKey || event.metaKey || lists.contains([core_key.code.BACKSPACE, core_key.code.DELETE], event.keyCode)) { return false; } } if (this.options.maxTextLength > 0) { if (this.$editable.text().length + pad > this.options.maxTextLength) { return true; } } return false; } /** * create range * @return {WrappedRange} */ }, { key: "createRange", value: function createRange() { this.focus(); this.setLastRange(); return this.getLastRange(); } /** * create a new range from the list of elements * * @param {list} dom element list * @return {WrappedRange} */ }, { key: "createRangeFromList", value: function createRangeFromList(lst) { var startRange = range.createFromNodeBefore(lists.head(lst)); var startPoint = startRange.getStartPoint(); var endRange = range.createFromNodeAfter(lists.last(lst)); var endPoint = endRange.getEndPoint(); return range.create(startPoint.node, startPoint.offset, endPoint.node, endPoint.offset); } /** * set the last range * * if given rng is exist, set rng as the last range * or create a new range at the end of the document * * @param {WrappedRange} rng */ }, { key: "setLastRange", value: function setLastRange(rng) { if (rng) { this.lastRange = rng; } else { this.lastRange = range.create(this.editable); if (external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.lastRange.sc).closest('.note-editable').length === 0) { this.lastRange = range.createFromBodyElement(this.editable); } } } /** * get the last range * * if there is a saved last range, return it * or create a new range and return it * * @return {WrappedRange} */ }, { key: "getLastRange", value: function getLastRange() { if (!this.lastRange) { this.setLastRange(); } return this.lastRange; } /** * saveRange * * save current range * * @param {Boolean} [thenCollapse=false] */ }, { key: "saveRange", value: function saveRange(thenCollapse) { if (thenCollapse) { this.getLastRange().collapse().select(); } } /** * restoreRange * * restore lately range */ }, { key: "restoreRange", value: function restoreRange() { if (this.lastRange) { this.lastRange.select(); this.focus(); } } }, { key: "saveTarget", value: function saveTarget(node) { this.$editable.data('target', node); } }, { key: "clearTarget", value: function clearTarget() { this.$editable.removeData('target'); } }, { key: "restoreTarget", value: function restoreTarget() { return this.$editable.data('target'); } /** * currentStyle * * current style * @return {Object|Boolean} unfocus */ }, { key: "currentStyle", value: function currentStyle() { var rng = range.create(); if (rng) { rng = rng.normalize(); } return rng ? this.style.current(rng) : this.style.fromNode(this.$editable); } /** * style from node * * @param {jQuery} $node * @return {Object} */ }, { key: "styleFromNode", value: function styleFromNode($node) { return this.style.fromNode($node); } /** * undo */ }, { key: "undo", value: function undo() { this.context.triggerEvent('before.command', this.$editable.html()); this.history.undo(); this.context.triggerEvent('change', this.$editable.html(), this.$editable); } /* * commit */ }, { key: "commit", value: function commit() { this.context.triggerEvent('before.command', this.$editable.html()); this.history.commit(); this.context.triggerEvent('change', this.$editable.html(), this.$editable); } /** * redo */ }, { key: "redo", value: function redo() { this.context.triggerEvent('before.command', this.$editable.html()); this.history.redo(); this.context.triggerEvent('change', this.$editable.html(), this.$editable); } /** * before command */ }, { key: "beforeCommand", value: function beforeCommand() { this.context.triggerEvent('before.command', this.$editable.html()); // Set styleWithCSS before run a command document.execCommand('styleWithCSS', false, this.options.styleWithCSS); // keep focus on editable before command execution this.focus(); } /** * after command * @param {Boolean} isPreventTrigger */ }, { key: "afterCommand", value: function afterCommand(isPreventTrigger) { this.normalizeContent(); this.history.recordUndo(); if (!isPreventTrigger) { this.context.triggerEvent('change', this.$editable.html(), this.$editable); } } /** * handle tab key */ }, { key: "tab", value: function tab() { var rng = this.getLastRange(); if (rng.isCollapsed() && rng.isOnCell()) { this.table.tab(rng); } else { if (this.options.tabSize === 0) { return false; } if (!this.isLimited(this.options.tabSize)) { this.beforeCommand(); this.typing.insertTab(rng, this.options.tabSize); this.afterCommand(); } } } /** * handle shift+tab key */ }, { key: "untab", value: function untab() { var rng = this.getLastRange(); if (rng.isCollapsed() && rng.isOnCell()) { this.table.tab(rng, true); } else { if (this.options.tabSize === 0) { return false; } } } /** * run given function between beforeCommand and afterCommand */ }, { key: "wrapCommand", value: function wrapCommand(fn) { return function () { this.beforeCommand(); fn.apply(this, arguments); this.afterCommand(); }; } /** * insert image * * @param {String} src * @param {String|Function} param * @return {Promise} */ }, { key: "insertImage", value: function insertImage(src, param) { var _this3 = this; return createImage(src, param).then(function ($image) { _this3.beforeCommand(); if (typeof param === 'function') { param($image); } else { if (typeof param === 'string') { $image.attr('data-filename', param); } $image.css('width', Math.min(_this3.$editable.width(), $image.width())); } $image.show(); _this3.getLastRange().insertNode($image[0]); _this3.setLastRange(range.createFromNodeAfter($image[0]).select()); _this3.afterCommand(); }).fail(function (e) { _this3.context.triggerEvent('image.upload.error', e); }); } /** * insertImages * @param {File[]} files */ }, { key: "insertImagesAsDataURL", value: function insertImagesAsDataURL(files) { var _this4 = this; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(files, function (idx, file) { var filename = file.name; if (_this4.options.maximumImageFileSize && _this4.options.maximumImageFileSize < file.size) { _this4.context.triggerEvent('image.upload.error', _this4.lang.image.maximumFileSizeError); } else { readFileAsDataURL(file).then(function (dataURL) { return _this4.insertImage(dataURL, filename); }).fail(function () { _this4.context.triggerEvent('image.upload.error'); }); } }); } /** * insertImagesOrCallback * @param {File[]} files */ }, { key: "insertImagesOrCallback", value: function insertImagesOrCallback(files) { var callbacks = this.options.callbacks; // If onImageUpload set, if (callbacks.onImageUpload) { this.context.triggerEvent('image.upload', files); // else insert Image as dataURL } else { this.insertImagesAsDataURL(files); } } /** * return selected plain text * @return {String} text */ }, { key: "getSelectedText", value: function getSelectedText() { var rng = this.getLastRange(); // if range on anchor, expand range with anchor if (rng.isOnAnchor()) { rng = range.createFromNode(dom.ancestor(rng.sc, dom.isAnchor)); } return rng.toString(); } }, { key: "onFormatBlock", value: function onFormatBlock(tagName, $target) { // [workaround] for MSIE, IE need `<` document.execCommand('FormatBlock', false, env.isMSIE ? '<' + tagName + '>' : tagName); // support custom class if ($target && $target.length) { // find the exact element has given tagName if ($target[0].tagName.toUpperCase() !== tagName.toUpperCase()) { $target = $target.find(tagName); } if ($target && $target.length) { var className = $target[0].className || ''; if (className) { var currentRange = this.createRange(); var $parent = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()([currentRange.sc, currentRange.ec]).closest(tagName); $parent.addClass(className); } } } } }, { key: "formatPara", value: function formatPara() { this.formatBlock('P'); } }, { key: "fontStyling", value: function fontStyling(target, value) { var rng = this.getLastRange(); if (rng !== '') { var spans = this.style.styleNodes(rng); this.$editor.find('.note-status-output').html(''); external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(spans).css(target, value); // [workaround] added styled bogus span for style // - also bogus character needed for cursor position if (rng.isCollapsed()) { var firstSpan = lists.head(spans); if (firstSpan && !dom.nodeLength(firstSpan)) { firstSpan.innerHTML = dom.ZERO_WIDTH_NBSP_CHAR; range.createFromNode(firstSpan.firstChild).select(); this.setLastRange(); this.$editable.data(KEY_BOGUS, firstSpan); } } else { this.setLastRange(this.createRangeFromList(spans).select()); } } else { var noteStatusOutput = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.now(); this.$editor.find('.note-status-output').html('

    ' + this.lang.output.noSelection + '
    '); setTimeout(function () { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('#note-status-output-' + noteStatusOutput).remove(); }, 5000); } } /** * unlink * * @type command */ }, { key: "unlink", value: function unlink() { var rng = this.getLastRange(); if (rng.isOnAnchor()) { var anchor = dom.ancestor(rng.sc, dom.isAnchor); rng = range.createFromNode(anchor); rng.select(); this.setLastRange(); this.beforeCommand(); document.execCommand('unlink'); this.afterCommand(); } } /** * returns link info * * @return {Object} * @return {WrappedRange} return.range * @return {String} return.text * @return {Boolean} [return.isNewWindow=true] * @return {String} [return.url=""] */ }, { key: "getLinkInfo", value: function getLinkInfo() { var rng = this.getLastRange().expand(dom.isAnchor); // Get the first anchor on range(for edit). var $anchor = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(lists.head(rng.nodes(dom.isAnchor))); var linkInfo = { range: rng, text: rng.toString(), url: $anchor.length ? $anchor.attr('href') : '' }; // When anchor exists, if ($anchor.length) { // Set isNewWindow by checking its target. linkInfo.isNewWindow = $anchor.attr('target') === '_blank'; } return linkInfo; } }, { key: "addRow", value: function addRow(position) { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.addRow(rng, position); this.afterCommand(); } } }, { key: "addCol", value: function addCol(position) { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.addCol(rng, position); this.afterCommand(); } } }, { key: "deleteRow", value: function deleteRow() { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.deleteRow(rng); this.afterCommand(); } } }, { key: "deleteCol", value: function deleteCol() { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.deleteCol(rng); this.afterCommand(); } } }, { key: "deleteTable", value: function deleteTable() { var rng = this.getLastRange(this.$editable); if (rng.isCollapsed() && rng.isOnCell()) { this.beforeCommand(); this.table.deleteTable(rng); this.afterCommand(); } } /** * @param {Position} pos * @param {jQuery} $target - target element * @param {Boolean} [bKeepRatio] - keep ratio */ }, { key: "resizeTo", value: function resizeTo(pos, $target, bKeepRatio) { var imageSize; if (bKeepRatio) { var newRatio = pos.y / pos.x; var ratio = $target.data('ratio'); imageSize = { width: ratio > newRatio ? pos.x : pos.y / ratio, height: ratio > newRatio ? pos.x * ratio : pos.y }; } else { imageSize = { width: pos.x, height: pos.y }; } $target.css(imageSize); } /** * returns whether editable area has focus or not. */ }, { key: "hasFocus", value: function hasFocus() { return this.$editable.is(':focus'); } /** * set focus */ }, { key: "focus", value: function focus() { // [workaround] Screen will move when page is scolled in IE. // - do focus when not focused if (!this.hasFocus()) { this.$editable.focus(); } } /** * returns whether contents is empty or not. * @return {Boolean} */ }, { key: "isEmpty", value: function isEmpty() { return dom.isEmpty(this.$editable[0]) || dom.emptyPara === this.$editable.html(); } /** * Removes all contents and restores the editable instance to an _emptyPara_. */ }, { key: "empty", value: function empty() { this.context.invoke('code', dom.emptyPara); } /** * normalize content */ }, { key: "normalizeContent", value: function normalizeContent() { this.$editable[0].normalize(); } }]); return Editor; }(); // CONCATENATED MODULE: ./src/js/base/module/Clipboard.js function Clipboard_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Clipboard_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Clipboard_createClass(Constructor, protoProps, staticProps) { if (protoProps) Clipboard_defineProperties(Constructor.prototype, protoProps); if (staticProps) Clipboard_defineProperties(Constructor, staticProps); return Constructor; } var Clipboard_Clipboard = /*#__PURE__*/function () { function Clipboard(context) { Clipboard_classCallCheck(this, Clipboard); this.context = context; this.$editable = context.layoutInfo.editable; } Clipboard_createClass(Clipboard, [{ key: "initialize", value: function initialize() { this.$editable.on('paste', this.pasteByEvent.bind(this)); } /** * paste by clipboard event * * @param {Event} event */ }, { key: "pasteByEvent", value: function pasteByEvent(event) { var _this = this; var clipboardData = event.originalEvent.clipboardData; if (clipboardData && clipboardData.items && clipboardData.items.length) { var item = clipboardData.items.length > 1 ? clipboardData.items[1] : lists.head(clipboardData.items); if (item.kind === 'file' && item.type.indexOf('image/') !== -1) { // paste img file this.context.invoke('editor.insertImagesOrCallback', [item.getAsFile()]); event.preventDefault(); } else if (item.kind === 'string') { // paste text with maxTextLength check if (this.context.invoke('editor.isLimited', clipboardData.getData('Text').length)) { event.preventDefault(); } } } else if (window.clipboardData) { // for IE var text = window.clipboardData.getData('text'); if (this.context.invoke('editor.isLimited', text.length)) { event.preventDefault(); } } // Call editor.afterCommand after proceeding default event handler setTimeout(function () { _this.context.invoke('editor.afterCommand'); }, 10); } }]); return Clipboard; }(); // CONCATENATED MODULE: ./src/js/base/module/Dropzone.js function Dropzone_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Dropzone_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Dropzone_createClass(Constructor, protoProps, staticProps) { if (protoProps) Dropzone_defineProperties(Constructor.prototype, protoProps); if (staticProps) Dropzone_defineProperties(Constructor, staticProps); return Constructor; } var Dropzone_Dropzone = /*#__PURE__*/function () { function Dropzone(context) { Dropzone_classCallCheck(this, Dropzone); this.context = context; this.$eventListener = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); this.$editor = context.layoutInfo.editor; this.$editable = context.layoutInfo.editable; this.options = context.options; this.lang = this.options.langInfo; this.documentEventHandlers = {}; this.$dropzone = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(['
    ', '
    ', '
    '].join('')).prependTo(this.$editor); } /** * attach Drag and Drop Events */ Dropzone_createClass(Dropzone, [{ key: "initialize", value: function initialize() { if (this.options.disableDragAndDrop) { // prevent default drop event this.documentEventHandlers.onDrop = function (e) { e.preventDefault(); }; // do not consider outside of dropzone this.$eventListener = this.$dropzone; this.$eventListener.on('drop', this.documentEventHandlers.onDrop); } else { this.attachDragAndDropEvent(); } } /** * attach Drag and Drop Events */ }, { key: "attachDragAndDropEvent", value: function attachDragAndDropEvent() { var _this = this; var collection = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(); var $dropzoneMessage = this.$dropzone.find('.note-dropzone-message'); this.documentEventHandlers.onDragenter = function (e) { var isCodeview = _this.context.invoke('codeview.isActivated'); var hasEditorSize = _this.$editor.width() > 0 && _this.$editor.height() > 0; if (!isCodeview && !collection.length && hasEditorSize) { _this.$editor.addClass('dragover'); _this.$dropzone.width(_this.$editor.width()); _this.$dropzone.height(_this.$editor.height()); $dropzoneMessage.text(_this.lang.image.dragImageHere); } collection = collection.add(e.target); }; this.documentEventHandlers.onDragleave = function (e) { collection = collection.not(e.target); // If nodeName is BODY, then just make it over (fix for IE) if (!collection.length || e.target.nodeName === 'BODY') { collection = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(); _this.$editor.removeClass('dragover'); } }; this.documentEventHandlers.onDrop = function () { collection = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(); _this.$editor.removeClass('dragover'); }; // show dropzone on dragenter when dragging a object to document // -but only if the editor is visible, i.e. has a positive width and height this.$eventListener.on('dragenter', this.documentEventHandlers.onDragenter).on('dragleave', this.documentEventHandlers.onDragleave).on('drop', this.documentEventHandlers.onDrop); // change dropzone's message on hover. this.$dropzone.on('dragenter', function () { _this.$dropzone.addClass('hover'); $dropzoneMessage.text(_this.lang.image.dropImage); }).on('dragleave', function () { _this.$dropzone.removeClass('hover'); $dropzoneMessage.text(_this.lang.image.dragImageHere); }); // attach dropImage this.$dropzone.on('drop', function (event) { var dataTransfer = event.originalEvent.dataTransfer; // stop the browser from opening the dropped content event.preventDefault(); if (dataTransfer && dataTransfer.files && dataTransfer.files.length) { _this.$editable.focus(); _this.context.invoke('editor.insertImagesOrCallback', dataTransfer.files); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(dataTransfer.types, function (idx, type) { // skip moz-specific types if (type.toLowerCase().indexOf('_moz_') > -1) { return; } var content = dataTransfer.getData(type); if (type.toLowerCase().indexOf('text') > -1) { _this.context.invoke('editor.pasteHTML', content); } else { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(content).each(function (idx, item) { _this.context.invoke('editor.insertNode', item); }); } }); } }).on('dragover', false); // prevent default dragover event } }, { key: "destroy", value: function destroy() { var _this2 = this; Object.keys(this.documentEventHandlers).forEach(function (key) { _this2.$eventListener.off(key.substr(2).toLowerCase(), _this2.documentEventHandlers[key]); }); this.documentEventHandlers = {}; } }]); return Dropzone; }(); // CONCATENATED MODULE: ./src/js/base/module/Codeview.js function _createForOfIteratorHelper(o) { if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) { var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var it, normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; } function Codeview_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Codeview_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Codeview_createClass(Constructor, protoProps, staticProps) { if (protoProps) Codeview_defineProperties(Constructor.prototype, protoProps); if (staticProps) Codeview_defineProperties(Constructor, staticProps); return Constructor; } /** * @class Codeview */ var Codeview_CodeView = /*#__PURE__*/function () { function CodeView(context) { Codeview_classCallCheck(this, CodeView); this.context = context; this.$editor = context.layoutInfo.editor; this.$editable = context.layoutInfo.editable; this.$codable = context.layoutInfo.codable; this.options = context.options; this.CodeMirrorConstructor = window.CodeMirror; if (this.options.codemirror.CodeMirrorConstructor) { this.CodeMirrorConstructor = this.options.codemirror.CodeMirrorConstructor; } } Codeview_createClass(CodeView, [{ key: "sync", value: function sync(html) { var isCodeview = this.isActivated(); var CodeMirror = this.CodeMirrorConstructor; if (isCodeview) { if (html) { if (CodeMirror) { this.$codable.data('cmEditor').getDoc().setValue(html); } else { this.$codable.val(html); } } else { if (CodeMirror) { this.$codable.data('cmEditor').save(); } } } } }, { key: "initialize", value: function initialize() { var _this = this; this.$codable.on('keyup', function (event) { if (event.keyCode === core_key.code.ESCAPE) { _this.deactivate(); } }); } /** * @return {Boolean} */ }, { key: "isActivated", value: function isActivated() { return this.$editor.hasClass('codeview'); } /** * toggle codeview */ }, { key: "toggle", value: function toggle() { if (this.isActivated()) { this.deactivate(); } else { this.activate(); } this.context.triggerEvent('codeview.toggled'); } /** * purify input value * @param value * @returns {*} */ }, { key: "purify", value: function purify(value) { if (this.options.codeviewFilter) { // filter code view regex value = value.replace(this.options.codeviewFilterRegex, ''); // allow specific iframe tag if (this.options.codeviewIframeFilter) { var whitelist = this.options.codeviewIframeWhitelistSrc.concat(this.options.codeviewIframeWhitelistSrcBase); value = value.replace(/(.*?(?:<\/iframe>)?)/gi, function (tag) { // remove if src attribute is duplicated if (/<.+src(?==?('|"|\s)?)[\s\S]+src(?=('|"|\s)?)[^>]*?>/i.test(tag)) { return ''; } var _iterator = _createForOfIteratorHelper(whitelist), _step; try { for (_iterator.s(); !(_step = _iterator.n()).done;) { var src = _step.value; // pass if src is trusted if (new RegExp('src="(https?:)?\/\/' + src.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') + '\/(.+)"').test(tag)) { return tag; } } } catch (err) { _iterator.e(err); } finally { _iterator.f(); } return ''; }); } } return value; } /** * activate code view */ }, { key: "activate", value: function activate() { var _this2 = this; var CodeMirror = this.CodeMirrorConstructor; this.$codable.val(dom.html(this.$editable, this.options.prettifyHtml)); this.$codable.height(this.$editable.height()); this.context.invoke('toolbar.updateCodeview', true); this.context.invoke('airPopover.updateCodeview', true); this.$editor.addClass('codeview'); this.$codable.focus(); // activate CodeMirror as codable if (CodeMirror) { var cmEditor = CodeMirror.fromTextArea(this.$codable[0], this.options.codemirror); // CodeMirror TernServer if (this.options.codemirror.tern) { var server = new CodeMirror.TernServer(this.options.codemirror.tern); cmEditor.ternServer = server; cmEditor.on('cursorActivity', function (cm) { server.updateArgHints(cm); }); } cmEditor.on('blur', function (event) { _this2.context.triggerEvent('blur.codeview', cmEditor.getValue(), event); }); cmEditor.on('change', function () { _this2.context.triggerEvent('change.codeview', cmEditor.getValue(), cmEditor); }); // CodeMirror hasn't Padding. cmEditor.setSize(null, this.$editable.outerHeight()); this.$codable.data('cmEditor', cmEditor); } else { this.$codable.on('blur', function (event) { _this2.context.triggerEvent('blur.codeview', _this2.$codable.val(), event); }); this.$codable.on('input', function () { _this2.context.triggerEvent('change.codeview', _this2.$codable.val(), _this2.$codable); }); } } /** * deactivate code view */ }, { key: "deactivate", value: function deactivate() { var CodeMirror = this.CodeMirrorConstructor; // deactivate CodeMirror as codable if (CodeMirror) { var cmEditor = this.$codable.data('cmEditor'); this.$codable.val(cmEditor.getValue()); cmEditor.toTextArea(); } var value = this.purify(dom.value(this.$codable, this.options.prettifyHtml) || dom.emptyPara); var isChange = this.$editable.html() !== value; this.$editable.html(value); this.$editable.height(this.options.height ? this.$codable.height() : 'auto'); this.$editor.removeClass('codeview'); if (isChange) { this.context.triggerEvent('change', this.$editable.html(), this.$editable); } this.$editable.focus(); this.context.invoke('toolbar.updateCodeview', false); this.context.invoke('airPopover.updateCodeview', false); } }, { key: "destroy", value: function destroy() { if (this.isActivated()) { this.deactivate(); } } }]); return CodeView; }(); // CONCATENATED MODULE: ./src/js/base/module/Statusbar.js function Statusbar_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Statusbar_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Statusbar_createClass(Constructor, protoProps, staticProps) { if (protoProps) Statusbar_defineProperties(Constructor.prototype, protoProps); if (staticProps) Statusbar_defineProperties(Constructor, staticProps); return Constructor; } var EDITABLE_PADDING = 24; var Statusbar_Statusbar = /*#__PURE__*/function () { function Statusbar(context) { Statusbar_classCallCheck(this, Statusbar); this.$document = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); this.$statusbar = context.layoutInfo.statusbar; this.$editable = context.layoutInfo.editable; this.options = context.options; } Statusbar_createClass(Statusbar, [{ key: "initialize", value: function initialize() { var _this = this; if (this.options.airMode || this.options.disableResizeEditor) { this.destroy(); return; } this.$statusbar.on('mousedown', function (event) { event.preventDefault(); event.stopPropagation(); var editableTop = _this.$editable.offset().top - _this.$document.scrollTop(); var onMouseMove = function onMouseMove(event) { var height = event.clientY - (editableTop + EDITABLE_PADDING); height = _this.options.minheight > 0 ? Math.max(height, _this.options.minheight) : height; height = _this.options.maxHeight > 0 ? Math.min(height, _this.options.maxHeight) : height; _this.$editable.height(height); }; _this.$document.on('mousemove', onMouseMove).one('mouseup', function () { _this.$document.off('mousemove', onMouseMove); }); }); } }, { key: "destroy", value: function destroy() { this.$statusbar.off(); this.$statusbar.addClass('locked'); } }]); return Statusbar; }(); // CONCATENATED MODULE: ./src/js/base/module/Fullscreen.js function Fullscreen_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Fullscreen_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Fullscreen_createClass(Constructor, protoProps, staticProps) { if (protoProps) Fullscreen_defineProperties(Constructor.prototype, protoProps); if (staticProps) Fullscreen_defineProperties(Constructor, staticProps); return Constructor; } var Fullscreen_Fullscreen = /*#__PURE__*/function () { function Fullscreen(context) { var _this = this; Fullscreen_classCallCheck(this, Fullscreen); this.context = context; this.$editor = context.layoutInfo.editor; this.$toolbar = context.layoutInfo.toolbar; this.$editable = context.layoutInfo.editable; this.$codable = context.layoutInfo.codable; this.$window = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(window); this.$scrollbar = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('html, body'); this.onResize = function () { _this.resizeTo({ h: _this.$window.height() - _this.$toolbar.outerHeight() }); }; } Fullscreen_createClass(Fullscreen, [{ key: "resizeTo", value: function resizeTo(size) { this.$editable.css('height', size.h); this.$codable.css('height', size.h); if (this.$codable.data('cmeditor')) { this.$codable.data('cmeditor').setsize(null, size.h); } } /** * toggle fullscreen */ }, { key: "toggle", value: function toggle() { this.$editor.toggleClass('fullscreen'); if (this.isFullscreen()) { this.$editable.data('orgHeight', this.$editable.css('height')); this.$editable.data('orgMaxHeight', this.$editable.css('maxHeight')); this.$editable.css('maxHeight', ''); this.$window.on('resize', this.onResize).trigger('resize'); this.$scrollbar.css('overflow', 'hidden'); } else { this.$window.off('resize', this.onResize); this.resizeTo({ h: this.$editable.data('orgHeight') }); this.$editable.css('maxHeight', this.$editable.css('orgMaxHeight')); this.$scrollbar.css('overflow', 'visible'); } this.context.invoke('toolbar.updateFullscreen', this.isFullscreen()); } }, { key: "isFullscreen", value: function isFullscreen() { return this.$editor.hasClass('fullscreen'); } }]); return Fullscreen; }(); // CONCATENATED MODULE: ./src/js/base/module/Handle.js function Handle_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Handle_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Handle_createClass(Constructor, protoProps, staticProps) { if (protoProps) Handle_defineProperties(Constructor.prototype, protoProps); if (staticProps) Handle_defineProperties(Constructor, staticProps); return Constructor; } var Handle_Handle = /*#__PURE__*/function () { function Handle(context) { var _this = this; Handle_classCallCheck(this, Handle); this.context = context; this.$document = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); this.$editingArea = context.layoutInfo.editingArea; this.options = context.options; this.lang = this.options.langInfo; this.events = { 'summernote.mousedown': function summernoteMousedown(we, e) { if (_this.update(e.target, e)) { e.preventDefault(); } }, 'summernote.keyup summernote.scroll summernote.change summernote.dialog.shown': function summernoteKeyupSummernoteScrollSummernoteChangeSummernoteDialogShown() { _this.update(); }, 'summernote.disable summernote.blur': function summernoteDisableSummernoteBlur() { _this.hide(); }, 'summernote.codeview.toggled': function summernoteCodeviewToggled() { _this.update(); } }; } Handle_createClass(Handle, [{ key: "initialize", value: function initialize() { var _this2 = this; this.$handle = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(['
    ', '
    ', '
    ', '
    ', '
    ', '
    ', '
    ', this.options.disableResizeImage ? '' : '
    ', '
    ', '
    '].join('')).prependTo(this.$editingArea); this.$handle.on('mousedown', function (event) { if (dom.isControlSizing(event.target)) { event.preventDefault(); event.stopPropagation(); var $target = _this2.$handle.find('.note-control-selection').data('target'); var posStart = $target.offset(); var scrollTop = _this2.$document.scrollTop(); var onMouseMove = function onMouseMove(event) { _this2.context.invoke('editor.resizeTo', { x: event.clientX - posStart.left, y: event.clientY - (posStart.top - scrollTop) }, $target, !event.shiftKey); _this2.update($target[0], event); }; _this2.$document.on('mousemove', onMouseMove).one('mouseup', function (e) { e.preventDefault(); _this2.$document.off('mousemove', onMouseMove); _this2.context.invoke('editor.afterCommand'); }); if (!$target.data('ratio')) { // original ratio. $target.data('ratio', $target.height() / $target.width()); } } }); // Listen for scrolling on the handle overlay. this.$handle.on('wheel', function (e) { e.preventDefault(); _this2.update(); }); } }, { key: "destroy", value: function destroy() { this.$handle.remove(); } }, { key: "update", value: function update(target, event) { if (this.context.isDisabled()) { return false; } var isImage = dom.isImg(target); var $selection = this.$handle.find('.note-control-selection'); this.context.invoke('imagePopover.update', target, event); if (isImage) { var $image = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(target); var position = $image.position(); var pos = { left: position.left + parseInt($image.css('marginLeft'), 10), top: position.top + parseInt($image.css('marginTop'), 10) }; // exclude margin var imageSize = { w: $image.outerWidth(false), h: $image.outerHeight(false) }; $selection.css({ display: 'block', left: pos.left, top: pos.top, width: imageSize.w, height: imageSize.h }).data('target', $image); // save current image element. var origImageObj = new Image(); origImageObj.src = $image.attr('src'); var sizingText = imageSize.w + 'x' + imageSize.h + ' (' + this.lang.image.original + ': ' + origImageObj.width + 'x' + origImageObj.height + ')'; $selection.find('.note-control-selection-info').text(sizingText); this.context.invoke('editor.saveTarget', target); } else { this.hide(); } return isImage; } /** * hide * * @param {jQuery} $handle */ }, { key: "hide", value: function hide() { this.context.invoke('editor.clearTarget'); this.$handle.children().hide(); } }]); return Handle; }(); // CONCATENATED MODULE: ./src/js/base/module/AutoLink.js function AutoLink_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function AutoLink_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function AutoLink_createClass(Constructor, protoProps, staticProps) { if (protoProps) AutoLink_defineProperties(Constructor.prototype, protoProps); if (staticProps) AutoLink_defineProperties(Constructor, staticProps); return Constructor; } var defaultScheme = 'http://'; var linkPattern = /^([A-Za-z][A-Za-z0-9+-.]*\:[\/]{2}|tel:|mailto:[A-Z0-9._%+-]+@)?(www\.)?(.+)$/i; var AutoLink_AutoLink = /*#__PURE__*/function () { function AutoLink(context) { var _this = this; AutoLink_classCallCheck(this, AutoLink); this.context = context; this.options = context.options; this.events = { 'summernote.keyup': function summernoteKeyup(we, e) { if (!e.isDefaultPrevented()) { _this.handleKeyup(e); } }, 'summernote.keydown': function summernoteKeydown(we, e) { _this.handleKeydown(e); } }; } AutoLink_createClass(AutoLink, [{ key: "initialize", value: function initialize() { this.lastWordRange = null; } }, { key: "destroy", value: function destroy() { this.lastWordRange = null; } }, { key: "replace", value: function replace() { if (!this.lastWordRange) { return; } var keyword = this.lastWordRange.toString(); var match = keyword.match(linkPattern); if (match && (match[1] || match[2])) { var link = match[1] ? keyword : defaultScheme + keyword; var urlText = this.options.showDomainOnlyForAutolink ? keyword.replace(/^(?:https?:\/\/)?(?:tel?:?)?(?:mailto?:?)?(?:www\.)?/i, '').split('/')[0] : keyword; var node = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('').html(urlText).attr('href', link)[0]; if (this.context.options.linkTargetBlank) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(node).attr('target', '_blank'); } this.lastWordRange.insertNode(node); this.lastWordRange = null; this.context.invoke('editor.focus'); } } }, { key: "handleKeydown", value: function handleKeydown(e) { if (lists.contains([core_key.code.ENTER, core_key.code.SPACE], e.keyCode)) { var wordRange = this.context.invoke('editor.createRange').getWordRange(); this.lastWordRange = wordRange; } } }, { key: "handleKeyup", value: function handleKeyup(e) { if (lists.contains([core_key.code.ENTER, core_key.code.SPACE], e.keyCode)) { this.replace(); } } }]); return AutoLink; }(); // CONCATENATED MODULE: ./src/js/base/module/AutoSync.js function AutoSync_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function AutoSync_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function AutoSync_createClass(Constructor, protoProps, staticProps) { if (protoProps) AutoSync_defineProperties(Constructor.prototype, protoProps); if (staticProps) AutoSync_defineProperties(Constructor, staticProps); return Constructor; } /** * textarea auto sync. */ var AutoSync_AutoSync = /*#__PURE__*/function () { function AutoSync(context) { var _this = this; AutoSync_classCallCheck(this, AutoSync); this.$note = context.layoutInfo.note; this.events = { 'summernote.change': function summernoteChange() { _this.$note.val(context.invoke('code')); } }; } AutoSync_createClass(AutoSync, [{ key: "shouldInitialize", value: function shouldInitialize() { return dom.isTextarea(this.$note[0]); } }]); return AutoSync; }(); // CONCATENATED MODULE: ./src/js/base/module/AutoReplace.js function AutoReplace_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function AutoReplace_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function AutoReplace_createClass(Constructor, protoProps, staticProps) { if (protoProps) AutoReplace_defineProperties(Constructor.prototype, protoProps); if (staticProps) AutoReplace_defineProperties(Constructor, staticProps); return Constructor; } var AutoReplace_AutoReplace = /*#__PURE__*/function () { function AutoReplace(context) { var _this = this; AutoReplace_classCallCheck(this, AutoReplace); this.context = context; this.options = context.options.replace || {}; this.keys = [core_key.code.ENTER, core_key.code.SPACE, core_key.code.PERIOD, core_key.code.COMMA, core_key.code.SEMICOLON, core_key.code.SLASH]; this.previousKeydownCode = null; this.events = { 'summernote.keyup': function summernoteKeyup(we, e) { if (!e.isDefaultPrevented()) { _this.handleKeyup(e); } }, 'summernote.keydown': function summernoteKeydown(we, e) { _this.handleKeydown(e); } }; } AutoReplace_createClass(AutoReplace, [{ key: "shouldInitialize", value: function shouldInitialize() { return !!this.options.match; } }, { key: "initialize", value: function initialize() { this.lastWord = null; } }, { key: "destroy", value: function destroy() { this.lastWord = null; } }, { key: "replace", value: function replace() { if (!this.lastWord) { return; } var self = this; var keyword = this.lastWord.toString(); this.options.match(keyword, function (match) { if (match) { var node = ''; if (typeof match === 'string') { node = dom.createText(match); } else if (match instanceof jQuery) { node = match[0]; } else if (match instanceof Node) { node = match; } if (!node) return; self.lastWord.insertNode(node); self.lastWord = null; self.context.invoke('editor.focus'); } }); } }, { key: "handleKeydown", value: function handleKeydown(e) { // this forces it to remember the last whole word, even if multiple termination keys are pressed // before the previous key is let go. if (this.previousKeydownCode && lists.contains(this.keys, this.previousKeydownCode)) { this.previousKeydownCode = e.keyCode; return; } if (lists.contains(this.keys, e.keyCode)) { var wordRange = this.context.invoke('editor.createRange').getWordRange(); this.lastWord = wordRange; } this.previousKeydownCode = e.keyCode; } }, { key: "handleKeyup", value: function handleKeyup(e) { if (lists.contains(this.keys, e.keyCode)) { this.replace(); } } }]); return AutoReplace; }(); // CONCATENATED MODULE: ./src/js/base/module/Placeholder.js function Placeholder_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Placeholder_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Placeholder_createClass(Constructor, protoProps, staticProps) { if (protoProps) Placeholder_defineProperties(Constructor.prototype, protoProps); if (staticProps) Placeholder_defineProperties(Constructor, staticProps); return Constructor; } var Placeholder_Placeholder = /*#__PURE__*/function () { function Placeholder(context) { var _this = this; Placeholder_classCallCheck(this, Placeholder); this.context = context; this.$editingArea = context.layoutInfo.editingArea; this.options = context.options; if (this.options.inheritPlaceholder === true) { // get placeholder value from the original element this.options.placeholder = this.context.$note.attr('placeholder') || this.options.placeholder; } this.events = { 'summernote.init summernote.change': function summernoteInitSummernoteChange() { _this.update(); }, 'summernote.codeview.toggled': function summernoteCodeviewToggled() { _this.update(); } }; } Placeholder_createClass(Placeholder, [{ key: "shouldInitialize", value: function shouldInitialize() { return !!this.options.placeholder; } }, { key: "initialize", value: function initialize() { var _this2 = this; this.$placeholder = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
    '); this.$placeholder.on('click', function () { _this2.context.invoke('focus'); }).html(this.options.placeholder).prependTo(this.$editingArea); this.update(); } }, { key: "destroy", value: function destroy() { this.$placeholder.remove(); } }, { key: "update", value: function update() { var isShow = !this.context.invoke('codeview.isActivated') && this.context.invoke('editor.isEmpty'); this.$placeholder.toggle(isShow); } }]); return Placeholder; }(); // CONCATENATED MODULE: ./src/js/base/module/Buttons.js function Buttons_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Buttons_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Buttons_createClass(Constructor, protoProps, staticProps) { if (protoProps) Buttons_defineProperties(Constructor.prototype, protoProps); if (staticProps) Buttons_defineProperties(Constructor, staticProps); return Constructor; } var Buttons_Buttons = /*#__PURE__*/function () { function Buttons(context) { Buttons_classCallCheck(this, Buttons); this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.context = context; this.$toolbar = context.layoutInfo.toolbar; this.options = context.options; this.lang = this.options.langInfo; this.invertedKeyMap = func.invertObject(this.options.keyMap[env.isMac ? 'mac' : 'pc']); } Buttons_createClass(Buttons, [{ key: "representShortcut", value: function representShortcut(editorMethod) { var shortcut = this.invertedKeyMap[editorMethod]; if (!this.options.shortcuts || !shortcut) { return ''; } if (env.isMac) { shortcut = shortcut.replace('CMD', '⌘').replace('SHIFT', '⇧'); } shortcut = shortcut.replace('BACKSLASH', '\\').replace('SLASH', '/').replace('LEFTBRACKET', '[').replace('RIGHTBRACKET', ']'); return ' (' + shortcut + ')'; } }, { key: "button", value: function button(o) { if (!this.options.tooltip && o.tooltip) { delete o.tooltip; } o.container = this.options.container; return this.ui.button(o); } }, { key: "initialize", value: function initialize() { this.addToolbarButtons(); this.addImagePopoverButtons(); this.addLinkPopoverButtons(); this.addTablePopoverButtons(); this.fontInstalledMap = {}; } }, { key: "destroy", value: function destroy() { delete this.fontInstalledMap; } }, { key: "isFontInstalled", value: function isFontInstalled(name) { if (!Object.prototype.hasOwnProperty.call(this.fontInstalledMap, name)) { this.fontInstalledMap[name] = env.isFontInstalled(name) || lists.contains(this.options.fontNamesIgnoreCheck, name); } return this.fontInstalledMap[name]; } }, { key: "isFontDeservedToAdd", value: function isFontDeservedToAdd(name) { name = name.toLowerCase(); return name !== '' && this.isFontInstalled(name) && env.genericFontFamilies.indexOf(name) === -1; } }, { key: "colorPalette", value: function colorPalette(className, tooltip, backColor, foreColor) { var _this = this; return this.ui.buttonGroup({ className: 'note-color ' + className, children: [this.button({ className: 'note-current-color-button', contents: this.ui.icon(this.options.icons.font + ' note-recent-color'), tooltip: tooltip, click: function click(e) { var $button = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(e.currentTarget); if (backColor && foreColor) { _this.context.invoke('editor.color', { backColor: $button.attr('data-backColor'), foreColor: $button.attr('data-foreColor') }); } else if (backColor) { _this.context.invoke('editor.color', { backColor: $button.attr('data-backColor') }); } else if (foreColor) { _this.context.invoke('editor.color', { foreColor: $button.attr('data-foreColor') }); } }, callback: function callback($button) { var $recentColor = $button.find('.note-recent-color'); if (backColor) { $recentColor.css('background-color', _this.options.colorButton.backColor); $button.attr('data-backColor', _this.options.colorButton.backColor); } if (foreColor) { $recentColor.css('color', _this.options.colorButton.foreColor); $button.attr('data-foreColor', _this.options.colorButton.foreColor); } else { $recentColor.css('color', 'transparent'); } } }), this.button({ className: 'dropdown-toggle', contents: this.ui.dropdownButtonContents('', this.options), tooltip: this.lang.color.more, data: { toggle: 'dropdown' } }), this.ui.dropdown({ items: (backColor ? ['
    ', '
    ' + this.lang.color.background + '
    ', '
    ', '', '
    ', '
    ', '
    ', '', '', '
    ', '
    ', '
    '].join('') : '') + (foreColor ? ['
    ', '
    ' + this.lang.color.foreground + '
    ', '
    ', '', '
    ', '
    ', '
    ', '', '', '
    ', // Fix missing Div, Commented to find easily if it's wrong '
    ', '
    '].join('') : ''), callback: function callback($dropdown) { $dropdown.find('.note-holder').each(function (idx, item) { var $holder = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); $holder.append(_this.ui.palette({ colors: _this.options.colors, colorsName: _this.options.colorsName, eventName: $holder.data('event'), container: _this.options.container, tooltip: _this.options.tooltip }).render()); }); /* TODO: do we have to record recent custom colors within cookies? */ var customColors = [['#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF', '#FFFFFF']]; $dropdown.find('.note-holder-custom').each(function (idx, item) { var $holder = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); $holder.append(_this.ui.palette({ colors: customColors, colorsName: customColors, eventName: $holder.data('event'), container: _this.options.container, tooltip: _this.options.tooltip }).render()); }); $dropdown.find('input[type=color]').each(function (idx, item) { external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item).change(function () { var $chip = $dropdown.find('#' + external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this).data('event')).find('.note-color-btn').first(); var color = this.value.toUpperCase(); $chip.css('background-color', color).attr('aria-label', color).attr('data-value', color).attr('data-original-title', color); $chip.click(); }); }); }, click: function click(event) { event.stopPropagation(); var $parent = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('.' + className).find('.note-dropdown-menu'); var $button = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(event.target); var eventName = $button.data('event'); var value = $button.attr('data-value'); if (eventName === 'openPalette') { var $picker = $parent.find('#' + value); var $palette = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()($parent.find('#' + $picker.data('event')).find('.note-color-row')[0]); // Shift palette chips var $chip = $palette.find('.note-color-btn').last().detach(); // Set chip attributes var color = $picker.val(); $chip.css('background-color', color).attr('aria-label', color).attr('data-value', color).attr('data-original-title', color); $palette.prepend($chip); $picker.click(); } else { if (lists.contains(['backColor', 'foreColor'], eventName)) { var key = eventName === 'backColor' ? 'background-color' : 'color'; var $color = $button.closest('.note-color').find('.note-recent-color'); var $currentButton = $button.closest('.note-color').find('.note-current-color-button'); $color.css(key, value); $currentButton.attr('data-' + eventName, value); } _this.context.invoke('editor.' + eventName, value); } } })] }).render(); } }, { key: "addToolbarButtons", value: function addToolbarButtons() { var _this2 = this; this.context.memo('button.style', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents(_this2.ui.icon(_this2.options.icons.magic), _this2.options), tooltip: _this2.lang.style.style, data: { toggle: 'dropdown' } }), _this2.ui.dropdown({ className: 'dropdown-style', items: _this2.options.styleTags, title: _this2.lang.style.style, template: function template(item) { // TBD: need to be simplified if (typeof item === 'string') { item = { tag: item, title: Object.prototype.hasOwnProperty.call(_this2.lang.style, item) ? _this2.lang.style[item] : item }; } var tag = item.tag; var title = item.title; var style = item.style ? ' style="' + item.style + '" ' : ''; var className = item.className ? ' class="' + item.className + '"' : ''; return '<' + tag + style + className + '>' + title + ''; }, click: _this2.context.createInvokeHandler('editor.formatBlock') })]).render(); }); var _loop = function _loop(styleIdx, styleLen) { var item = _this2.options.styleTags[styleIdx]; _this2.context.memo('button.style.' + item, function () { return _this2.button({ className: 'note-btn-style-' + item, contents: '
    ' + item.toUpperCase() + '
    ', tooltip: _this2.lang.style[item], click: _this2.context.createInvokeHandler('editor.formatBlock') }).render(); }); }; for (var styleIdx = 0, styleLen = this.options.styleTags.length; styleIdx < styleLen; styleIdx++) { _loop(styleIdx, styleLen); } this.context.memo('button.bold', function () { return _this2.button({ className: 'note-btn-bold', contents: _this2.ui.icon(_this2.options.icons.bold), tooltip: _this2.lang.font.bold + _this2.representShortcut('bold'), click: _this2.context.createInvokeHandlerAndUpdateState('editor.bold') }).render(); }); this.context.memo('button.italic', function () { return _this2.button({ className: 'note-btn-italic', contents: _this2.ui.icon(_this2.options.icons.italic), tooltip: _this2.lang.font.italic + _this2.representShortcut('italic'), click: _this2.context.createInvokeHandlerAndUpdateState('editor.italic') }).render(); }); this.context.memo('button.underline', function () { return _this2.button({ className: 'note-btn-underline', contents: _this2.ui.icon(_this2.options.icons.underline), tooltip: _this2.lang.font.underline + _this2.representShortcut('underline'), click: _this2.context.createInvokeHandlerAndUpdateState('editor.underline') }).render(); }); this.context.memo('button.clear', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.eraser), tooltip: _this2.lang.font.clear + _this2.representShortcut('removeFormat'), click: _this2.context.createInvokeHandler('editor.removeFormat') }).render(); }); this.context.memo('button.strikethrough', function () { return _this2.button({ className: 'note-btn-strikethrough', contents: _this2.ui.icon(_this2.options.icons.strikethrough), tooltip: _this2.lang.font.strikethrough + _this2.representShortcut('strikethrough'), click: _this2.context.createInvokeHandlerAndUpdateState('editor.strikethrough') }).render(); }); this.context.memo('button.superscript', function () { return _this2.button({ className: 'note-btn-superscript', contents: _this2.ui.icon(_this2.options.icons.superscript), tooltip: _this2.lang.font.superscript, click: _this2.context.createInvokeHandlerAndUpdateState('editor.superscript') }).render(); }); this.context.memo('button.subscript', function () { return _this2.button({ className: 'note-btn-subscript', contents: _this2.ui.icon(_this2.options.icons.subscript), tooltip: _this2.lang.font.subscript, click: _this2.context.createInvokeHandlerAndUpdateState('editor.subscript') }).render(); }); this.context.memo('button.fontname', function () { var styleInfo = _this2.context.invoke('editor.currentStyle'); if (_this2.options.addDefaultFonts) { // Add 'default' fonts into the fontnames array if not exist external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(styleInfo['font-family'].split(','), function (idx, fontname) { fontname = fontname.trim().replace(/['"]+/g, ''); if (_this2.isFontDeservedToAdd(fontname)) { if (_this2.options.fontNames.indexOf(fontname) === -1) { _this2.options.fontNames.push(fontname); } } }); } return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents('', _this2.options), tooltip: _this2.lang.font.name, data: { toggle: 'dropdown' } }), _this2.ui.dropdownCheck({ className: 'dropdown-fontname', checkClassName: _this2.options.icons.menuCheck, items: _this2.options.fontNames.filter(_this2.isFontInstalled.bind(_this2)), title: _this2.lang.font.name, template: function template(item) { return '' + item + ''; }, click: _this2.context.createInvokeHandlerAndUpdateState('editor.fontName') })]).render(); }); this.context.memo('button.fontsize', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents('', _this2.options), tooltip: _this2.lang.font.size, data: { toggle: 'dropdown' } }), _this2.ui.dropdownCheck({ className: 'dropdown-fontsize', checkClassName: _this2.options.icons.menuCheck, items: _this2.options.fontSizes, title: _this2.lang.font.size, click: _this2.context.createInvokeHandlerAndUpdateState('editor.fontSize') })]).render(); }); this.context.memo('button.fontsizeunit', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents('', _this2.options), tooltip: _this2.lang.font.sizeunit, data: { toggle: 'dropdown' } }), _this2.ui.dropdownCheck({ className: 'dropdown-fontsizeunit', checkClassName: _this2.options.icons.menuCheck, items: _this2.options.fontSizeUnits, title: _this2.lang.font.sizeunit, click: _this2.context.createInvokeHandlerAndUpdateState('editor.fontSizeUnit') })]).render(); }); this.context.memo('button.color', function () { return _this2.colorPalette('note-color-all', _this2.lang.color.recent, true, true); }); this.context.memo('button.forecolor', function () { return _this2.colorPalette('note-color-fore', _this2.lang.color.foreground, false, true); }); this.context.memo('button.backcolor', function () { return _this2.colorPalette('note-color-back', _this2.lang.color.background, true, false); }); this.context.memo('button.ul', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.unorderedlist), tooltip: _this2.lang.lists.unordered + _this2.representShortcut('insertUnorderedList'), click: _this2.context.createInvokeHandler('editor.insertUnorderedList') }).render(); }); this.context.memo('button.ol', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.orderedlist), tooltip: _this2.lang.lists.ordered + _this2.representShortcut('insertOrderedList'), click: _this2.context.createInvokeHandler('editor.insertOrderedList') }).render(); }); var justifyLeft = this.button({ contents: this.ui.icon(this.options.icons.alignLeft), tooltip: this.lang.paragraph.left + this.representShortcut('justifyLeft'), click: this.context.createInvokeHandler('editor.justifyLeft') }); var justifyCenter = this.button({ contents: this.ui.icon(this.options.icons.alignCenter), tooltip: this.lang.paragraph.center + this.representShortcut('justifyCenter'), click: this.context.createInvokeHandler('editor.justifyCenter') }); var justifyRight = this.button({ contents: this.ui.icon(this.options.icons.alignRight), tooltip: this.lang.paragraph.right + this.representShortcut('justifyRight'), click: this.context.createInvokeHandler('editor.justifyRight') }); var justifyFull = this.button({ contents: this.ui.icon(this.options.icons.alignJustify), tooltip: this.lang.paragraph.justify + this.representShortcut('justifyFull'), click: this.context.createInvokeHandler('editor.justifyFull') }); var outdent = this.button({ contents: this.ui.icon(this.options.icons.outdent), tooltip: this.lang.paragraph.outdent + this.representShortcut('outdent'), click: this.context.createInvokeHandler('editor.outdent') }); var indent = this.button({ contents: this.ui.icon(this.options.icons.indent), tooltip: this.lang.paragraph.indent + this.representShortcut('indent'), click: this.context.createInvokeHandler('editor.indent') }); this.context.memo('button.justifyLeft', func.invoke(justifyLeft, 'render')); this.context.memo('button.justifyCenter', func.invoke(justifyCenter, 'render')); this.context.memo('button.justifyRight', func.invoke(justifyRight, 'render')); this.context.memo('button.justifyFull', func.invoke(justifyFull, 'render')); this.context.memo('button.outdent', func.invoke(outdent, 'render')); this.context.memo('button.indent', func.invoke(indent, 'render')); this.context.memo('button.paragraph', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents(_this2.ui.icon(_this2.options.icons.alignLeft), _this2.options), tooltip: _this2.lang.paragraph.paragraph, data: { toggle: 'dropdown' } }), _this2.ui.dropdown([_this2.ui.buttonGroup({ className: 'note-align', children: [justifyLeft, justifyCenter, justifyRight, justifyFull] }), _this2.ui.buttonGroup({ className: 'note-list', children: [outdent, indent] })])]).render(); }); this.context.memo('button.height', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents(_this2.ui.icon(_this2.options.icons.textHeight), _this2.options), tooltip: _this2.lang.font.height, data: { toggle: 'dropdown' } }), _this2.ui.dropdownCheck({ items: _this2.options.lineHeights, checkClassName: _this2.options.icons.menuCheck, className: 'dropdown-line-height', title: _this2.lang.font.height, click: _this2.context.createInvokeHandler('editor.lineHeight') })]).render(); }); this.context.memo('button.table', function () { return _this2.ui.buttonGroup([_this2.button({ className: 'dropdown-toggle', contents: _this2.ui.dropdownButtonContents(_this2.ui.icon(_this2.options.icons.table), _this2.options), tooltip: _this2.lang.table.table, data: { toggle: 'dropdown' } }), _this2.ui.dropdown({ title: _this2.lang.table.table, className: 'note-table', items: ['
    ', '
    ', '
    ', '
    ', '
    ', '
    1 x 1
    '].join('') })], { callback: function callback($node) { var $catcher = $node.find('.note-dimension-picker-mousecatcher'); $catcher.css({ width: _this2.options.insertTableMaxSize.col + 'em', height: _this2.options.insertTableMaxSize.row + 'em' }).mousedown(_this2.context.createInvokeHandler('editor.insertTable')).on('mousemove', _this2.tableMoveHandler.bind(_this2)); } }).render(); }); this.context.memo('button.link', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.link), tooltip: _this2.lang.link.link + _this2.representShortcut('linkDialog.show'), click: _this2.context.createInvokeHandler('linkDialog.show') }).render(); }); this.context.memo('button.picture', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.picture), tooltip: _this2.lang.image.image, click: _this2.context.createInvokeHandler('imageDialog.show') }).render(); }); this.context.memo('button.video', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.video), tooltip: _this2.lang.video.video, click: _this2.context.createInvokeHandler('videoDialog.show') }).render(); }); this.context.memo('button.hr', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.minus), tooltip: _this2.lang.hr.insert + _this2.representShortcut('insertHorizontalRule'), click: _this2.context.createInvokeHandler('editor.insertHorizontalRule') }).render(); }); this.context.memo('button.fullscreen', function () { return _this2.button({ className: 'btn-fullscreen note-codeview-keep', contents: _this2.ui.icon(_this2.options.icons.arrowsAlt), tooltip: _this2.lang.options.fullscreen, click: _this2.context.createInvokeHandler('fullscreen.toggle') }).render(); }); this.context.memo('button.codeview', function () { return _this2.button({ className: 'btn-codeview note-codeview-keep', contents: _this2.ui.icon(_this2.options.icons.code), tooltip: _this2.lang.options.codeview, click: _this2.context.createInvokeHandler('codeview.toggle') }).render(); }); this.context.memo('button.redo', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.redo), tooltip: _this2.lang.history.redo + _this2.representShortcut('redo'), click: _this2.context.createInvokeHandler('editor.redo') }).render(); }); this.context.memo('button.undo', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.undo), tooltip: _this2.lang.history.undo + _this2.representShortcut('undo'), click: _this2.context.createInvokeHandler('editor.undo') }).render(); }); this.context.memo('button.help', function () { return _this2.button({ contents: _this2.ui.icon(_this2.options.icons.question), tooltip: _this2.lang.options.help, click: _this2.context.createInvokeHandler('helpDialog.show') }).render(); }); } /** * image: [ * ['imageResize', ['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone']], * ['float', ['floatLeft', 'floatRight', 'floatNone']], * ['remove', ['removeMedia']], * ], */ }, { key: "addImagePopoverButtons", value: function addImagePopoverButtons() { var _this3 = this; // Image Size Buttons this.context.memo('button.resizeFull', function () { return _this3.button({ contents: '100%', tooltip: _this3.lang.image.resizeFull, click: _this3.context.createInvokeHandler('editor.resize', '1') }).render(); }); this.context.memo('button.resizeHalf', function () { return _this3.button({ contents: '50%', tooltip: _this3.lang.image.resizeHalf, click: _this3.context.createInvokeHandler('editor.resize', '0.5') }).render(); }); this.context.memo('button.resizeQuarter', function () { return _this3.button({ contents: '25%', tooltip: _this3.lang.image.resizeQuarter, click: _this3.context.createInvokeHandler('editor.resize', '0.25') }).render(); }); this.context.memo('button.resizeNone', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.rollback), tooltip: _this3.lang.image.resizeNone, click: _this3.context.createInvokeHandler('editor.resize', '0') }).render(); }); // Float Buttons this.context.memo('button.floatLeft', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.floatLeft), tooltip: _this3.lang.image.floatLeft, click: _this3.context.createInvokeHandler('editor.floatMe', 'left') }).render(); }); this.context.memo('button.floatRight', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.floatRight), tooltip: _this3.lang.image.floatRight, click: _this3.context.createInvokeHandler('editor.floatMe', 'right') }).render(); }); this.context.memo('button.floatNone', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.rollback), tooltip: _this3.lang.image.floatNone, click: _this3.context.createInvokeHandler('editor.floatMe', 'none') }).render(); }); // Remove Buttons this.context.memo('button.removeMedia', function () { return _this3.button({ contents: _this3.ui.icon(_this3.options.icons.trash), tooltip: _this3.lang.image.remove, click: _this3.context.createInvokeHandler('editor.removeMedia') }).render(); }); } }, { key: "addLinkPopoverButtons", value: function addLinkPopoverButtons() { var _this4 = this; this.context.memo('button.linkDialogShow', function () { return _this4.button({ contents: _this4.ui.icon(_this4.options.icons.link), tooltip: _this4.lang.link.edit, click: _this4.context.createInvokeHandler('linkDialog.show') }).render(); }); this.context.memo('button.unlink', function () { return _this4.button({ contents: _this4.ui.icon(_this4.options.icons.unlink), tooltip: _this4.lang.link.unlink, click: _this4.context.createInvokeHandler('editor.unlink') }).render(); }); } /** * table : [ * ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']], * ['delete', ['deleteRow', 'deleteCol', 'deleteTable']] * ], */ }, { key: "addTablePopoverButtons", value: function addTablePopoverButtons() { var _this5 = this; this.context.memo('button.addRowUp', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.rowAbove), tooltip: _this5.lang.table.addRowAbove, click: _this5.context.createInvokeHandler('editor.addRow', 'top') }).render(); }); this.context.memo('button.addRowDown', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.rowBelow), tooltip: _this5.lang.table.addRowBelow, click: _this5.context.createInvokeHandler('editor.addRow', 'bottom') }).render(); }); this.context.memo('button.addColLeft', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.colBefore), tooltip: _this5.lang.table.addColLeft, click: _this5.context.createInvokeHandler('editor.addCol', 'left') }).render(); }); this.context.memo('button.addColRight', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.colAfter), tooltip: _this5.lang.table.addColRight, click: _this5.context.createInvokeHandler('editor.addCol', 'right') }).render(); }); this.context.memo('button.deleteRow', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.rowRemove), tooltip: _this5.lang.table.delRow, click: _this5.context.createInvokeHandler('editor.deleteRow') }).render(); }); this.context.memo('button.deleteCol', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.colRemove), tooltip: _this5.lang.table.delCol, click: _this5.context.createInvokeHandler('editor.deleteCol') }).render(); }); this.context.memo('button.deleteTable', function () { return _this5.button({ className: 'btn-md', contents: _this5.ui.icon(_this5.options.icons.trash), tooltip: _this5.lang.table.delTable, click: _this5.context.createInvokeHandler('editor.deleteTable') }).render(); }); } }, { key: "build", value: function build($container, groups) { for (var groupIdx = 0, groupLen = groups.length; groupIdx < groupLen; groupIdx++) { var group = groups[groupIdx]; var groupName = Array.isArray(group) ? group[0] : group; var buttons = Array.isArray(group) ? group.length === 1 ? [group[0]] : group[1] : [group]; var $group = this.ui.buttonGroup({ className: 'note-' + groupName }).render(); for (var idx = 0, len = buttons.length; idx < len; idx++) { var btn = this.context.memo('button.' + buttons[idx]); if (btn) { $group.append(typeof btn === 'function' ? btn(this.context) : btn); } } $group.appendTo($container); } } /** * @param {jQuery} [$container] */ }, { key: "updateCurrentStyle", value: function updateCurrentStyle($container) { var _this6 = this; var $cont = $container || this.$toolbar; var styleInfo = this.context.invoke('editor.currentStyle'); this.updateBtnStates($cont, { '.note-btn-bold': function noteBtnBold() { return styleInfo['font-bold'] === 'bold'; }, '.note-btn-italic': function noteBtnItalic() { return styleInfo['font-italic'] === 'italic'; }, '.note-btn-underline': function noteBtnUnderline() { return styleInfo['font-underline'] === 'underline'; }, '.note-btn-subscript': function noteBtnSubscript() { return styleInfo['font-subscript'] === 'subscript'; }, '.note-btn-superscript': function noteBtnSuperscript() { return styleInfo['font-superscript'] === 'superscript'; }, '.note-btn-strikethrough': function noteBtnStrikethrough() { return styleInfo['font-strikethrough'] === 'strikethrough'; } }); if (styleInfo['font-family']) { var fontNames = styleInfo['font-family'].split(',').map(function (name) { return name.replace(/[\'\"]/g, '').replace(/\s+$/, '').replace(/^\s+/, ''); }); var fontName = lists.find(fontNames, this.isFontInstalled.bind(this)); $cont.find('.dropdown-fontname a').each(function (idx, item) { var $item = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); // always compare string to avoid creating another func. var isChecked = $item.data('value') + '' === fontName + ''; $item.toggleClass('checked', isChecked); }); $cont.find('.note-current-fontname').text(fontName).css('font-family', fontName); } if (styleInfo['font-size']) { var fontSize = styleInfo['font-size']; $cont.find('.dropdown-fontsize a').each(function (idx, item) { var $item = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); // always compare with string to avoid creating another func. var isChecked = $item.data('value') + '' === fontSize + ''; $item.toggleClass('checked', isChecked); }); $cont.find('.note-current-fontsize').text(fontSize); var fontSizeUnit = styleInfo['font-size-unit']; $cont.find('.dropdown-fontsizeunit a').each(function (idx, item) { var $item = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item); var isChecked = $item.data('value') + '' === fontSizeUnit + ''; $item.toggleClass('checked', isChecked); }); $cont.find('.note-current-fontsizeunit').text(fontSizeUnit); } if (styleInfo['line-height']) { var lineHeight = styleInfo['line-height']; $cont.find('.dropdown-line-height li a').each(function (idx, item) { // always compare with string to avoid creating another func. var isChecked = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(item).data('value') + '' === lineHeight + ''; _this6.className = isChecked ? 'checked' : ''; }); } } }, { key: "updateBtnStates", value: function updateBtnStates($container, infos) { var _this7 = this; external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.each(infos, function (selector, pred) { _this7.ui.toggleBtnActive($container.find(selector), pred()); }); } }, { key: "tableMoveHandler", value: function tableMoveHandler(event) { var PX_PER_EM = 18; var $picker = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(event.target.parentNode); // target is mousecatcher var $dimensionDisplay = $picker.next(); var $catcher = $picker.find('.note-dimension-picker-mousecatcher'); var $highlighted = $picker.find('.note-dimension-picker-highlighted'); var $unhighlighted = $picker.find('.note-dimension-picker-unhighlighted'); var posOffset; // HTML5 with jQuery - e.offsetX is undefined in Firefox if (event.offsetX === undefined) { var posCatcher = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(event.target).offset(); posOffset = { x: event.pageX - posCatcher.left, y: event.pageY - posCatcher.top }; } else { posOffset = { x: event.offsetX, y: event.offsetY }; } var dim = { c: Math.ceil(posOffset.x / PX_PER_EM) || 1, r: Math.ceil(posOffset.y / PX_PER_EM) || 1 }; $highlighted.css({ width: dim.c + 'em', height: dim.r + 'em' }); $catcher.data('value', dim.c + 'x' + dim.r); if (dim.c > 3 && dim.c < this.options.insertTableMaxSize.col) { $unhighlighted.css({ width: dim.c + 1 + 'em' }); } if (dim.r > 3 && dim.r < this.options.insertTableMaxSize.row) { $unhighlighted.css({ height: dim.r + 1 + 'em' }); } $dimensionDisplay.html(dim.c + ' x ' + dim.r); } }]); return Buttons; }(); // CONCATENATED MODULE: ./src/js/base/module/Toolbar.js function Toolbar_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function Toolbar_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function Toolbar_createClass(Constructor, protoProps, staticProps) { if (protoProps) Toolbar_defineProperties(Constructor.prototype, protoProps); if (staticProps) Toolbar_defineProperties(Constructor, staticProps); return Constructor; } var Toolbar_Toolbar = /*#__PURE__*/function () { function Toolbar(context) { Toolbar_classCallCheck(this, Toolbar); this.context = context; this.$window = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(window); this.$document = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document); this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.$note = context.layoutInfo.note; this.$editor = context.layoutInfo.editor; this.$toolbar = context.layoutInfo.toolbar; this.$editable = context.layoutInfo.editable; this.$statusbar = context.layoutInfo.statusbar; this.options = context.options; this.isFollowing = false; this.followScroll = this.followScroll.bind(this); } Toolbar_createClass(Toolbar, [{ key: "shouldInitialize", value: function shouldInitialize() { return !this.options.airMode; } }, { key: "initialize", value: function initialize() { var _this = this; this.options.toolbar = this.options.toolbar || []; if (!this.options.toolbar.length) { this.$toolbar.hide(); } else { this.context.invoke('buttons.build', this.$toolbar, this.options.toolbar); } if (this.options.toolbarContainer) { this.$toolbar.appendTo(this.options.toolbarContainer); } this.changeContainer(false); this.$note.on('summernote.keyup summernote.mouseup summernote.change', function () { _this.context.invoke('buttons.updateCurrentStyle'); }); this.context.invoke('buttons.updateCurrentStyle'); if (this.options.followingToolbar) { this.$window.on('scroll resize', this.followScroll); } } }, { key: "destroy", value: function destroy() { this.$toolbar.children().remove(); if (this.options.followingToolbar) { this.$window.off('scroll resize', this.followScroll); } } }, { key: "followScroll", value: function followScroll() { if (this.$editor.hasClass('fullscreen')) { return false; } var editorHeight = this.$editor.outerHeight(); var editorWidth = this.$editor.width(); var toolbarHeight = this.$toolbar.height(); var statusbarHeight = this.$statusbar.height(); // check if the web app is currently using another static bar var otherBarHeight = 0; if (this.options.otherStaticBar) { otherBarHeight = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.options.otherStaticBar).outerHeight(); } var currentOffset = this.$document.scrollTop(); var editorOffsetTop = this.$editor.offset().top; var editorOffsetBottom = editorOffsetTop + editorHeight; var activateOffset = editorOffsetTop - otherBarHeight; var deactivateOffsetBottom = editorOffsetBottom - otherBarHeight - toolbarHeight - statusbarHeight; if (!this.isFollowing && currentOffset > activateOffset && currentOffset < deactivateOffsetBottom - toolbarHeight) { this.isFollowing = true; this.$editable.css({ marginTop: this.$toolbar.outerHeight() }); this.$toolbar.css({ position: 'fixed', top: otherBarHeight, width: editorWidth, zIndex: 1000 }); } else if (this.isFollowing && (currentOffset < activateOffset || currentOffset > deactivateOffsetBottom)) { this.isFollowing = false; this.$toolbar.css({ position: 'relative', top: 0, width: '100%', zIndex: 'auto' }); this.$editable.css({ marginTop: '' }); } } }, { key: "changeContainer", value: function changeContainer(isFullscreen) { if (isFullscreen) { this.$toolbar.prependTo(this.$editor); } else { if (this.options.toolbarContainer) { this.$toolbar.appendTo(this.options.toolbarContainer); } } if (this.options.followingToolbar) { this.followScroll(); } } }, { key: "updateFullscreen", value: function updateFullscreen(isFullscreen) { this.ui.toggleBtnActive(this.$toolbar.find('.btn-fullscreen'), isFullscreen); this.changeContainer(isFullscreen); } }, { key: "updateCodeview", value: function updateCodeview(isCodeview) { this.ui.toggleBtnActive(this.$toolbar.find('.btn-codeview'), isCodeview); if (isCodeview) { this.deactivate(); } else { this.activate(); } } }, { key: "activate", value: function activate(isIncludeCodeview) { var $btn = this.$toolbar.find('button'); if (!isIncludeCodeview) { $btn = $btn.not('.note-codeview-keep'); } this.ui.toggleBtn($btn, true); } }, { key: "deactivate", value: function deactivate(isIncludeCodeview) { var $btn = this.$toolbar.find('button'); if (!isIncludeCodeview) { $btn = $btn.not('.note-codeview-keep'); } this.ui.toggleBtn($btn, false); } }]); return Toolbar; }(); // CONCATENATED MODULE: ./src/js/base/module/LinkDialog.js function LinkDialog_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function LinkDialog_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function LinkDialog_createClass(Constructor, protoProps, staticProps) { if (protoProps) LinkDialog_defineProperties(Constructor.prototype, protoProps); if (staticProps) LinkDialog_defineProperties(Constructor, staticProps); return Constructor; } var LinkDialog_LinkDialog = /*#__PURE__*/function () { function LinkDialog(context) { LinkDialog_classCallCheck(this, LinkDialog); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.$body = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document.body); this.$editor = context.layoutInfo.editor; this.options = context.options; this.lang = this.options.langInfo; context.memo('help.linkDialog.show', this.options.langInfo.help['linkDialog.show']); } LinkDialog_createClass(LinkDialog, [{ key: "initialize", value: function initialize() { var $container = this.options.dialogsInBody ? this.$body : this.options.container; var body = ['
    ', ""), ""), '
    ', '
    ', ""), ""), '
    ', !this.options.disableLinkTarget ? external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
    ').append(this.ui.checkbox({ className: 'sn-checkbox-open-in-new-window', text: this.lang.link.openInNewWindow, checked: true }).render()).html() : '', external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()('
    ').append(this.ui.checkbox({ className: 'sn-checkbox-use-protocol', text: this.lang.link.useProtocol, checked: true }).render()).html()].join(''); var buttonClass = 'btn btn-primary note-btn note-btn-primary note-link-btn'; var footer = ""); this.$dialog = this.ui.dialog({ className: 'link-dialog', title: this.lang.link.insert, fade: this.options.dialogsFade, body: body, footer: footer }).render().appendTo($container); } }, { key: "destroy", value: function destroy() { this.ui.hideDialog(this.$dialog); this.$dialog.remove(); } }, { key: "bindEnterKey", value: function bindEnterKey($input, $btn) { $input.on('keypress', function (event) { if (event.keyCode === core_key.code.ENTER) { event.preventDefault(); $btn.trigger('click'); } }); } /** * toggle update button */ }, { key: "toggleLinkBtn", value: function toggleLinkBtn($linkBtn, $linkText, $linkUrl) { this.ui.toggleBtn($linkBtn, $linkText.val() && $linkUrl.val()); } /** * Show link dialog and set event handlers on dialog controls. * * @param {Object} linkInfo * @return {Promise} */ }, { key: "showLinkDialog", value: function showLinkDialog(linkInfo) { var _this = this; return external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.Deferred(function (deferred) { var $linkText = _this.$dialog.find('.note-link-text'); var $linkUrl = _this.$dialog.find('.note-link-url'); var $linkBtn = _this.$dialog.find('.note-link-btn'); var $openInNewWindow = _this.$dialog.find('.sn-checkbox-open-in-new-window input[type=checkbox]'); var $useProtocol = _this.$dialog.find('.sn-checkbox-use-protocol input[type=checkbox]'); _this.ui.onDialogShown(_this.$dialog, function () { _this.context.triggerEvent('dialog.shown'); // If no url was given and given text is valid URL then copy that into URL Field if (!linkInfo.url && func.isValidUrl(linkInfo.text)) { linkInfo.url = linkInfo.text; } $linkText.on('input paste propertychange', function () { // If linktext was modified by input events, // cloning text from linkUrl will be stopped. linkInfo.text = $linkText.val(); _this.toggleLinkBtn($linkBtn, $linkText, $linkUrl); }).val(linkInfo.text); $linkUrl.on('input paste propertychange', function () { // Display same text on `Text to display` as default // when linktext has no text if (!linkInfo.text) { $linkText.val($linkUrl.val()); } _this.toggleLinkBtn($linkBtn, $linkText, $linkUrl); }).val(linkInfo.url); if (!env.isSupportTouch) { $linkUrl.trigger('focus'); } _this.toggleLinkBtn($linkBtn, $linkText, $linkUrl); _this.bindEnterKey($linkUrl, $linkBtn); _this.bindEnterKey($linkText, $linkBtn); var isNewWindowChecked = linkInfo.isNewWindow !== undefined ? linkInfo.isNewWindow : _this.context.options.linkTargetBlank; $openInNewWindow.prop('checked', isNewWindowChecked); var useProtocolChecked = linkInfo.url ? false : _this.context.options.useProtocol; $useProtocol.prop('checked', useProtocolChecked); $linkBtn.one('click', function (event) { event.preventDefault(); deferred.resolve({ range: linkInfo.range, url: $linkUrl.val(), text: $linkText.val(), isNewWindow: $openInNewWindow.is(':checked'), checkProtocol: $useProtocol.is(':checked') }); _this.ui.hideDialog(_this.$dialog); }); }); _this.ui.onDialogHidden(_this.$dialog, function () { // detach events $linkText.off(); $linkUrl.off(); $linkBtn.off(); if (deferred.state() === 'pending') { deferred.reject(); } }); _this.ui.showDialog(_this.$dialog); }).promise(); } /** * @param {Object} layoutInfo */ }, { key: "show", value: function show() { var _this2 = this; var linkInfo = this.context.invoke('editor.getLinkInfo'); this.context.invoke('editor.saveRange'); this.showLinkDialog(linkInfo).then(function (linkInfo) { _this2.context.invoke('editor.restoreRange'); _this2.context.invoke('editor.createLink', linkInfo); }).fail(function () { _this2.context.invoke('editor.restoreRange'); }); } }]); return LinkDialog; }(); // CONCATENATED MODULE: ./src/js/base/module/LinkPopover.js function LinkPopover_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function LinkPopover_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function LinkPopover_createClass(Constructor, protoProps, staticProps) { if (protoProps) LinkPopover_defineProperties(Constructor.prototype, protoProps); if (staticProps) LinkPopover_defineProperties(Constructor, staticProps); return Constructor; } var LinkPopover_LinkPopover = /*#__PURE__*/function () { function LinkPopover(context) { var _this = this; LinkPopover_classCallCheck(this, LinkPopover); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.options = context.options; this.events = { 'summernote.keyup summernote.mouseup summernote.change summernote.scroll': function summernoteKeyupSummernoteMouseupSummernoteChangeSummernoteScroll() { _this.update(); }, 'summernote.disable summernote.dialog.shown summernote.blur': function summernoteDisableSummernoteDialogShownSummernoteBlur() { _this.hide(); } }; } LinkPopover_createClass(LinkPopover, [{ key: "shouldInitialize", value: function shouldInitialize() { return !lists.isEmpty(this.options.popover.link); } }, { key: "initialize", value: function initialize() { this.$popover = this.ui.popover({ className: 'note-link-popover', callback: function callback($node) { var $content = $node.find('.popover-content,.note-popover-content'); $content.prepend(' '); } }).render().appendTo(this.options.container); var $content = this.$popover.find('.popover-content,.note-popover-content'); this.context.invoke('buttons.build', $content, this.options.popover.link); this.$popover.on('mousedown', function (e) { e.preventDefault(); }); } }, { key: "destroy", value: function destroy() { this.$popover.remove(); } }, { key: "update", value: function update() { // Prevent focusing on editable when invoke('code') is executed if (!this.context.invoke('editor.hasFocus')) { this.hide(); return; } var rng = this.context.invoke('editor.getLastRange'); if (rng.isCollapsed() && rng.isOnAnchor()) { var anchor = dom.ancestor(rng.sc, dom.isAnchor); var href = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(anchor).attr('href'); this.$popover.find('a').attr('href', href).text(href); var pos = dom.posFromPlaceholder(anchor); var containerOffset = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.options.container).offset(); pos.top -= containerOffset.top; pos.left -= containerOffset.left; this.$popover.css({ display: 'block', left: pos.left, top: pos.top }); } else { this.hide(); } } }, { key: "hide", value: function hide() { this.$popover.hide(); } }]); return LinkPopover; }(); // CONCATENATED MODULE: ./src/js/base/module/ImageDialog.js function ImageDialog_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function ImageDialog_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function ImageDialog_createClass(Constructor, protoProps, staticProps) { if (protoProps) ImageDialog_defineProperties(Constructor.prototype, protoProps); if (staticProps) ImageDialog_defineProperties(Constructor, staticProps); return Constructor; } var ImageDialog_ImageDialog = /*#__PURE__*/function () { function ImageDialog(context) { ImageDialog_classCallCheck(this, ImageDialog); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.$body = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document.body); this.$editor = context.layoutInfo.editor; this.options = context.options; this.lang = this.options.langInfo; } ImageDialog_createClass(ImageDialog, [{ key: "initialize", value: function initialize() { var imageLimitation = ''; if (this.options.maximumImageFileSize) { var unit = Math.floor(Math.log(this.options.maximumImageFileSize) / Math.log(1024)); var readableSize = (this.options.maximumImageFileSize / Math.pow(1024, unit)).toFixed(2) * 1 + ' ' + ' KMGTP'[unit] + 'B'; imageLimitation = "".concat(this.lang.image.maximumFileSize + ' : ' + readableSize, ""); } var $container = this.options.dialogsInBody ? this.$body : this.options.container; var body = ['
    ', '', '', imageLimitation, '
    ', '
    ', '', '', '
    '].join(''); var buttonClass = 'btn btn-primary note-btn note-btn-primary note-image-btn'; var footer = ""); this.$dialog = this.ui.dialog({ title: this.lang.image.insert, fade: this.options.dialogsFade, body: body, footer: footer }).render().appendTo($container); } }, { key: "destroy", value: function destroy() { this.ui.hideDialog(this.$dialog); this.$dialog.remove(); } }, { key: "bindEnterKey", value: function bindEnterKey($input, $btn) { $input.on('keypress', function (event) { if (event.keyCode === core_key.code.ENTER) { event.preventDefault(); $btn.trigger('click'); } }); } }, { key: "show", value: function show() { var _this = this; this.context.invoke('editor.saveRange'); this.showImageDialog().then(function (data) { // [workaround] hide dialog before restore range for IE range focus _this.ui.hideDialog(_this.$dialog); _this.context.invoke('editor.restoreRange'); if (typeof data === 'string') { // image url // If onImageLinkInsert set, if (_this.options.callbacks.onImageLinkInsert) { _this.context.triggerEvent('image.link.insert', data); } else { _this.context.invoke('editor.insertImage', data); } } else { // array of files _this.context.invoke('editor.insertImagesOrCallback', data); } }).fail(function () { _this.context.invoke('editor.restoreRange'); }); } /** * show image dialog * * @param {jQuery} $dialog * @return {Promise} */ }, { key: "showImageDialog", value: function showImageDialog() { var _this2 = this; return external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.Deferred(function (deferred) { var $imageInput = _this2.$dialog.find('.note-image-input'); var $imageUrl = _this2.$dialog.find('.note-image-url'); var $imageBtn = _this2.$dialog.find('.note-image-btn'); _this2.ui.onDialogShown(_this2.$dialog, function () { _this2.context.triggerEvent('dialog.shown'); // Cloning imageInput to clear element. $imageInput.replaceWith($imageInput.clone().on('change', function (event) { deferred.resolve(event.target.files || event.target.value); }).val('')); $imageUrl.on('input paste propertychange', function () { _this2.ui.toggleBtn($imageBtn, $imageUrl.val()); }).val(''); if (!env.isSupportTouch) { $imageUrl.trigger('focus'); } $imageBtn.click(function (event) { event.preventDefault(); deferred.resolve($imageUrl.val()); }); _this2.bindEnterKey($imageUrl, $imageBtn); }); _this2.ui.onDialogHidden(_this2.$dialog, function () { $imageInput.off(); $imageUrl.off(); $imageBtn.off(); if (deferred.state() === 'pending') { deferred.reject(); } }); _this2.ui.showDialog(_this2.$dialog); }); } }]); return ImageDialog; }(); // CONCATENATED MODULE: ./src/js/base/module/ImagePopover.js function ImagePopover_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function ImagePopover_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function ImagePopover_createClass(Constructor, protoProps, staticProps) { if (protoProps) ImagePopover_defineProperties(Constructor.prototype, protoProps); if (staticProps) ImagePopover_defineProperties(Constructor, staticProps); return Constructor; } /** * Image popover module * mouse events that show/hide popover will be handled by Handle.js. * Handle.js will receive the events and invoke 'imagePopover.update'. */ var ImagePopover_ImagePopover = /*#__PURE__*/function () { function ImagePopover(context) { var _this = this; ImagePopover_classCallCheck(this, ImagePopover); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.editable = context.layoutInfo.editable[0]; this.options = context.options; this.events = { 'summernote.disable summernote.blur': function summernoteDisableSummernoteBlur() { _this.hide(); } }; } ImagePopover_createClass(ImagePopover, [{ key: "shouldInitialize", value: function shouldInitialize() { return !lists.isEmpty(this.options.popover.image); } }, { key: "initialize", value: function initialize() { this.$popover = this.ui.popover({ className: 'note-image-popover' }).render().appendTo(this.options.container); var $content = this.$popover.find('.popover-content,.note-popover-content'); this.context.invoke('buttons.build', $content, this.options.popover.image); this.$popover.on('mousedown', function (e) { e.preventDefault(); }); } }, { key: "destroy", value: function destroy() { this.$popover.remove(); } }, { key: "update", value: function update(target, event) { if (dom.isImg(target)) { var position = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(target).offset(); var containerOffset = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.options.container).offset(); var pos = {}; if (this.options.popatmouse) { pos.left = event.pageX - 20; pos.top = event.pageY; } else { pos = position; } pos.top -= containerOffset.top; pos.left -= containerOffset.left; this.$popover.css({ display: 'block', left: pos.left, top: pos.top }); } else { this.hide(); } } }, { key: "hide", value: function hide() { this.$popover.hide(); } }]); return ImagePopover; }(); // CONCATENATED MODULE: ./src/js/base/module/TablePopover.js function TablePopover_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function TablePopover_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function TablePopover_createClass(Constructor, protoProps, staticProps) { if (protoProps) TablePopover_defineProperties(Constructor.prototype, protoProps); if (staticProps) TablePopover_defineProperties(Constructor, staticProps); return Constructor; } var TablePopover_TablePopover = /*#__PURE__*/function () { function TablePopover(context) { var _this = this; TablePopover_classCallCheck(this, TablePopover); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.options = context.options; this.events = { 'summernote.mousedown': function summernoteMousedown(we, e) { _this.update(e.target); }, 'summernote.keyup summernote.scroll summernote.change': function summernoteKeyupSummernoteScrollSummernoteChange() { _this.update(); }, 'summernote.disable summernote.blur': function summernoteDisableSummernoteBlur() { _this.hide(); } }; } TablePopover_createClass(TablePopover, [{ key: "shouldInitialize", value: function shouldInitialize() { return !lists.isEmpty(this.options.popover.table); } }, { key: "initialize", value: function initialize() { this.$popover = this.ui.popover({ className: 'note-table-popover' }).render().appendTo(this.options.container); var $content = this.$popover.find('.popover-content,.note-popover-content'); this.context.invoke('buttons.build', $content, this.options.popover.table); // [workaround] Disable Firefox's default table editor if (env.isFF) { document.execCommand('enableInlineTableEditing', false, false); } this.$popover.on('mousedown', function (e) { e.preventDefault(); }); } }, { key: "destroy", value: function destroy() { this.$popover.remove(); } }, { key: "update", value: function update(target) { if (this.context.isDisabled()) { return false; } var isCell = dom.isCell(target); if (isCell) { var pos = dom.posFromPlaceholder(target); var containerOffset = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(this.options.container).offset(); pos.top -= containerOffset.top; pos.left -= containerOffset.left; this.$popover.css({ display: 'block', left: pos.left, top: pos.top }); } else { this.hide(); } return isCell; } }, { key: "hide", value: function hide() { this.$popover.hide(); } }]); return TablePopover; }(); // CONCATENATED MODULE: ./src/js/base/module/VideoDialog.js function VideoDialog_classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function VideoDialog_defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function VideoDialog_createClass(Constructor, protoProps, staticProps) { if (protoProps) VideoDialog_defineProperties(Constructor.prototype, protoProps); if (staticProps) VideoDialog_defineProperties(Constructor, staticProps); return Constructor; } var VideoDialog_VideoDialog = /*#__PURE__*/function () { function VideoDialog(context) { VideoDialog_classCallCheck(this, VideoDialog); this.context = context; this.ui = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default.a.summernote.ui; this.$body = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(document.body); this.$editor = context.layoutInfo.editor; this.options = context.options; this.lang = this.options.langInfo; } VideoDialog_createClass(VideoDialog, [{ key: "initialize", value: function initialize() { var $container = this.options.dialogsInBody ? this.$body : this.options.container; var body = ['
    ', ""), ""), '
    '].join(''); var buttonClass = 'btn btn-primary note-btn note-btn-primary note-video-btn'; var footer = ""); this.$dialog = this.ui.dialog({ title: this.lang.video.insert, fade: this.options.dialogsFade, body: body, footer: footer }).render().appendTo($container); } }, { key: "destroy", value: function destroy() { this.ui.hideDialog(this.$dialog); this.$dialog.remove(); } }, { key: "bindEnterKey", value: function bindEnterKey($input, $btn) { $input.on('keypress', function (event) { if (event.keyCode === core_key.code.ENTER) { event.preventDefault(); $btn.trigger('click'); } }); } }, { key: "createVideoNode", value: function createVideoNode(url) { // video url patterns(youtube, instagram, vimeo, dailymotion, youku, mp4, ogg, webm) var ytRegExp = /\/\/(?:(?:www|m)\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([\w|-]{11})(?:(?:[\?&]t=)(\S+))?$/; var ytRegExpForStart = /^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?$/; var ytMatch = url.match(ytRegExp); var igRegExp = /(?:www\.|\/\/)instagram\.com\/p\/(.[a-zA-Z0-9_-]*)/; var igMatch = url.match(igRegExp); var vRegExp = /\/\/vine\.co\/v\/([a-zA-Z0-9]+)/; var vMatch = url.match(vRegExp); var vimRegExp = /\/\/(player\.)?vimeo\.com\/([a-z]*\/)*(\d+)[?]?.*/; var vimMatch = url.match(vimRegExp); var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/; var dmMatch = url.match(dmRegExp); var youkuRegExp = /\/\/v\.youku\.com\/v_show\/id_(\w+)=*\.html/; var youkuMatch = url.match(youkuRegExp); var qqRegExp = /\/\/v\.qq\.com.*?vid=(.+)/; var qqMatch = url.match(qqRegExp); var qqRegExp2 = /\/\/v\.qq\.com\/x?\/?(page|cover).*?\/([^\/]+)\.html\??.*/; var qqMatch2 = url.match(qqRegExp2); var mp4RegExp = /^.+.(mp4|m4v)$/; var mp4Match = url.match(mp4RegExp); var oggRegExp = /^.+.(ogg|ogv)$/; var oggMatch = url.match(oggRegExp); var webmRegExp = /^.+.(webm)$/; var webmMatch = url.match(webmRegExp); var fbRegExp = /(?:www\.|\/\/)facebook\.com\/([^\/]+)\/videos\/([0-9]+)/; var fbMatch = url.match(fbRegExp); var $video; if (ytMatch && ytMatch[1].length === 11) { var youtubeId = ytMatch[1]; var start = 0; if (typeof ytMatch[2] !== 'undefined') { var ytMatchForStart = ytMatch[2].match(ytRegExpForStart); if (ytMatchForStart) { for (var n = [3600, 60, 1], i = 0, r = n.length; i < r; i++) { start += typeof ytMatchForStart[i + 1] !== 'undefined' ? n[i] * parseInt(ytMatchForStart[i + 1], 10) : 0; } } } $video = external_root_jQuery_commonjs2_jquery_commonjs_jquery_amd_jquery_default()(' @else @include('dashboard.widgets.previews._preview') @endif
    ================================================ FILE: resources/views/components/widgets/previews/head.blade.php ================================================ @php /** * @var \App\Models\Entity $entity * @var \App\Models\Character $model * @var \App\Models\Campaign $campaign * @var \App\Models\CampaignDashboardWidget $widget */ @endphp ================================================ FILE: resources/views/components/word-count.blade.php ================================================ ================================================ FILE: resources/views/confirms/delete.blade.php ================================================
    @csrf {{ __('confirm.delete.title') }}

    {!! __('confirm.delete.helper', ['name' => '' . $name . '']) !!}

    @if ($permanent)

    {{ __('crud.delete_modal.permanent') }}

    @endif @if ($mirrored)

    {{ __('entities/relations.delete_mirrored.helper') }}

    @endif @includeWhen(!$permanent, 'layouts.callouts.recoverable')
    ================================================ FILE: resources/views/confirms/editing.blade.php ================================================
    {{ __('confirm/editing.title') }}

    {{ __('confirm/editing.description') }}

    {{ __('confirm/editing.members') }}

      @foreach ($editingUsers as $user)
    • {!! __('confirm/editing.user', [ 'user' => '' . $user->name . '', 'since' => \Carbon\Carbon::createFromTimeString($user->pivot->created_at)->diffForHumans() ]); !!}
    • @endforeach
    ================================================ FILE: resources/views/connections/web-premium.blade.php ================================================

    {!! __('connections/web.cta.text', ['amount' => '' . config('limits.campaigns.web') . '']) !!}

    ================================================ FILE: resources/views/connections/web.blade.php ================================================ @extends('layouts.rich', [ 'title' => __('connections/web.title') . ' - ' . $campaign->name, 'breadcrumbs' => false, 'mainTitle' => false, 'pageClass' => 'connections-web-page' ]) @section('content')
    @endsection @section('scripts') @vite('resources/js/connections/web.js') @endsection @section('modals') @include('connections.web-premium') @endsection ================================================ FILE: resources/views/conversations/form/_entry.blade.php ================================================ value => __('conversations.targets.members'), \App\Enums\ConversationTarget::characters->value => __('conversations.targets.characters'), ]; ?> @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Conversation::class, 'trans' => 'conversations']) @include('cruds.fields.tags') @include('cruds.fields.closed') @include('cruds.fields.image') ================================================ FILE: resources/views/conversations/participants/_actions.blade.php ================================================ @can('update', $model->entity) membersList($model->participantsList(false, true)); ?> @if($model->forCharacters() || count($memberList) > 0)
    @if ($model->forCharacters()) @include('cruds.fields.character', ['allowNew' => false]) @else @endif
    @endif @endcan ================================================ FILE: resources/views/conversations/participants/_form.blade.php ================================================

    {!! __('conversations.participants.helper', ['name' => $model->name]) !!}

    @forelse ($model->participants as $participant) @if ($participant->isMember() || (auth()->check() && auth()->user()->can('view', $participant->entity()->entity ?? false)))
    @if ($participant->isMember()) @else
    {!! $participant->entity()->name !!}
    @endif @can('update', $model->entity)
    @endcan
    @endif @empty

    {{ __('conversations.hints.empty') }}

    @endforelse
    ================================================ FILE: resources/views/conversations/participants.blade.php ================================================ @extends('layouts.ajax', [ 'title' => __('conversations.participants.title', ['name' => $model->name]), 'breadcrumbs' => [ ['url' => route('conversations.index', $campaign), 'label' => __('entities.conversations')], ['url' => route('conversations.show', [$campaign, $model->id]), 'label' => $model->name], __('crud.update'), ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('conversations.participants.modal', ['name' => $model->name]), 'content' => 'conversations.participants._form', 'actions' => 'conversations.participants._actions', 'skipCancel' => true, ]) @endsection ================================================ FILE: resources/views/conversations/show.blade.php ================================================ @php $translations = json_encode([ 'edit' => __('crud.edit'), 'remove' => __('crud.remove'), 'is_updated' => __('conversations.messages.is_updated'), 'is_closed' => __('conversations.show.is_closed'), 'load_previous' => __('conversations.messages.load_previous'), 'user_unknown' => __('crud.users.unknown'), ]); @endphp @section('entity-header-actions-override')
    @can('update', $entity) {{ __('conversations.fields.participants') }} {{ $entity->child->participants->count() }} @include('entities.headers.toggle') @include('entities.headers.actions') @endcan
    @endsection
    @include('entities.components.header', [ 'entityHeaderActions' => 'entity-header-actions-override', ])
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts')
    @include('entities.components.pins')
    @section('scripts') @parent @vite('resources/js/conversation.js') @endsection ================================================ FILE: resources/views/creatures/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Creature::class, 'trans' => 'creatures']) @include('cruds.fields.parent') @include('cruds.fields.locations', ['from' => $model ?? null, 'quickCreator' => true]) @include('cruds.fields.entry2') @include('cruds.fields.status') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/creatures/panels/creatures.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/creatures/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true])
    @include('entities.components.pins')
    ================================================ FILE: resources/views/cruds/_table.blade.php ================================================ @if (isset($entityType) && $models->isEmpty() && !$filterService->hasFilters()) @else
    @include($name . '.datagrid')
    @include('ads.inline') @includeWhen($models->hasPages() && !$models->onFirstPage() && auth()->check(), 'cruds.helpers.pagination', ['action' => 'index']) @includeWhen(isset($datagridActions) && auth()->check() && $filteredCount > 0, 'cruds.datagrids.bulks.actions') @if ($unfilteredCount != $filteredCount)

    {{ __('crud.filters.filtered', ['count' => $filteredCount, 'total' => $unfilteredCount, 'entity' => __('entities.' . $name)]) }}

    @endif @if($models->hasPages()) {{ $models ->appends(isset($filterService) ? $filterService->pagination() : null) ->onEachSide(0) ->links(null, [ 'settingsLink' => base64_encode(route($route . '.index', $campaign)) ]) }} @endif @endif ================================================ FILE: resources/views/cruds/clear-filters.blade.php ================================================
    {{ __('filters.helpers.guest') }}
    @if ($filterService->activeFiltersCount() > 0) @if (isset($entityType)) {{ __('crud.filters.clear') }} @else {{ __('crud.filters.clear') }} @endif @endif
    ================================================ FILE: resources/views/cruds/datagrids/_grid.blade.php ================================================ @php if ($model->child) { $model = $model->child; } if (empty($model->entity)) { return; } $stacked = !isset($flat) && method_exists($model, 'children') && !isset($isParent) ? min(2, $model->children_count) : null; $dataAttributes = []; if ($model->is_private) { $dataAttributes[] = 'private'; } $statusKey = $model->entity->statusKey(); $statusClass = ''; if ($statusKey !== null) { $dataAttributes[] = $statusKey; $statusClass = $model->entity->entityType->code . '-' . $statusKey; } @endphp @if ($stacked > 0)
    entity->type)) data-type="{{ \Illuminate\Support\Str::slug($model->entity->type) }}" @endif> @if ($model->is_private)
    @endif
    {!! $model->name !!} @if ($model instanceof \App\Models\Map && $model->explorable()) {{ __('maps.actions.explore') }} @elseif ($model instanceof \App\Models\Whiteboard) {{ __('whiteboards.actions.draw') }} @endif
    @for ($s = 0; $s < $stacked; $s++)
    @endfor
    @else
    entity->type)) data-type="{{ \Illuminate\Support\Str::slug($model->entity->type) }}" @endif> @if ($model->is_private)
    @endif
    {!! $model->name !!} @if ($model instanceof \App\Models\Map && $model->explorable()) {{ __('maps.actions.explore') }} @elseif ($model instanceof \App\Models\Whiteboard) {{ __('whiteboards.actions.draw') }} @endif
    @endif ================================================ FILE: resources/views/cruds/datagrids/_row-actions.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/bulks/actions.blade.php ================================================ @php $dropdownActions = []; if (auth()->check() && auth()->user()->isAdmin()) { if ($datagridActions->hasBulkEditing()) { $dropdownActions[] = [ 'data' => ['target' => 'bulk-edit', 'bulk-action' => 'batch', 'toggle' => 'dialog'], 'class' => 'bulk-edit', 'icon' => 'edit', 'text' => __('crud.bulk.actions.edit') ]; } if ($datagridActions->hasBulkPermissions()) { $dropdownActions[] = [ 'data' => ['target' => 'primary-dialog', 'bulk-action' => 'ajax', 'toggle' => 'dialog', 'url' => route('bulk.permissions', [$campaign, 'entity_type' => $entityType->id])], 'class' => 'bulk-permissions', 'icon' => 'lock', 'text' => __('crud.bulk.actions.permissions') ]; } if ($datagridActions->hasBulkTemplate() && $campaign->enabled('entity_attributes')) { $dropdownActions[] = [ 'data' => ['target' => 'primary-dialog', 'bulk-action' => 'ajax', 'toggle' => 'dialog', 'url' => route('bulk.templates', [$campaign, 'entity_type' => $entityType->id])], 'class' => 'bulk-templates', 'icon' => 'fa-regular fa-th-list', 'text' => __('crud.bulk.actions.kits') ]; } if ($datagridActions->hasBulkTransform()) { $dropdownActions[] = [ 'data' => ['target' => 'primary-dialog', 'bulk-action' => 'ajax', 'toggle' => 'dialog', 'url' => route('bulk.transform', [$campaign, 'entity_type' => $entityType->id])], 'class' => 'bulk-transform', 'icon' => 'fa-regular fa-arrows-rotate', 'text' => __('entities/actions.convert') ]; } if ($datagridActions->hasBulkCopy()) { $dropdownActions[] = [ 'data' => ['target' => 'primary-dialog', 'bulk-action' => 'ajax', 'toggle' => 'dialog', 'url' => route('bulk.copy-to-campaign', [$campaign, 'entity_type' => $entityType->id])], 'class' => 'bulk-copy-campaign', 'icon' => 'fa-regular fa-clone', 'text' => __('crud.actions.copy_to_campaign') ]; } } if ($datagridActions->hasBulkPrint()) { $dropdownActions[] = [ 'class' => 'bulk-print', 'icon' => 'fa-regular fa-print', 'text' => __('crud.actions.print'), ]; } if ($model instanceof \App\Models\Relation && auth()->user()->can('delete', $model)) { $dropdownActions[] = 'divider'; $dropdownActions[] = [ 'data' => ['target' => 'primary-dialog', 'bulk-action' => 'ajax', 'toggle' => 'dialog', 'url' => route('bulk.delete-relations', [$campaign])], 'class' => 'text-error-content hover:bg-error', 'icon' => 'trash', 'text' => __('crud.remove') ]; } elseif (isset($entityType) && auth()->check() && auth()->user()->can('deleteEntities', [$entityType, $campaign])) { $dropdownActions[] = 'divider'; $dropdownActions[] = [ 'data' => ['target' => 'primary-dialog', 'bulk-action' => 'ajax', 'toggle' => 'dialog', 'url' => route('bulk.delete', [$campaign, 'entity_type' => $entityType->id])], 'class' => 'text-error-content hover:bg-error', 'icon' => 'trash', 'text' => __('crud.remove') ]; } @endphp @if (!empty($dropdownActions))
    @endif ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/_batch-footer.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/_batch.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('crud.bulk.edit.title'), 'content' => 'cruds.datagrids.bulks.modals.forms._batch', ]) @foreach ($entities as $id) @endforeach ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/_copy_campaign.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('crud.copy_to_campaign.bulk_title'), 'content' => 'cruds.datagrids.bulks.modals.forms._copy', 'submit' => __('crud.actions.copy_to_campaign'), ]) @if (!empty($entities)) @foreach ($entities as $id) @endforeach @else @endif ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/_permissions.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('crud.bulk.permissions.title'), 'content' => 'cruds.datagrids.bulks.modals.forms._permissions', 'submit' => __('crud.bulk.actions.permissions'), ]) @if (!empty($entities)) @foreach ($entities as $id) @endforeach @else @endif ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/_templates.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('crud.bulk_templates.bulk_title'), 'content' => 'cruds.datagrids.bulks.modals.forms._templates', 'submit' => __('crud.actions.apply'), ]) @if (!empty($entities)) @foreach ($entities as $id) @endforeach @else @endif ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/_transform.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('entities/transform.panel.bulk_title'), 'content' => 'cruds.datagrids.bulks.modals.forms._transform', 'submit' => __('entities/transform.actions.convert'), ]) @if (!empty($entities)) @foreach ($entities as $id) @endforeach @else @endif ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/ajax.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/batch.blade.php ================================================ @foreach ($bulk->fields() as $field) @php $trimmed = \Illuminate\Support\Str::before($field, '_id'); $isParent = isset($entityType) ? \Illuminate\Support\Str::contains($trimmed, $entityType->code) : false; @endphp {!! $fieldCount % 2 === 0 ? '' : null !!} @include('cruds.fields.' . $trimmed, [ 'trans' => $name, 'base' => $model, 'bulk' => true, 'parent' => \Illuminate\Support\Str::plural($trimmed) == $name, 'allowNew' => false, 'dropdownParent' => '#bulk-edit', 'route' => null, 'isParent' => $isParent, ]) @php $fieldCount++; @endphp @endforeach @if (!empty($entities)) @foreach ($entities as $id) @endforeach @else @endif @isset($entityType) @endisset ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/delete/_footer.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/delete/_form.blade.php ================================================

    {{ __('confirm.delete.bulk') }}

    @if(isset($datagrid) && !$datagrid->hasBulkPermissions())

    {{ __('crud.delete_modal.permanent') }}

    @endif
    @includeWhen(!isset($datagrid) || $datagrid->hasBulkPermissions(), 'layouts.callouts.recoverable') @includeWhen(isset($datagrid) && $datagrid instanceof \App\Datagrids\Actions\RelationDatagridActions, 'cruds.datagrids.bulks.modals.delete._mirrored')
    ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/delete/_mirrored.blade.php ================================================

    ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/delete/delete.blade.php ================================================ @include('partials.forms._dialog', [ 'title' =>__('crud.delete_modal.title'), 'content' => 'cruds.datagrids.bulks.modals.delete._form', 'footer' => 'cruds.datagrids.bulks.modals.delete._footer', ]) @if (!empty($entities)) @foreach ($entities as $id) @endforeach @else @endif ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/delete/relation.blade.php ================================================ @include('partials.forms._dialog', [ 'title' =>__('crud.delete_modal.title'), 'content' => 'cruds.datagrids.bulks.modals.delete._form', 'footer' => 'cruds.datagrids.bulks.modals.delete._footer', ]) ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/forms/_batch.blade.php ================================================ @foreach ($bulk->fields() as $field) @php $trimmed = \Illuminate\Support\Str::before($field, '_id'); $isParent = $field === 'parent_id'; @endphp {!! $fieldCount % 2 === 0 ? '' : null !!} @include('cruds.fields.' . $trimmed, [ 'trans' => $isParent || $trimmed === 'type' ? 'crud' : $entityType->pluralCode(), 'base' => $model ?? null, 'bulk' => true, 'parent' => false, 'allowNew' => false, 'dropdownParent' => '#primary-dialog', 'route' => null, 'isParent' => $isParent, ]) @php $fieldCount++; @endphp @endforeach ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/forms/_copy.blade.php ================================================

    {{ __('entities/move.panel.description_bulk_copy') }}

    @includeIf($type . '.bulk.modals._copy_to_campaign')
    ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/forms/_permissions.blade.php ================================================ __('crud.permissions.actions.bulk_entity.allow'), 'ignore' => __('crud.permissions.actions.bulk.ignore'), 'deny' => __('crud.permissions.actions.bulk_entity.deny'), 'inherit' => __('crud.permissions.actions.bulk_entity.inherit'), ]; ?> @foreach ($campaign->roles()->withoutAdmin()->get() as $role) @if (!$role->is_public) @else @endif @endforeach @foreach ($campaign->members()->with('user')->withoutAdmins()->paginate(20) as $member) @endforeach
    {{ __('crud.permissions.fields.role') }} {{ __('crud.permissions.actions.view') }} {{ __('crud.permissions.actions.edit') }} {{ __('crud.permissions.actions.delete') }} {{ __('entities.articles') }}
    {{ $role->name }}
     
    {{ __('crud.permissions.fields.member') }} {{ __('crud.permissions.actions.view') }} {{ __('crud.permissions.actions.edit') }} {{ __('crud.permissions.actions.delete') }} {{ __('entities.articles') }}
    {{ $member->user->name }}
    ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/forms/_templates.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/bulks/modals/forms/_transform.blade.php ================================================

    {{ __('entities/transform.panel.bulk_description') }}

    ================================================ FILE: resources/views/cruds/datagrids/bulks/modals.blade.php ================================================ @includeWhen(isset($bulk), 'cruds.datagrids.bulks.modals.batch') ================================================ FILE: resources/views/cruds/datagrids/explore.blade.php ================================================
    @if (!empty($parent))
    @if ($parent->parent) {{ __('datagrids.actions.back_to', ['name' => $parent->parent->name]) }} @else {{ __('crud.actions.back') }} @endif
    @includeWhen(!isset($entityType) || $entityType->isCustom(), 'entities.index._entity', ['model' => $parent, 'isParent' => true]) @includeWhen(isset($entityType) && $entityType->isStandard(), 'cruds.datagrids._grid', ['model' => $parent, 'isParent' => true]) @endif @forelse ($models as $model) @includeWhen(!isset($entityType) || $entityType->isCustom(), 'entities.index._entity') @includeWhen(isset($entityType) && $entityType->isStandard(), 'cruds.datagrids._grid') @empty @isset($entityType) @else

    {{ __('search.no_results') }}

    @endif @endforelse
    @include('ads.inline') @if($models instanceof \Illuminate\Pagination\LengthAwarePaginator && $models->hasPages()) {{ $models ->appends(isset($filterService) ? $filterService->pagination() : (isset($term) ? ['term' => $term] : null)) ->onEachSide(0) ->links(null, ['settingsLink' => base64_encode(route($route, $route === 'entities.index' ? [$campaign, $entityType] : $campaign))]) }} @endif ================================================ FILE: resources/views/cruds/datagrids/filters/_archived.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/filters/_array.blade.php ================================================ @php $options = [ '' => __('crud.filters.options.include'), 'children' => __('crud.filters.options.children'), 'exclude' => __('crud.filters.options.exclude'), 'none' => __('crud.filters.options.none'), ]; if (!isset($field['withChildren']) || $field['withChildren'] !== true) { unset($options['children']); } if ($field['type'] == 'selectMultiple' && isset($models)) { $selectedModels = $models; $ids = array_keys($selectedModels); } elseif (!empty($model) ) { $selectedModels = [$model->id => $model->name]; } else { $selectedModels = []; } @endphp
    @if ($field['type'] == 'selectMultiple') @endif
    ================================================ FILE: resources/views/cruds/datagrids/filters/_attributes.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/filters/_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/filters/_connection.blade.php ================================================ @php if ($filterService->single('connection_target')) { $model = \App\Models\Entity::find($filterService->single('connection_target')); } @endphp ================================================ FILE: resources/views/cruds/datagrids/filters/_date-range.blade.php ================================================
    ================================================ FILE: resources/views/cruds/datagrids/filters/_date.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/filters/_element-role.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/filters/_select.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/filters/_sex.blade.php ================================================ @foreach (\App\Facades\CharacterCache::campaign($campaign)->genderSuggestion() as $suggestion) @endforeach ================================================ FILE: resources/views/cruds/datagrids/filters/_status.blade.php ================================================ @php $categoryStatuses = \App\Models\CategoryStatus::where('category_id', $entityType->id) ->orderBy('sort_order') ->get(); $currentValue = $filterService->filterValue($field); @endphp ================================================ FILE: resources/views/cruds/datagrids/filters/_tag.blade.php ================================================
    ================================================ FILE: resources/views/cruds/datagrids/filters/_template.blade.php ================================================ ================================================ FILE: resources/views/cruds/datagrids/filters/_type.blade.php ================================================ @foreach (\App\Facades\EntityCache::campaign($campaign)->typeSuggestion($entityType) as $suggestion) @endforeach ================================================ FILE: resources/views/cruds/datagrids/filters/datagrid-filter.blade.php ================================================ @php /** * @var \App\Services\FilterService $filterService */ $activeFilters = $filterService->activeFiltersCount(); @endphp
    @if ($activeFilters > 0) {{ $activeFilters }} @endif
    @if ($activeFilters > 0) @if (empty($bookmark) && isset($entityType)) @can('create', \App\Models\Bookmark::class)
    @endcan @endif @if (isset($entityType)) {{ __('crud.filters.clear') }} @else {{ __('crud.filters.clear') }} @endif @endif
    @section('modals') @parent() @endsection ================================================ FILE: resources/views/cruds/datagrids/sorters/simple-sorter.blade.php ================================================ @if (!empty($datagridSorter) && auth()->check()) @php $baseRoute = request()->url() . '?' . (!empty($allMembers) ? 'all_members=1&' : (isset($filter) ? $filter : null)); @endphp @endif ================================================ FILE: resources/views/cruds/fields/_image_preview.blade.php ================================================
    @if (isset($target) && !empty($target)) {{ __('crud.remove') }} @endif
    ================================================ FILE: resources/views/cruds/fields/ability.blade.php ================================================ @if (!$campaign->enabled('abilities')) @endif @php $preset = null; if (isset($model) && $model->ability) { $preset = $model->ability; } elseif (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Ability::class); } @endphp ================================================ FILE: resources/views/cruds/fields/age.blade.php ================================================ @php $fieldID = uniqid('age_'); @endphp ================================================ FILE: resources/views/cruds/fields/attitude.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/attribute_template.blade.php ================================================ @inject('attributeTemplateService', 'App\Services\AttributeService') @php $attributeTemplates = $attributeTemplateService->campaign($campaign)->templateList() @endphp @if (empty($attributeTemplates)) @endif
    ================================================ FILE: resources/views/cruds/fields/author.blade.php ================================================ @include('cruds.fields.entity', [ 'name' => 'author_id', 'preset' => !empty($model) && $model->author ? $model->author : null, 'relation' => 'author', 'label' => __('journals.fields.author'), ]) ================================================ FILE: resources/views/cruds/fields/auto_applied_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/calendar.blade.php ================================================ @if (!$campaign->enabled('calendars')) @endif @if (!isset($preset)) @php $preset = null; if (isset($model) && $model->calendar) { $preset = $model->calendar; } elseif (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Calendar::class); } @endphp @endif ================================================ FILE: resources/views/cruds/fields/character.blade.php ================================================ @if (isset($campaign) && !$campaign->enabled('characters')) @php return @endphp @endif @if (!isset($preset)) @php $preset = null; if (isset($model) && $model->character) { $preset = $model->character; } else { $preset = FormCopy::field('character')->child()->select(); } @endphp @endif ================================================ FILE: resources/views/cruds/fields/characters.blade.php ================================================ @if (!$campaign->enabled('characters')) @endif
    @include('components.form.characters', ['options' => [ 'model' => $model ?? FormCopy::model(), 'dynamicNew' => $dynamicNew ?? $quickCreator ?? false, 'required' => $required ?? false ]])
    ================================================ FILE: resources/views/cruds/fields/charges.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/closed.blade.php ================================================ child->is_closed ?? $model->is_closed ?? false)) checked="checked" @endif /> ================================================ FILE: resources/views/cruds/fields/colour.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/colour_picker.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/completed_choice.blade.php ================================================
    ================================================ FILE: resources/views/cruds/fields/creators.blade.php ================================================ itemCreators as $itemCreator) { $selectedOption[$itemCreator->creator->id] = strip_tags($itemCreator->creator->name); } } ?> @if (isset($bulk) && $bulk)
    @endif @if (isset($bulk) && $bulk)
    @else @endif ================================================ FILE: resources/views/cruds/fields/creature.blade.php ================================================ @if (!$campaign->enabled('creatures')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Creature::class); } @endphp ================================================ FILE: resources/views/cruds/fields/date.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/dead_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/defunct_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/destroyed.blade.php ================================================ child->is_destroyed ?? $model->is_destroyed ?? false)) checked="checked" @endif /> ================================================ FILE: resources/views/cruds/fields/destroyed_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/draggable_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/enabled_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/entity-name.blade.php ================================================ id ?? null; $placeholder = __('entries/fields.name.placeholder'); // Entity names can contain ", ', emojis, & and other special characters $modelValue = htmlspecialchars(old('name', str_replace('&', '&', $model->name ?? ''))); $label = __('crud.fields.name'); // Existing aliases serialised for the Vue component (empty on create) $entityAliasCount = isset($entity) && $entity->id ? $entity->aliases()->count() : 0; $existingAliases = isset($entity) && $entity->id ? $entity->aliases()->get()->map(fn (\App\Models\EntityAsset $a) => [ 'id' => $a->id, 'name' => $a->name, 'visibility' => match ($a->visibility_id) { \App\Enums\Visibility::Admin => 'admin', \App\Enums\Visibility::AdminSelf => 'admin-self', \App\Enums\Visibility::Self => 'self', \App\Enums\Visibility::Member => 'members', default => 'all', }, ])->toArray() : []; // Compute effective alias limit for this entity's component. // null = unlimited (boosted campaign), integer = remaining slots for this entity. if ($campaign->boosted() || empty(config('limits.campaigns.aliases'))) { $aliasLimit = null; } else { $campaignAliasCount = $campaign->entityAliases()->count(); $campaignMaxAliases = config('limits.campaigns.aliases'); $aliasLimit = max(0, $campaignMaxAliases - $campaignAliasCount + $entityAliasCount); } $upgradeUrl = route('settings.premium'); $i18n = json_encode([ 'addAlias' => __('entities/aliases.actions.add'), 'aliasPlaceholder' => __('entities/aliases.placeholders.name'), 'duplicateWarning' => __('entities.creator.duplicate'), 'save' => __('crud.save'), 'delete' => __('crud.remove'), 'aliasLimitReached' => __('entities/aliases.limit', [ 'amount' => $campaignAliasCount ?? 0, 'max' => config('limits.campaigns.aliases'), 'upgrade' => '' . __('callouts.actions.upgrade') . '' ]), 'upgrade' => 'Upgrade to Premium', 'visibilityAll' => __('crud.visibilities.all'), 'visibilityMembers' => __('crud.visibilities.members'), 'visibilityAdmin' => __('crud.visibilities.admin'), 'visibilityAdminSelf'=> __('crud.visibilities.admin-self'), 'visibilitySelf' => __('crud.visibilities.self'), ]); ?>
    ================================================ FILE: resources/views/cruds/fields/entity.blade.php ================================================ @if (!isset($preset)) @php $preset = null; if (isset($model) && $model->{$relation ?? 'entity'}) { $preset = $model->{$relation ?? 'entity'}; } elseif (!isset($bulk)) { $preset = FormCopy::field($relation ?? 'entity')->child()->select($isParent ?? false, \App\Models\Entity::class); } @endphp @endif @if (isset($required)) @php $allowClear = false;@endphp @endif ================================================ FILE: resources/views/cruds/fields/entity_header.blade.php ================================================ @php $name = 'entity_header_uuid'; $label = __('fields.gallery-header.description'); $description = __('crud.fields.image'); if (isset($bulk)) { $name = 'entity_header'; $label = __('fields.header-image.title'); $description = ''; } $preset = null; if (isset($model) && $model->entity && $model->entity->header_uuid) { $preset = $model->entity->header; } else { $preset = FormCopy::field('header')->select(); } @endphp @if (isset($bulk) && !$campaign->boosted()) @include('cruds.fields.helpers.boosted', ['key' => 'fields.header-image.boosted-description']) @else @php // If the image is from the gallery and the user can't browse or upload, hide the field $canBrowse = auth()->user()->can('browse', [\App\Models\Image::class, $campaign]); if (!empty($model->entity) && !empty($model->entity->header) && !$canBrowse) { @endphp @php return; } @endphp
    @if (!isset($bulk))
    @if (!empty($model->entity) && !empty($model->entity->header_uuid) && !empty($model->entity->header)) @include('cruds.fields._image_preview', [ 'image' => $model->entity->header->getUrl(194, 144, 'header_image'), 'title' => $model->name, ]) @endif
    @endif
    @endif @if (!empty($model->entity) && !empty($model->entity->image_uuid) && empty($model->entity->image)) @endif ================================================ FILE: resources/views/cruds/fields/entity_image.blade.php ================================================ @php $preset = null; $name = 'entity_image_uuid'; if (isset($bulk)) { $name = 'entity_image'; } if (isset($model) && $model->entity && $model->entity->image_uuid) { $preset = $model->entity->image; } else { $preset = FormCopy::field('image')->select(); } @endphp @if(!isset($bulk))

    {{ __('fields.gallery-image.description') }}

    @endif
    @if (!isset($bulk) && !empty($model->entity) && !empty($model->entity->image_uuid) && !empty($model->entity->image)) @endif
    @if (!empty($model->entity) && !empty($model->entity->image_uuid) && empty($model->entity->image)) @endif ================================================ FILE: resources/views/cruds/fields/entity_link.blade.php ================================================ @if ($entity) @dd('Unknow call') @endif ================================================ FILE: resources/views/cruds/fields/entity_locations.blade.php ================================================ @if (!$campaign->enabled('locations')) @endif @if (isset($bulk) && $bulk)
    @endif @include('components.form.locations', ['options' => [ 'model' => $model->entity ?? $source ?? null, 'source' => $source ?? null, 'dynamicNew' => $dynamicNew ?? $quickCreator ?? false ]]) @if (isset($bulk) && $bulk)
    @endif ================================================ FILE: resources/views/cruds/fields/entity_type.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/entry.blade.php ================================================ @php $editor = auth()->user()->editor; if (request()->has('tiptap')) { $editor = 'tiptap'; } elseif (request()->has('tinymce')) { $editor = 'legacy'; } elseif (request()->has('summernote')) { $editor = ''; } $fieldName = $fieldName ?? 'entry'; $fieldNameForEdition = $fieldName . 'ForEdition'; @endphp @if ($editor === 'tiptap') @include('editors.tiptap_editor', ['model' => $model, 'fieldName' => $fieldName, 'contentFieldName' => $fieldNameForEdition]) @else @endif ================================================ FILE: resources/views/cruds/fields/entry2.blade.php ================================================
    {{ __('crud.helpers.linking') }}
    @include('cruds.fields.entry', ['model' => $entity ?? null])
    ================================================ FILE: resources/views/cruds/fields/event.blade.php ================================================ @if (!$campaign->enabled('events')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Event::class); } @endphp ================================================ FILE: resources/views/cruds/fields/extinct_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/families.blade.php ================================================ @if (!$campaign->enabled('families')) @endif
    @include('components.form.families', ['options' => [ 'model' => $model ?? FormCopy::model(), 'dynamicNew' => $dynamicNew ?? $quickCreator ?? false ]])
    ================================================ FILE: resources/views/cruds/fields/family.blade.php ================================================ @if (!$campaign->enabled('families')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Family::class); } @endphp ================================================ FILE: resources/views/cruds/fields/format.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/helpers/boosted.blade.php ================================================

    @can('boost', auth()->user()) {!! __($key, ['boosted-campaign' => '' . __('concept.boosted-campaign') . '']) !!} @else {!! __($key, ['boosted-campaign' => '' . __('concept.premium-campaign') . '']) !!} @endif

    ================================================ FILE: resources/views/cruds/fields/helpers/private.blade.php ================================================

    {!! __('crud.fields.is_private_v3', [ 'admin-role' => '' . $campaign->adminRoleName() . '', ]) !!}

    ================================================ FILE: resources/views/cruds/fields/helpers/share.blade.php ================================================ @auth @if (isset($campaign) && !$campaign->boosted()) @can('boost', auth()->user())

    {!! __('callouts.subscribe.share-booster', ['campaign' => $campaign->name]) !!}

    @else

    {!! __('callouts.subscribe.share-premium', ['campaign' => $campaign->name]) !!}

    @endif @endif @else {{ __('callouts.subscribe.pitch-image', ['max' => $max]) }} @endif ================================================ FILE: resources/views/cruds/fields/helpers/superboosted.blade.php ================================================

    @can('boost', auth()->user()) {!! __($key, ['boosted-campaign' => '' . __('concept.superboosted-campaign') . '']) !!} @else {!! __($key, ['boosted-campaign' => '' . __('concept.premium-campaign') . '']) !!} @endif

    ================================================ FILE: resources/views/cruds/fields/hide_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/icon.blade.php ================================================ @if($campaign->boosted()) {!! __('entities/links.helpers.icon', [ 'fontawesome' => 'FontAwesome', 'rpgawesome' => 'RPGAwesome', 'docs' => '' . __('footer.documentation') . '', 'example' => ' fa-solid fa-horse', ]) !!} @else @can('boost', auth()->user()) {!! __('callouts.booster.pitches.icon', [ 'boosted-campaign' => '' . __('concept.premium-campaign') . '', 'fontawesome' => 'FontAwesome', ]) !!} @else {!! __('callouts.booster.pitches.icon', [ 'boosted-campaign' => '' . __('concept.premium-campaign') . '', 'fontawesome' => 'FontAwesome', ]) !!} @endif @endif ================================================ FILE: resources/views/cruds/fields/image-gallery.blade.php ================================================ entity)) { $entity = $model->entity; } elseif (!isset($entity) && isset($source)) { $entity = $source instanceof \App\Models\Entity ? $source : $source->child; } $formats = 'PNG, JPG, GIF, WebP'; $inputFileTypes = '.jpg, .jpeg, .png, .gif, .webp'; $max = 25; $from = null; if (isset($size) && $size == 'map') { $formats = 'PNG, JPG, SVG, WebP'; $inputFileTypes = '.jpg, .jpeg, .png, .gif, .webp, .svg'; $max = 50; $from = 'map'; } $isUnlimited = empty($from === 'map' ? config('limits.filesize.map') : config('limits.filesize.image.standard')); $label = $imageLabel ?? 'crud.fields.image'; $previewThumbnail = null; if (!empty($entity) && $entity->hasImage()) { $previewThumbnail = Avatar::entity($entity)->size(192, 144)->thumbnail(); } elseif (isset($model) && method_exists($model, 'thumbnail') && !empty($model->image)) { $previewThumbnail = $model->thumbnail(192, 144); } elseif (isset($source) && !empty($source->entity->image_uuid) && !empty($source->entity->image)) { $previewThumbnail = $source->entity->image->getUrl(192, 144); } // If the image is from the gallery and the user can't browse or upload, disable the field $canBrowse = isset($campaign) && (auth()->user()->can('browse', [\App\Models\Image::class, $campaign]) || auth()->user()->can('create', [\App\Models\Image::class, $campaign])); $fieldname = $fieldname ?? 'entity_image_uuid'; if (!empty($entity) && !empty($entity->image) && !$canBrowse) { ?>image_path) || isset($model) && !empty($model->image_path); ?> @php $translations = json_encode([ 'cancel' => __('crud.cancel'), 'remove' => __('crud.remove'), 'url' => __('gallery.actions.url'), 'gallery' => __('gallery.actions.gallery'), 'unauthorized' => __('gallery.download.errors.unauthorized'), 'browse' => [ 'title' => __('gallery.browse.title'), 'layouts' => [ 'small' => __('gallery.browse.layouts.small'), 'large' => __('gallery.browse.layouts.large'), ], 'search' => [ 'placeholder' => __('gallery.browse.search.placeholder'), ], 'unauthorized' => __('gallery.browse.unauthorized'), ], 'cta_title' => __('gallery.cta.title'), 'cta_action' => __('gallery.cta.action'), 'cta_helper' => __('gallery.cta.helper', [ 'premium-campaign' => '' . __('concept.premium-campaign') . '', 'size' => \Illuminate\Support\Number::format(config('limits.gallery.premium') / (1024 * 1024), 2) ]), ]); @endphp @php $uuid = null; if (isset($entity) && $entity->image_uuid) { $uuid = $entity->image_uuid; } @endphp

    @if ($isUnlimited) {{ __('crud.hints.image_formats', ['formats' => $formats]) }} @else {{ __('crud.hints.image_limitations', ['formats' => $formats, 'size' => (isset($size) ? Limit::readable()->map()->upload() : Limit::readable()->upload())]) }} @includeWhen(config('services.stripe.enabled'), 'cruds.fields.helpers.share') @endif @if (isset($recommended)) {{ __('crud.hints.image_dimension', ['dimension' => $recommended]) }} @endif

    ================================================ FILE: resources/views/cruds/fields/image-old.blade.php ================================================ entity) && !empty($model->entity->image_uuid) && !empty($model->entity->image)) { $previewThumbnail = $model->entity->image->getUrl(192, 144); $canDelete = false; } elseif (!empty($entity) && !empty($entity->image_path)) { $previewThumbnail = Avatar::entity($entity)->size(192, 144)->thumbnail(); } elseif (isset($model) && method_exists($model, 'thumbnail') && !empty($model->image)) { $previewThumbnail = $model->thumbnail(200, 160); } elseif (isset($image)) { $previewThumbnail = $image; } // If the image is from the gallery and the user can't browse or upload, disable the field $canBrowse = isset($campaign) && auth()->user()->can('browse', [\App\Models\Image::class, $campaign]); if (!empty($model->entity) && !empty($model->entity->image) && !$canBrowse) { ?>
    @if(!isset($isModule))
    @endif @php $preset = null; if (isset($model) && $model->entity && $model->entity->image_uuid) { $preset = $model->entity->image; } else { $preset = FormCopy::field('image')->select(); } @endphp @if (isset($campaign) && (!isset($campaignImage) || !$campaignImage) && !isset($gallery)) @if (!empty($model->entity) && !empty($model->entity->image_uuid) && empty($model->entity->image)) @endif @endif

    @if ($isUnlimited) {{ __('crud.hints.image_formats', ['formats' => $formats]) }} @else {{ __('crud.hints.image_limitations', ['formats' => $formats, 'size' => (isset($size) ? Limit::readable()->map()->upload() : Limit::readable()->upload())]) }} @includeWhen(config('services.stripe.enabled'), 'cruds.fields.helpers.share') @endif @if (isset($recommended)) {{ __('crud.hints.image_dimension', ['dimension' => $recommended]) }} @endif

    @if (!empty($previewThumbnail))
    @include('cruds.fields._image_preview', [ 'image' => $previewThumbnail, 'title' => $model->name, 'target' => !isset($removable) && $canDelete && (empty($imageRequired) || !$imageRequired) ? 'remove-image' : null, ])
    @elseif (isset($campaignImage) && $campaignImage && !isset($isModule))
    @include('cruds.fields._image_preview', [ 'image' => 'https://th.kanka.io/UngNKwPxKUPKSZ4z_Qjc9QiyeQs=/280x210/smart/src/app/backgrounds/mountain-background-medium.jpg', 'title' => 'Default', ])
    @endif
    ================================================ FILE: resources/views/cruds/fields/image.blade.php ================================================ @include('cruds.fields.image-gallery') ================================================ FILE: resources/views/cruds/fields/instigator.blade.php ================================================ @include('cruds.fields.entity', [ 'name' => 'instigator_id', 'preset' => !empty($model) && $model->instigator ? $model->instigator : null, 'relation' => 'instigator', 'label' => __('quests.fields.instigator'), ]) ================================================ FILE: resources/views/cruds/fields/is_active.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/is_pinned.blade.php ================================================ @php $pinnedOptions = [ 0 => __('pins.options.no'), 1 => __('pins.options.yes') ]; @endphp ================================================ FILE: resources/views/cruds/fields/item.blade.php ================================================ @if (!$campaign->enabled('items')) @endif @if (!isset($preset)) @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Item::class); } @endphp @endif ================================================ FILE: resources/views/cruds/fields/journal.blade.php ================================================ @if (!$campaign->enabled('journals')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Journal::class); } @endphp ================================================ FILE: resources/views/cruds/fields/location.blade.php ================================================ @if (!$campaign->enabled('locations')) @endif @php $preset = null; if (isset($model) && $model->location) { $preset = $model->location; } elseif (isset($model) && ($isParent ?? false) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field(isset($isParent) && $isParent ? 'parent' : 'location')->child()->select($isParent ?? false, \App\Models\Location::class); } @endphp ================================================ FILE: resources/views/cruds/fields/locations.blade.php ================================================ @if (!$campaign->enabled('locations')) @endif @if (isset($bulk) && $bulk)
    @endif @include('components.form.locations', ['options' => [ 'model' => $model ?? FormCopy::model(), 'source' => $source ?? null, 'dynamicNew' => $dynamicNew ?? $quickCreator ?? false ]]) @if (isset($bulk) && $bulk)
    @endif ================================================ FILE: resources/views/cruds/fields/map.blade.php ================================================ @if (!$campaign->enabled('maps')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Map::class); } @endphp ================================================ FILE: resources/views/cruds/fields/name.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/note.blade.php ================================================ @if (!$campaign->enabled('notes')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Note::class); } @endphp ================================================ FILE: resources/views/cruds/fields/organisation.blade.php ================================================ @if (!$campaign->enabled('organisations')) @endif @php $field = isset($isParent) && $isParent ? 'parent' : 'organisation'; $preset = null; if (isset($model) && $model->$field) { $preset = $model->$field; } elseif (!isset($bulk)) { $preset = FormCopy::field($field)->child()->select($isParent ?? false, \App\Models\Organisation::class); } @endphp ================================================ FILE: resources/views/cruds/fields/organisations.blade.php ================================================ @if (!$campaign->enabled('organisations')) @endif @include('components.form.organisations', ['options' => [ 'model' => $model ?? FormCopy::model(), 'source' => $source ?? null, ]]) ================================================ FILE: resources/views/cruds/fields/owner.blade.php ================================================ @php $required = !isset($bulk); $preset = $owner ?? null; @endphp @include('cruds.fields.entity', [ 'name' => 'owner_id', 'label' => __('entities/relations.fields.owner'), 'allowClear' => false, 'route' => null, ]) ================================================ FILE: resources/views/cruds/fields/parent.blade.php ================================================ @php $preset = null; if (isset($entity) && $entity instanceof \App\Models\Entity && $entity->parent) { $preset = $entity->parent; } elseif (isset($model) && $model instanceof \App\Models\Entity && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->select(true, \App\Models\Entity::class); } @endphp ================================================ FILE: resources/views/cruds/fields/parent_attribute_template.blade.php ================================================ @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\AttributeTemplate::class); } @endphp ================================================ FILE: resources/views/cruds/fields/pinned.blade.php ================================================ @php $pinnedOptions = [ 0 => __('pins.options.no'), 1 => __('pins.options.yes') ]; @endphp ================================================ FILE: resources/views/cruds/fields/pinned_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/position.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/price.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/privacy_callout.blade.php ================================================ @php $isPrivate = old('is_private', $source->is_private ?? $model->is_private ?? $campaign->entity_visibility) @endphp
    ================================================ FILE: resources/views/cruds/fields/private_choice.blade.php ================================================ @can('admin', $campaign) @endif ================================================ FILE: resources/views/cruds/fields/pronouns.blade.php ================================================ @php $fieldID = uniqid('pronouns_'); @endphp @foreach (\App\Facades\CharacterCache::campaign($campaign)->pronounSuggestion() as $suggestion) @endforeach ================================================ FILE: resources/views/cruds/fields/quest.blade.php ================================================ @if (!$campaign->enabled('quests')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Quest::class); } @endphp ================================================ FILE: resources/views/cruds/fields/race.blade.php ================================================ @if (!$campaign->enabled('races')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Race::class); } @endphp ================================================ FILE: resources/views/cruds/fields/races.blade.php ================================================ @if (!$campaign->enabled('races')) @endif @include('components.form.races', ['options' => [ 'model' => $model ?? FormCopy::model(), 'dynamicNew' => $dynamicNew ?? $quickCreator ?? false ]]) ================================================ FILE: resources/views/cruds/fields/relation.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/save.blade.php ================================================ @if (isset($onlySave)) @else
    @includeWhen(!isset($disableCancel), 'partials.or_cancel')
    @endif ================================================ FILE: resources/views/cruds/fields/sex.blade.php ================================================ @php $fieldID = uniqid('gender_'); @endphp @foreach (\App\Facades\CharacterCache::campaign($campaign)->genderSuggestion() as $gender) @endforeach ================================================ FILE: resources/views/cruds/fields/size.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/status.blade.php ================================================ @php $statusEntityType = $entityType ?? $entity->entityType ?? null; $categoryStatuses = $statusEntityType ? \App\Models\CategoryStatus::where('category_id', $statusEntityType->id)->orderBy('sort_order')->get() : collect(); $isBulk = $bulk ?? false; @endphp @if ($categoryStatuses->isNotEmpty()) @php $hasDefault = $categoryStatuses->contains('is_default', true); $statusOptions = []; if ($isBulk) { $statusOptions[''] = ''; $statusOptions['remove'] = __('entities/statuses.remove'); } elseif (! $hasDefault) { $statusOptions[''] = ''; } foreach ($categoryStatuses as $catStatus) { $statusOptions[$catStatus->id] = $catStatus->setRelation('entityType', $statusEntityType)->name(); } $selectedStatus = $isBulk ? '' : old('status_id', $source->status_id ?? $entity->status_id ?? ($hasDefault ? $categoryStatuses->firstWhere('is_default', true)->id : '')); @endphp @endif ================================================ FILE: resources/views/cruds/fields/tag.blade.php ================================================ @if (!$campaign->enabled('tags')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Tag::class); } @endphp ================================================ FILE: resources/views/cruds/fields/tags.blade.php ================================================ @if ($campaign->enabled('tags')) @if (isset($bulk) && $bulk)
    @endif @if (isset($bulk) && $bulk)
    @endif @endif ================================================ FILE: resources/views/cruds/fields/target.blade.php ================================================ @php $required = !isset($bulk); $preset = $target ?? null; @endphp @if(empty($relation) && $required) @else @include('cruds.fields.entity', [ 'name' => 'target_id', 'label' => __('entities/relations.fields.targets'), 'placeholder' => __('crud.placeholders.search'), 'allowClear' => false, 'route' => null, ]) @endif ================================================ FILE: resources/views/cruds/fields/template.blade.php ================================================ @if (isset($required)) @php $allowClear = false;@endphp @endif ================================================ FILE: resources/views/cruds/fields/timeline.blade.php ================================================ @if (!$campaign->enabled('timelines')) @endif @php $preset = null; if (isset($model) && $model->parent) { $preset = $model->parent; } elseif (!isset($bulk)) { $preset = FormCopy::field('parent')->child()->select($isParent ?? false, \App\Models\Timeline::class); } @endphp ================================================ FILE: resources/views/cruds/fields/title.blade.php ================================================ @php $fieldID = uniqid('title_'); @endphp ================================================ FILE: resources/views/cruds/fields/tooltip_choice.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/type.blade.php ================================================ @php $fieldID = uniqid('type_'); @endphp ================================================ FILE: resources/views/cruds/fields/unmirror.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/update_mirrored.blade.php ================================================ ================================================ FILE: resources/views/cruds/fields/visibility.blade.php ================================================ @include('cruds.fields.visibility_id') ================================================ FILE: resources/views/cruds/fields/visibility_id.blade.php ================================================ value] = __('crud.visibilities.all'); if (auth()->user()->isAdmin()) { $options[Visibility::Admin->value] = __('crud.visibilities.admin'); $options[Visibility::Member->value] = __('crud.visibilities.members'); } if (!isset($model) || ($model->created_by == auth()->user()->id)) { $options[Visibility::Self->value] = __('crud.visibilities.self'); $options[Visibility::AdminSelf->value] = __('crud.visibilities.admin-self'); } // If it's a visibility self & admin, and we're not the creator, we can't change this if (isset($model) && $model->visibility_id === Visibility::AdminSelf && $model->created_by !== auth()->user()->id) { $options = [Visibility::AdminSelf->value => __('crud.visibilities.admin-self')]; } // The visibility is set to admin, but we're not an admin, don't allow changing // as it's a custom permission for the user to be able to edit this model. if (isset($model)) { $locked = false; // Set to admin but not an admin? An admin created this element, and with custom permissions (like on a post) // is allowing a non-admin to edit the post, so we can't have them changing the visibility. if ($model->visibility_id === Visibility::Admin && !auth()->user()->isAdmin()) { $locked = true; } // If the visibility is set to self but the user didn't create it, don't allow changing it, as only the person // who created is allowed to change the visibility. if (in_array($model->visibility_id, [Visibility::Self, Visibility::AdminSelf]) && $model->created_by != auth()->user()->id) { $locked = true; } if ($locked) { ?> ================================================ FILE: resources/views/cruds/fields/weight.blade.php ================================================ ================================================ FILE: resources/views/cruds/forms/_attributes.blade.php ================================================ entity; } elseif (isset($source)) { if ($source instanceof \App\Models\Entity) { if (auth()->user()->can('attributes', [$source])) { $entity = $source; } } elseif (auth()->user()->can('view-attributes', [$source->entity, $campaign])) { $entity = $source->entity; } } ?>
    @if (!empty($entity)) @elseif (!empty($source)) @else @endif
    @section('scripts') @parent @vite('resources/js/attributes.js') @vite('resources/js/attributes-manager.js') @endsection ================================================ FILE: resources/views/cruds/forms/_calendar.blade.php ================================================ calendarReminder()) { $oldCalendarID = $source->calendarReminder()->calendar_id; $sourceReminder = $source->calendarReminder(); } elseif (!empty($post) && $post->calendarReminder()) { $oldCalendarID = $post->calendarReminder()->calendar_id; $sourceReminder = $post->calendarReminder(); } if (!empty($post)) { $model = $post; } elseif (isset($entity)) { $model = $entity; } $calendar = null; if (!empty($oldCalendarID)) { $calendar = \App\Models\Calendar::find($oldCalendarID); } $opened = (isset($model) && $model->hasCalendar()) || !empty($oldCalendarID); ?> @if (isset($model) && $model->hasCalendarButNoAccess()) @php return; @endphp @endif
    " data-default-calendar="{{ ($onlyOneCalendar ? $calendars->first()->id : null) }}" @click="opened = !opened" x-show="!opened"> @php $calendarModule = \App\Models\EntityType::default()->where('code', 'calendar')->first(); @endphp {{ __('entities/reminders.actions.add') }}

    {{ __('entities/reminders.helpers.pitch') }}

    @if (count($calendars) == 1) @else
    @endif
    ================================================ FILE: resources/views/cruds/forms/_copy.blade.php ================================================

    {{ __('crud.helpers.copy_options') }}

    filled('template'))) checked="checked" @endif /> filled('template'))) checked="checked" @endif /> filled('template'))) checked="checked" @endif /> @if ($campaign->boosted()) filled('template'))) checked="checked" @endif /> @endif filled('template'))) checked="checked" @endif /> @if (view()->exists($entityType->pluralCode() . '.form._copy')) @include($entityType->pluralCode() . '.form._copy') @endif ================================================ FILE: resources/views/cruds/forms/_errors.blade.php ================================================ @include('partials.errors') {{ __('partials.errors.title') }} {{ __('partials.errors.description') }}
    {{ __('errors.403.title') }}

    {!! __('errors.403.body') !!}

    {!! __('errors.403-form.help') !!}

    ================================================ FILE: resources/views/cruds/forms/_permission.blade.php ================================================ @inject('permissionService', 'App\Services\PermissionService') @php /** * @var \App\Services\PermissionService $permissionService * @var \App\Models\CampaignUser $member * @var \App\Models\CampaignRole $role * @var \App\Models\Campaign $campaign * @var \App\Models\EntityType $entityType */ if (isset($source)) { $permissionService->entityPermissions($source); } if (isset($entity)) { $permissionService->entityPermissions($entity); $permissionService->entityType($entity->entityType); } else { $permissionService->entityType($entityType); } $actions = [ 'allow' => __('crud.permissions.actions.bulk_entity.allow'), 'deny' => __('crud.permissions.actions.bulk_entity.deny'), 'inherit' => __('crud.permissions.actions.bulk_entity.inherit'), ]; $hidden = false; if (!empty($source) && $source->is_private) { $hidden = true; } elseif (!empty($model) && $model->is_private) { $hidden = true; } @endphp @can('admin', $campaign) @include('cruds.fields.privacy_callout', ['privacyToggle' => true]) @endif
    {{ __('entities/permissions.privacy.warning') }}

    {!! __('entities/permissions.privacy.text', [ 'admin' => '' . $campaign->adminRoleName() . '', ]) !!}

    @include('cruds.permissions.permissions_table', ['skipUsers' => true, 'campaign'])
    ================================================ FILE: resources/views/cruds/forms/_premium.blade.php ================================================ @php $translations = json_encode([ 'cancel' => __('crud.actions.cancel'), 'remove' => __('crud.remove'), 'url' => __('gallery.actions.url'), 'gallery' => __('gallery.actions.gallery'), 'browse' => [ 'title' => __('gallery.browse.title'), 'layouts' => [ 'small' => __('gallery.browse.layouts.small'), 'large' => __('gallery.browse.layouts.large'), ], 'search' => [ 'placeholder' => __('gallery.browse.search.placeholder'), ], ], ]); @endphp @if($campaign->boosted())

    {{ __('entities/tooltips.helper') }}

    @php $tooltipTags = [ 'text' => 'b, i, strong, a, h1-6', 'layout' => 'p, div, span', ]; @endphp {!! __('entities/tooltips.formatting', $tooltipTags) !!} @else @include('cruds.fields.helpers.boosted', ['key' => 'entities/tooltips.premium']) @endif
    @if ($campaign->boosted()) @php $headerUrlPreset = null; if (!empty($source) && $source->header_image) { $headerUrlPreset = Storage::url($source->header_image); } elseif (!empty($source) && $source->header) { $headerUrlPreset = $source->header->getUrl(192, 144); } elseif (isset($entity) && $entity->header) { $headerUrlPreset = $entity->header->getUrl(192, 144); } @endphp

    {{ __('fields.header-image.description') }}

    @if (isset($entity) && $entity->header_image)
    @include('cruds.fields._image_preview', [ 'image' => $entity->thumbnail(120), 'title' => $entity->name, 'target' => 'remove-header_image', ])
    @else @endif @else @include('cruds.fields.helpers.boosted', ['key' => 'fields.header-image.boosted-description']) @endif
    ================================================ FILE: resources/views/cruds/forms/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => $title, 'breadcrumbs' => [ ['url' => Breadcrumb::campaign($campaign)->index($name), 'label' => $plural], __('crud.create'), ], 'mainTitle' => false, 'centered' => true, ]) @section('content') @include('ads.top') @if ($entityType->isCharacter() && request()->get('from') === 'onboarding')

    {{ __('onboarding/characters.title') }}

    {{ __('onboarding/characters.text') }}

    {{ __('onboarding/characters.finisher') }}

    @elseif ($entityType->isLocation() && request()->get('from') === 'onboarding')

    {{ __('onboarding/locations.title') }}

    {{ __('onboarding/locations.text') }}

    {{ __('onboarding/locations.finisher') }}

    @endif @include('cruds.forms._errors') @endsection @include('editors.editor') @includeIf($name . '.forms._tutorial') ================================================ FILE: resources/views/cruds/forms/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('crud.titles.editing', ['name' => $model->name]) . ' - ' . __('entities.' . $name), 'breadcrumbs' => (isset($entity) ? [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('crud.edit'), ] : [ __('crud.edit'), ]), 'mainTitle' => false, 'entity' => null, 'centered' => true, ]) @section('content') @include('ads.top') @include('cruds.forms._errors') @if(!empty($entity) && $campaign->hasEditingWarning()) @endif @endsection @include('editors.editor') @section('modals') @parent @includeWhen(!empty($editingUsers) && !empty($entity), 'cruds.forms.edit_warning', ['model' => $model]) @endsection ================================================ FILE: resources/views/cruds/forms/edit_warning.blade.php ================================================ id; } elseif ($model instanceof \App\Models\Campaign) { $modelName = null; $modelId = $model->id; } elseif ($model instanceof \App\Models\TimelineElement) { $modelName = 'TimelineElement'; $modelId = $model->id; } elseif ($model instanceof \App\Models\QuestElement) { $modelName = 'TimelineElement'; $modelId = $model->id; } else { $modelName = 'Entity'; $modelId = $model->id; } ?> ================================================ FILE: resources/views/cruds/forms/limit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __($name . '.create.title'), 'breadcrumbs' => [ ['url' => Breadcrumb::campaign($campaign)->index($name), 'label' => __('entities.' . $name)], __('crud.create'), ] ]) @section('content') {{ __('campaigns/limits.title') }}

    {{ __('campaigns/limits.pitch', ['limit' => $limit, 'thing' => $thing]) }}

    @endsection ================================================ FILE: resources/views/cruds/helpers/pagination.blade.php ================================================

    {!! __('crud.helpers.pagination.text', ['settings' => '' . __('crud.helpers.pagination.settings') . '']) !!}

    ================================================ FILE: resources/views/cruds/index.blade.php ================================================ @extends('layouts.app', [ 'title' => $titleKey ?? __('entities.' . $langKey), 'seoTitle' => ($titleKey ?? __('entities.' . $langKey)) . ' - ' . $campaign->name, 'breadcrumbs' => false, 'canonical' => true, 'bodyClass' => 'kanka-' . $name, ]) @section('entity-header')

    {!! $titleKey ?? __('entities.' . $langKey) !!}

    @if($mode === 'table' && $parent) @include('entities.index.actions.parent') @endif @includeWhen(isset($route) && $route !== 'relations', 'layouts.datagrid._togglers', ['route' => $name . '.index']) @if (isset($entityType)) @includeIf('entities.index.actions.' . $entityType->code) @includeWhen(isset($model) && auth()->check() && auth()->user()->can('create', [$entityType, $campaign]), 'cruds.lists._create') @else @includeWhen(isset($route) && $route === 'relations', 'entities.index.actions.connection') @includeWhen(isset($model) && auth()->check() && auth()->user()->can('create', $model), 'cruds.lists._create') @endif
    @endsection @section('content') @include('partials.errors') @include('ads.top')
    @if (auth()->guest()) @include('cruds.clear-filters') @else @if (isset($route))
    @includeWhen(isset($model) && $model->hasSearchableFields(), 'layouts.datagrid.search', ['route' => [$route . '.index', $campaign]]) @includeWhen(isset($filter) && $filter !== false, 'cruds.datagrids.filters.datagrid-filter', ['route' => $route . '.index', $campaign])
    @endif @endif @if (!isset($mode) || $mode === 'grid') @include('cruds.datagrids.explore', ['route' => $route . '.index']) @else @if (isset($entityType)) @include('cruds._table') @else @include('cruds._table') @endif @endif
    @endsection @section('modals') @parent @includeWhen(auth()->check(), 'cruds.datagrids.bulks.modals') @endsection @section('og') @endsection ================================================ FILE: resources/views/cruds/lists/_create.blade.php ================================================
    @if(!in_array($name, ['bookmarks', 'relations'])) @endif
    ================================================ FILE: resources/views/cruds/overview.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true])
    @include('entities.components.pins')
    ================================================ FILE: resources/views/cruds/permissions/permissions_table.blade.php ================================================ __('crud.permissions.actions.bulk_entity.allow'), 'deny' => __('crud.permissions.actions.bulk_entity.deny'), 'inherit' => __('crud.permissions.actions.bulk_entity.inherit'), ]; $permissionService->campaign($campaign); $hasActionCol = isset($showPermissionActions) && auth()->user()->isAdmin(); $cols = 'md:grid-cols-5'; if ($hasActionCol) { $cols = 'md:grid-cols-6'; } $moduleName = isset($entityType) ? $entityType->name() : $entity->entityType->name(); ?>

    {!! __('crud.permissions.helpers.setup', [ 'allow' => '' . __('crud.permissions.actions.bulk_entity.allow') . '', 'deny' => '' . __('crud.permissions.actions.bulk_entity.deny') . '', 'inherit' => '' . __('crud.permissions.actions.bulk_entity.inherit') . '', ]) !!}

    @foreach ($campaign->roles()->withoutAdmin()->get() as $role) @php $permissionService->reset()->role($role) @endphp
    @can('update', $role) {!! $role->name !!} @if ($role->isPublic() && !$campaign->isPublic()) @endif @else {!! $role->name !!} @endcan
    @if ($permissionService->inherited()) {{ __('permissions.roles.inherited', ['role' => $role->name, 'module' => $moduleName]) }} @endif
    @if (!$role->isPublic())
    @if ($permissionService->inherited()) {{ __('permissions.roles.inherited', ['role' => $role->name, 'module' => $moduleName]) }} @endif
    @if ($permissionService->inherited()) {{ __('permissions.roles.inherited', ['role' => $role->name, 'module' => $moduleName]) }} @endif
    @if ($permissionService->inherited()) {{ __('permissions.roles.inherited', ['role' => $role->name, 'module' => $moduleName]) }} @endif
    @else
    @endif
    @endforeach
    @if (isset($skipUsers) && $skipUsers && $campaign->nonAdminMembers->count() > config('limits.campaigns.members'))

    {{ __('crud.permissions.too_many_members', ['number' => config('limits.campaigns.members')]) }}

    @else
    @foreach ($campaign->nonAdminMembers as $member) @php $permissionService->reset()->user($member->user); @endphp
    @if ($member->user->hasAvatar()) @else
    {{ $member->user->initials() }}
    @endif
    {!! $member->user->name !!}
    @if ($permissionService->inherited()) @php $inheritedHelper = __('permissions.members.inherited', [ 'role' => e($permissionService->inheritedRoleName()), 'member' => $member->user->name ]); @endphp {{ $inheritedHelper }} @endif
    @if ($permissionService->inherited()) @php $inheritedHelper = __('permissions.members.inherited', [ 'role' => e($permissionService->inheritedRoleName()), 'member' => $member->user->name ]); @endphp {{ $inheritedHelper }} @endif
    @if ($permissionService->inherited()) @php $inheritedHelper = __('permissions.members.inherited', [ 'role' => e($permissionService->inheritedRoleName()), 'member' => $member->user->name ]); @endphp {{ $inheritedHelper }} @endif
    @if ($permissionService->inherited()) @php $inheritedHelper = __('permissions.members.inherited', [ 'role' => e($permissionService->inheritedRoleName()), 'member' => $member->user->name ]); @endphp {{ $inheritedHelper}} @endif
    @if ($hasActionCol) @can('switch', $member) @endcan @endif
    @endforeach
    @endif
    ================================================ FILE: resources/views/cruds/permissions.blade.php ================================================ /permissions * @var \App\Models\Entity $entity * @var \App\Models\CampaignRole $role * @var \App\Models\CampaignUser $member * @var \App\Services\PermissionService $permissionService */ ?> @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('crud.permissions.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('crud.edit'), ] ]) @section('content') @inject('permissionService', 'App\Services\PermissionService') @php $permissions = $permissionService->entityType($entity->entityType)->entityPermissions($entity); @endphp @include('partials.forms._dialog', [ 'title' => __('crud.permissions.title', ['name' => $entity->name]), 'content' => 'cruds.permissions.permissions_table', 'articleClass' => 'max-w-3xl', 'showPermissionActions' => true ]) @endsection ================================================ FILE: resources/views/cruds/show.blade.php ================================================ @php $headerImage = true; @endphp @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => $entity->name . ' - ' . $campaign->name, 'breadcrumbs' => false, 'canonical' => true, 'mainTitle' => false, 'bodyClass' => 'entity-story', ]) @include('entities.components.og') @section('entity-header-actions')
    @include('entities.headers.toggle') @include('entities.headers.actions')
    @endsection @section('content') @if($entity->entityType->isStandard() && view()->exists($entity->entityType->pluralCode() . '.show')) @include($entity->entityType->pluralCode() . '.show') @else @include('cruds.overview') @endif @endsection ================================================ FILE: resources/views/cruds/subview.blade.php ================================================ @include('entities.components.og') @include($fullview) ================================================ FILE: resources/views/dashboard/_widget.blade.php ================================================
    widget == Widget::Campaign) data-toggle="dialog" data-url="{{ route('campaigns.dashboard-header.edit', ['campaign' => $campaign, 'campaignDashboardWidget' => $widget]) }}" @else data-toggle="dialog" data-url="{{ route('campaign_dashboard_widgets.edit', [$campaign, $widget]) }}" @endif @if ($widget->widget == Widget::Campaign && $campaign->header_image) style="background-image: url('{{ Img::crop(1200, 400)->url($campaign->header_image) }}')" @endif >
    @if (!empty($widget->conf('text'))) {{ $widget->conf('text') }} ({{ __('dashboards/widgets/' . $widget->widget->value . '.name') }}) @else {{ __('dashboards/widgets/' . $widget->widget->value . '.name') }} @endif
    @if ($widget->entity) @endif @if (in_array($widget->widget, [Widget::Recent, Widget::Random]))

    @if ($widget->entityType) {!! $widget->entityType->plural() !!} @elseif (!empty($widget->conf('singular'))) {{ __('dashboard.widgets.recent.singular') }} @else {{ __('dashboard.widgets.recent.all-entities') }} @endif @if (!empty($widget->conf('filters'))) @endif

    @endif @if ($widget->widget === Widget::Gallery && !empty($widget->conf('folder_id'))) @php $galleryFolder = \App\Models\Image::find($widget->conf('folder_id')); @endphp @if ($galleryFolder)

    {{ $galleryFolder->name }}

    @endif @endif @if ($widget->tags->isNotEmpty())
    @foreach ($widget->tags as $tag) @include ('tags._badge') @endforeach
    @endif
    ================================================ FILE: resources/views/dashboard/dashboards/_form.blade.php ================================================ @empty($dashboard)

    {!! __('dashboard.dashboards.create.helper', ['name' => $campaign->name]) !!}

    @endif
    {{ __('campaigns.members.fields.role') }}
    {{ __('dashboard.dashboards.fields.visibility') }}
    @foreach($campaign->roles as $role) @endforeach
    @if(!empty($source)) @endif
    ================================================ FILE: resources/views/dashboard/dashboards/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => trans('dashboard.dashboards.create.title'), 'description' => '', 'breadcrumbs' => [] ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('dashboard.dashboards.create.title'), 'content' => 'dashboard.dashboards._form', ]) @endsection ================================================ FILE: resources/views/dashboard/dashboards/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('dashboard.dashboards.update.title', ['name' => $dashboard->name]), 'description' => '', 'breadcrumbs' => [] ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('dashboard.dashboards.update.title', ['name' => $dashboard->name]), 'content' => 'dashboard.dashboards._form', ]) @endsection ================================================ FILE: resources/views/dashboard/dialogs/onboarding.blade.php ================================================ @php $translations = json_encode([ 'title' => __('dashboards/onboarding.title'), 'intro' => __('dashboards/onboarding.intro'), 'name' => __('dashboards/onboarding.fields.name'), 'placeholder' => __('dashboards/onboarding.placeholders.name'), 'type-title' => __('dashboards/onboarding.selection.title'), 'type-intro' => __('dashboards/onboarding.selection.intro'), 'type-helper' => __('dashboards/onboarding.selection.helper'), 'skip' => __('dashboards/onboarding.actions.skip'), 'continue' => __('dashboards/onboarding.actions.continue'), 'worldbuilding' => __('dashboards/onboarding.selection.worldbuilding'), 'worldbuilding-description' => __('dashboards/onboarding.selection.worldbuilding-description'), 'campaign' => __('dashboards/onboarding.selection.campaign'), 'campaign-description' => __('dashboards/onboarding.selection.campaign-description'), 'story' => __('dashboards/onboarding.selection.story'), 'story-description' => __('dashboards/onboarding.selection.story-description'), ]); @endphp
    ================================================ FILE: resources/views/dashboard/setup.blade.php ================================================ @extends('layouts.app', [ 'title' => __('dashboard.setup.title'), 'breadcrumbs' => [ __('dashboard.setup.title') ], 'mainTitle' => '', 'centered' => true, ]) @php $widgetClass = 'widget rounded-xl h-28 shadow-xs hover:shadow-md cursor-pointer bg-box' ; $overlayClass = 'rounded-xl flex gap-2 flex-col p-2 items-center h-full'; $hasDashboards = !$dashboards->isEmpty() || !empty($dashboard); @endphp @section('content')
    @if ($hasDashboards) @else

    @if ($dashboard) {!! $dashboard->name !!} @else {{ __('dashboard.dashboards.default.title') }} @endif

    @endif @if (config('limits.campaigns.premium'))
    @if($dashboard) @endif
    @endif
    @empty($dashboard)

    {!! __('dashboard.dashboards.default.text', ['campaign' => $campaign->name]) !!}

    @endif @include('partials.errors')

    {!! __('dashboard.setup.tutorial.text', [ 'blog' => '' . __('dashboard.setup.tutorial.blog') . '', ]) !!}

    @if (empty($dashboard))
    header_image) style="background-image: url({{ Img::crop(1200, 400)->url($campaign->header_image) }})" @endif data-toggle="dialog" data-url="{{ route('campaigns.dashboard-header.edit', $campaign) }}" >
    {{ __('dashboards/widgets/campaign.name') }}
    @endif @foreach ($widgets as $widget) @includeWhen($widget->visible(), '.dashboard._widget') @endforeach
    {{ __('dashboards/setup.actions.add') }}
    @include('editors.editor', ['dialogsInBody' => true]) @endsection @section('scripts') @vite('resources/js/dashboard.js') @endsection @section('styles') @vite('resources/css/dashboard.css') @endsection ================================================ FILE: resources/views/dashboard/widgets/_actions.blade.php ================================================ @can ('follow', $campaign) @endcan @can('apply', $campaign) @endcan @cannot('update', $campaign) @if(!empty($dashboards)) @else @can('dashboard', $campaign) {{ __('dashboard.actions.customise') }} @endcan @endif @endcannot @can('update', $campaign) @endcan ================================================ FILE: resources/views/dashboard/widgets/_calendar.blade.php ================================================ entity; $calendar = $entity->child; // Todo: move this to the query if (empty($calendar) || $calendar->missingDetails()) { return; } ?>
    ================================================ FILE: resources/views/dashboard/widgets/_campaign.blade.php ================================================ @section('content-header')
    header_image)) style="background-image: url('{{ Img::crop(1200, 400)->url($campaign->header_image) }}')" @endif>
    @include('dashboard.widgets._actions')
    @if ($campaign->hasPreview())
    {!! $campaign->preview() !!}
    @endif
    @endsection ================================================ FILE: resources/views/dashboard/widgets/_gallery.blade.php ================================================ ================================================ FILE: resources/views/dashboard/widgets/_header.blade.php ================================================ {{-- Check if the header is linked to an entity and if the user has read permissions on it --}} @if ($widget->entity && !$widget->entity->isMissingChild()) <{{ $widget->customSize() }} class="text-2xl widget-header-text text-center my-4 {{ $widget->customClass($campaign) }}" id="dashboard-widget-{{ $widget->id }}"> {{ $widget->conf('text') }} customSize() }}> @else <{{ $widget->customSize() }} class="widget-header-text text-center my-4 {{ $widget->customClass($campaign) }} text-2xl " id="dashboard-widget-{{ $widget->id }}"> {{ $widget->conf('text') }} customSize() }}> @endif ================================================ FILE: resources/views/dashboard/widgets/_help.blade.php ================================================ {{ __('dashboards/widgets/help.title') }} ================================================ FILE: resources/views/dashboard/widgets/_join.blade.php ================================================ {{ __('dashboards/widgets/join.title') }}
    {!! $campaign->getFilter(\App\Enums\CampaignFilterType::Intro) !!}

    {{ __('campaigns/applications.fields.schedule') }}

    {!! $campaign->getFilter(\App\Enums\CampaignFilterType::Schedule) !!}, {!! $campaign->getFilter(\App\Enums\CampaignFilterType::Timezone) !!}

    {{ __('campaigns/applications.fields.player_count') }}

    Current players: {{$campaign->users()->count() }}

    Max players: {{ $campaign->getFilter(\App\Enums\CampaignFilterType::PlayerCount)}}


    @guest {{ __('dashboards/widgets/join.register') }} @endguest @can('apply', $campaign) @endcan
    ================================================ FILE: resources/views/dashboard/widgets/_onboarding.blade.php ================================================
    ================================================ FILE: resources/views/dashboard/widgets/_preview.blade.php ================================================ entity; if (empty($entity) || $entity->isMissingChild()) { return; } $specificPreview = 'dashboard.widgets.previews.' . $entity->entityType->code; $customName = !empty($widget->conf('text')) ? str_replace('{name}', $entity->name, $widget->conf('text')) : null; \App\Facades\Dashboard::add($entity); foreach ($entity->mentions as $mention) { if (!$mention->isEntity() || !$mention->target) { continue; } \App\Facades\Mentions::preloadEntity($mention->target); } ?> @if(view()->exists($specificPreview)) @include($specificPreview, ['entity' => $entity]) @else @endif ================================================ FILE: resources/views/dashboard/widgets/_random.blade.php ================================================ ================================================ FILE: resources/views/dashboard/widgets/_recent.blade.php ================================================ @inject('moduleService', 'App\Services\Campaign\ModuleService') conf('entity'); $entities = $widget->entities(); if (($widget->conf('singular'))) { $entityString = !empty($entityType) ? (!$widget->conf('singular') ? $entityType : $moduleService->singular($entityType, 'entities.' . Str::plural($entityType))) : null; if ($entities->count() > 0) { $entity = $entities[0]; if (!$entity->isMissingChild()) { ?> @include('dashboard.widgets._preview', [ 'entity' => $entity, ]) conf('singular') ? $entityType : $moduleService->plural($entityType, 'entities.' . Str::plural($entityType))) : null; } ?>

    @if (!empty($widget->tags)) @foreach ($widget->tags as $tag) @endforeach @endif

    @if (!empty($widget->conf('singular')))

    {{ __('search.lookup.empty') }}

    @else
    @include('dashboard.widgets._recent_list')
    @endif
    ================================================ FILE: resources/views/dashboard/widgets/_recent_list.blade.php ================================================
    @livewire('entity-listing', ['widget' => $widget, 'campaign' => $campaign])
    ================================================ FILE: resources/views/dashboard/widgets/_selection.blade.php ================================================ @php $newWidgetListClass = 'rounded-xl border cursor-pointer btn-outline flex gap-2 p-2 px-4 items-center'; @endphp @if ($dashboard)

    {!! __('dashboard.widgets.create.helper', ['name' => $dashboard->name]) !!}

    @else

    {!! __('dashboard.widgets.create.helper-default') !!}

    @endif
    @php $calendarModule = \App\Models\EntityType::default()->where('code', 'calendar')->first(); @endphp @if($campaign->isOpen()) @endif @if(!empty($dashboard)) @endif @if ($withOnboarding) @endif @if ($withHelp) @endif
    ================================================ FILE: resources/views/dashboard/widgets/_welcome.blade.php ================================================ {{ __('dashboards/widgets/welcome.title', ['kanka' => config('app.name')]) }}

    {!! __('dashboards/widgets/welcome.intros.1', [ 'user' => auth()->check() ? '' . auth()->user()->name . '' : __('crud.users.unknown'), 'characters' => '' . __('entities.characters') . '', 'locations' => '' . __('entities.locations') . '', ]) !!}

    {!! __('dashboards/widgets/welcome.intros.2', [ 'new-entity' => ' ' . __('crud.create') . ' ', 'letter' => 'N', 'characters' => ' ' . __('entities.character') . '', 'entities' => '' . __('entities.entries') . ' ', ]) !!}

    {!! __('dashboards/widgets/welcome.intros.3', [ ]) !!}

    ================================================ FILE: resources/views/dashboard/widgets/calendar/_reminder.blade.php ================================================ remindable instanceof \App\Models\Post && !$reminder->remindable->entity) { return; } ?>
  • @if ($reminder->isPost()) {!! $reminder->remindable->name !!} ({!! $reminder->remindable->entity->name !!}) @else {!! $reminder->remindable->name !!} @endif @if (config('app.debug')) @if (isset($future)) ({{ $reminder->date() }}, in {{ $reminder->inDays() }} days) @else ({{ $reminder->date() }}, {{ $reminder->daysAgo() }} days ago) @endif @endif
    @if (!empty($reminder->comment)) @endif @if ($reminder->is_recurring) @endif
  • ================================================ FILE: resources/views/dashboard/widgets/calendar/body.blade.php ================================================ @inject ('reminderService', 'App\Services\Calendars\ReminderService') entity; if (empty($entity)) { return; } $calendar = $calendar ?? $entity->child; $upcomingEvents = $reminderService->calendar($calendar)->upcoming(); $previousEvents = $reminderService->past(); //$previousEvents = new \Illuminate\Support\Collection(); // Get the current day's weather effect. $weather = $calendar->calendarWeather() ->year($calendar->currentYear()) ->month($calendar->currentMonth()) ->where('day', $calendar->currentDay()) ->first(); $daysService = new \App\Services\Calendars\DaysService(); $totalDays = $daysService->calendar($calendar) ->intercalary(true) ->year($calendar->currentYear()) ->month($calendar->currentMonth()) ->daysToDate(); $moonService = new \App\Services\Calendars\MoonService(); $moonService->calendar($calendar); $moonService->build( $totalDays, $calendar->daysInYear() ); $currentMoons = $moonService->get($calendar->currentDay()); $weekdays = $calendar->weekdays(); $weekdayCount = count($weekdays); $currentWeekdayName = null; if ($weekdayCount > 0) { $weekdayIndex = (($totalDays + $calendar->start_offset + $calendar->currentDay() - 1) % $weekdayCount + $weekdayCount) % $weekdayCount; $currentWeekdayName = $weekdays[$weekdayIndex] ?? null; } ?>
    @can('update', $entity) {{ __('dashboard.widgets.calendar.actions.previous') }} {{ $calendar->niceDate() }} {{ __('dashboard.widgets.calendar.actions.next') }} @else {{ $calendar->niceDate() }} @endcan @if (!empty($currentMoons))
    @foreach ($currentMoons as $moon) @endforeach
    @endif
    @if ($currentWeekdayName)
    {{ $currentWeekdayName }}
    @endif @if ($weather)
    {{ $weather->weatherName() }}
    @endif @if ($previousEvents->isNotEmpty())
    {{ __('dashboard.widgets.calendar.previous_events') }} {{ __('helpers.calendar-widget.info') }}
      @foreach ($previousEvents->take(5) as $reminder) @includeWhen($reminder->remindable, 'dashboard.widgets.calendar._reminder') @endforeach
    @endif @if ($upcomingEvents->isNotEmpty())
    {{ __('dashboard.widgets.calendar.upcoming_events') }} {{ __('helpers.calendar-widget.info') }}
      @foreach ($upcomingEvents->take(5) as $reminder) @includeWhen($reminder->remindable, 'dashboard.widgets.calendar._reminder', ['future' => true]) @endforeach
    @endif
    ================================================ FILE: resources/views/dashboard/widgets/forms/_boosted.blade.php ================================================
    @include ('partials.boost_icon')

    {!! __('callouts.premium.multiple', ['campaign' => $campaign->name]) !!}

    @can('boost', auth()->user()) {!! __('settings/premium.actions.unlock', ['campaign' => $campaign->name]) !!} @else {!! __('callouts.premium.learn-more') !!} @endif
    ================================================ FILE: resources/views/dashboard/widgets/forms/_calendar.blade.php ================================================ @php $boosted = $campaign->boosted() @endphp
    @include('cruds.fields.entity', [ 'required' => true, 'allowClear' => false, 'allowNew' => false, 'route' => 'search.calendars'])
    @include('dashboard.widgets.forms._name') @include('dashboard.widgets.forms._width') @includeWhen(!empty($dashboards), 'dashboard.widgets.forms._dashboard')
    @includeWhen(!$boosted, 'dashboard.widgets.forms._boosted') @include('dashboard.widgets.forms._class') ================================================ FILE: resources/views/dashboard/widgets/forms/_campaign.blade.php ================================================

    {{ __('dashboard.widgets.campaign.helper') }}

    ================================================ FILE: resources/views/dashboard/widgets/forms/_class.blade.php ================================================ ================================================ FILE: resources/views/dashboard/widgets/forms/_dashboard.blade.php ================================================ ================================================ FILE: resources/views/dashboard/widgets/forms/_display.blade.php ================================================ @php $displayOptions = [ 0 => __('dashboard.widgets.preview.displays.expand'), 1 => __('dashboard.widgets.preview.displays.full'), 2 => __('entries/tabs.properties'), ]; @endphp ================================================ FILE: resources/views/dashboard/widgets/forms/_gallery.blade.php ================================================ @inject('imageModel', 'App\Models\Image') @php $boosted = $campaign->premium(); $folders = $imageModel::where('campaign_id', $campaign->id) ->where('is_folder', true) ->orderBy('name', 'asc') ->pluck('name', 'id') ->prepend(__('crud.select'), '') ->toArray(); @endphp conf('show_name') : false)) checked="checked" @endif id="config-show-name" /> @include('dashboard.widgets.forms._name') @include('dashboard.widgets.forms._width') @includeWhen(!empty($dashboards), 'dashboard.widgets.forms._dashboard') @includeWhen(!$boosted, 'dashboard.widgets.forms._boosted') @include('dashboard.widgets.forms._class') ================================================ FILE: resources/views/dashboard/widgets/forms/_header.blade.php ================================================ @php $boosted = $campaign->boosted() @endphp @include('dashboard.widgets.forms._name') @include('dashboard.widgets.forms._width') @include('dashboard.widgets.forms._size') @include('cruds.fields.entity', ['label' => __('dashboard.widgets.fields.optional-entity')]) @includeWhen(!$boosted, 'dashboard.widgets.forms._boosted') @include('dashboard.widgets.forms._class') ================================================ FILE: resources/views/dashboard/widgets/forms/_header_select.blade.php ================================================ conf('entity-header') : false)) checked="checked" @endif id="config-entity-header" @if (!$boosted) disabled="disabled" @endif /> ================================================ FILE: resources/views/dashboard/widgets/forms/_help.blade.php ================================================

    {{ __('dashboards/widgets/help.description') }}

    ================================================ FILE: resources/views/dashboard/widgets/forms/_join.blade.php ================================================

    {{ __('dashboards/widgets/join.description') }}

    ================================================ FILE: resources/views/dashboard/widgets/forms/_name.blade.php ================================================ ================================================ FILE: resources/views/dashboard/widgets/forms/_onboarding.blade.php ================================================

    {{ __('dashboards/widgets/onboarding.description') }}

    ================================================ FILE: resources/views/dashboard/widgets/forms/_preview.blade.php ================================================ @php $boosted = $campaign->boosted() @endphp
    @include('cruds.fields.entity', ['required' => true])
    @include('dashboard.widgets.forms._display') @include('dashboard.widgets.forms._name') @include('dashboard.widgets.forms._width') @includeWhen(!empty($dashboards), 'dashboard.widgets.forms._dashboard')
    @includeWhen(!$boosted, 'dashboard.widgets.forms._boosted') @include('dashboard.widgets.forms._header_select') @include('dashboard.widgets.forms._related') @include('dashboard.widgets.forms._class') ================================================ FILE: resources/views/dashboard/widgets/forms/_random.blade.php ================================================ @inject('entityTypeService', 'App\Services\EntityTypeService') @php $boosted = $campaign->boosted(); $entityTypes = $entityTypeService ->campaign($campaign) ->exclude([config('entities.ids.bookmark')]) ->prepend(['' => __('dashboard.widgets.random.type.all')]) ->toSelect(); @endphp @include('dashboard.widgets.forms._name', ['random' => true]) @include('dashboard.widgets.forms._display') @include('dashboard.widgets.forms._width') @include('dashboard.widgets.forms._tags') @includeWhen(!empty($dashboards), 'dashboard.widgets.forms._dashboard') @includeWhen(!$boosted, 'dashboard.widgets.forms._boosted') @include('dashboard.widgets.forms._header_select') @include('dashboard.widgets.forms._related') @include('dashboard.widgets.forms._class') ================================================ FILE: resources/views/dashboard/widgets/forms/_recent.blade.php ================================================ @inject('entityTypeService', 'App\Services\EntityTypeService') @php $advancedFilters = [ '' => '', 'unmentioned' => __('dashboard.widgets.recent.advanced_filters.unmentioned'), 'mentionless' => __('dashboard.widgets.recent.advanced_filters.mentionless'), ]; $boosted = $campaign->boosted(); @endphp @php $singularChecked = old('config[singular]', isset($model) ? $model->conf('singular') : false); @endphp @include('dashboard.widgets.forms._tags')
    @if($campaign->boosted()) @include('dashboard.widgets.forms._header_select') @include('dashboard.widgets.forms._related') @else

    {!! __('dashboard.widgets.advanced_options_boosted', [ 'boosted_campaign' => '' . __('concept.premium-campaigns') . '']) !!}

    @endif
    @include('dashboard.widgets.forms._name') @include('dashboard.widgets.forms._width') @includeWhen(!empty($dashboards), 'dashboard.widgets.forms._dashboard')
    @includeWhen(!$boosted, 'dashboard.widgets.forms._boosted') @include('dashboard.widgets.forms._class') ================================================ FILE: resources/views/dashboard/widgets/forms/_related.blade.php ================================================
    conf('attributes') : false)) checked="checked" @endif id="config-attributes" @if (!$boosted) disabled="disabled" @endif />
    conf('relations') : false)) checked="checked" @endif id="config-relations" @if (!$boosted) disabled="disabled" @endif />
    conf('members') : false)) checked="checked" @endif id="config-members" @if (!$boosted) disabled="disabled" @endif />
    ================================================ FILE: resources/views/dashboard/widgets/forms/_size.blade.php ================================================ @php $options = [ 'h1' => 'H1', 'h2' => 'H2', null => 'H3', 'h4' => 'H4', 'h5' => 'H5', 'h6' => 'H6', ]; @endphp ================================================ FILE: resources/views/dashboard/widgets/forms/_tags.blade.php ================================================

    {{ __('dashboard.widgets.recent.tags') }}

    ================================================ FILE: resources/views/dashboard/widgets/forms/_welcome.blade.php ================================================

    {{ __('dashboards/widgets/welcome.description') }}

    ================================================ FILE: resources/views/dashboard/widgets/forms/_width.blade.php ================================================ @php $options = [ 0 => __('dashboard.widgets.widths.0'), 12 => __('dashboard.widgets.widths.12'), 3 => __('dashboard.widgets.widths.3'), 4 => __('dashboard.widgets.widths.4'), 6 => __('dashboard.widgets.widths.6'), 8 => __('dashboard.widgets.widths.8'), 9 => __('dashboard.widgets.widths.9') ]; @endphp ================================================ FILE: resources/views/dashboard/widgets/forms/create.blade.php ================================================ @php $mode = 'create'; @endphp @include('partials.forms._dialog', [ 'title' => __('dashboard.widgets.create.title'), 'content' => 'dashboard.widgets.forms._' . $widget, ]) @if(empty($dashboards) && !empty($dashboard)) @endif ================================================ FILE: resources/views/dashboard/widgets/forms/edit.blade.php ================================================ @include('partials.errors') @include('partials.forms._dialog', [ 'mode' => 'edit', 'title' => __('dashboards/widgets/' . $model->widget->value . '.name'), 'titleIcon' => '', 'content' => 'dashboard.widgets.forms._' . $widget, 'deleteID' => '#delete-form-widget-' . $model->id, ]) @if(empty($dashboards) && !empty($dashboard)) @endif ================================================ FILE: resources/views/dashboard/widgets/previews/_attributes.blade.php ================================================ @if(!$campaign->boosted() || !$widget->showAttributes()) @php return @endphp @endif @inject('attributeService', 'App\Services\AttributeService')
      @include('entities.components.attributes')
    ================================================ FILE: resources/views/dashboard/widgets/previews/_full.blade.php ================================================ {!! $slot !!} @if ($entity->hasEntry())
    {!! $entity->parsedEntry() !!}
    @endif @include('dashboard.widgets.previews._members') @include('dashboard.widgets.previews._relations') @include('dashboard.widgets.previews._attributes') ================================================ FILE: resources/views/dashboard/widgets/previews/_members.blade.php ================================================ @if(!$campaign->boosted() || !$widget->showMembers($entity)) @php return @endphp @endif @php $child = null; if (isset($model)) { $child = $model; } else { $child = $entity->child; } $members = $entity->isFamily() ? $child->members()->with('entity')->orderBy('name')->get() : $child->members()->with(['character', 'character.entity']) ->leftJoin('characters', 'characters.id', '=', 'organisation_member.character_id') ->orderBy('characters.name') ->get(); @endphp
    @if($entity->isFamily())
    @foreach ($members as $member) @if (empty($member->entity)) @continue @endif
    @endforeach
    @else
    @foreach ($members as $member) @if (empty($member->character)) @continue @endif
    {{ $member->role }}
    @endforeach
    @endif
    ================================================ FILE: resources/views/dashboard/widgets/previews/_preview.blade.php ================================================
    {!! $slot !!} @if ($entity->hasEntry())
    {!! $entity->parsedEntry() !!}
    @endif @include('dashboard.widgets.previews._members') @include('dashboard.widgets.previews._relations') @include('dashboard.widgets.previews._attributes')
    {{ __('Click to toggle') }}
    ================================================ FILE: resources/views/dashboard/widgets/previews/_relations.blade.php ================================================ @if(!$campaign->boosted() || !$widget->showRelations()) @php return @endphp @endif @inject('attributeService', 'App\Services\AttributeService')
    @include('entities.components.relations')
    ================================================ FILE: resources/views/dashboard/widgets/previews/character.blade.php ================================================ ================================================ FILE: resources/views/dashboard/widgets/previews/conversation.blade.php ================================================ child; ?> {{ $conversation->participants()->count() }}
    @foreach ($conversation->messages()->with(['character', 'user'])->orderByDesc('created_at')->take(5)->get() as $message) @if (empty($message->user) && empty($message->character)) @continue @endif
    @if ($message->isMine()) {{ $message->created_at->diffForHumans() }} @if ($message->user) {{ $message->user->name }} @elseif ($message->character) {{ $message->character->name }} @endif @elseif (!empty($message->user_id)) {{ $message->user ? $message->user->name : null }} {{ $message->created_at->diffForHumans() }} @else {{ $message->character->name }} {{ $message->created_at->diffForHumans() }} @endif
    @if (!empty($message->user_id)) @if ($message->user->hasAvatar()) @endif @elseif (!empty($message->character_id)) {{ $message->character->name }} @endif
    {{ $message->message }}
    @endforeach
    ================================================ FILE: resources/views/dashboard/widgets/previews/map.blade.php ================================================ child; ?> @if(!$map->explorable()) {!! $entity->name !!}

    {{ __('maps.errors.dashboard.missing') }}

    @php return @endphp @endif @section('scripts') @parent @include('maps._setup') @endsection @section('styles') @parent @endsection ================================================ FILE: resources/views/dashboard/widgets/previews/quest.blade.php ================================================ @if (!empty($entity->child?->instigator))
    {{ __('quests.fields.instigator') }}
    @endif
    ================================================ FILE: resources/views/dashboard/widgets/previews/random-map.blade.php ================================================ {{ __('Rendering a random map isn\'t currently supported.') }} ================================================ FILE: resources/views/dashboard/widgets/selection/footer.blade.php ================================================ ================================================ FILE: resources/views/dashboard/widgets/selection.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('dashboard.widgets.create.title'), 'content' => 'dashboard.widgets._selection', 'footer' => 'dashboard.widgets.selection.footer', ]) ================================================ FILE: resources/views/datagrids/subscription.blade.php ================================================ {{ __('datagrids.subscription.title') }}

    {!! __('datagrids.subscription.helper', ['max' => '100']) !!}

    {{ __('datagrids.subscription.cta') }} ================================================ FILE: resources/views/dice_rolls/_results.blade.php ================================================ child->diceRollResults()->with('creator')->orderBy('created_at', 'DESC')->paginate(); ?>

    {{ __('dice_rolls.index.actions.results') }}

    @can('view', $entity) {{ __('dice_rolls.results.actions.add') }} @endcan
    @foreach ($r as $relation) @endforeach
    {{ __('dice_rolls.results.fields.creator') }} {{ __('dice_rolls.results.fields.result') }} {{ __('dice_rolls.results.fields.date') }}
    {{ $relation->creator->name }} {{ $relation->results }} {{ $relation->updated_at->diffForHumans() }} @can('delete', $entity) @endcan
    @if ($r->hasPages())
    {{ $r->fragment('tab_relation')->links() }}
    @endif ================================================ FILE: resources/views/dice_rolls/datagrid.blade.php ================================================ {!! $datagrid ->columns([ // Avatar [ 'type' => 'avatar' ], // Name 'name', 'parameters', [ 'label' => __('entities.character'), 'field' => 'character.name', 'render' => function(\App\Models\DiceRoll $model) use ($campaign) { if ($model->character && $model->character->entity) { return \Illuminate\Support\Facades\Blade::renderComponent( new \App\View\Components\EntityLink($model->character->entity, $campaign) ); } } ], [ 'label' => __('dice_rolls.fields.rolls'), 'render' => function($model) { return $model->diceRollResults()->count(); }, 'disableSort' => true, ], [ 'type' => 'is_private', ], ]) ->options([ 'route' => 'dice_rolls.index', 'baseRoute' => 'dice_rolls', 'trans' => 'dice_rolls.fields.', ] ) !!} ================================================ FILE: resources/views/dice_rolls/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.character', ['name' => 'character_id']) @include('cruds.fields.tags') {{ __('dice_rolls.hints.parameters') }} @include('cruds.fields.image') ================================================ FILE: resources/views/dice_rolls/results.blade.php ================================================ @extends('layouts.app', [ 'title' => __('dice_roll_results.index.title'), 'seoTitle' => __('dice_roll_results.index.title') . ' - ' . $campaign->name, 'breadcrumbs' => false, 'canonical' => true, 'bodyClass' => 'kanka-dice-roll-results', ]) @section('entity-header')

    {!! __('dice_roll_results.index.title') !!}

    @endsection @section('content') @include('partials.errors') @include('ads.top')
    @foreach ($models as $model) @endforeach
    {{ __('entities.dice_roll') }} {{ __('entities.character') }} {{ __('crud.fields.creator') }} {{ __('dice_rolls.fields.results') }} {{ __('dice_rolls.results.fields.date') }}
    {!! $model->user->name !!} {!! \Illuminate\Support\Number::format($model->results ?? 0) !!} {{ $model->updated_at->diffForHumans() }}
    @if($models->hasPages())
    {{ $models->onEachSide(0)->links() }}
    @endif
    @endsection ================================================ FILE: resources/views/dice_rolls/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('dice_rolls._results') @include('entities.components.posts')
    @include('entities.components.pins')
    ================================================ FILE: resources/views/editors/ckeditor.blade.php ================================================ @section('scripts') @endsection @section('styles') @endsection ================================================ FILE: resources/views/editors/editor.blade.php ================================================ @php $editor = auth()->user()->editor; if (request()->has('tiptap')) { $editor = 'tiptap'; } elseif (request()->has('tinymce')) { $editor = 'legacy'; } elseif (request()->has('summernote')) { $editor = ''; } @endphp @if ($editor === 'tiptap') @once @include('editors.tiptap') @endonce @elseif($editor === 'legacy') @include('editors.tinymce') @else @once @include('editors.summernote') @endonce @endif ================================================ FILE: resources/views/editors/summernote.blade.php ================================================ @section('scripts') @parent @vite('resources/js/editors/summernote.js') @if (!in_array(app()->getLocale(), ['en-US', 'en'])) @endif @endsection @section('styles') @parent @if (config('app.asset_url')) @else @endif @endsection @section('modals') @parent
    entity) data-attributes="{{ route('search.attributes', [$campaign, $model->entity]) }}" @elseif (!empty($entity)) data-attributes="{{ route('search.attributes', [$campaign, $entity]) }}" @endif data-locale="{{ app()->getLocale() }}">
    @if(isset($campaign) && $campaign !== null) @endif @endsection ================================================ FILE: resources/views/editors/tinymce.blade.php ================================================ @section('styles') @parent @if (config('app.asset_url')) @else @endif @endsection @section('scripts') @parent @endsection ================================================ FILE: resources/views/editors/tiptap.blade.php ================================================ @section('scripts') @parent @vite('resources/js/editors/tiptap/index.js') @endsection ================================================ FILE: resources/views/editors/tiptap_editor.blade.php ================================================ @php $fieldName = $fieldName ?? 'entry'; $contentModel = $source ?? $model ?? null; $contentFieldName = $contentFieldName ?? $fieldName; $content = $contentModel?->{$contentFieldName} ?? ''; @endphp
    ================================================ FILE: resources/views/emails/2024/base.blade.php ================================================
    @include('emails.2024.header') @yield('content') @include('emails.2024.footer')
    ================================================ FILE: resources/views/emails/2024/footer.blade.php ================================================
    Owlchester SNC
    Chemin Ella Maillart 16, Geneve
    Switzerland

    @if ($layout != 'welcome') This email was automatically sent to you by Kanka.io. @else {{ __('emails/welcome/2024.why') }} @endif

    ================================================ FILE: resources/views/emails/2024/header.blade.php ================================================
    ================================================ FILE: resources/views/emails/activity/email-change-md.blade.php ================================================ # Updated Email {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }}, {{ __('emails/activity/email.first', [ 'email' => '[' . $user->email . '](mailto:' . $user->email . ')' ]) }} {!! __('emails/activity/password.help', [ 'email' => '[' . config('app.email') . '](mailto:' . config('app.email') . ')' ]) !!} _Jay & Jon_ ================================================ FILE: resources/views/emails/activity/password.blade.php ================================================ # New password {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }}, {{ __('emails/activity/password.first') }} {!! __('emails/activity/password.help', [ 'email' => '[' . config('app.email') . '](mailto:' . config('app.email') . ')' ]) !!} _Jay & Jon_ ================================================ FILE: resources/views/emails/base.blade.php ================================================
    @yield('content')
    ================================================ FILE: resources/views/emails/purge/first/md.blade.php ================================================ {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }}, @if (!empty($campaigns)) {{ __('emails/purge/first.intro_campaigns', [ 'amount' => config('purge.users.first.limit'), 'duration' => config('purge.users.first.inactivity'), ]) }} {{ __('emails/purge/first.warning.campaigns', [ 'email' => $user->email ]) }} @foreach ($campaigns as $campaign) - [{{ $campaign->name }}]({{ route('dashboard', $campaign) }}) @endforeach @else {{ __('emails/purge/first.intro_account', [ 'amount' => config('purge.users.first.limit'), 'duration' => config('purge.users.first.inactivity') ]) }} {{ __('emails/purge/first.warning.account', [ 'email' => $user->email ]) }} @endif {{ __('emails/purge/first.keep', ['amount' => config('purge.users.first.limit')]) }} {{ __('emails/purge/first.assure') }} {!! __('emails/purge/first.help', [ 'discord' => '[Discord](https://kanka.io/go/discord)', 'email' => '[' . config('app.email') . '](mailto:' . config('app.email') . ')', ]) !!} _Jay & Jon_ ================================================ FILE: resources/views/emails/purge/second/md.blade.php ================================================ {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }}, {{ __('emails/purge/second.intro', [ 'amount' => config('purge.users.second.limit'), 'duration' => config('purge.users.first.inactivity'), 'email' => $user->email ]) }} @if (!empty($campaigns)) {{ __('emails/purge/first.warning.campaigns', [ 'email' => $user->email ]) }} @foreach ($campaigns as $campaign) - [{{ $campaign->name }}]({{ route('dashboard', $campaign) }}) @endforeach @else {{ __('emails/purge/first.warning.account', [ 'email' => $user->email ]) }} @endif {{ __('emails/purge/first.keep', ['amount' => config('purge.users.second.limit')]) }} {{ __('emails/purge/first.assure') }} {!! __('emails/purge/first.help', [ 'discord' => '[Discord](https://kanka.io/go/discord)', 'email' => '[' . config('app.email') . '](mailto:' . config('app.email') . ')', ]) !!} _Jay & Jon_ ================================================ FILE: resources/views/emails/subscriptions/cancelled/md.blade.php ================================================ # Subscription cancellation [{{ $user->name }}](https://admin.kanka.io/users/{{ $user->id }}) cancelled. **Reason:** {{ __('settings.subscription.cancel.options.' . ($cancellation->reason === 'custom' ? 'other' : $cancellation->reason)) }} @if (!empty($cancellation->secondary)) **Secondary:** {{ __('subscriptions/cancellation.secondary.' . $cancellation->reason . '.' . $cancellation->secondary) }} @endif @if (!empty($cancellation->custom)) **Custom:** > {!! nl2br(e($cancellation->custom)) !!} @endif **Subscribed since:** {{ $user->subscription('kanka')?->created_at->isoFormat('MMMM D, Y') }} ================================================ FILE: resources/views/emails/subscriptions/cancelled/user-md.blade.php ================================================ **Cancellation confirmation** We're sorry to see you go, {{ $user->name }}. You’re always welcome to rejoin our party and renew your subscription at any time to: - Regain access to all your changes, exactly the way you left them - Ad free experience - Increased upload size - [And way more](https:{{ \App\Facades\Domain::toFront('pricing') }}) This email serves as confirmation that you have cancelled the renewal of your **{{ $cancellation->tier }}** subscription on Kanka. You will continue to have access to your subscription bonuses until **{{ $user->subscription('kanka')?->ends_at?->isoFormat('MMMM D, Y') }}**. Thank you for being part of our community! __Jay & Jon__ ================================================ FILE: resources/views/emails/subscriptions/changed/md.blade.php ================================================ # Subscription change Changed subscription for {{ $user->pledge }} [{{ $user->name }}](https://admin.kanka.io/users/{{ $user->id }}) (#{{ $user->id }}) [{{ $user->email }}](mailto:{{ $user->email }}) @if (!empty($custom)) **Reason provided:** {!! nl2br(e($custom)) !!} @elseif (!empty($reason)) **Reason provided:** {{ __('settings.subscription.cancel.options.' . $reason) }} @endif ================================================ FILE: resources/views/emails/subscriptions/charge-failed/user-md.blade.php ================================================ # Subscription issue {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }}, This is an automatic notification. We were unable to charge your card for your Kanka subscription. Our system will try again in a few days. In the meantime, please verify your card details in your [billing information](https://app.kanka.io/settings/billing/payment-method?s=charge-failed"). If we are unable to charge your card, we'll unfortunately have to cancel your subscription to Kanka. {{ __('emails/subscriptions/upcoming.closing') }} __Jay & Jon__ ================================================ FILE: resources/views/emails/subscriptions/deleted/user-html.blade.php ================================================

    Dear {{ $user->name }},

    We're having trouble charging your payment method. Please verify your details in your Kanka account and update them if necessary. We'll try again in a few days. If the charge fails 3 times, your subscription will be automatically cancelled.

    The Kanka Team

    ================================================ FILE: resources/views/emails/subscriptions/expiring/user-md.blade.php ================================================ # {{ __('emails/subscriptions/expiring.title') }} {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }}, {{ __('emails/subscriptions/expiring.primary', [ 'brand' => ucfirst($user->pm_type), 'last' => $user->pm_last_four, ]) }} {!! __('emails/subscriptions/expiring.valid', ['action' => '[' . __('emails/subscriptions/expiring.action') . '](' . route('billing.payment-method') . ')']) !!} {{ __('emails/subscriptions/upcoming.closing') }} __Jay & Jon__ ================================================ FILE: resources/views/emails/subscriptions/failed/user-html.blade.php ================================================ @extends('emails.base', [ 'utmSource' => 'subscription', 'utmCampaign' => 'expiring-card' ]) @section('content')

    Subscription issue

    {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }},

    This is an automatic notification. We tried and failed to charge your card three times, and subsequently your subscription has been automatically cancelled. We hope to see you back soon!

    {{ __('emails/subscriptions/upcoming.closing') }}< The Kanka Team

    @endsection ================================================ FILE: resources/views/emails/subscriptions/new/elemental.blade.php ================================================ Hi {{ $user->name }}, Thank you for becoming an {{ $tier->name }}! We just wanted to write a few words to let you know that we appreciate your support, and that we’re here if you need anything. You can contact us via email, or you can also link your Kanka account to [Discord](https://kanka.io/go/discord) in your [account settings]({{ route('settings.apps') }}), and get access to the exclusive {{ $tier->name }} channel there. You can now enjoy Kanka with bigger upload sizes, vote on the [roadmap]({{ route('roadmap') }}), and [premium campaigns]({{ route('settings.premium') }}). Unlock premium features Part of being an Elemental means that you get more say in how we shape community votes. If you have any priorities that you would like to see added, feel free to share them with us, and we will see how we can fit them into the votes, or if they overlap with requests made by other Elementals. Good to have you among us {{ $user->name }}, __Jay & Jon__ ================================================ FILE: resources/views/emails/subscriptions/new/md.blade.php ================================================ @if ($trial) # Free trial conversion @else # New subscription @endif - **User ID:** {{ $user->id }} - **Username:** [{{ $user->name }}](https://admin.kanka.io/users/{{ $user->id }}) - **Email:** {{ $user->email }} - **Account age:** {{ $user->created_at->diffForHumans() }} ({{ $user->created_at->format('d.m.Y') }}) - **Subscription tier:** {{ $user->pledge ?? 'N/A' }} - **Subscription currency:** {{ $user->currencySymbol() }} - **User country:** {{ $country ?? 'N/A' }} @if ($trial) - **Trial started:** {{ $trial->created_at->format('d.m.Y') }} @endif @if ($lastCancel) --- ### Previous subscription info - **Previously cancelled:** {{ $lastCancel->tier }} {{ $lastCancel->created_at->diffForHumans() }} ({{ $lastCancel->created_at->format('d.m.Y') }}) - **Reason provided:** {{ $lastCancel->reason }} @if (!empty($lastCancel->custom)) - **Custom message:** {!! nl2br(e($lastCancel->custom)) !!} @endif @endif @if ($discord = $user->apps->where('app', 'discord')->first()) - **Discord:** {{ $discord->settings['username'] }} @endif @if ($user->referrer) - **Referred by:** [{{ $user->referrer->name }}](https://admin.kanka.io/users/{{ $user->referrer->id }}) @endif @if (!empty($user->settings['tracking'])) - **Ad campaign:** {{ is_array($user->settings['tracking']) ? collect($user->settings['tracking'])->implode(' ') : $user->settings['tracking'] }} @endif ================================================ FILE: resources/views/emails/subscriptions/new/owlbear.blade.php ================================================ Hi {{ $user->name }}, Thank you for becoming a {{ $tier->name }}! We just wanted to write a few words to let you know that we appreciate your support, and that we’re here if you need anything. You can contact us via email, or you can also link your Kanka account to [Discord](https://kanka.io/go/discord) in your [account settings]({{ route('settings.apps') }}), and get access to the exclusive {{ $tier->name }} channel there. You can now enjoy Kanka with bigger upload sizes, vote on the [roadmap]({{ route('roadmap') }}), and [premium campaigns]({{ route('settings.premium') }}). Unlock premium features Good to have you among us {{ $user->name }}, __Jay & Jon__ ================================================ FILE: resources/views/emails/subscriptions/new/wyvern.blade.php ================================================ Hi {{ $user->name }}, Thank you for becoming a {{ $tier->name }}! We just wanted to write a few words to let you know that we appreciate your support, and that we’re here if you need anything. You can contact us via email, or you can also link your Kanka account to [Discord](https://kanka.io/go/discord) in your [account settings]({{ route('settings.apps') }}), and get access to the exclusive {{ $tier->name }} channel there. You can now enjoy Kanka with bigger upload sizes, vote on the [roadmap]({{ route('roadmap') }}), and [premium campaigns]({{ route('settings.premium') }}). Unlock premium features Good to have you among us {{ $user->name }}, __Jay & Jon__ ================================================ FILE: resources/views/emails/subscriptions/paypal-expiring/user.blade.php ================================================ {{ __('emails/subscriptions/paypal-expiring.dear', ['name' => $user->name]) }}, {{ __('emails/subscriptions/paypal-expiring.intro', ['date' => $expiryDate]) }} {{ __('emails/subscriptions/paypal-expiring.loss.title') }} @if ($premiumCampaignName) - {{ trans_choice('emails/subscriptions/paypal-expiring.loss.campaign', $premiumCampaignCount - 1, ['campaign' => $premiumCampaignName, 'count' => $premiumCampaignCount - 1]) }} @if ($players > 0) - {{ trans_choice('emails/subscriptions/paypal-expiring.loss.players', $players, ['count' => $players]) }} @endif @if ($plugins > 0) - {{ trans_choice('emails/subscriptions/paypal-expiring.loss.plugins', $plugins, ['count' => $plugins]) }} @endif @endif - {{ __('emails/subscriptions/paypal-expiring.loss.ads') }} @if ($discord) - {{ __('emails/subscriptions/paypal-expiring.loss.discord', ['role' => $user->pledge]) }} @endif {{ __('emails/subscriptions/paypal-expiring.cta') }} {{ __('emails/subscriptions/paypal-expiring.closing') }} __Jay & Jon__ ================================================ FILE: resources/views/emails/subscriptions/trial/md.blade.php ================================================ # New subscription trial accepted New subscription trial for user [{{ $user->name }}](https://admin.kanka.io/users/{{ $user->id }}) (#{{ $user->id }}) {{ $user->email }}. Account created {{ $user->created_at->diffForHumans() }} ({{ $user->created_at->format('d.m.Y') }}). @if ($discord = $user->apps->where('app', 'discord')->first()) Discord: {{ $discord->settings['username'] }} @endif ================================================ FILE: resources/views/emails/subscriptions/upcoming/user.blade.php ================================================ {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }}, {{ __('emails/subscriptions/upcoming.primary', [ 'brand' => ucfirst($user->pm_type), 'last' => $user->pm_last_four, 'date' => now()->addDays(15)->isoFormat('MMMM D, Y') ]) }} {{ __('emails/subscriptions/upcoming.notice') }} {{ __('emails/subscriptions/upcoming.valid') }} {!! __('emails/subscriptions/upcoming.cancel', ['link' => '[' . __('emails/subscriptions/upcoming.link') . '](' . route('settings.subscription') . ')']) !!} {{ __('emails/subscriptions/upcoming.closing') }} __Jay & Jon_ ================================================ FILE: resources/views/emails/subscriptions/validation/user-html.blade.php ================================================ @extends('emails.base', [ 'utmSource' => 'validation', 'utmCampaign' => 'failed-charge' ]) @section('content')

    Email Validation

    {{ __('emails/subscriptions/upcoming.dear', ['name' => $user->name]) }},

    This is an automatic notification.

    To validate the email for your Kanka account click here. This link will expire in 24 hours.

    If the above link doesnt work, open the following URL in your web browser {{ $url }}

    {{ __('emails/subscriptions/upcoming.closing') }}
    The Kanka Team

    @endsection ================================================ FILE: resources/views/emails/welcome/2024/html.blade.php ================================================ @extends('emails.2024.base', [ 'utmSource' => 'welcome', 'utmCampaign' => 'onboarding' ]) @section('content')

    {!! __('emails/welcome/2024.header', ['name' => $user->name]) !!}

    {!! __('emails/welcome/2024.lead_1', ['name' => $user->name]) !!}

    {!! __('emails/welcome/2024.lead_2', ['name' => $user->name]) !!}

    {{ __('emails/welcome/2024.what_now') }}

    {!! __('emails/welcome/2024.what_1', [ 'start' => '' . __('emails/welcome/2024.what_new') . '' ]) !!}

    {{ __('emails/welcome/2024.what_2') }}

    {{ __('emails/welcome/2024.closing') }}


    Jay & Jon


    {!! __('emails/welcome/2024.ps', [ 'discord' => 'Discord', 'email' => '' . config('app.email') . '', ]) !!}


    @endsection ================================================ FILE: resources/views/emails/welcome/2024/text.blade.php ================================================ # **{!! __('emails/welcome/2024.header', ['name' => $user->name]) !!}** {!! __('emails/welcome/2024.lead_1') !!} {!! __('emails/welcome/2024.lead_2') !!} ## **{!! __('emails/welcome/2024.what_now') !!}** {!! __('emails/welcome/2024.what_1', [ 'start' => __('emails/welcome/2024.what_new'), ]) !!} {!! __('emails/welcome/2024.what_2') !!} - {!! __('emails/welcome/2024.what_3', [ 'kb' => '[' . __('footer.kb') . '](' . \App\Facades\Domain::toFront('kb') . ')', ]) !!} - {!! __('emails/welcome/2024.what_4', [ 'doc' => '[' . __('footer.documentation') . '](https://docs.kanka.io/en/latest/index.html)', ]) !!} - {!! __('emails/welcome/2024.what_5', [ 'campaigns' => '[' . __('footer.public-campaigns') . '](https://kanka.io/campaigns)', ]) !!} {!! __('emails/welcome/2024.closing') !!} _Jay and Jon_ {!! __('emails/welcome/2024.ps', [ 'email' => '[' . config('app.email') . '](mailto:' . config('app.email') . ')', 'discord' => '[Discord](https://kanka.io/go/discord)', ]) !!} ================================================ FILE: resources/views/emails/welcome/html.blade.php ================================================ @extends('emails.base', [ 'utmSource' => 'newsletter', 'utmCampaign' => 'onboarding' ]) @section('content')
    {{ __('emails/welcome.2023.preview', ['name' => $user->name]) }}

    {{ __('emails/welcome.2023.intro.header', [ 'name' => $user->name ]) }}

    {{ __('emails/welcome.2023.intro.text_1', ['name' => $user->name]) }}

    {{ __('emails/welcome.2023.intro.text_2') }}

    {{ __('emails/welcome.2023.intro.link') }}

    {{ __('emails/welcome.2023.basics.title') }}

    {!! __('emails/welcome.2023.basics.text_1', [ 'kb' => '' . __('footer.kb') . '', 'doc' => ' ' . __('footer.documentation') . '', ]) !!}

    {{ __('emails/welcome.2023.chat.title') }}

    {!! __('emails/welcome.2023.chat.text_1', [ 'discord' => 'Discord', 'email' => '' . config('app.email') . '' ]) !!}

    Jay & Jon @endsection ================================================ FILE: resources/views/emails/welcome/text.blade.php ================================================ {!! __('emails/welcome.2023.intro.text_1', [ 'name' => $user->name, ]) !!} {!! __('emails/welcome.2023.intro.text_2') !!} {{ __('emails/welcome.2023.basics.title') }} {!! __('emails/welcome.2023.basics.text_1', [ 'kb' => __('footer.kb') . ' (' . \App\Facades\Domain::toFront('kb') . ')', 'doc' => __('footer.documentation') . ' (https://docs.kanka.io/en/latest/index.html)', ]) !!} {{ __('emails/welcome.2023.chat.title') }} {!! __('emails/welcome.2023.chat.text_1', [ 'email' => config('app.email'), 'discord' => 'Discord (https://kanka.io/go/discord)', ]) !!} Jay & Jon ================================================ FILE: resources/views/entities/components/_files.blade.php ================================================ @foreach ($entity->files as $file) {{ $file->name }} @endforeach ================================================ FILE: resources/views/entities/components/assets.blade.php ================================================ @foreach ($entity->pinnedFiles as $asset) @if ($asset->hiddenImage()) @continue @endif @if ($asset->isAudio())
    {!! $asset->name !!}
    @else {!! $asset->name !!} @endif @endforeach ================================================ FILE: resources/views/entities/components/attributes.blade.php ================================================ starredAttributes(); ?> @if (count($attributes) > 0) @foreach ($attributes as $attribute)
    is_private) data-private="true" @endif> {!! $attribute->name() !!} @if ($attribute->isCheckbox()) @if ($attribute->value) @else {{ __('general.no') }} @endif @elseif ($attribute->isText())

    {!! nl2br($attribute->mappedValue()) !!}

    @elseif (!$attribute->isSection())

    {!! $attribute->mappedValue() !!}

    @endif
    @endforeach @endif @section('scripts') @parent @vite('resources/js/attributes.js') @endsection @section('modals') @parent @endsection ================================================ FILE: resources/views/entities/components/calendar.blade.php ================================================ @if ($entity->hasCalendar() && $model->calendar)

    {{ __('crud.fields.calendar_date') }}
    {{ $model->calendar->name }}

    @endif ================================================ FILE: resources/views/entities/components/elasped_events.blade.php ================================================ elapsedEvents; // Prepare the birth and death events $distinctCalendars = []; $birth = null; $death = null; foreach ($elapsed as $event) { if (empty($event->calendar) || $event->isCalendarDate()) { continue; } if ($event->isBirth()) { $distinctCalendars[$event->calendar_id]['birth'] = $event; } elseif ($event->isDeath()) { if (!isset($distinctCalendars[$event->calendar_id]['death'])) { $distinctCalendars[$event->calendar_id]['death'] = $event; continue; } // Already have a death? Take the new one if it's older $older = false; /** @var \App\Models\EntityEvent $previous */ $previous = $distinctCalendars[$event->calendar_id]['death']; if ($previous->year < $event->year) { $older = true; } elseif ($previous->year == $event->year) { if ($previous->month < $event->month) { $older = true; } elseif ($previous->month == $event->month) { if ($previous->day < $event->day) { $older = true; } } } if ($older) { $distinctCalendars[$event->calendar_id]['death'] = $event; } } } ?> @foreach ($distinctCalendars as $calendarId => $calendarEvents) @php /** * @var \App\Models\EntityEvent|null $birth * @var \App\Models\EntityEvent|null $death */ $birth = $calendarEvents['birth'] ?? null; $death = $calendarEvents['death'] ?? null; @endphp @if (!empty($birth) && !empty($death))
  • {{ __('characters.fields.life') }}
  • @elseif (!empty($birth))
  • {{ __('entities/events.types.birth') }}
    {{ $birth->readableDate() }} ({{ $birth->calcElapsed() }})
  • @elseif (!empty($death))
  • {{ __('entities/events.types.death') }}
    {{ $death->readableDate() }} ({{ $death->calcElapsed() }})
  • @endif @endforeach ================================================ FILE: resources/views/entities/components/entry.blade.php ================================================ @if ($entity->hasEntry())
    @if (auth()->check()) @can('update', [$entity]) @endcan @endif {!! $entity->parsedEntry() !!}
    @endif @include('ads.inline', ['cta' => true]) @includeWhen($entity->isCharacter() && $entity->child->is_appearance_pinned, 'characters.panels._appearance') @includeWhen($entity->isCharacter() && $entity->child->is_personality_pinned, 'characters.panels._personality') ================================================ FILE: resources/views/entities/components/header.blade.php ================================================ original(); $imagePath = Avatar::entity($entity)->size(192)->thumbnail(); $imagePathXL = Avatar::entity($entity)->size(400)->thumbnail(); $imagePathMobile = Avatar::entity($entity)->size(192)->thumbnail(); $imageVisibility = $entity->image ? $entity->image->visibility_id : null; $imageClass = (!empty($imageVisibility) ? 'visibility-' . strtolower($imageVisibility->name) : null); $entityType = $entityType ?? $entity->entityType; $addTagsUrl = route('entity.tags-add', [$campaign, $entity]); /** @var \App\Models\Tag[] $entityTags */ $entityTags = $entity->visibleTags(); $buttonsClass = 1; $headerStatus = $entity->status; if ($headerStatus && $headerStatus->icon) { $buttonsClass++; } if (auth()->check() && auth()->user()->isAdmin()) { $buttonsClass ++; } $hasBanner = false; if($campaign->boosted() && $entity->hasHeaderImage()) { $hasBanner = true; $headerImageUrl = $entity->getHeaderUrl(); $headerImageSquare = $entity->getHeaderUrl(400, 400); $headerImageS = $entity->getHeaderUrl(800, 267); $headerImageM = $entity->getHeaderUrl(1200, 400); $headerImageL = $entity->getHeaderUrl(1800, 400); $headerImageXL = $entity->getHeaderUrl(2400, 400); } $breadcrumb = Breadcrumb::campaign($campaign)->entity($entity)->list(); ?>
    @if ($imageUrl)
    @can('update', $entity) @if(isset($printing) && $printing) {{ $entity->name }} @endif @if (!isset($printing)) {{ $entity->name }} @endif @else @if(isset($printing) && $printing) {{ $entity->name }} @else {{ $entity->name }} @endif @endcan
    @endif
    1. @if (!empty($bookmark)) {!! $bookmark->name !!} @else {!! $breadcrumb['label'] !!} @endif

    {!! $entity->name !!}

    @if ($headerStatus && $headerStatus->icon) {{ $headerStatus->name() }} @endif @can('admin', $campaign) {{ __('entities/permissions.quick.screen-reader') }} @endif
    @if (($entity->isCharacter() || $entity->isLocation()) && !empty($entity->child->title))
    {!! $entity->child->title !!}
    @endif
    @if($entityTags->count() > 0) @foreach ($entityTags as $tag) @if (!$tag->entity) @continue @endif @include ('tags._badge') @endforeach @endif @can('update', $entity) {{ __('entities/tags.create.title') }} @endcan
    @if ($entity->entityType->isCustom()) @includeIf('entities.headers._custom') @else @includeIf('entities.headers._' . $entity->entityType->code) @endif
    @yield($entityHeaderActions ?? 'entity-header-actions')
    @if ($hasBanner && $entity->header && $entity->header->visibility_id !== \App\Enums\Visibility::All) @php $headerHelper = __('entities/image.gallery_permissions.' . strtolower($entity->header->visibility_id->name), ['admin' => $campaign->adminRoleName(), 'creator' => $entity->header->creator?->name]); @endphp @can('visibility', $entity->header) @else @endcan @endif @if ($hasBanner) {{ $entity->name }} header image @endif
    @section('modals') @parent @if (!$campaign->boosted())

    {{ __('entities/image.call-to-action') }}

    @can('boost', auth()->user()) {!! __('callouts.premium.unlock', ['campaign' => $campaign->name]) !!} @else

    {{ __('callouts.booster.limitation') }}

    {!! __('callouts.premium.learn-more') !!} @endif
    @endif
    @endsection @if ($entity->archived_at)
    {{ __('entries/archive.banner') }} @can('update', $entity) {{ __('entities/actions.unarchive.title') }} @endcan
    @endif @section('styles') @parent @endsection ================================================ FILE: resources/views/entities/components/history.blade.php ================================================ ================================================ FILE: resources/views/entities/components/links.blade.php ================================================ ================================================ FILE: resources/views/entities/components/members.blade.php ================================================ child->pinnedMembers; $previousRelation = null; ?> @foreach ($models as $member) @if(!empty($previousRelation) && $previousRelation == $member->role)
    @if ($entity->isCharacter() && $member->organisation->entity) @elseif ($member->character && $member->character->entity) @endif
    @else
    {{ $member->role }}
    @if ($entity->isCharacter()) @else @endif
    @php $previousRelation = $member->role @endphp @endif @endforeach ================================================ FILE: resources/views/entities/components/menu.blade.php ================================================ ================================================ FILE: resources/views/entities/components/menu_v2.blade.php ================================================
    @includeWhen(isset($withPins), 'entities.components.pins') @include('entities.components.menu') @include('ads.siderail_left')
    ================================================ FILE: resources/views/entities/components/og.blade.php ================================================ @php /** @var \App\Models\Entity $entity */ @endphp @section('og') @if ($tooltip = $entity->mappedPreview())@endif @if ($entity->hasImage())@endif @endsection ================================================ FILE: resources/views/entities/components/pins.blade.php ================================================ @php /** @var \App\Models\Entity $entity */ $forceShow = false; if ($entity->entityType->isStandard() && method_exists($entity->child, 'pinnedMembers') && !$entity->child->pinnedMembers->isEmpty()) { $forceShow = true; } if (auth()->check() && auth()->user()->can('update', $entity)) { $forceShow = true; } @endphp ================================================ FILE: resources/views/entities/components/posts/children.blade.php ================================================ 1, ]; $datagridOptions = []; if (request()->get('m') == \App\Enums\Descendants::All->value || (!request()->has('m') && $campaign->defaultDescendantsMode() === \App\Enums\Descendants::All)) { $routeOptions['m'] = \App\Enums\Descendants::All; } $routeOptions = Datagrid::initOptions($routeOptions); $datagridOptions = ['datagridUrl' => route($route, $routeOptions)] ; ?>
    @include('layouts.datagrid._table', $datagridOptions)
    ================================================ FILE: resources/views/entities/components/posts/custom.blade.php ================================================ visibleTags(); ?>

    {!! $post->name !!}

    @if (app()->hasDebugModeEnabled()) ({{ $post->position }}) @endif @auth @can('visibility', $post) @else @include('icons.visibility', ['icon' => $post->visibilityIcon()]) @endif @endif
    @if($post->layout?->code == 'inventory') @include('entities.pages.inventory._grid', ['isPost' => true]) @elseif ($post->layout?->code == 'attributes') @include('entities.pages.attributes.render', ['isPost' => true]) @elseif ($post->layout?->code == 'abilities') @include('entities.pages.abilities._abilities', ['isPost' => true]) @elseif ($post->layout?->code == 'assets') @include('entities.pages.assets._assets', ['assets' => $entity->assets, 'isPost' => true]) @elseif ($post->layout?->code == 'connection_map') @include('entities.pages.relations._map', ['option' => null, 'isPost' => true, 'mode' => 'map']) @elseif($post->layout?->code == 'children') @include('entities.components.posts.children') @elseif ($post->layout?->code == 'character_orgs' && $entity->isCharacter()) @include('characters.panels.organisations', ['character' => $entity->child]) @elseif ($post->layout?->code == 'quest_elements' && $entity->isQuest()) @include('quests.elements._post') @elseif ($post->layout?->code == 'location_characters' && $entity->isLocation()) @include('locations.panels.characters', ['init' => true]) @elseif ($post->layout?->code == 'location_events' && $entity->isLocation()) @include('locations.panels.events', ['init' => true]) @elseif ($post->layout?->code == 'location_quests' && $entity->isLocation()) @include('locations.panels.quests', ['init' => true]) @elseif ($post->layout?->code == 'reminders') @include('entities.pages.reminders._post') @endif
    ================================================ FILE: resources/views/entities/components/posts/standard.blade.php ================================================ visibleTags(); ?>

    {!! $post->name !!}

    @if (app()->hasDebugModeEnabled()) ({{ $post->position }}) @endif @can('post', [$entity, 'edit', $post]) @can('visibility', $post) @else @include('icons.visibility', ['icon' => $post->visibilityIcon()]) @endif @endif
    ================================================ FILE: resources/views/entities/components/posts.blade.php ================================================ @php /** * @var \App\Models\Entity $entity * @var \App\Models\Post $post * @var \Illuminate\Database\Eloquent\Collection $posts */ $wrapper = false; $entryShown = false; $adShown = false; if (!isset($posts)) { $pagination = config('limits.pagination'); $posts = $entity->posts()->with([ 'permissions', 'calendarDate', 'calendarDate.calendar', 'calendarDate.calendar.entity', 'location', 'location', 'layout', 'tags' => function ($sub) { $sub->select('tags.id', 'tags.name', 'tags.slug', 'tags.colour', 'tags.icon', 'tags.is_hidden'); } ])->ordered()->paginate($pagination); $wrapper = true; } $first = $posts->first(); $postCount = 0; @endphp @if (isset($withEntry) && ($posts->count() === 0 || (!empty($first) && $first->position >= 0))) @include('entities.components.entry') @php $entryShown = true; $adShown = true; @endphp @endif @if ($wrapper && $posts->count() > 0)
    @endif @foreach ($posts as $post) @if ($post->layout_id && isset($printing) && $printing === true) @continue @endif @if (isset($withEntry) && !$entryShown && $post->position >= 0) @include('entities.components.entry') @php $entryShown = true @endphp @endif @if ($post->layout_id && !$campaign->superboosted()) @continue @endif @include('entities.pages.posts.show') @includeWhen($postCount > 0 && $postCount % 3 === 0, 'ads.inline', ['count' => $postCount]) @php $postCount++; @endphp @endforeach @if (isset($withEntry) && !$entryShown) @include('entities.components.entry') @php $entryShown = true; $adShown = true; @endphp @endif
    @if ($posts->currentPage() < $posts->lastPage()) @endif @if ($wrapper && $posts->count() > 0)
    @endif @if (!request()->ajax() && $entity && !$entity->isType([config('entities.ids.map'), config('entities.ids.timeline'), config('entities.ids.calendar')])) @can('post', [$entity]) @endcan @endif ================================================ FILE: resources/views/entities/components/profile/_aliases.blade.php ================================================ @php $firstAlias = true @endphp
    {{ __('entities/profile.aliases') }}
    @foreach ($entity->aliases as $alias) {!! $alias->name !!} @includeWhen(!$alias->isVisibleAll(), 'icons.visibility', ['icon' => $alias->visibilityIcon()]) @endforeach
    ================================================ FILE: resources/views/entities/components/profile/_events.blade.php ================================================ elapsedEvents; // Prepare birth and death events $distinctCalendars = []; $birth = null; $death = null; foreach ($elapsed as $event) { if (empty($event->calendar) || $event->isCalendarDate()) { continue; } if ($event->isBirth() || $event->isFounded()) { $distinctCalendars[$event->calendar_id]['birth'] = $event; } elseif ($event->isDeath()) { if (!isset($distinctCalendars[$event->calendar_id]['death'])) { $distinctCalendars[$event->calendar_id]['death'] = $event; continue; } // Already have a death? Take the new one if it's older $older = false; /** @var \App\Models\EntityEvent $previous */ $previous = $distinctCalendars[$event->calendar_id]['death']; if ($previous->year < $event->year) { $older = true; } elseif ($previous->year == $event->year) { if ($previous->month < $event->month) { $older = true; } elseif ($previous->month == $event->month) { if ($previous->day < $event->day) { $older = true; } } } if ($older) { $distinctCalendars[$event->calendar_id]['death'] = $event; } } } ?> @foreach ($distinctCalendars as $calendarId => $calendarEvents) @php $birth = $calendarEvents['birth'] ?? null; $death = $calendarEvents['death'] ?? null; @endphp @if (!empty($birth) && !empty($death))
    {{ __('characters.fields.life') }}
    {{ $birth->readableDate() }} {{ $death->readableDate() }} ({{ $birth->calcElapsed($death) }})
    @elseif (!empty($birth)) @php $yearsAgo = $birth->calcElapsed() @endphp
    @if ($birth->isBirth())
    {{ __('entities/events.types.birth') }}
    @else
    {{ __('entities/events.types.founded') }}
    @endif {{ $birth->readableDate() }} ({{ $event->isBirth() ? $yearsAgo : trans_choice('entities/events.years-ago', $yearsAgo, ['count' => $yearsAgo]) }})
    @elseif (!empty($death))
    {{ __('entities/events.types.death') }}
    {{ $death->readableDate() }} (✝{{ $death->calcElapsed() }})
    @endif @endforeach ================================================ FILE: resources/views/entities/components/profile/_location.blade.php ================================================ @if ($campaign->enabled('locations') && !empty($child->location) && $child->location->entity)
    {!! \App\Facades\Module::singular(config('entities.ids.location'), __('entities.location')) !!}
    @endif ================================================ FILE: resources/views/entities/components/profile/_locations.blade.php ================================================ @if ($campaign->enabled('locations') && !$entity->locations->isEmpty())
    {!! \App\Facades\Module::plural(config('entities.ids.location'), __('entities.locations')) !!}
    @php $existingLocations = []; @endphp @foreach ($entity->locations as $location) @if(!empty($existingLocations[$location->id])) @continue @endif @php $existingLocations[$location->id] = true; @endphp @endforeach
    @endif ================================================ FILE: resources/views/entities/components/profile/_reminder.blade.php ================================================ @if ($entity->calendarReminder())
    {{ __('crud.fields.calendar_date') }}
    {{ $entity->calendarReminder()->readableDate() }}
    @endif ================================================ FILE: resources/views/entities/components/profile/_type.blade.php ================================================ @if (!empty($entity->type))
    {{ __('crud.fields.type') }}
    @auth {!! $entity->type !!} @else {!! $entity->type !!} @endauth
    @endif ================================================ FILE: resources/views/entities/components/profile/abilities.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @if (!empty($child->charges))
    {{ __('abilities.fields.charges') }}
    {{ $child->charges }}
    @endif @include('entities.components.profile._type')
    ================================================ FILE: resources/views/entities/components/profile/attribute_templates.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @if (!empty($child->attributeTemplate))
    {{ __('crud.fields.parent') }}
    @endif
    {{ __('attribute_templates.fields.is_enabled') }}
    @if ($child->is_enabled) {{ __('general.yes')}} @else {{ __('general.no')}} @endif
    @if (!empty($child->entityType))
    {{ __('attribute_templates.fields.auto_apply') }}
    {!! $child->entityType->name() !!}
    @endif
    ================================================ FILE: resources/views/entities/components/profile/character_families.blade.php ================================================ @php $existingFamilies = []; @endphp @foreach ($child->characterFamilies as $family) @if(!empty($existingFamilies[$family->family_id])) @continue @endif @php $existingFamilies[$family->family_id] = true; @endphp @if ($family->is_private) @endif @endforeach ================================================ FILE: resources/views/entities/components/profile/character_races.blade.php ================================================ @php $existingRaces = []; @endphp @foreach ($child->characterRaces as $race) @if(!empty($existingRaces[$race->race_id])) @continue @endif @php $existingRaces[$race->race_id] = true; @endphp @if ($race->is_private) @endif @endforeach ================================================ FILE: resources/views/entities/components/profile/characters.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @php //$appearances = $child->characterTraits()->appearance()->orderBy('default_order')->get(); //$traits = $child->characterTraits()->personality()->orderBy('default_order')->get(); @endphp @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @if ($campaign->enabled('families') && !$child->characterFamilies->isEmpty())
    {!! \App\Facades\Module::singular(config('entities.ids.family'), __('entities.families')) !!} @can('update', $entity) @endif
    @include('entities.components.profile.character_families')
    @endif @if (!$child->characterRaces->isEmpty() || $child->hasAge()) @if (!$child->characterRaces->isEmpty() && !$child->hasAge())
    {!! \App\Facades\Module::plural(config('entities.ids.race'), __('entities.races')) !!} @can('update', $entity) @endif
    @include('entities.components.profile.character_races')
    @elseif ($child->characterRaces->isEmpty() && $child->hasAge())
    {{ __('characters.fields.age') }}
    {{ is_numeric($child->age) ? \Illuminate\Support\Number::format($child->age) : $child->age }}
    @else
    {!! \App\Facades\Module::plural(config('entities.ids.race'), __('entities.races')) !!} @can('update', $entity) @endif , {{ __('characters.fields.age') }}
    @include('entities.components.profile.character_races')
    , {{ is_numeric($child->age) ? \Illuminate\Support\Number::format($child->age) : $child->age }}
    @endif @endif @if (!empty($child->sex) || !empty($child->pronouns)) @if (!empty($child->sex) && empty($child->pronouns))
    {{ __('characters.fields.sex') }}
    {!! $child->sex !!}
    @elseif (empty($child->sex) && !empty($child->pronouns))
    {{ __('characters.fields.pronouns') }}
    {!! $child->pronouns !!}
    @else
    {{ __('characters.fields.sex') }}, {{ __('characters.fields.pronouns') }}
    {!! $child->sex !!}, {!! $child->pronouns !!}
    @endif @endif @include('entities.components.profile._events') @include('entities.components.profile._type')
    ================================================ FILE: resources/views/entities/components/profile/conversations.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif
    {{ __('conversations.fields.participants') }}
    {{ __('conversations.targets.' . ($child->forCharacters() ? 'characters' : 'members')) }}
    @include('entities.components.profile._type')
    ================================================ FILE: resources/views/entities/components/profile/creatures.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._locations') @include('entities.components.profile._type') ================================================ FILE: resources/views/entities/components/profile/custom.blade.php ================================================ @if (empty($entity->type) && $entity->locations->isEmpty() && $entity->aliases->isEmpty()) @php return @endphp @endif @include('entities.components.profile._type') @include('entities.components.profile._locations') @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') ================================================ FILE: resources/views/entities/components/profile/dice_rolls.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @if ($child->parameters)
    {{ __('dice_rolls.fields.parameters') }}
    {{ $child->parameters }}
    @endif @if ($child->character)
    {!! \App\Facades\Module::singular(config('entities.ids.character'), __('entities.character')) !!}
    @endif
    ================================================ FILE: resources/views/entities/components/profile/events.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._locations') @include('entities.components.profile._type') @include('entities.components.profile._reminder') ================================================ FILE: resources/views/entities/components/profile/families.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @if (!empty($entity->parent))
    {!! \App\Facades\Module::singular(config('entities.ids.family'), __('entities.family')) !!}
    @endif @include('entities.components.profile._type') @include('entities.components.profile._events')
    ================================================ FILE: resources/views/entities/components/profile/items.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @if ($child->price)
    {{ __('items.fields.price') }}
    {!! $child->price !!}
    @endif @if ($child->size)
    {{ __('items.fields.size') }}
    {!! $child->size !!}
    @endif @if ($child->weight)
    {{ __('items.fields.weight') }}
    {!! $child->weight !!}
    @endif @include('entities.components.profile._location') @if ($child->itemCreators->isNotEmpty())
    {{ __('items.fields.creators') }}
    @foreach ($child->itemCreators as $itemCreator) @endforeach
    @endif @include('entities.components.profile._type')
    ================================================ FILE: resources/views/entities/components/profile/journals.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._location') @if ($child->date)
    {{ __('journals.fields.date') }}
    @endif @if ($child->author)
    {{ __('journals.fields.author') }}
    @endif @include('entities.components.profile._reminder') @include('entities.components.profile._type')
    ================================================ FILE: resources/views/entities/components/profile/locations.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._type') @include('entities.components.profile._events') @if (!$child->maps->isEmpty())
    {!! \App\Facades\Module::singular(config('entities.ids.map'), __('entities.map')) !!}
    @foreach ($child->maps as $map) @include('maps._explore-link')
    @endforeach
    @endif
    ================================================ FILE: resources/views/entities/components/profile/maps.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._type') ================================================ FILE: resources/views/entities/components/profile/notes.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._type') ================================================ FILE: resources/views/entities/components/profile/organisations.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._locations') @include('entities.components.profile._type') @include('entities.components.profile._events') ================================================ FILE: resources/views/entities/components/profile/quests.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @if (!empty($child->instigator))
    {{ __('quests.fields.instigator') }}
    @endif @if ($child->date)
    {{ __('journals.fields.date') }}
    @endif @include('entities.components.profile._reminder') @include('entities.components.profile._location') @include('entities.components.profile._type')
    ================================================ FILE: resources/views/entities/components/profile/races.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._locations') @include('entities.components.profile._type') ================================================ FILE: resources/views/entities/components/profile/tags.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @if ($child->hasColour())
    {{ __('crud.fields.colour') }}
    {{ $child->colour }}
    @endif @if ($child->hasIcon())
    {{ __('tags.fields.icon') }}
    {{ $child->icon }}
    @endif @include('entities.components.profile._type')
    ================================================ FILE: resources/views/entities/components/profile/timelines.blade.php ================================================ child; ?> @if (!$child->showProfileInfo()) @php return @endphp @endif @includeWhen($entity->aliases->isNotEmpty(), 'entities.components.profile._aliases') @include('entities.components.profile._type') ================================================ FILE: resources/views/entities/components/relations.blade.php ================================================ pinnedRelations; $previousRelation = null; if (count($models) === 0) { return; } ?> @foreach ($models as $relation) @if(!empty($previousRelation) && $previousRelation == $relation->relation)
    @else
    {{ $relation->relation }}
    @php $previousRelation = $relation->relation @endphp @endif @endforeach ================================================ FILE: resources/views/entities/components/tooltip.blade.php ================================================
    @if (!$hasImage) @endif {!! $entity->name !!} @if ($entity->status?->icon) @endif
    @if ($entity->entityType->isStandard() && method_exists($entity->child, 'tooltipSubtitle')) {!! $entity->child->tooltipSubtitle() !!} @endif
    @if ($tags->isNotEmpty())
    @foreach ($tags as $tag) @if (!$tag->entity) @continue @endif @include ('tags._badge') @endforeach
    @endif @if ($campaign->premium() && $render === 'attributes') @else
    @if ($entity->hasEntry()) {!! $tooltip !!} @else {!! __('entities/tooltips.empty', ['name' => $entity->name]) !!} @if (auth()->check() and auth()->user()->can('update', $entity)) {{ __('entities/tooltips.fix') }} @endif @endif
    @endif
    ================================================ FILE: resources/views/entities/creator/_created.blade.php ================================================

    {!! $success !!}

    ================================================ FILE: resources/views/entities/creator/form.blade.php ================================================ @if (isset($entityType))
    @else @endif @csrf @if (isset($origin))
    {{ __('entities.creator.modes.default') }}
    @endif
    @if (!isset($origin)) @include('entities.creator.header.header') @endif
    @includeWhen(!empty($success), 'entities.creator._created') @if ($mode === 'bulk') @else @endif @includeWhen($mode === 'bulk', '.entities.creator.forms.template') {{ __('entities.creator.actions.more') }}
    @php $allowNew = false; $dropdownParent = '#primary-dialog';@endphp @if (isset($entityType)) @if ($entityType->isCustom()) @include('entities.creator.forms.custom') @else @include('entities.creator.forms.' . $entityType->code) @endif @else @include('entities.creator.forms.post') @endif @if (!isset($entityType) || !in_array($entityType->id, [config('entities.ids.attribute_template')]))
    @include('cruds.fields.tags', ['dropdownParent' => '#quick-creator-tags-field'])
    @endif @if (isset($entityType) && auth()->user()->isAdmin()) @include('cruds.fields.privacy_callout') @endif
    @if (empty($origin)) @if ($mode !== 'bulk') @endif
    @else @endif
    @if (!empty($target)) @endif @if (!empty($multi)) @endif
    ================================================ FILE: resources/views/entities/creator/forms/ability.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Ability::class, 'trans' => 'abilities']) @include('cruds.fields.parent') ================================================ FILE: resources/views/entities/creator/forms/attribute_template.blade.php ================================================ @include('cruds.fields.parent') ================================================ FILE: resources/views/entities/creator/forms/calendar.blade.php ================================================
    @include('cruds.fields.type', ['base' => \App\Models\Calendar::class, 'trans' => 'calendars']) @include('cruds.fields.parent')
    ================================================ FILE: resources/views/entities/creator/forms/character.blade.php ================================================ @include('cruds.fields.title') @include('cruds.fields.type', ['base' => \App\Models\Character::class, 'trans' => 'characters']) @include('cruds.fields.families', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.family'))->first(), $campaign])]) @include('cruds.fields.races', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.race'))->first(), $campaign])]) @include('cruds.fields.locations', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.location'))->first(), $campaign])]) @include('cruds.fields.sex', ['base' => \App\Models\Character::class, 'trans' => 'characters']) @include('cruds.fields.age', ['trans' => 'characters']) ================================================ FILE: resources/views/entities/creator/forms/conversation.blade.php ================================================ value => __('conversations.targets.members'), \App\Enums\ConversationTarget::characters->value => __('conversations.targets.characters'), ]; ?> @include('cruds.fields.type', ['base' => \App\Models\Conversation::class, 'trans' => 'conversations']) ================================================ FILE: resources/views/entities/creator/forms/creature.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Creature::class, 'trans' => 'creatures']) @include('cruds.fields.parent') @include('cruds.fields.locations', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.location'))->first(), $campaign])]) ================================================ FILE: resources/views/entities/creator/forms/custom.blade.php ================================================ @include('cruds.fields.type', ['trans' => 'entries', 'placeholder' => __('entries/fields.type.placeholder')]) ================================================ FILE: resources/views/entities/creator/forms/dice_roll.blade.php ================================================ ================================================ FILE: resources/views/entities/creator/forms/event.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Event::class, 'trans' => 'events']) @include('cruds.fields.location') ================================================ FILE: resources/views/entities/creator/forms/family.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Timeline::class, 'trans' => 'families']) @include('cruds.fields.parent') @include('cruds.fields.location', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.location'))->first(), $campaign])]) ================================================ FILE: resources/views/entities/creator/forms/item.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Item::class, 'trans' => 'items']) @include('cruds.fields.parent') @include('cruds.fields.price', ['trans' => 'items']) @include('cruds.fields.size', ['trans' => 'items']) @include('cruds.fields.weight', ['trans' => 'items']) @include('cruds.fields.location', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.location'))->first(), $campaign])]) @include('cruds.fields.creators') ================================================ FILE: resources/views/entities/creator/forms/journal.blade.php ================================================
    @include('cruds.fields.type', ['base' => \App\Models\Journal::class, 'trans' => 'journals']) @include('cruds.fields.parent')
    @include('cruds.fields.author') ================================================ FILE: resources/views/entities/creator/forms/location.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Location::class, 'trans' => 'locations']) @include('cruds.fields.parent') ================================================ FILE: resources/views/entities/creator/forms/map.blade.php ================================================
    @include('cruds.fields.type', ['base' => \App\Models\Map::class, 'trans' => 'maps']) @include('cruds.fields.parent') @include('cruds.fields.location', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.location'))->first(), $campaign])])
    ================================================ FILE: resources/views/entities/creator/forms/note.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Note::class, 'trans' => 'notes']) @include('cruds.fields.parent') ================================================ FILE: resources/views/entities/creator/forms/organisation.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Organisation::class, 'trans' => 'organisations']) @include('cruds.fields.parent') @include('cruds.fields.locations', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.location'))->first(), $campaign])]) ================================================ FILE: resources/views/entities/creator/forms/post.blade.php ================================================
    @include('cruds.fields.entity', ['required' => true])
    @include('cruds.fields.visibility_id')
    ================================================ FILE: resources/views/entities/creator/forms/quest.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Quest::class, 'trans' => 'quests']) @include('cruds.fields.parent') @include('cruds.fields.instigator') @include('cruds.fields.location', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.location'))->first(), $campaign])]) ================================================ FILE: resources/views/entities/creator/forms/race.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Race::class, 'trans' => 'races']) @include('cruds.fields.parent') @include('cruds.fields.locations', ['dynamicNew' => auth()->user()->can('create', [$campaign->getEntityTypes()->where('id', config('entities.ids.location'))->first(), $campaign])]) ================================================ FILE: resources/views/entities/creator/forms/tag.blade.php ================================================ @include('cruds.fields.type', ['base' => \App\Models\Tag::class, 'trans' => 'tags']) @include('cruds.fields.parent') @include('cruds.fields.colour_picker', ['dropdownParent' => '#primary-dialog']) ================================================ FILE: resources/views/entities/creator/forms/template.blade.php ================================================ @include('cruds.fields.template', ['helper' => __('entities.creator.helpers.archetype'), 'label' => __('entities.archetype'), 'placeholder' => __('crud.placeholders.search')]) ================================================ FILE: resources/views/entities/creator/forms/timeline.blade.php ================================================
    @include('cruds.fields.type', ['base' => \App\Models\Timeline::class, 'trans' => 'timelines']) @include('cruds.fields.parent')
    ================================================ FILE: resources/views/entities/creator/forms/whiteboard.blade.php ================================================
    @include('cruds.fields.type', ['base' => \App\Models\Whiteboard::class, 'trans' => 'whiteboards'])
    ================================================ FILE: resources/views/entities/creator/header/_dropdown.blade.php ================================================ @php @endphp @if ($dropdownEntityType == $entityType) {!! $entityType->name() !!} @else @php $data = [ 'toggle' => 'entity-creator', 'url' => $dropdownEntityType instanceof \App\Models\EntityType ? route('entity-creator.form', [$campaign, 'entity_type' => $dropdownEntityType, 'mode' => $mode ?? null]) : route('entity-creator.post', [$campaign, 'mode' => $mode ?? null]), 'entity-type' => 'entity', 'type' => 'inline', ]; @endphp @if ($dropdownEntityType instanceof \App\Models\EntityType) {!! $dropdownEntityType->name() !!} @else {{ __('entities.article') }} @endif @endif ================================================ FILE: resources/views/entities/creator/header/header.blade.php ================================================
    @if ($mode === 'bulk') {{ __('entities.creator.modes.bulk') }} @elseif ($mode === 'templates') {{ __('entities.creator.modes.archetypes') }} @else {{ __('entities.creator.modes.default') }} @endif
    @if (empty($target)) @else
    {!! $newLabel !!}
    @endif
    @if (empty($target))
    @if (isset($entityType))
    @else
    @endif
    @endif
    ================================================ FILE: resources/views/entities/creator/selection/_abilities.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'ability', 'plural' => 'abilities', 'id' => config('entities.ids.ability'), ]) @include('entities.creator.selection._full', ['key' => 'abilities'])
    ================================================ FILE: resources/views/entities/creator/selection/_attribute_templates.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'attribute_template', 'plural' => 'attribute_templates', 'id' => config('entities.ids.attribute_template'), ]) @include('entities.creator.selection._full', ['key' => 'attribute_templates'])
    ================================================ FILE: resources/views/entities/creator/selection/_calendars.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'calendar', 'plural' => 'calendars', 'id' => config('entities.ids.calendar'), ]) @include('entities.creator.selection._full', ['key' => 'calendars'])
    ================================================ FILE: resources/views/entities/creator/selection/_characters.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'character', 'plural' => 'characters', 'id' => config('entities.ids.character'), ]) @include('entities.creator.selection._full', ['key' => 'characters'])
    ================================================ FILE: resources/views/entities/creator/selection/_conversations.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'conversation', 'plural' => 'conversations', 'id' => config('entities.ids.conversation'), ]) @include('entities.creator.selection._full', ['key' => 'conversations'])
    ================================================ FILE: resources/views/entities/creator/selection/_creatures.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'creature', 'plural' => 'creatures', 'id' => config('entities.ids.creature'), ]) @include('entities.creator.selection._full', ['key' => 'creatures'])
    ================================================ FILE: resources/views/entities/creator/selection/_dice_rolls.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'dice_roll', 'plural' => 'dice_rolls', 'id' => config('entities.ids.dice_roll'), ]) @include('entities.creator.selection._full', ['key' => 'dice_rolls'])
    ================================================ FILE: resources/views/entities/creator/selection/_events.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'event', 'plural' => 'events', 'id' => config('entities.ids.event'), ]) @include('entities.creator.selection._full', ['key' => 'events'])
    ================================================ FILE: resources/views/entities/creator/selection/_families.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'family', 'plural' => 'families', 'id' => config('entities.ids.family'), ]) @include('entities.creator.selection._full', ['key' => 'families'])
    ================================================ FILE: resources/views/entities/creator/selection/_full.blade.php ================================================ Go to the full form for creating a new {{ $entityType->name() }} ================================================ FILE: resources/views/entities/creator/selection/_items.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'item', 'plural' => 'items', 'id' => config('entities.ids.item'), ]) @include('entities.creator.selection._full', ['key' => 'items'])
    ================================================ FILE: resources/views/entities/creator/selection/_journals.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'journal', 'plural' => 'journals', 'id' => config('entities.ids.journal'), ]) @include('entities.creator.selection._full', ['key' => 'journals'])
    ================================================ FILE: resources/views/entities/creator/selection/_locations.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'location', 'plural' => 'locations', 'id' => config('entities.ids.location'), ]) @include('entities.creator.selection._full', ['key' => 'locations'])
    ================================================ FILE: resources/views/entities/creator/selection/_main.blade.php ================================================ @php /** @var \App\Models\EntityType $entityType $entityType */ @endphp
    {!! $entityType->name() !!}
    ================================================ FILE: resources/views/entities/creator/selection/_maps.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'map', 'plural' => 'maps', 'id' => config('entities.ids.map'), ]) @include('entities.creator.selection._full', ['key' => 'maps'])
    ================================================ FILE: resources/views/entities/creator/selection/_notes.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'note', 'plural' => 'notes', 'id' => config('entities.ids.note'), ]) @include('entities.creator.selection._full', ['key' => 'notes'])
    ================================================ FILE: resources/views/entities/creator/selection/_organisations.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'organisation', 'plural' => 'organisations', 'id' => config('entities.ids.organisation'), ]) @include('entities.creator.selection._full', ['key' => 'organisations'])
    ================================================ FILE: resources/views/entities/creator/selection/_posts.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'post', 'plural' => 'posts', 'icon' => 'fa-duotone fa-pen' ])
    ================================================ FILE: resources/views/entities/creator/selection/_quests.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'quest', 'plural' => 'quests', 'id' => config('entities.ids.quest'), ]) @include('entities.creator.selection._full', ['key' => 'quests'])
    ================================================ FILE: resources/views/entities/creator/selection/_races.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'race', 'plural' => 'races', 'id' => config('entities.ids.race'), ]) @include('entities.creator.selection._full', ['key' => 'races'])
    ================================================ FILE: resources/views/entities/creator/selection/_tags.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'tag', 'plural' => 'tags', 'id' => config('entities.ids.tag'), ]) @include('entities.creator.selection._full', ['key' => 'tags'])
    ================================================ FILE: resources/views/entities/creator/selection/_timelines.blade.php ================================================
    @include('entities.creator.selection._main', [ 'singular' => 'timeline', 'plural' => 'timelines', 'id' => config('entities.ids.timeline'), ]) @include('entities.creator.selection._full', ['key' => 'timelines'])
    ================================================ FILE: resources/views/entities/creator/selection/all.blade.php ================================================ @php $half = ceil(count($entityTypes) / 2); $i = 0; @endphp
    {{ __('entities.creator.titles.everything') }}
    @foreach ($entityTypes as $entityType) @if ($i == $half)
    @endif
    @if ($entityType instanceof \App\Models\EntityType) @include('entities.creator.selection._main') @include('entities.creator.selection._full') @else {!! __('entities.article') !!} @endif
    @php $i++; @endphp @endforeach
    ================================================ FILE: resources/views/entities/creator/selection/popular.blade.php ================================================
    {{ __('entities.creator.titles.quick-access') }}
    @foreach ($popular as $entityType)
    @include('entities.creator.selection._main') @include('entities.creator.selection._full')
    @endforeach
    ================================================ FILE: resources/views/entities/creator/selection.blade.php ================================================ @if (!isset($new)) {{ __('Quick creator dialog') }} @endif
    @includeWhen(isset($new), 'entities.creator._created', ['success' => $new ?? null, 'dismissable' => false])
    @if ($popular->isNotEmpty()) @endif
    @include('entities.creator.selection.all')

    {!! __('entities.creator.missing_v2', [ 'learn-more' => '' . ' ' . __('front/newsletter.actions.learn_more') . '']) !!}

    ================================================ FILE: resources/views/entities/creator/templates.blade.php ================================================ @if (isset($origin))
    {{ __('entities.creator.modes.default') }}
    @endif
    ================================================ FILE: resources/views/entities/forms/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities.creator.title') . ' - ' . $entityType->name(), 'breadcrumbs' => [ ['url' => Breadcrumb::campaign($campaign)->entityType($entityType)->index(), 'label' => $entityType->plural()], __('crud.create'), ], 'mainTitle' => false, 'centered' => true, ]) @section('content') @include('cruds.forms._errors') @endsection @include('editors.editor') @includeIf($entityType->pluralCode() . '.forms._tutorial') ================================================ FILE: resources/views/entities/forms/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('crud.titles.editing', ['name' => $entity->name]) . ' - ' . $campaign->name, 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('crud.edit'), ], 'mainTitle' => false, 'centered' => true, 'entity' => null, ]) @section('content') @include('cruds.forms._errors') @if($entity && $campaign->hasEditingWarning()) @endif @endsection @include('editors.editor') @section('modals') @parent @includeWhen(!empty($editingUsers), 'cruds.forms.edit_warning', ['model' => $entity]) @endsection ================================================ FILE: resources/views/entities/forms/entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['trans' => 'crud']) @include('cruds.fields.parent', ['trans' => 'crud', 'is_parent' => true]) @include('cruds.fields.locations', ['from' => $entity ?? null, 'quickCreator' => true, 'model' => $entity ?? $source ?? null]) @include('cruds.fields.entry2') @include('cruds.fields.status') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/entities/headers/__entity-locations.blade.php ================================================ enabled('locations') || $entity->locations->isEmpty()) { return; } ?>
      @foreach ($entity->locations as $location)
    • @endforeach
    ================================================ FILE: resources/views/entities/headers/__location.blade.php ================================================ enabled('locations') || !$entity->child->location || !$entity->child->location->entity) { return; } ?>
    @if ($entity->child->location->parent && $entity->child->location->parent->entity) {!! __('crud.fields.locations', [ 'first' => Blade::renderComponent( new \App\View\Components\EntityLink($entity->child->location->entity, $campaign) ), 'second' => Blade::renderComponent( new \App\View\Components\EntityLink($entity->child->location->parent->entity, $campaign) ), ]) !!} @else @endif
    ================================================ FILE: resources/views/entities/headers/__parent.blade.php ================================================ @if ($entity->parent)
    @endif ================================================ FILE: resources/views/entities/headers/__parent_location.blade.php ================================================ enabled('locations') || !$entity->parent) { return; } ?>
    @if ($entity->parent->parent) {!! __('crud.fields.locations', [ 'first' => \Illuminate\Support\Facades\Blade::renderComponent( new \App\View\Components\EntityLink($entity->parent, $campaign) ), 'second' => \Illuminate\Support\Facades\Blade::renderComponent( new \App\View\Components\EntityLink($entity->parent->parent, $campaign) ), ]) !!} @else @endif
    ================================================ FILE: resources/views/entities/headers/_ability.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/_calendar.blade.php ================================================ @if ($entity->child->date)
    {!! $entity->child->niceDate() !!}
    @endif ================================================ FILE: resources/views/entities/headers/_character.blade.php ================================================ @if (!$campaign->enabled('locations') || $entity->locations->isEmpty()) @endif
    @include('entities.headers.__entity-locations')
    ================================================ FILE: resources/views/entities/headers/_creature.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/_custom.blade.php ================================================ @if ($entity->parent)
    @endif ================================================ FILE: resources/views/entities/headers/_event.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') @if($entity->child->date)
    {{ $entity->child->date }}
    @endif ================================================ FILE: resources/views/entities/headers/_family.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') @include('entities.headers.__location') ================================================ FILE: resources/views/entities/headers/_item.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/_journal.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') @if($entity->child->date)
    @endif ================================================ FILE: resources/views/entities/headers/_location.blade.php ================================================ @include('entities.headers.__parent_location') ================================================ FILE: resources/views/entities/headers/_map.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') @includeWhen($entity->child->location, 'entities.headers.__location') ================================================ FILE: resources/views/entities/headers/_note.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/_organisation.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/_quest.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/_race.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/_tag.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/_timeline.blade.php ================================================ @includeWhen($entity->parent, 'entities.headers.__parent') ================================================ FILE: resources/views/entities/headers/actions.blade.php ================================================ @if (!isset($edit) || $edit !== false) @can('update', $entity) {{ __('crud.edit') }} @endcan @endif ================================================ FILE: resources/views/entities/headers/toggle.blade.php ================================================
    ================================================ FILE: resources/views/entities/index/_actions.blade.php ================================================ @foreach ($actions as $action) @if (empty($action['policy']) || (auth()->check() && auth()->user()->can($action['policy'], $model))) {!! $action['label'] !!} @endif @endforeach ================================================ FILE: resources/views/entities/index/_create.blade.php ================================================
    ================================================ FILE: resources/views/entities/index/_entity.blade.php ================================================ @php /** @var \App\Models\Entity $model */ $stacked = !isset($flat) && !isset($isParent) ? min(2, $model->children_count) : null; $dataAttributes = []; if ($model->is_private) { $dataAttributes[] = 'private'; } @endphp @if ($stacked > 0)
    type)) data-type="{{ \Illuminate\Support\Str::slug($model->type) }}" @endif> @if ($model->is_private)
    @endif
    {!! $model->name !!}
    @for ($s = 0; $s < $stacked; $s++)
    @endfor
    @else
    type)) data-type="{{ \Illuminate\Support\Str::slug($model->type) }}" @endif> @if ($model->is_private)
    @endif
    {!! $model->name !!}
    @endif ================================================ FILE: resources/views/entities/index/actions/attribute_template.blade.php ================================================ ================================================ FILE: resources/views/entities/index/actions/bookmark.blade.php ================================================ ================================================ FILE: resources/views/entities/index/actions/connection.blade.php ================================================ {{ __('connections/web.actions.view') }} ================================================ FILE: resources/views/entities/index/actions/conversation.blade.php ================================================ ================================================ FILE: resources/views/entities/index/actions/dice_roll.blade.php ================================================ ================================================ FILE: resources/views/entities/index/actions/parent.blade.php ================================================ @if ($parent->parent) @else @endif ================================================ FILE: resources/views/entities/index/explore.blade.php ================================================
    @section('scripts') @parent @vite(['resources/js/entities/explore.js']) @endsection ================================================ FILE: resources/views/entities/index/filters.blade.php ================================================ @php /** * @var \App\Models\Campaign $campaign * @var \App\Models\EntityType $entityType * @var \App\Services\FilterService $filterService */ use Illuminate\Support\Arr; @endphp {{ __('crud.filters.title') }} @if (auth()->guest())

    {{ __('filters.helpers.guest') }}

    @else
    @include('cruds.datagrids.filters._type', ['field' => 'type'])
    @include('cruds.datagrids.filters._array', ['field' => ['id' => uniqid('locations_'), 'field' => 'locations', 'data' => []], 'value' => $filterService->filterValue('locations')])
    @include('cruds.datagrids.filters._choice', ['field' => 'is_private'])
    @include('cruds.datagrids.filters._choice', ['field' => 'has_image'])
    @include('cruds.datagrids.filters._choice', ['field' => 'has_entity_files'])
    @include('cruds.datagrids.filters._choice', ['field' => 'has_entry'])
    @include('cruds.datagrids.filters._choice', ['field' => 'has_posts'])
    @include('cruds.datagrids.filters._choice', ['field' => 'template'])
    @include('cruds.datagrids.filters._archived', ['field' => 'archived'])
    @include('cruds.datagrids.filters._tag', ['value' => $filterService->filterValue('tags'), 'field' => ['field' => 'tags']])
    @include('cruds.datagrids.filters._attributes') @endif
    @if (auth()->check())
    activeFiltersCount() > 0) data-clipboard="{{ $filterService->clipboardFilters() }}" data-toast="{{ __('filters.alerts.copy') }}" onclick="return false" @endif data-toggle="tooltip" data-title="{{ __('crud.filters.copy_helper') }}"> {{ __('crud.filters.copy_to_clipboard') }} {{ __('crud.filters.mobile.copy') }} @if ($filterService->activeFiltersCount() > 0) {{ __('crud.filters.mobile.clear') }} @endif {{ __('helpers.filters.title') }}
    @endif
    ================================================ FILE: resources/views/entities/index/index.blade.php ================================================ @extends('layouts.app', [ 'title' => $title, 'skipTitle' => true, 'seoTitle' => $title . ' - ' . $campaign->name, 'breadcrumbs' => false, 'canonical' => true, 'bodyClass' => 'kanka-' . $entityType->code, ]) @section('content') @include('partials.errors') @include('ads.top') @include('entities.index.explore') @endsection @section('modals') @parent @includeWhen(auth()->check(), 'cruds.datagrids.bulks.modals') @endsection @section('og') @endsection ================================================ FILE: resources/views/entities/markdown/_locations.blade.php ================================================ @if ($campaign->enabled('locations') && $entity->locations->isNotEmpty()) ## {!! __('crud.tabs.profile') !!} - **{!! \App\Facades\Module::plural(config('entities.ids.location'), __('entities.locations')) !!}:** {!! $entity->locations->pluck('name')->map(fn($name) => html_entity_decode($name, ENT_QUOTES, 'UTF-8'))->implode(', ') !!} @endif ================================================ FILE: resources/views/entities/markdown/aliases.blade.php ================================================ ## {{ __('entries/tabs.aliases') }} @foreach ($entity->aliases as $alias) - {!! $alias->name !!} @endforeach ================================================ FILE: resources/views/entities/markdown/base.blade.php ================================================ @if ($entity->image) ![avatar]({!! $entity->image->url() !!}) @endif @if ($entity->header) ![header]({!! $entity->header->getUrl(480, 270) !!}) @endif # {!! html_entity_decode($entity->name, ENT_QUOTES, 'UTF-8') !!} @if ($entity->type) - **{!! __('crud.fields.type') !!}:** {!! html_entity_decode($entity->type, ENT_QUOTES, 'UTF-8') !!} @endif @if ($entity->tags->count() > 0) - **{!! __('entities.tags') !!}:** {!! implode(', ', $entityData['tags']) !!} @endif - **{!! __('crud.fields.visibility') !!}:** {!! $entity->is_private ? __('campaigns/visibilities.titles.private') : __('campaigns/visibilities.titles.public') !!} @if($entity->hasEntry()) --- ## {!! __('fields.description.label') !!} {!! $converter->convert((string) $entityData['entry']) !!} --- @endif @if ($entity->is_template || $entity->archived_at) ### {!! __('entities.notes') !!} @endif @if ($entity->is_template) - {!! __('crud.fields.template') !!}: Yes @endif @if ($entity->archived_at) - {!! __('crud.fields.archived') !!}: Yes @endif @if (!empty($entityData['attributes'])) ## {!! __('entries/tabs.properties') !!} {!! $entityData['attributes'] !!} @endif @if (!empty($entityData['relations'])) ## {!! __('entries/tabs.relations') !!} {!! $entityData['relations'] !!} @endif @includeWhen($entity->isCharacter(), 'entities.markdown.characters') @includeWhen($entity->isQuest(), 'entities.markdown.quests') @includeWhen($entity->isOrganisation(), 'entities.markdown.organisations') @includeWhen($entity->isLocation(), 'entities.markdown.locations') @includeWhen($entity->isCreature(), 'entities.markdown.creatures') @includeWhen($entity->isRace(), 'entities.markdown.races') @includeWhen($entity->isEvent(), 'entities.markdown.events') @includeWhen($entity->isFamily(), 'entities.markdown.families') @includeWhen($entity->isItem(), 'entities.markdown.items') @includeWhen($entity->aliases->isNotEmpty(), 'entities.markdown.aliases') @if ($entity->hasPins()) ## {!! __('entities/pins.title') !!} | {!! __('crud.fields.name') !!} | {!! __('export.content') !!} | |:-|:-| @if(!$entity->pinnedFiles->isEmpty()) @foreach ($entity->pinnedFiles as $asset) | {!! $asset->name !!} | {!! $asset->url() !!} | @endforeach @endif @if($entity->hasChild() && method_exists($entity->child, 'pinnedMembers') && !$entity->child->pinnedMembers->isEmpty()) @foreach ($entity->child->pinnedMembers as $member) @if ($member instanceof \App\Models\Character) | {!! $member->organisation->name !!} | {!! $member->role !!} | @else | {!! $member->character->name !!} | {!! $member->role !!} | @endif @endforeach @endif @endif @foreach ($entity->posts as $post) @if(isset($entityData['posts'][$post->id])) ## {!! $post->name !!} {!! $converter->convert($entityData['posts'][$post->id]) !!} @endif @endforeach ================================================ FILE: resources/views/entities/markdown/characters.blade.php ================================================ @if ($entity->status || !empty($entity->child->title) || !empty($entity->child->age) || !empty($entity->child->sex) || !empty($entity->child->pronouns)) ## {!! __('crud.tabs.profile') !!} @endif @if (!empty($entity->child->title)) - **{!! __('characters.fields.title') !!}** {!! $entity->child->title !!} @endif @if (!empty($entity->child->age)) - **{!! __('characters.fields.age') !!}** {!! $entity->child->age !!} @endif @if (!empty($entity->child->sex)) - **{!! __('characters.fields.sex') !!}** {!! $entity->child->sex !!} @endif @if (!empty($entity->child->pronouns)) - **{!! __('characters.fields.pronouns') !!}** {!! $entity->child->pronouns !!} @endif @if ($entity->status) - {!! $entity->status->setRelation('entityType', $entity->entityType)->name() !!} @endif @if ($entity->child->characterTraits->count() > 0) @foreach ($entity->child->characterTraits->groupBy('section_id') as $sectionId => $traits) ### {!! $sectionId == App\Models\CharacterTrait::SECTION_APPEARANCE ? __('characters.sections.appearance') : __('characters.sections.personality') !!} @foreach ($traits as $trait) - {!! $trait->name !!}@if($sectionId == App\Models\CharacterTrait::SECTION_APPEARANCE): {!! $trait->entry !!}@endif @endforeach @endforeach @endif ================================================ FILE: resources/views/entities/markdown/creatures.blade.php ================================================ @if ($entity->status) ## {!! __('crud.tabs.profile') !!} @endif * {{ $entity->status->setRelation('entityType', $entity->entityType)->name() }} ================================================ FILE: resources/views/entities/markdown/events.blade.php ================================================ @include('entities.markdown._locations') ================================================ FILE: resources/views/entities/markdown/families.blade.php ================================================ @if ($entity->status) ## {!! __('crud.tabs.profile') !!} * {{ $entity->status->setRelation('entityType', $entity->entityType)->name() }} @endif ================================================ FILE: resources/views/entities/markdown/index.blade.php ================================================ @php use Illuminate\Support\Str; @endphp # {!! __('export.index') !!} @foreach ($index as $key => $subIndex) ### {!! Str::beforeLast($key, '_') !!} @foreach($subIndex as $entity){!! $entity !!}@endforeach @endforeach ================================================ FILE: resources/views/entities/markdown/items.blade.php ================================================ @if (!empty($entity->child->price) || !empty($entity->child->size) || !empty($entity->child->weight) || $entity->child->itemCreators->isNotEmpty()) ## {!! __('crud.tabs.profile') !!} @endif @if (!empty($entity->child->price)) - **{!! __('items.fields.price') !!}** {!! $entity->child->price !!} @endif @if (!empty($entity->child->size)) - **{!! __('items.fields.size') !!}** {!! $entity->child->size !!} @endif @if (!empty($entity->child->weight)) - **{!! __('items.fields.weight') !!}** {!! $entity->child->weight !!} @endif @if ($entity->child->itemCreators->isNotEmpty()) - **{!! __('items.fields.creators') !!}** @foreach ($entity->child->itemCreators as $itemCreator){!! $itemCreator->creator->name !!}@if (!$loop->last), @endif @endforeach @endif ================================================ FILE: resources/views/entities/markdown/locations.blade.php ================================================ @if (!empty($entity->child->title) || $entity->status) ## {!! __('crud.tabs.profile') !!} @endif @if (!empty($entity->child->title)) - **{!! __('locations.fields.title') !!}** {!! $entity->child->title !!} @endif @if ($entity->status) * {{ $entity->status->setRelation('entityType', $entity->entityType)->name() }} @endif ================================================ FILE: resources/views/entities/markdown/organisations.blade.php ================================================ @if ($entity->status) ## {!! __('crud.tabs.profile') !!} * {{ __('organisations.hints.is_defunct') }} @endif ================================================ FILE: resources/views/entities/markdown/quests.blade.php ================================================ @if ($entity->child->status_id !== \App\Enums\QuestStatus::notStarted) ## {!! __('crud.tabs.profile') !!} * {{ __('quests.fields.status') }}: {{ __('quests.status.' . ['not_started', 'ongoing', 'completed', 'abandoned'][$entity->child->status_id->value]) }} @endif ================================================ FILE: resources/views/entities/markdown/races.blade.php ================================================ @if ($entity->status) ## {!! __('crud.tabs.profile') !!} * {{ $entity->status->setRelation('entityType', $entity->entityType)->name() }} @endif ================================================ FILE: resources/views/entities/pages/abilities/_abilities.blade.php ================================================
    @section('scripts') @parent @vite('resources/js/abilities.js') @endsection ================================================ FILE: resources/views/entities/pages/abilities/_buttons.blade.php ================================================
    {{ __('crud.add') }}
    ================================================ FILE: resources/views/entities/pages/abilities/_edit_form.blade.php ================================================ {!! __('entities/abilities.helpers.note', [ 'code' => '[character:4092]', 'attr' => '{Strength}' ]) !!} @include('cruds.fields.visibility_id', ['model' => $ability]) ================================================ FILE: resources/views/entities/pages/abilities/_form.blade.php ================================================

    {{ __('entities/abilities.create.helper', ['name' => $entity->name]) }}

    @include('components.form.abilities', ['options' => ['exclude-entity' => $entity->id]]) @include('cruds.fields.visibility_id')
    ================================================ FILE: resources/views/entities/pages/abilities/_import.blade.php ================================================ @if (!empty($races))

    {!! __('entities/abilities.import.helper', ['name' => $entity->name]) !!}

      @foreach ($races as $name => $count)
    • {!! trans_choice('entities/abilities.import.race_abilities', $count, ['name' => $name, 'count' => $count]) !!}
    • @endforeach
    @else

    {!! __('entities/abilities.import.no_abilities', ['name' => $entity->name]) !!}

    @endif
    ================================================ FILE: resources/views/entities/pages/abilities/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/abilities.create.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_abilities.index', [$campaign, $entity]), 'label' => __('entities.ability')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/abilities.create.title', ['name' => $entity->name]), 'content' => 'entities.pages.abilities._form', ]) @endsection ================================================ FILE: resources/views/entities/pages/abilities/import.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/abilities.actions.sync'), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_abilities.index', [$campaign, $entity]), 'label' => __('entities.ability')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/abilities.actions.sync'), 'content' => 'entities.pages.abilities._import', 'submit' => __('entities/abilities.actions.add'), 'disableSubmit' => empty($races), ]) @endsection ================================================ FILE: resources/views/entities/pages/abilities/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('crud.tabs.abilities') . " - {$entity->name} - {$campaign->name}", 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-abilities' ]) @include('entities.components.og') @section('entity-header-actions')
    @can('update', $entity) @include('entities.pages.abilities._buttons') @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'abilities', 'view' => 'entities.pages.abilities.render', 'entity' => $entity, ]) @endsection @section('modals') @parent @endsection ================================================ FILE: resources/views/entities/pages/abilities/render.blade.php ================================================

    {!! __('entities/abilities.show.helper', ['name' => $entity->name]) !!}

    @include('entities.pages.abilities._abilities') ================================================ FILE: resources/views/entities/pages/abilities/reorder/_reorder.blade.php ================================================
    @foreach($parents as $key => $parent)
    @if ($key === "") {{ __('entities/abilities.reorder.parentless') }} @else {!! $parent[0]->ability->entity->parent?->name !!} @endif
    @foreach($parent as $ability)
    {!! $ability->ability->name !!} @if ($ability->ability->type) ({!! $ability->ability->type!!}) @endif
    @endforeach
    @endforeach
    ================================================ FILE: resources/views/entities/pages/abilities/reorder/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('entities/abilities.show.title', ['name' => $entity->name]), 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-abilities' ]) @section('content') @include('entities.pages.subpage', [ 'active' => 'abilities', 'view' => 'entities.pages.abilities.reorder._reorder', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/abilities/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => trans('entities/abilities.update.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_abilities.index', [$campaign, $entity]), 'label' => __('entities.ability')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/abilities.update.title', ['name' => $entity->name]), 'content' => 'entities.pages.abilities._edit_form', 'deleteID' => '#delete-ability-' . $ability->id, ]) @endsection ================================================ FILE: resources/views/entities/pages/aliases/_form.blade.php ================================================ @if (!isset($entityAsset))

    {!! __('entities/aliases.create.helper', ['name' => $entity->name, 'code' => '@']) !!}

    @endif @include('cruds.fields.is_pinned', ['model' => $entityAsset ?? null, 'fieldName' => 'is_pinned']) @include('cruds.fields.visibility_id', ['model' => $entityAsset ?? null]) ================================================ FILE: resources/views/entities/pages/aliases/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/aliases.create.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_assets.index', [$campaign, $entity->id]), 'label' => __('entries/tabs.media')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/aliases.create.title', ['name' => $entity->name]), 'content' => 'entities.pages.aliases._form', ]) @endsection ================================================ FILE: resources/views/entities/pages/aliases/not-premium.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/aliases/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/aliases.update.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_assets.index', [$campaign, $entity->id]), 'label' => __('entries/tabs.media')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/aliases.update.title', ['name' => $entity->name]), 'content' => 'entities.pages.aliases._form', 'deleteID' => '#delete-alias-' . $entityAsset->id, ]) @endsection ================================================ FILE: resources/views/entities/pages/assets/_assets.blade.php ================================================
    @forelse ($assets as $asset) @if ($asset->hiddenImage()) @continue @endif @includeWhen($asset->isFile(), 'entities.pages.assets._file') @includeWhen($asset->isLink(), 'entities.pages.assets._link') @empty @endforelse
    @section('modals') @parent @endsection ================================================ FILE: resources/views/entities/pages/assets/_buttons.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/assets/_file.blade.php ================================================
    isImage()) style="background-image: url({{ $asset->imageUrl() }})"@endif> @if (!$asset->isImage()) @endif
    {!! $asset->name !!}
    @can('update', $entity) @endif @include('icons.visibility', ['icon' => $asset->visibilityIcon()])
    ================================================ FILE: resources/views/entities/pages/assets/_link.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/assets/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('entries/tabs.media') . " - {$entity->name} - {$campaign->name}", 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-assets' ]) @include('entities.components.og') @section('entity-header-actions')
    @can('update', $entity) @include('entities.pages.assets._buttons') @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'assets', 'view' => 'entities.pages.assets._assets', 'entity' => $entity, ]) @endsection @section('modals') @parent @endsection ================================================ FILE: resources/views/entities/pages/attribute-templates/_actions.blade.php ================================================
    ================================================ FILE: resources/views/entities/pages/attribute-templates/_form.blade.php ================================================ {!! __('attributes/templates.pitch', [ 'boosted-campaign' => '' . __('concept.premium-campaigns') . '', 'marketplace' => '' . __('footer.plugins') . '' ]) !!} ================================================ FILE: resources/views/entities/pages/attribute-templates/apply.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/attributes.template.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entities.attribute_template') ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('partials.forms.form', [ 'title' => __('entities.attribute_template'), 'content' => 'entities.pages.attribute-templates._form', 'actions' => 'entities.pages.attribute-templates._actions', ]) @endsection ================================================ FILE: resources/views/entities/pages/attributes/_buttons.blade.php ================================================ @can('update', $entity) {{ __('entities/attributes.actions.manage') }} @endif ================================================ FILE: resources/views/entities/pages/attributes/_story.blade.php ================================================

    {{ __('entries/tabs.properties') }}

    @include('entities.pages.attributes.render')
    @section('scripts') @parent @vite('resources/js/attributes.js') @endsection @section('modals') @parent @endsection ================================================ FILE: resources/views/entities/pages/attributes/dashboard.blade.php ================================================ @extends('layouts.widget', [ 'title' => __('entities/attributes.show.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-attributes' ]) @section('content')
    @include('entities.pages.attributes.render')
    @endsection @section('scripts') @parent @vite('resources/js/attributes.js') @endsection @section('styles') @parent @endsection ================================================ FILE: resources/views/entities/pages/attributes/edit.blade.php ================================================ user()->isAdmin(); ?> @extends('layouts.app', [ 'title' => __('entities/attributes.index.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entries/tabs.properties'), ], 'mainTitle' => false, 'centered' => true, ]) @section('content')
    {{ __('crud.cancel') }}
    @if (auth()->user()->isAdmin() && $entity->is_attributes_private)

    is_attributes_private ?? false)) checked="checked" @endif />

    This feature is deprecated. By unchecking it, you will no longer be able to activate it. You can now toggle the private status of all attributes of the entity at once instead.

    @endif
    @endsection @section('scripts') @vite('resources/js/attributes.js') @vite('resources/js/attributes-manager.js') @endsection ================================================ FILE: resources/views/entities/pages/attributes/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() || request()->filled('dashboard') ? 'ajax' : 'app'), [ 'title' => __('entries/tabs.properties') . " - {$entity->name} - {$campaign->name}", 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-attributes' ]) @include('entities.components.og') @section('entity-header-actions')
    @can('attributes', [$entity]) @include('entities.pages.attributes._buttons') @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'attributes', 'view' => 'entities.pages.attributes.main', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/attributes/live/_form.blade.php ================================================ @if ($attribute->isCheckbox())
    value) checked="checked" @endif />
    @elseif ($attribute->isText()) @elseif ($attribute->isNumber()) validConstraints()) max="{{ $attribute->numberMax() }}" min="{{ $attribute->numberMin() }}" @endif /> @elseif ($attribute->validConstraints())

    {{ __('entities/attributes.ranges.text', ['options' => $attribute->listRangeText()]) }}

    @else @endif
    ================================================ FILE: resources/views/entities/pages/attributes/live/edit.blade.php ================================================ @include('partials.forms._dialog', [ 'title' =>__('entities/attributes.live.title', ['attribute' => $attribute->name()]), 'content' => 'entities.pages.attributes.live._form', 'submit' => __('crud.update'), 'dropdownParent' => '#primary-dialog', ]) @if (!empty($uid))@endif ================================================ FILE: resources/views/entities/pages/attributes/main.blade.php ================================================ {{ __('onboarding/attributes.title') }}

    {!! __('onboarding/attributes.text', [ 'name' => $entity->name, ]) !!}

    @include('entities.pages.attributes.render') ================================================ FILE: resources/views/entities/pages/attributes/render.blade.php ================================================ @inject('templateService', 'App\Services\Attributes\TemplateService') entityAttributes->where('name', '_layout')->first(); if (!empty($layout)) { $template = $template ?? $templateService->communityTemplate($layout->value); $marketplaceTemplate = $marketplaceTemplate ?? $templateService->campaign($campaign)->marketplaceTemplate($layout->value); } ?> @if (!empty($template) && $entity->hasChild()) @include($template->view(), ['model' => $model ?? $entity->child]) @elseif (!empty($marketplaceTemplate)) @include('entities.pages.attributes.rendering.marketplace', ['plugin' => $marketplaceTemplate]) @else @include('entities.pages.attributes.rendering.default', [ 'attributes' => $entity->attributes()->with('entity')->ordered()->get() ]) @endif @section('scripts') @parent @vite('resources/js/attributes.js') @endsection @section('modals') @parent @endsection ================================================ FILE: resources/views/entities/pages/attributes/rendering/default.blade.php ================================================ count() === 0) { return; } $sections = \App\Facades\Attributes::organise($attributes); $uid = 1; ?>
    @foreach ($sections as $section)
    @if (!empty($section['id'])) @if (auth()->check() && auth()->user()->isAdmin() && $section['is_private'] == true) @endif {!! $section['name'] !!} @else {{ __('entities/attributes.sections.unorganised') }} @endif
    @foreach ($section['attributes'] as $attribute)
    @if ($attribute->isNumber()) @elseif ($attribute->isCheckbox()) @else @endif
    @if (auth()->check() && auth()->user()->isAdmin() && $attribute->is_private) @endif @if ($attribute->validConstraints()) {!! $attribute->name() !!} @else {!! $attribute->name() !!} @endif
    @if ($attribute->isCheckbox()) @if ($attribute->value) @else @endif @elseif ($attribute->isText()) {!! nl2br($attribute->mappedValue()) !!} @else {!! $attribute->mappedValue() !!} @endif @if(\App\Facades\Attributes::isLoop($attribute->name)) @endif
    @if (!isset($fromDashboard) || !$fromDashboard) @can('update', $entity) @endcan @endif
    @endforeach
    @endforeach
    ================================================ FILE: resources/views/entities/pages/attributes/rendering/marketplace.blade.php ================================================ entity; } ?> @if ($plugin->version->isDraft()) {{ __('This plugin is a draft, meaning only its authors can see it rendered.') }} @endif
    @section('styles') @parent @endsection @section('scripts') @parent @endsection ================================================ FILE: resources/views/entities/pages/children/children.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/entities/pages/children/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/children.title') . ' - ' . $entity->name, 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-children' ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'children', 'view' => 'entities.pages.children.children', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/entry/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/story.update.title', ['entity' => $entity->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('fields.description.label'), __('crud.edit') ], 'mainTitle' => false, 'entity' => null ]) @section('content') @include('partials.errors')
    @include('partials.footer_cancel')
    @include('cruds.fields.entry', ['model' => $entity ?? null])
    {{-- For bragi --}} @if ($entity->isCharacter()) @endif @endsection @include('editors.editor', $entity->isCharacter() ? ['name' => 'characters'] : []) ================================================ FILE: resources/views/entities/pages/files/_form.blade.php ================================================ @if(!isset($entityAsset))

    {{ __('entities/files.create.helper', ['name' => $entity->name]) }}

    {{ __('crud.files.hints.limitations', ['formats' => 'jpg, jpeg, png, gif, webp, pdf, xls(x), csv, mp3, ogg, json', 'size' => Limit::readable()->upload()]) }} @include('cruds.fields.helpers.share', ['max' => 25]) @endif @include('cruds.fields.is_pinned', ['model' => $entityAsset ?? null, 'fieldName' => 'is_pinned']) @include('cruds.fields.visibility_id', ['model' => $entityAsset ?? null])
    ================================================ FILE: resources/views/entities/pages/files/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/files.create.title', ['entity' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_assets.index', [$campaign, $entity->id]), 'label' => __('entries/tabs.media')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/files.create.title', ['entity' => $entity->name]), 'content' => 'entities.pages.files._form', ]) @endsection ================================================ FILE: resources/views/entities/pages/files/max.blade.php ================================================ @if ($campaign->superboosted()) {{ __('entities/files.call-to-action.max.limit') }} @else {!! __('entities/files.call-to-action.upgrade.limit', ['limit' => config('limits.campaigns.files.standard')]) !!} @endif @if ($campaign->superboosted())

    {{ __('entities/files.call-to-action.max.helper') }}

    @else

    {!! __('entities/files.call-to-action.upgrade.premium') !!}

    @endif
    ================================================ FILE: resources/views/entities/pages/files/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/files.update.title'), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_assets.index', [$campaign, $entity]), 'label' => __('entries/tabs.media')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/files.update.title'), 'content' => 'entities.pages.files._form', 'deleteID' => '#delete-file-' . $entityAsset->id, ]) @endsection ================================================ FILE: resources/views/entities/pages/image/_form.blade.php ================================================ @include('cruds.fields.image', ['imageRequired' => false, 'dropdownParent' => '#primary-dialog']) ================================================ FILE: resources/views/entities/pages/image/focus.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => trans('entities/image.focus.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entities/image.focus.breadcrumb') ], 'bodyClass' => 'entity-image-focus', 'centered' => true, 'entity' => null ]) @php $source = empty($entity->image_path) && !empty($entity->image_uuid) ? $entity->image : $entity; @endphp @section('content') @include('partials.errors') @if ($campaign->boosted() && empty($entity->image_path) && !empty($entity->image_uuid))

    {!! __('entities/image.focus.warning', ['gallery' => '' . __('sidebar.gallery') . '']) !!}

    @endif @if ($campaign->boosted()) @if(!empty($entity->image_path))

    {{ __('entities/image.focus.helper') }}

    @endif
    img
    @else

    {!! __('entities/image.focus.unboosted', [ 'boosted-campaigns' => '' . __('concept.premium-campaigns') . '' ]) !!}

    {{ __('crud.actions.back') }} @endif
    @endsection @section('scripts') @parent @vite('resources/js/story.js') @endsection ================================================ FILE: resources/views/entities/pages/image/replace.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => trans('entities/image.replace.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entities/image.replace.breadcrumb') ], 'mainTitle' => false, 'bodyClass' => 'entity-image-replace' ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/image.replace.title', ['name' => $entity->name]), 'content' => 'entities.pages.image._form', 'submit' => __('entities/image.actions.save-replace'), ]) @endsection ================================================ FILE: resources/views/entities/pages/inventory/_buttons.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/inventory/_copy.blade.php ================================================

    {{ __('entities/inventories.copy.helper', ['name' => $entity->name]) }}

    @include('cruds.fields.entity', [ 'name' => 'entity_id', 'required' => true, 'label' => __('entities.entry'), ])
    ================================================ FILE: resources/views/entities/pages/inventory/_form.blade.php ================================================ @php $preset = null; if (isset($inventory) && $inventory->image_uuid) { $preset = $inventory->image; } if (isset($inventory)) { $positionPreset = $inventory->position; } @endphp @if (!isset($inventory))

    {{ __('entities/inventories.create.helper', ['name' => $entity->name]) }}

    @endif {{ __('entities/inventories.helpers.name') }} @include('cruds.fields.item', [ 'preset' => (!empty($inventory) && $inventory->item ? $inventory->item: false), 'allowNew' => false, 'dropdownParent' => request()->ajax() ? '#primary-dialog' : null, 'required' => true, 'multiple' => isset($multiple), ]) @if (!empty($inventory) && !empty($inventory->image_uuid) && !empty($inventory->image))
    @include('cruds.fields._image_preview', [ 'image' => $inventory->image->getUrl(192, 144), 'title' => $inventory->name, ])
    @endif
    is_equipped ?? false)) checked="checked" @endif /> copy_item_entry ?? false)) checked="checked" @endif /> @include('cruds.fields.visibility_id', ['model' => $inventory ?? null]) {{ __('entities/inventories.helpers.description') }}
    ================================================ FILE: resources/views/entities/pages/inventory/_generate.blade.php ================================================

    {{ __('entities/inventories.generate.helper', ['name' => $entity->name]) }}

    @include('cruds.fields.tags')
    ================================================ FILE: resources/views/entities/pages/inventory/_grid.blade.php ================================================ @php /** * @var \App\Models\Entity $entity * @var \App\Models\Inventory $item */ @endphp
    {{ __('entities/inventories.togglers.show.quantity') }} {{ __('entities/inventories.togglers.hide.quantity') }} {{ __('entities/inventories.togglers.show.price') }} {{ __('entities/inventories.togglers.hide.price') }} {{ __('entities/inventories.togglers.show.size') }} {{ __('entities/inventories.togglers.hide.size') }} {{ __('entities/inventories.togglers.show.weight') }} {{ __('entities/inventories.togglers.hide.weight') }}
    @foreach ($entity->orderedInventory() as $position => $items)
    {!! $position ?? __('entities/inventories.show.unsorted') !!}
    @can('inventory', $entity) {{ __('crud.remove') }} @endcan
    @foreach ($items as $item) @include('entities.pages.inventory._item') @endforeach
    @endforeach
    ================================================ FILE: resources/views/entities/pages/inventory/_inventory.blade.php ================================================
    @auth @endif @foreach ($inventory as $item) @if (!empty($item->item_id) && empty($item->item)) @continue @endif @if ($previousPosition != $item->position) @php $posCount++; @endphp position; ?> @endif @auth @can('inventory', $entity) @endcan @endif @endforeach
    {{ __('entities.item') }} {{ __('entities/inventories.fields.qty') }} {{ __('crud.fields.visibility') }}
    {!! $item->position ?: '' . __('entities/inventories.show.unsorted') . '' !!}
    @if ($item->is_equipped) {{ __('entities/inventories.fields.is_equipped') }} @endif @if ($item->item) {!! $item->name !!} @else {!! $item->name !!} @endif
    @if ($item->item && $item->item->entity && $item->copy_item_entry) {!! $item->item->entity->parsedEntry() !!} @else {{ $item->description }} @endif
    {{ \Illuminate\Support\Number::format($item->amount ?? 0) }} @include('icons.visibility', ['icon' => $item->visibilityIcon()])
    ================================================ FILE: resources/views/entities/pages/inventory/_item.blade.php ================================================ @php /** @var \App\Models\Inventory $item **/ $image = false; if ($item->item && $item->item->entity->hasImage()) { $image = \App\Facades\Avatar::entity($item->item->entity)->size(192)->thumbnail(); } elseif ($item->image) { $image = $item->image->getUrl(192); } $itemName = ''; if ($item->item) { $itemName = $item->name ?: $item->item->name; } else { $itemName = $item->name; } @endphp ================================================ FILE: resources/views/entities/pages/inventory/_table.blade.php ================================================ @includeWhen($inventory->count() > 0, 'entities.pages.inventory._inventory') @section('modals') @parent @endsection ================================================ FILE: resources/views/entities/pages/inventory/_thumbnail.blade.php ================================================ @php /** @var \App\Models\Inventory $item **/ @endphp @if ($item->item && $item->item->entity->hasImage())
    @elseif ($item->image)
    @else
    @endif ================================================ FILE: resources/views/entities/pages/inventory/copy.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/inventories.create.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.inventory', [$campaign, $entity->id]), 'label' => __('crud.tabs.inventory')], ] ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/inventories.actions.copy_inventory', ['name' => $entity->name]), 'content' => 'entities.pages.inventory._copy', 'submit' => __('entities/inventories.actions.copy_inventory'), ]) @endsection ================================================ FILE: resources/views/entities/pages/inventory/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/inventories.create.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.inventory', [$campaign, $entity->id]), 'label' => __('crud.tabs.inventory')], ] ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/inventories.create.title', ['name' => $entity->name]), 'content' => 'entities.pages.inventory._form', 'submit' => __('entities/inventories.actions.multiple'), 'multiple' => true, ]) @endsection ================================================ FILE: resources/views/entities/pages/inventory/details.blade.php ================================================ {!! $inventory->itemName() !!}
    @include('entities.pages.inventory._thumbnail', ['item' => $inventory])
    @if ($inventory->item) @if ($inventory->item->price)
    {{ $inventory->item->price }}
    {{ __('items.fields.price') }}
    @endif @if ($inventory->item->size)
    {{ $inventory->item->size }}
    {{ __('items.fields.size') }}
    @endif @if ($inventory->item->weight)
    {{ $inventory->item->weight }}
    {{ __('items.fields.weight') }}
    @endif @if ($inventory->item->location)
    {{ \App\Facades\Module::singular(config('entities.ids.location'), __('entities.location')) }}
    @endif @endif
    @include('icons.visibility', ['icon' => $inventory->visibilityIcon()])
    {{ __($inventory->visibilityName()) }}
    {{ __('crud.fields.visibility') }}

    @if ($inventory->item) @else {!! $inventory->name !!} @endif

    @if ($inventory->item)
    @foreach ($inventory->item->entity->tags()->with('entity')->get() as $tag) @if (!$tag->entity) @continue @endif @endforeach
    @endif

    @if ($inventory->item && $inventory->copy_item_entry) {!! $inventory->item->entity->parsedEntry() !!} @else {!! $inventory->description !!} @endif

    @can('inventory', $entity) {{ __('crud.edit') }} @endcan ================================================ FILE: resources/views/entities/pages/inventory/generate.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/inventories.generate.title'), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.inventory', [$campaign, $entity->id]), 'label' => __('crud.tabs.inventory')], ] ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/inventories.generate.title'), 'content' => 'entities.pages.inventory._generate', 'submit' => __('entities/inventories.actions.generate'), ]) @endsection ================================================ FILE: resources/views/entities/pages/inventory/grid.blade.php ================================================
    @includeWhen($entity->orderedInventory()->count() > 0, 'entities.pages.inventory._grid')
    ================================================ FILE: resources/views/entities/pages/inventory/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('crud.tabs.inventory') . " - {$entity->name} - {$campaign->name}", 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-inventory', ]) @include('entities.components.og') @section('entity-header-actions')
    @can('inventory', $entity) @include('entities.pages.inventory._buttons') @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'inventory', 'view' => 'entities.pages.inventory.render', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/inventory/render.blade.php ================================================

    {!! __('entities/inventories.tutorials.all', ['name' => $entity->name]) !!}

    @include('entities.pages.inventory.grid') ================================================ FILE: resources/views/entities/pages/inventory/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/inventories.update.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.inventory', [$campaign, $entity->id]), 'label' => __('crud.tabs.inventory')], ] ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/inventories.update.title', ['name' => $entity->name]), 'content' => 'entities.pages.inventory._form', 'deleteID' => '#delete-inventory-' . $inventory->id, ]) @endsection ================================================ FILE: resources/views/entities/pages/links/_form.blade.php ================================================ @if (!isset($entityAsset))

    {{ __('entities/links.create.helper', ['name' => $entity->name]) }}

    @endif @include('cruds.fields.icon', ['iconFieldName' => 'metadata[icon]', 'placeholder' => 'fa-brands fa-d-and-d-beyond, ra ra-aura', 'model' => $entityAsset ?? null]) @include('cruds.fields.visibility_id', ['model' => $entityAsset ?? null])
    ================================================ FILE: resources/views/entities/pages/links/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/links.create.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_assets.index', [$campaign, $entity->id]), 'label' => trans('entries/tabs.media')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/links.create.title', ['name' => $entity->name]), 'content' => 'entities.pages.links._form', ]) @endsection ================================================ FILE: resources/views/entities/pages/links/go.blade.php ================================================ @extends('layouts.app', [ 'title' => __('entities/links.go.title', ['name' => $entity->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entities/assets.actions.link') ], 'mainTitle' => false, 'centered' => true, ]) @section('content')

    {{ __('entities/links.go.title') }}

    {!! __('entities/links.go.description', ['link' => '' . $entityAsset->metadata['url'] . '']) !!}

    {{ __('entities/links.go.actions.trust') }}
    @endsection ================================================ FILE: resources/views/entities/pages/links/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/links.update.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.entity_assets.index', [$campaign, $entity->id]), 'label' => __('entries/tabs.media')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/links.update.title', ['name' => $entity->name]), 'content' => 'entities.pages.links._form', 'deleteID' => '#delete-link-' . $entityAsset->id, ]) @endsection ================================================ FILE: resources/views/entities/pages/logs/_logs.blade.php ================================================
    @foreach ($logs as $log)
    @if ($log->isPost()) {!! $log->parent->name !!} @endif {!! __('entities/logs.actions.' . $log->actionCode()) !!}
    {{ $log->created_at->diffForHumans() }}
    @if ($log->user) @else {{ __('crud.history.unknown') }} @endif @if ($log->impersonator) ({{ __('entities/logs.impersonated', ['name' => $log->impersonator->name]) }}) @endif
    @if ($campaign->superboosted() && !empty($log->changes)) {{ __('entities/logs.actions.reveal') }} @elseif (!$campaign->superboosted()) {{ __('entities/logs.actions.reveal') }} @endif
    @if ($campaign->superboosted() && !empty($log->changes))

    {{ __('history.helpers.changes') }}

      @foreach ($log->changes as $attribute => $value) @if (is_array($value)) @continue @endif
    • {!! $log->attributeKey($transKey, $attribute) !!}@if (!empty($value) || $log->isBoolean($attribute)):@endif @if ($log->isBoolean($attribute)) @if ($value) {{ __('general.yes') }} @else {{ __('general.no') }} @endif @elseif (!empty($value)) {!! $value !!} @endif
    • @endforeach
    @elseif (!$campaign->superboosted())

    {!! __('entities/logs.call-to-action', ['amount' => config('entities.logs')]) !!}

    @can('boost', auth()->user()) {!! __('settings/premium.actions.unlock', ['campaign' => $campaign->name]) !!} @else {!! __('callouts.premium.learn-more') !!} @endif
    @endif
    @endforeach @if ($logs->hasPages()) {{ $logs->onEachSide(0)->links() }} @endif
    ================================================ FILE: resources/views/entities/pages/logs/_modal.blade.php ================================================
    {!! $entity->name !!} @if (isset($post)) - {!! $post->name !!} @endif
    {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- @foreach ($logs as $log)--}} {{-- @if ($log->action < 7 || $log->post)--}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- @endif--}} {{-- @if ($campaign->superboosted() && !empty($log->changes))--}} {{-- --}} {{-- --}} {{-- --}} {{-- @endif--}} {{-- @endforeach--}} {{-- @if (!$campaign->superboosted())--}} {{-- --}} {{-- --}} {{-- --}} {{-- @endif--}} {{-- --}} {{--
    {{ __('entities/logs.fields.action') }}{{ __('campaigns.members.fields.name') }}{{ __('entities/logs.fields.date') }}
    --}} {{-- {{ __('entities/logs.actions.' . $log->actionCode(), ['post' => $log->post?->name]) }}--}} {{-- @if ($log->user)--}} {{-- {!! $log->user->name !!}--}} {{-- @else--}} {{-- {{ __('crud.history.unknown') }}--}} {{-- @endif--}} {{-- @if ($log->impersonator)--}} {{-- ({{ __('entities/logs.impersonated', ['name' => $log->impersonator->name]) }})--}} {{-- @endif--}} {{-- --}} {{-- --}} {{-- {{ $log->created_at->diffForHumans() }}--}} {{-- --}} {{-- --}} {{-- @if ($campaign->superboosted())--}} {{-- @if(!empty($log->changes))--}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- @endif--}} {{-- @else--}} {{-- --}} {{-- --}} {{-- --}} {{-- --}} {{-- @endif--}} {{--
    --}}
    ================================================ FILE: resources/views/entities/pages/logs/history.blade.php ================================================ @can('history', [$model->entity, $campaign])

    @if ($entity) {!! __('crud.history.created_clean', [ 'name' => (!empty($entity->created_by) ? '' . e(\App\Facades\UserCache::name($entity->created_by)) . '' : __('crud.history.unknown')), 'date' => '' . $model->created_at->diffForHumans() . '', ]) !!}. {!! __('crud.history.updated_clean', [ 'name' => (!empty($entity->updated_by) ? '' . e(\App\Facades\UserCache::name($entity->updated_by)) . '' : __('crud.history.unknown')), 'date' => '' . $model->updated_at->diffForHumans() . '', ]) !!} @can('update', $entity)
    @endcan @else {!! __('crud.history.created_clean', [ 'name' => (!empty($model->created_by) ? '' . e(\App\Facades\UserCache::name($model->created_by)) . '': __('crud.history.unknown')), 'date' => '' . $model->created_at->diffForHumans() . '', ]) !!}. {!! __('crud.history.updated_clean', [ 'name' => (!empty($model->updated_by) ? '' . e(\App\Facades\UserCache::name($model->updated_by)). '': __('crud.history.unknown')), 'date' =>'' . $model->updated_at->diffForHumans() . '', ]) !!} @endif

    @endcan ================================================ FILE: resources/views/entities/pages/logs/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/logs.show.title', ['name' => $entity->name]), 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-logs' ]) @include('entities.components.og') @section('entity-header-actions')
    @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'logs', 'view' => 'entities.pages.logs._logs', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/mentions/mentions.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/mentions.show.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-mentions' ]) @section('entity-header-actions')
    @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'mentions', 'view' => 'entities.pages.mentions.render', 'entity' => $entity, ]) @endsection @section('modals')

    {{ __('entities/mentions.helper') }}

    @endsection ================================================ FILE: resources/views/entities/pages/mentions/render.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/entities/pages/move/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('entities/move.title', ['name' => $entity->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entities/actions.transfer'), ], 'centered' => true, 'entity' => null, ]) @section('content') @include('partials.errors')

    {{ __('entities/move.panel.description') }}

    @can('update', $entity) @else @endcan @if ($entity->entityType->isCustom()) {!! __('entities/move.warnings.custom', ['module' => $entity->entityType->plural()]) !!} @endif @includeIf($entity->entityType->pluralCode() . '.bulk.modals._copy_to_campaign')
    @endsection ================================================ FILE: resources/views/entities/pages/posts/_actions.blade.php ================================================ @can('post', [$entity, 'edit', $post]) {{ __('crud.edit') }} @endcan @if (!isset($more)) @php $title = '[post:' . $post->id . ']'; $data = [ 'title' => $title, 'toggle' => 'tooltip', 'clipboard' => $title, 'toast' => __('entities/notes.copy_mention.success') ]; @endphp {{ __('entities/notes.copy_mention.copy') }} @endif @can('admin', $campaign) {{ __('articles.actions.move') }} @endif @can('setPostTemplates', $campaign) @if ($post->isTemplate()) {{ __('posts/templates.actions.unset') }} @else {{ __('posts/templates.actions.set') }} @endif @endcan @can('update', $entity) {{ __('crud.history.view') }} @endcan @can('delete', $entity) @php $url = route('confirm-delete', [$campaign, 'route' => route('entities.posts.destroy', [$campaign, $entity, $post]), 'name' => $post->name]); @endphp {{ __('posts.remove.title') }} @endcan ================================================ FILE: resources/views/entities/pages/posts/_boosted.blade.php ================================================

    {!! __('callouts.premium.limitation', ['campaign' => $campaign->name]) !!} @can('boost', auth()->user()) {!! __('settings/premium.actions.unlock', ['campaign' => $campaign->name]) !!} @else {!! __('callouts.premium.learn-more') !!} @endif

    ================================================ FILE: resources/views/entities/pages/posts/_form.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/posts/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'seoTitle' => __('posts.create.title') . ' - ' . $entity->name . ' - ' . $campaign->name, 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entities.articles'), __('posts.create.title') ], 'mainTitle' => false, 'centered' => true, 'entity' => null, ]) @section('content') @include('ads.top') {!! __('onboarding/posts.title') !!}

    {!! __('onboarding/posts.text') !!}

    @include('cruds.forms._errors') @include('entities.pages.posts._form') @endsection @include('editors.editor', $entity->isCharacter() ? ['name' => 'characters'] : []) ================================================ FILE: resources/views/entities/pages/posts/dialogs/_role-footer.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/posts/dialogs/_user-footer.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/posts/dialogs/_visibility.blade.php ================================================

    {!! __('posts.visibility.helper', ['name' => $post->name]) !!}

    @include('cruds.fields.visibility_id')
    ================================================ FILE: resources/views/entities/pages/posts/dialogs/visibility.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('posts.visibility.title'), 'breadcrumbs' => [ ] ]) @section('content') @include('partials.forms.form', [ 'title' => __('posts.visibility.title'), 'content' => 'entities.pages.posts.dialogs._visibility', 'model' => $post ]) @endsection ================================================ FILE: resources/views/entities/pages/posts/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('crud.edit') . ' - ' . $model->name . ' - ' . $entity->name . ' - ' . $campaign->name, 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entities.articles'), __('crud.update'), ], 'mainTitle' => false, 'centered' => true, 'entity' => null, ]) @section('content') @include('ads.top') @include('cruds.forms._errors') @include('entities.pages.posts._form') @if(!empty($model) && $campaign->hasEditingWarning()) @endif @if(!empty($from)) @endif @endsection @include('editors.editor', $entity->isCharacter() ? ['name' => 'characters'] : []) @section('modals') @parent @includeWhen(!empty($editingUsers) && !empty($model), 'cruds.forms.edit_warning', ['model' => $model, 'entity' => $entity]) @endsection ================================================ FILE: resources/views/entities/pages/posts/forms/_layout.blade.php ================================================ __('entities/notes.collapsed.open'), 1 => __('entities/notes.collapsed.closed') ]; $defaultCollapsed = null; if (!isset($model) && !empty($campaign->ui_settings['post_collapsed'])) { $defaultCollapsed = 1; } ?>
    boosted()) disabled="disabled" @endif class="w-full" id="config[class]" /> @includeWhen(!$campaign->boosted(), 'entities.pages.posts._boosted')
    ================================================ FILE: resources/views/entities/pages/posts/forms/_main.blade.php ================================================ layout_id) { $layoutHelper = true; } } $options = $entity->postPositionOptions(!empty($model->position) ? $model->position : null); $last = array_key_last($options); $bragiName = $entity->isCharacter() ? $entity->name : null; ?>
    @if (isset($layoutOptions)) @if (!$campaign->superboosted())

    {{ __('post_layouts.premium') }} {{ __('general.learn-more') }}

    @endif
    @endif @if (isset($layoutHelper)) @php $helper = __('post_layouts.helper', [ 'subpage' => '' . $model->layout->name() . '', 'name' => $entity->name ]); @endphp @endif @include('cruds.fields.entry', ['model' => $model ?? null]) @include('cruds.fields.location', ['from' => null]) @if (isset($model)) @include('cruds.forms._calendar', ['post' => $model]) @else @include('cruds.forms._calendar') @endif
    ================================================ FILE: resources/views/entities/pages/posts/forms/_permissions.blade.php ================================================ __('crud.view'), 1 => __('crud.edit'), 2 => __('crud.permissions.actions.bulk.deny') ]; ?>

    {!! __('articles.helpers.permissions', [ 'visibility' => '' . __('crud.fields.visibility') . '' ]) !!}

    /features/articles.html#permissions
    @if(!empty($model)) @foreach ($model->permissions()->onlyRoles()->with('role')->get() as $perm)
    @endforeach @foreach ($model->permissions()->onlyUsers()->with('user')->get() as $perm)
    @endforeach @endif
    @section('modals') @parent

    {{ __('posts.permissions.helpers.members') }}

    @include('components.form.user', ['options' => [ 'dropdownParent' => '#post-new-user', 'multiple' => true ]])

    {{ __('posts.permissions.helpers.roles') }}

    @include('components.form.role', ['options' => [ 'dropdownParent' => '#post-new-role', 'multiple' => true, ]])
    @endsection ================================================ FILE: resources/views/entities/pages/posts/forms/_save-options.blade.php ================================================
    @if(!empty($model) && $model->exists) @endif
    ================================================ FILE: resources/views/entities/pages/posts/forms/_templates.blade.php ================================================

    {{ __('posts/templates.helper') }}

    @foreach ($templates as $template)
    {!! $template->name !!} @can('post', [$template->entity, 'edit', $template]) @endcan
    @endforeach
    ================================================ FILE: resources/views/entities/pages/posts/forms/_visibility.blade.php ================================================ value => 'fa-regular fa-eye', Visibility::Admin->value => 'fa-regular fa-lock', Visibility::AdminSelf->value => 'fa-regular fa-user-lock', Visibility::Self->value => 'fa-regular fa-user-secret', Visibility::Member->value => 'fa-regular fa-users', ]; $visibilitySelected = (int) old('visibility_id', isset($model) && $model->exists ? ($model->visibility_id instanceof Visibility ? $model->visibility_id->value : $model->visibility_id) : $campaign->defaultVisibility()->value ); // Locked: admin-only visibility edited by non-admin, or self/admin-self edited by non-creator if (isset($model) && $model->exists) { $locked = false; if ($model->visibility_id === Visibility::Admin && !auth()->user()->isAdmin()) { $locked = true; } if (in_array($model->visibility_id, [Visibility::Self, Visibility::AdminSelf]) && $model->created_by != auth()->user()->id) { $locked = true; } if ($locked) { $lockedValue = $model->visibility_id instanceof Visibility ? $model->visibility_id->value : $model->visibility_id; ?> exists) { $visibilityOptions = $model->visibilityOptions(); } else { $visibilityOptions = []; $visibilityOptions[Visibility::All->value] = __('crud.visibilities.all'); if (auth()->user()->isAdmin()) { $visibilityOptions[Visibility::Admin->value] = __('crud.visibilities.admin'); $visibilityOptions[Visibility::Member->value] = __('crud.visibilities.members'); } $visibilityOptions[Visibility::Self->value] = __('crud.visibilities.self'); $visibilityOptions[Visibility::AdminSelf->value] = __('crud.visibilities.admin-self'); } ?> ================================================ FILE: resources/views/entities/pages/posts/layouts/index.blade.php ================================================ {{ __('post_layouts.pitch.title') }}

    {!! __('post_layouts.pitch.custom', ['entity' => $entity->name]) !!}

    ================================================ FILE: resources/views/entities/pages/posts/logs/_logs.blade.php ================================================
    @foreach ($logs as $log)
    {!! __('entities/logs.actions.' . $log->actionCode()) !!} - {!! $post->name !!}
    {{ $log->created_at->diffForHumans() }}
    @if ($log->user) @else {{ __('crud.history.unknown') }} @endif @if ($log->impersonator) ({{ __('entities/logs.impersonated', ['name' => $log->impersonator->name]) }}) @endif
    @if ($campaign->superboosted() && !empty($log->changes)) {{ __('entities/logs.actions.reveal') }} @elseif (!$campaign->superboosted()) {{ __('entities/logs.actions.reveal') }} @endif
    @if ($campaign->superboosted() && !empty($log->changes))

    {{ __('history.helpers.changes') }}

      @foreach ($log->changes as $attribute => $value) @if (is_array($value)) @continue @endif
    • {!! $log->attributeKey($transKey, $attribute) !!}@if (!empty($value) || $log->isBoolean($attribute)):@endif @if ($log->isBoolean($attribute)) @if ($value) {{ __('general.yes') }} @else {{ __('general.no') }} @endif @elseif (!empty($value)) {!! $value !!} @endif
    • @endforeach
    @elseif (!$campaign->superboosted())

    {!! __('entities/logs.call-to-action', ['amount' => config('entities.logs')]) !!}

    @can('boost', auth()->user()) {!! __('settings/premium.actions.unlock', ['campaign' => $campaign->name]) !!} @else {!! __('callouts.premium.learn-more') !!} @endif
    @endif
    @endforeach @if ($logs->hasPages()) {{ $logs->onEachSide(0)->links() }} @endif
    ================================================ FILE: resources/views/entities/pages/posts/logs/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/logs.show.title', ['name' => $entity->name]), 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-logs' ]) @include('entities.components.og') @section('content') @include('entities.pages.subpage', [ 'active' => 'logs', 'view' => 'entities.pages.posts.logs._logs', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/posts/move/form.blade.php ================================================

    {!! __('posts.move.helper', ['name' => $post->name]) !!}

    ================================================ FILE: resources/views/entities/pages/posts/move/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('posts.move.title'), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('crud.actions.move'), ], 'entity' => null, ]) @section('content') @include('partials.errors') @include('partials.forms.form', [ 'title' => __('posts.move.title'), 'content' => 'entities.pages.posts.move.form', 'submit' => auth()->check() && auth()->user()->can('update', $entity) ? __('entities/move.actions.transfer') : __('entities/move.actions.copy'), ]) @endsection ================================================ FILE: resources/views/entities/pages/posts/show.blade.php ================================================ @includeWhen($post->layout_id && $campaign->superboosted(), 'entities.components.posts.custom') @includeWhen(!$post->layout_id, 'entities.components.posts.standard') ================================================ FILE: resources/views/entities/pages/print/_abilities.blade.php ================================================ @inject('abilities', 'App\Services\Abilities\AbilityService') @php $entityAbilities = $abilities->campaign($campaign)->entity($entity)->get() @endphp @endforeach
    ================================================ FILE: resources/views/entities/pages/print/_attributes.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/print/_inventory.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/print/_relations.blade.php ================================================ @php Datagrid::layout(\App\Renderers\Layouts\Entity\Relation::class) ->route('entities.relations_table', [$campaign, $entity, 'mode' => 'table']); $rows = $entity ->allRelationships() ->sort(request()->only(['o', 'k'])) ->paginate() ->withPath(route('entities.relations_table', [$campaign, $entity, 'mode' => 'table'])); @endphp ================================================ FILE: resources/views/entities/pages/print/print-bulk.blade.php ================================================ @php $headerImage = true; @endphp @extends('layouts.print', [ 'title' => __('Print'), 'breadcrumbs' => false, 'canonical' => true, 'mainTitle' => false, 'bodyClass' => 'entity-story' ]) @section('content') @foreach ($entities as $model) @php if ($model instanceof \App\Models\Entity) { $entity = $model; } else { $entity = $model->entity; } $name = $entity->entityType->pluralCode() @endphp @if(view()->exists($entity->entityType->pluralCode() . '.show')) @include($entity->entityType->pluralCode() . '.show') @else @include('cruds.overview') @endif @includeIf('entities.pages.profile._' . $entity->entityType->code) @includeIf($entity->entityType->pluralCode() . '._print') @includeWhen($entity->abilities->count() > 0, 'entities.pages.print._abilities') @includeWhen($entity->inventories->count() > 0, 'entities.pages.print._inventory') @includeWhen($entity->relationships->count() > 0, 'entities.pages.print._relations') @includeWhen($entity->attributes->count() > 0 && !$entity->isAttributeTemplate(), 'entities.pages.print._attributes')
    @endforeach @endsection @section('styles') @parent @endsection ================================================ FILE: resources/views/entities/pages/print/print.blade.php ================================================ @php $headerImage = true; @endphp @extends('layouts.print', [ 'title' => $entity->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'canonical' => true, 'mainTitle' => false, 'bodyClass' => 'entity-story' ]) @section('content') @if(view()->exists($name . '.show')) @include($name . '.show') @else @include('cruds.overview') @endif @includeIf('entities.pages.profile._' . $entity->entityType->code) @includeIf($name . '._print') @includeWhen($entity->abilities->count() > 0, 'entities.pages.print._abilities') @includeWhen($entity->inventories->count() > 0, 'entities.pages.print._inventory') @includeWhen($entity->relationships->count() > 0, 'entities.pages.print._relations') @includeWhen($entity->attributes->count() > 0 && !$entity->isAttributeTemplate(), 'entities.pages.print._attributes') @endsection @section('styles') @parent @endsection ================================================ FILE: resources/views/entities/pages/print/profile/_events.blade.php ================================================ elapsedEvents; // Prepare birth and death events $distinctCalendars = []; $birth = null; $death = null; foreach ($elapsed as $event) { if (empty($event->calendar) || $event->isCalendarDate()) { continue; } if ($event->isBirth() || $event->isFounded()) { $distinctCalendars[$event->calendar_id]['birth'] = $event; } elseif ($event->isDeath()) { if (!isset($distinctCalendars[$event->calendar_id]['death'])) { $distinctCalendars[$event->calendar_id]['death'] = $event; continue; } // Already have a death? Take the new one if it's older $older = false; /** @var \App\Models\EntityEvent $previous */ $previous = $distinctCalendars[$event->calendar_id]['death']; if ($previous->year < $event->year) { $older = true; } elseif ($previous->year == $event->year) { if ($previous->month < $event->month) { $older = true; } elseif ($previous->month == $event->month) { if ($previous->day < $event->day) { $older = true; } } } if ($older) { $distinctCalendars[$event->calendar_id]['death'] = $event; } } } ?> @foreach ($distinctCalendars as $calendarId => $calendarEvents) @php $birth = $calendarEvents['birth'] ?? null; $death = $calendarEvents['death'] ?? null; @endphp @if (!empty($birth) && !empty($death)) | {{ __('characters.fields.life') }} | {{ $birth->readableDate() }} ✝ {{ $death->readableDate() }} ({{ $birth->calcElapsed($death) }}) | @elseif (!empty($birth)) @php $yearsAgo = $birth->calcElapsed() @endphp @if ($birth->isBirth()) | {{ __('entities/events.types.birth') }} | {{ $birth->readableDate() }} ({{ $event->isBirth() ? $yearsAgo : trans_choice('entities/events.years-ago', $yearsAgo, ['count' => $yearsAgo]) }}) | @else | {{ __('entities/events.types.founded') }} | {{ $birth->readableDate() }} ({{ $event->isBirth() ? $yearsAgo : trans_choice('entities/events.years-ago', $yearsAgo, ['count' => $yearsAgo]) }}) | @endif @elseif (!empty($death)) | {{ __('entities/events.types.death') }} | {{ $death->readableDate() }} (✝{{ $death->calcElapsed() }}) | @endif @endforeach ================================================ FILE: resources/views/entities/pages/print/profile/_location.blade.php ================================================ @if ($campaign->enabled('locations') && !empty($model->location)) | {!! \App\Facades\Module::singular(config('entities.ids.location'), __('entities.location')) !!} | {!! $model->location->name !!} | @endif ================================================ FILE: resources/views/entities/pages/print/profile/_locations.blade.php ================================================ @if ($campaign->enabled('locations') && !$model->entity->locations->isEmpty()) @php $existingLocations = []; $counter = 0; @endphp | {!! \App\Facades\Module::plural(config('entities.ids.location'), __('entities.locations')) !!} | @foreach ($model->entity->locations as $location) @if(!empty($existingLocations[$location->id])) @continue @endif @php $existingLocations[$location->id] = true; @endphp {!! $location->name !!}@if ($counter < $model->entity->locations->count())@php $counter++; @endphp, @endif @endforeach | @endif ================================================ FILE: resources/views/entities/pages/print/profile/_reminder.blade.php ================================================ @if ($entity->calendarReminder()) | {{ __('crud.fields.calendar_date') }} | {{ $entity->calendarReminder()->readableDate() }} | @endif ================================================ FILE: resources/views/entities/pages/print/profile/_type.blade.php ================================================ @if (!empty($model->type)) @php $defaultOptions = [$campaign]; @endphp | {{ __('crud.fields.type') }} | {{ $model->type }} | @endif ================================================ FILE: resources/views/entities/pages/print/profile/abilities.blade.php ================================================ @if (!empty($model->charges)) | {{ __('abilities.fields.charges') }} | {{ $model->charges }} | @endif @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/attribute_templates.blade.php ================================================ @if (!empty($model->attributeTemplate)) | {{ __('crud.fields.parent') }} | {!! $model->attributeTemplate->name !!} | @endif @if (!empty($model->entityType)) | {{ __('attribute_templates.fields.auto_apply') }} | {!! $model->entityType->name() !!} | @endif ================================================ FILE: resources/views/entities/pages/print/profile/characters.blade.php ================================================ @if ($campaign->enabled('families') && !$model->characterFamilies->isEmpty()) @php $existingFamilies = []; $counter = 0; @endphp | {!! \App\Facades\Module::singular(config('entities.ids.family'), __('entities.families')) !!} | @foreach ($model->characterFamilies as $family) @if(!empty($existingFamilies[$family->family_id])) @continue @endif @php $existingRaces[$family->family_id] = true; @endphp {!! $family->family->name !!}@if ($counter < $model->characterFamilies->count() - 1)@php $counter++; @endphp, @endif @endforeach | @endif @if (!$model->characterRaces->isEmpty() || $model->hasAge()) @if (!$model->characterRaces->isEmpty() && !$model->hasAge()) @php $existingRaces = []; $counter = 0; @endphp | {!! \App\Facades\Module::plural(config('entities.ids.race'), __('entities.races')) !!} | @foreach ($model->characterRaces as $race) @if(!empty($existingRaces[$race->race_id])) @continue @endif @php $existingRaces[$race->race_id] = true; @endphp {!! $race->race->name !!}@if ($counter < $model->characterRaces->count() - 1)@php $counter++; @endphp, @endif @endforeach | @elseif ($model->characterRaces->isEmpty() && $model->hasAge()) | {{ __('characters.fields.age') }} | {{ $model->age }} | @else @php $existingRaces = []; $counter = 0; @endphp | {!! \App\Facades\Module::plural(config('entities.ids.race'), __('entities.races')) !!} | @foreach ($model->characterRaces as $race) @if(!empty($existingRaces[$race->race_id])) @continue @endif @php $existingRaces[$race->race_id] = true; @endphp {!! $race->race->name !!}@if ($counter < $model->characterRaces->count() - 1)@php $counter++; @endphp, @endif @endforeach | | {{ __('characters.fields.age') }} | {{ $model->age }} | @endif @endif @if (!empty($model->sex) || !empty($model->pronouns)) @if (!empty($model->sex)) | {{ __('characters.fields.sex') }} | {{ $model->sex }} | @endif @if (!empty($model->pronouns)) | {{ __('characters.fields.pronouns') }} | {{ $model->pronouns }} | @endif @endif @include('entities.pages.print.profile._events') @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/conversations.blade.php ================================================ | {{ __('conversations.fields.participants') }} | {{ __('conversations.targets.' . ($model->forCharacters() ? 'characters' : 'members')) }} | @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/creatures.blade.php ================================================ @include('entities.pages.print.profile._locations') @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/dice_rolls.blade.php ================================================ @if ($model->parameters) | {{ __('dice_rolls.fields.parameters') }} | {{ $model->parameters }} | @endif @if ($model->character) | {!! \App\Facades\Module::singular(config('entities.ids.character'), __('entities.character')) !!} | {!! $model->character->name !!} | @endif ================================================ FILE: resources/views/entities/pages/print/profile/events.blade.php ================================================ @include('entities.pages.print.profile._locations') @include('entities.pages.print.profile._type') @include('entities.pages.print.profile._reminder') ================================================ FILE: resources/views/entities/pages/print/profile/families.blade.php ================================================ @if (!empty($model->family)) | {!! \App\Facades\Module::singular(config('entities.ids.family'), __('entities.family')) !!} | {!! $model->family->name !!} | @endif @include('entities.pages.print.profile._type') @include('entities.pages.print.profile._events') ================================================ FILE: resources/views/entities/pages/print/profile/items.blade.php ================================================ @if ($model->price) {{ __('items.fields.price') }} | {{ $model->price }} | @endif @if ($model->size) | {{ __('items.fields.size') }} | {{ $model->size }} | @endif @if ($model->weight) | {{ __('items.fields.weight') }} | {{ $model->weight }} | @endif @include('entities.components.profile._location') @if ($model->itemCreators->isNotEmpty()) | {{ __('items.fields.creators') }} | @foreach ($model->itemCreators as $itemCreator){!! $itemCreator->creator->name !!}@if (!$loop->last), @endif @endforeach | @endif @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/journals.blade.php ================================================ @include('entities.components.profile._location') @if ($model->date) | {{ __('journals.fields.date') }} | | @endif @if ($model->author && $model->author) | {{ __('journals.fields.author') }} | {!! $model->author->name !!} | @endif @include('entities.pages.print.profile._reminder') @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/locations.blade.php ================================================ @include('entities.pages.print.profile._type') @include('entities.pages.print.profile._events') @if (!$model->maps->isEmpty()) @php $counter = 0; @endphp | {!! \App\Facades\Module::singular(config('entities.ids.map'), __('entities.map')) !!} | @foreach ($model->maps as $map) {!! $map->name !!} @if ($counter > $model->maps->count() - 1) @php $counter++ @endphp, @endif @endforeach | @endif ================================================ FILE: resources/views/entities/pages/print/profile/maps.blade.php ================================================ @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/notes.blade.php ================================================ @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/organisations.blade.php ================================================ @include('entities.pages.print.profile._location') @include('entities.pages.print.profile._type') @include('entities.pages.print.profile._events') ================================================ FILE: resources/views/entities/pages/print/profile/quests.blade.php ================================================ @if (!empty($model->instigator)) | {{ __('quests.fields.instigator') }} | {!! $model->instigator->name !!} | @endif @if (!empty($model->location)) | {{ __('quests.fields.location') }} | {!! $model->location->name !!} | @endif @if ($model->date) | {{ __('journals.fields.date') }} | | @endif @include('entities.pages.print.profile._reminder') @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/races.blade.php ================================================ @include('entities.pages.print.profile._locations') @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/tags.blade.php ================================================ @if (!empty($model->colour)) | {{ __('crud.fields.colour') }} | {{ $model->colour }} | @endif @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/print/profile/timelines.blade.php ================================================ @include('entities.pages.print.profile._type') ================================================ FILE: resources/views/entities/pages/privacy/_body.blade.php ================================================
    is_private) checked="checked" @endif" x-model="private">
    is_private) checked="checked" @endif" x-model="private">

    {{ __('entities/permissions.quick.viewable-by') }}

    @if (!empty($visibility['roles']) || !empty($visibility['users']))
    @foreach ($visibility['roles'] as $role) @can('update', $role) {!! $role->name !!} @else {!! $role->name !!} @endif @if ($role->isPublic() && $campaign->isPrivate()) @endif @endforeach @foreach ($visibility['users'] as $user)
    @if ($user->hasAvatar()) @else @endif {!! $user->name !!}
    @endforeach
    @else

    {{ __('entities/permissions.quick.empty-permissions') }}

    @endif
    ================================================ FILE: resources/views/entities/pages/privacy/_footer.blade.php ================================================ ================================================ FILE: resources/views/entities/pages/privacy/index.blade.php ================================================ @include('partials.forms.form', [ 'title' => __('entities/permissions.quick.title', ['name' => $entity->name]) , 'content' => 'entities.pages.privacy._body', 'actions' => 'entities.pages.privacy._footer', ]) ================================================ FILE: resources/views/entities/pages/profile/_character.blade.php ================================================ character; } $appearances = $model->characterTraits()->appearance()->orderBy('default_order')->get(); $traits = $model->characterTraits()->personality()->orderBy('default_order')->get(); ?>
    @if ($model->title)

    {{ __('characters.fields.title') }}
    {!! $model->title !!}

    @endif @if ($entity->type)

    {{ __('crud.fields.type') }}
    {!! $entity->type !!}

    @endif @if ($campaign->enabled('races') && !$model->characterRaces->isEmpty()) @php $existingRaces = []; @endphp @foreach ($model->characterRaces as $race) @if(!empty($existingRaces[$race->race_id])) @continue @endif @php $existingRaces[$race->race_id] = true; @endphp

    {{ \App\Facades\Module::plural(config('entities.ids.race'), __('entities.races')) }}

    @endforeach @endif @if ($campaign->enabled('families') && !$model->characterFamilies->isEmpty()) @php $existingFamilies = []; @endphp @foreach ($model->characterFamilies as $family) @if(!empty($existingFamilies[$family->family_id])) @continue @endif @php $existingFamilies[$family->family_id] = true; @endphp

    {{ \App\Facades\Module::plural(config('entities.ids.family'), __('entities.families')) }}

    @endforeach @endif @if ($model->age || $model->age === '0')

    {{ __('characters.fields.age') }}
    {{ $model->age }}

    @endif @if ($model->sex)

    {{ __('characters.fields.sex') }}
    {!! $model->sex !!}

    @endif @if ($model->pronouns)

    {{ __('characters.fields.pronouns') }}
    {!! $model->pronouns !!}

    @endif
      @include('entities.components.elasped_events')
    @if (count($appearances) > 0)

    {{ __('characters.sections.appearance') }}

    @foreach ($appearances as $trait)

    {{ $trait->name }}
    {{ $trait->entry }}

    @endforeach
    @endif @if (auth()->check() && auth()->user()->can('personality', $model) && count($traits) > 0)

    {{ __('characters.sections.personality') }}

    @can('personality', $model) @if (!$model->is_personality_visible) @else @endif @endif
    @foreach ($traits as $trait)

    {{ $trait->name }}
    {!! nl2br(\App\Facades\Mentions::mapAny($trait, 'entry')) !!}

    @endforeach
    @endif ================================================ FILE: resources/views/entities/pages/profile/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/profile.show.title', ['name' => $entity->name]), 'breadcrumbs' => false, 'canonical' => true, 'mainTitle' => false, 'bodyClass' => 'entity-profile' ]) @include('entities.components.og') @section('entity-header-actions')
    @can('update', $entity) {{ __('entities/profile.actions.edit_profile') }} @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'profile', 'view' => 'entities.pages.profile._' . $entity->entityType->code, 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/relations/_buttons.blade.php ================================================ @can('relation', $entity) @endcan ================================================ FILE: resources/views/entities/pages/relations/_form.blade.php ================================================ @empty($relation)

    {!! __('entities/relations.create.helper', ['name' => $entity->name]) !!}

    @endif
    @include('cruds.fields.target', ['target' => !empty($relation) ? $relation->target : null])
    @include('cruds.fields.relation') @include('cruds.fields.attitude', ['model' => $relation ?? null]) @if(empty($relation) && (!isset($mirror) || $mirror == true)) @include('entities.pages.relations.fields.mirrored') @endif @if (!empty($relation) && !empty($relation->isMirrored())) @include('entities.pages.relations.fields.unmirror') @endif
    @include('cruds.fields.visibility_id', ['model' => isset($relation) ? $relation : null]) @include('cruds.fields.colour_picker', request()->ajax() ? ['dropdownParent' => '#primary-dialog', 'model' => $relation ?? null] : ['model' => $relation ?? null]) @include('cruds.fields.pinned', ['model' => $relation ?? null])
    @if (!empty($mode)) @endif ================================================ FILE: resources/views/entities/pages/relations/_map.blade.php ================================================ __('entities/relations.options.relations'), 'only_relations' => __('entities/relations.options.only_relations'), 'related' => __('entities/relations.options.related'), 'mentions' => __('entities/relations.options.mentions'), ]; ?> @if(!$campaign->boosted())

    {{ __('entities/relations.call-to-action') }}

    @endif
    @section('scripts') @parent @vite('resources/js/relations.js') @endsection @section('styles') @parent @vite('resources/css/relations.css') @endsection ================================================ FILE: resources/views/entities/pages/relations/_related.blade.php ================================================

    {{ __('entities/relations.panels.related') }}

    @foreach ($connections as $connection) @endforeach
    @if(request()->get('order') == 'name' || !request()->has('order')) {{ __('fields.entry.label') }} @else {{ __('crud.fields.name') }} @endif @if(request()->get('order') == 'type_id') {{ __('crud.fields.category') }} @else {{ __('crud.fields.category') }} @endif {{ __('entities/relations.fields.role') }}
    @if ($connection->isMap() == 'map') @includeWhen($connection->map->explorable(), 'maps._explore-link', ['map' => $connection->map]) @endif {{ $connection->entityType->name() }} {{ $connectionService->connectionsText($connection->id) }}
    @if ($connections->hasPages())
    {{ $connections->appends(['mode' => $mode, 'order' => request()->get('order')])->fragment('entity-connections')->onEachSide(0)->links() }}
    @endif
    ================================================ FILE: resources/views/entities/pages/relations/_tables.blade.php ================================================

    {{ __('sidebar.relations') }}

    @if ($rows->count() === 0)

    {{ __('entities/relations.helpers.no_relations') }}

    @can('relation', $entity) @endcan @else
    @include('layouts.datagrid._table')
    @endif
    @includeWhen(!$connections->isEmpty(), 'entities.pages.relations._related') @section('modals') @parent @include('layouts.datagrid.delete-forms', ['models' => Datagrid::deleteForms(), 'params' => []]) @endsection ================================================ FILE: resources/views/entities/pages/relations/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/relations.create.new_title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.relations.index', [$campaign, $entity->id]), 'label' => __('entries/tabs.relations')], __('crud.actions.new') ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/relations.create.new_title', ['name' => $entity->name]), 'content' => 'entities.pages.relations._form', ]) @endsection ================================================ FILE: resources/views/entities/pages/relations/fields/mirrored.blade.php ================================================
    ================================================ FILE: resources/views/entities/pages/relations/fields/unmirror.blade.php ================================================

    {{ __('entities/relations.linked.label') }}

    {!! __('entities/relations.linked.helper', [ 'link' => '" . $relation->target->name . '' ]) !!}

    ================================================ FILE: resources/views/entities/pages/relations/full-form/_entry.blade.php ================================================ @include('cruds.fields.owner', ['owner' => !empty($relation) ? $relation->owner : null]) @include('cruds.fields.target', ['target' => !empty($relation) ? $relation->target : null]) @include('cruds.fields.relation') @include('cruds.fields.attitude', ['model' => $relation ?? null]) @if(empty($relation) && (!isset($mirror) || $mirror == true)) @include('entities.pages.relations.fields.mirrored') @endif @if (!empty($relation) && !empty($relation->isMirrored())) @include('entities.pages.relations.fields.unmirror') @endif
    @include('cruds.fields.visibility_id', ['model' => $relation ?? null]) @include('cruds.fields.colour_picker', request()->ajax() ? ['dropdownParent' => '#primary-dialog'] : []) @include('cruds.fields.pinned')
    @if (!empty($mode)) @endif ================================================ FILE: resources/views/entities/pages/relations/full-form/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __($langKey . '.create.new_title'), 'breadcrumbs' => [ ['url' => Breadcrumb::campaign($campaign)->index($name), 'label' => __('entities.relations')], __('crud.create'), ], 'centered' => true, ]) @section('content') @include('cruds.forms._errors') @if(request()->get('from')) @endif @endsection @include('editors.editor') ================================================ FILE: resources/views/entities/pages/relations/full-form/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __($langKey . '.update.title', [ 'source' => $relation->owner->name, 'target' => $relation->target->name, ]), 'breadcrumbs' => [ ['url' => Breadcrumb::campaign($campaign)->index($name), 'label' => __('entities.relations')], __('crud.update'), ], 'centered' => true, ]) @section('content') @include('cruds.forms._errors') @endsection @include('editors.editor') ================================================ FILE: resources/views/entities/pages/relations/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entries/tabs.relations') . " - {$entity->name} - {$campaign->name}", 'breadcrumbs' => false, 'canonical' => true, 'mainTitle' => false, 'bodyClass' => 'entity-relations' ]) @section('entity-header-actions')
    @if ($mode == 'map' || (empty($mode) && $campaign->boosted())) @else @endif @include('entities.pages.relations._buttons') @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'relations', 'view' => 'entities.pages.relations.render', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/relations/render.blade.php ================================================ @includeWhen($mode == 'map' || (empty($mode) && $campaign->boosted()), 'entities.pages.relations._map') @includeWhen($mode == 'table' || (empty($mode) && !$campaign->boosted()), 'entities.pages.relations._tables') ================================================ FILE: resources/views/entities/pages/relations/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/relations.update.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ['url' => route('entities.relations.index', [$campaign, $entity->id]), 'label' => __('entries/tabs.relations')], ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/relations.update.title', [ 'source' => '' . $entity->name . '', 'target' => '' . $relation->target->name . '', ]), 'content' => 'entities.pages.relations._form', 'deleteID' => '#delete-relation-' . $relation->id, ]) @if(!empty($from)) @endif @if ($relation->isMirrored())@endif @if (!empty($from)) @endif @endsection ================================================ FILE: resources/views/entities/pages/reminders/_list.blade.php ================================================ {!! __('onboarding/reminders.title') !!}

    {!! __('onboarding/reminders.text', ['name' => $entity->name]) !!}

    @if ($rows->count() > 0)
    @include('layouts.datagrid._table')
    @endif @section('modals') @parent @include('layouts.datagrid.delete-forms', ['models' => Datagrid::deleteForms(), 'params' => []]) @endsection ================================================ FILE: resources/views/entities/pages/reminders/_post.blade.php ================================================ @php $routeOptions = [ $campaign, 'entity' => $entity, 'init' => 1 ]; $routeOptions = Datagrid::initOptions($routeOptions); $datagridOptions = ['datagridUrl' => route('entities.reminders.index', $routeOptions)] ; @endphp
    @include('layouts.datagrid._table', $datagridOptions)
    ================================================ FILE: resources/views/entities/pages/reminders/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('crud.tabs.reminders') . " - {$entity->name} - {$campaign->name}", 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-reminders' ]) @include('entities.components.og') @section('entity-header-actions')
    @can('reminders', $entity) {{ __('entities/events.show.actions.add') }} @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'reminders', 'view' => 'entities.pages.reminders._list', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/share/setup.blade.php ================================================ @php $shareTranslations = [ 'status_hidden' => __('entities/share.status.hidden'), 'helper_hidden' => __('entities/share.helpers.hidden_explanation'), 'helper_hidden_unlisted' => __('entities/share.helpers.hidden_unlisted_explanation'), 'status_public' => __('entities/share.status.public'), 'helper_public' => __('entities/share.helpers.public_explanation'), 'status_unlisted' => __('entities/share.status.unlisted'), 'helper_unlisted' => __('entities/share.helpers.unlisted_explanation'), 'status_private' => __('entities/share.status.private'), 'helper_private' => __('entities/share.helpers.private_explanation'), 'warning_entity_permissions' => __('entities/share.helpers.entity_permissions_warning'), 'field_visibility_mode' => __('entities/share.fields.visibility_mode'), 'option_entity_public' => __('entities/share.options.make_entity_public', ['name' => $entity->name]), 'option_global_public' => __('entities/share.options.make_all_public', ['module' => $entity->entityType->plural()]), 'field_campaign_access' => __('entities/share.fields.campaign_access'), 'helper_campaign_access' => __('entities/share.helpers.campaign_access'), 'helper_member_link' => __('entities/share.helpers.member-link'), 'label_member_link' => __('entities/share.labels.member_link'), 'label_public_link' => __('entities/share.labels.public_link'), 'btn_save' => __('crud.actions.save-changes'), 'btn_make_public' => __('entities/share.buttons.make_public'), 'btn_copy' => __('entities/share.buttons.copy'), 'btn_close' => __('crud.actions.close'), 'success_copied_public' => __('entities/share.success.copied_public'), 'success_copied_members' => __('entities/share.success.copied_members'), 'success_updated' => __('entities/share.success.updated'), 'error_generic' => __('errors.500.body.1'), 'title' => __('entities/share.title'), ]; @endphp @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/share.title', ['name' => $campaign->name]), 'breadcrumbs' => [ __('entities/share.title') ] ]) @section('content')
    @endsection ================================================ FILE: resources/views/entities/pages/story/_reorder.blade.php ================================================ posts()->ordered()->get(); $startWithStory = false; $firstPost = $posts->first(); // If the first note has a positive position, it's after the entry field if ($firstPost && $firstPost->position >= 0) { $startWithStory = true; $hasEntry = true; } ?>

    {!! __('entities/story.reorder.helper') !!}

    @includeWhen($startWithStory, 'entities.pages.story.reorder._story') @foreach($posts as $note) @if (!$hasEntry && $note->position >= 0) @php $hasEntry = true @endphp @include('entities.pages.story.reorder._story') @endif
    {!! $note->name !!}
    @endforeach @includeWhen(!$hasEntry, 'entities.pages.story.reorder._story')
    ================================================ FILE: resources/views/entities/pages/story/reorder/_story.blade.php ================================================
    {{ __('fields.description.label') }}
    ================================================ FILE: resources/views/entities/pages/story/reorder.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/story.reorder.panel_title') . " - {$entity->name} - {$campaign->name}", 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'entity-story-reorder' ]) @section('content') @include('entities.pages.subpage', [ 'active' => 'story', 'view' => 'entities.pages.story._reorder', 'entity' => $entity, ]) @endsection ================================================ FILE: resources/views/entities/pages/subpage.blade.php ================================================ @include('partials.errors') @include('ads.cta')
    @include('entities.components.header')
    @include('entities.components.menu_v2', [ 'active' => $active, ])
    @includeIf($view)
    ================================================ FILE: resources/views/entities/pages/tags/_form.blade.php ================================================

    {{ __('entities/tags.create.helper', ['name' => $entity->name]) }}

    @include('cruds.fields.tags', ['model' => $entity, 'enableNew' => true]) ================================================ FILE: resources/views/entities/pages/tags/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('entities/tags.create.title', ['name' => $entity->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), ], 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('entities/tags.create.title', ['name' => $entity->name]), 'content' => 'entities.pages.tags._form', 'submit' => __('crud.save'), ]) @endsection ================================================ FILE: resources/views/entities/pages/transform/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('entities/transform.title', ['name' => $entity->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($entity)->list(), Breadcrumb::show(), __('entities/actions.convert'), ], 'centered' => true, 'entity' => null, ]) @section('content') @include('partials.errors')

    {{ __('entities/transform.panel.title') }}

    {{ $entity->entityType->name() }}

    @if (!empty($confirm))
      @foreach ($confirm as $label => $number)
    • {{ __($label) }}: {{ \Illuminate\Support\Number::format($number) }}
    • @endforeach
    @else

    {{ __('entities/transform.panel.warning') }}

    guides/transform.html {{ __('entities/transform.documentation') }}
    @endif
    @endsection ================================================ FILE: resources/views/errors/403.blade.php ================================================ @extends('layouts.error', [ 'error' => 403 ]) ================================================ FILE: resources/views/errors/404.blade.php ================================================ @extends('layouts.error', [ 'error' => 404 ]) ================================================ FILE: resources/views/errors/500.blade.php ================================================ @extends('layouts.error', [ 'error' => 500 ]) ================================================ FILE: resources/views/errors/503.blade.php ================================================ @extends('layouts.error', [ 'error' => 503 ]) ================================================ FILE: resources/views/errors/images/403-image.blade.php ================================================ ================================================ FILE: resources/views/errors/images/404-image.blade.php ================================================ ================================================ FILE: resources/views/errors/images/500-image.blade.php ================================================ ================================================ FILE: resources/views/errors/images/503-image.blade.php ================================================ ================================================ FILE: resources/views/errors/maintenance.blade.php ================================================ @php AdCache::adless() @endphp @include('layouts.tracking.tracking') {{ __('errors.503.title') }} @include('layouts.links.icons') @vite('resources/css/front.css') @include('layouts.front.nav', ['minimal' => true])
    @if (false)

    Server maintenance

    Kanka is currently unavailable due to planned server maintenance.

    This maintenance is planned to last until 15:30 UTC.

    @else

    {{ __('errors.503.title') }}

    {{ __('errors.503.body.1') }}

    {{ __('errors.503.body.2') }}

    @endif

    Join us over on our Discord to be notified as soon as the maintenance is over.

    @yield('content') @includeWhen(Route::has('home'), 'front.footer') ================================================ FILE: resources/views/errors/private-campaign.blade.php ================================================ @extends('layouts.error', [ 'error' => 403 ]) @section('content')

    {{ __('errors.private-campaign.title') }}

    @guest

    {{ __('errors.private-campaign.guest.helper') }}

    {{ __('errors.private-campaign.guest.login') }}

    @else

    {{ __('errors.private-campaign.auth.helper') }}

    @endguest @endsection ================================================ FILE: resources/views/events/events.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'events', 'view' => 'events.panels.events', ]) @endsection ================================================ FILE: resources/views/events/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Event::class, 'trans' => 'events']) @include('cruds.fields.parent') @include('cruds.fields.entity_locations') @include('cruds.fields.entry2') @include('cruds.fields.tags') @include('cruds.fields.image') @include('cruds.forms._calendar', ['source' => $source]) ================================================ FILE: resources/views/events/panels/events.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/families/families.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'families', 'view' => 'families.panels.families', ]) @endsection ================================================ FILE: resources/views/families/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Family::class, 'trans' => 'families']) @include('cruds.fields.parent') @include('cruds.fields.location') @include('cruds.fields.entry2') @if ($campaign->enabled('characters')) @include('components.form.family_members', ['options' => [ 'model' => $model ?? FormCopy::model(), 'source' => $source ?? null, ]]) @endif @include('cruds.fields.status') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/families/members/_form.blade.php ================================================

    {{ __('families.members.create.helper', ['name' => $model->name]) }}

    @include('cruds.fields.characters', ['quickCreator' => false, 'required' => true, 'model' => null])
    ================================================ FILE: resources/views/families/members/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('families.members.create.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($model->entity)->list(), Breadcrumb::show() ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('families.members.create.title', ['name' => $model->name]), 'content' => 'families.members._form', 'submit' => __('organisations.members.actions.add_multiple'), ]) @endsection ================================================ FILE: resources/views/families/members.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . \App\Facades\Module::plural(config('entities.ids.character'), __('entities.characters')), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('content') @include('entities.pages.subpage', [ 'active' => 'members', 'view' => 'families.panels._members', ]) @endsection ================================================ FILE: resources/views/families/panels/_members.blade.php ================================================ child; $datagridOptions = [ $campaign, $model, 'init' => 1 ]; if (request()->get('m') == \App\Enums\Descendants::All->value || (!request()->has('m') && $campaign->defaultDescendantsMode() === \App\Enums\Descendants::All)) { $allMembers = true; $datagridOptions['m'] = \App\Enums\Descendants::All; } $datagridOptions = Datagrid::initOptions($datagridOptions); $direct = \Illuminate\Support\Number::format($model->members()->has('entity')->count()); $all = \Illuminate\Support\Number::format($model->allMembers()->count()); ?>
    @include('layouts.datagrid._table', ['datagridUrl' => route('families.members', $datagridOptions)])
    ================================================ FILE: resources/views/families/panels/families.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/families/panels/tree.blade.php ================================================
    @section('scripts') @parent @vite('resources/js/family-tree-vue.js') @endsection @section('styles') @parent @vite('resources/css/families/tree.css') @endsection ================================================ FILE: resources/views/families/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true]) @include('families.panels._members')
    @include('entities.components.pins')
    ================================================ FILE: resources/views/families/trees/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('families/trees.title', ['name' => $family->name]), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('content') @include('entities.pages.subpage', [ 'active' => 'tree', 'view' => 'families.panels.tree', 'entity' => $family->entity, 'model' => $family, ]) @endsection ================================================ FILE: resources/views/filters/form.blade.php ================================================ @php /** * @var \App\Models\Campaign $campaign * @var \App\Models\EntityType $entityType * @var \App\Services\FilterService $filterService */ use Illuminate\Support\Arr; if (isset($entityType) && $entityType->hasEntity()) { $formRoute = ['entities.index', $campaign, $entityType]; $resetRoute = route('entities.index', [$campaign, $entityType, 'reset-filter' => 'true']); } else { $formRoute = [$route, $campaign]; $resetRoute = route($route, [$campaign, 'reset-filter' => 'true']); } @endphp {{ __('crud.filters.title') }} @if (auth()->guest())

    {{ __('filters.helpers.guest') }}

    @else @foreach ($filters as $field) @if ($field === 'attributes') @php $hasAttributeFilters = true @endphp @continue @elseif ($field === 'connections') @php $hasConnectionFilters = true @endphp @continue @endif
    @if (is_array($field)) single($field['field']); if (!empty($value) && $field['type'] == 'select2') { $modelclass = new $field['model']; $model = $modelclass->find($value); } // Support for fields with multiple models selected if (Arr::get($field, 'multiple') === true && $field['type'] == 'selectMultiple') { $value = $filterService->filterValue($field['field']); if (!empty($value)) { $modelclass = new $field['model']; $models = $modelclass->whereIn('id', $value)->get()->pluck('name', 'id')->toArray(); } } ?> @if ($field['type'] === 'tag') @include('cruds.datagrids.filters._tag', ['value' => $filterService->filterValue('tags')]) @elseif ($field['type'] === 'text') @elseif ($field['type'] === 'number') @elseif ($field['type'] === 'select') @include('cruds.datagrids.filters._select') @else @include('cruds.datagrids.filters._array') @endif @else @php $labelField = $field === 'status_id' ? 'status' : $field; @endphp @if ($field === 'status_id' && isset($entityType)) @include('cruds.datagrids.filters._status') @elseif ($filterService->isCheckbox($field)) @include('cruds.datagrids.filters._choice') @elseif ($field === 'type' && !empty($entityModel)) @include('cruds.datagrids.filters._type') @elseif ($field === 'sex' && !empty($entityModel)) @include('cruds.datagrids.filters._sex') @elseif ($field === 'date') @include('cruds.datagrids.filters._date') @elseif ($field === 'element_role') @include('cruds.datagrids.filters._element-role') @elseif ($field === 'date_range') @include('cruds.datagrids.filters._date-range') @elseif ($field === 'template') @include('cruds.datagrids.filters._template') @elseif ($field === 'archived') @include('cruds.datagrids.filters._archived') @else @endif @endif
    @endforeach
    @includeWhen($hasAttributeFilters, 'cruds.datagrids.filters._attributes') @includeWhen(isset($hasConnectionFilters), 'cruds.datagrids.filters._connection') @endif
    @if (auth()->check())
    activeFiltersCount() > 0) data-clipboard="{{ $filterService->clipboardFilters() }}" data-toast="{{ __('filters.alerts.copy') }}" onclick="return false" @endif data-toggle="tooltip" data-title="{{ __('crud.filters.copy_helper') }}"> {{ __('crud.filters.copy_to_clipboard') }} {{ __('crud.filters.mobile.copy') }} @if ($filterService->activeFiltersCount() > 0) {{ __('crud.filters.mobile.clear') }} @endif {{ __('helpers.filters.title') }}
    @endif
    ================================================ FILE: resources/views/filters/save_form.blade.php ================================================ {{ __('filters.actions.bookmark') }}

    {{ __('filters.bookmark.helper') }}

    @if ($campaign->boosted()) @else {!! __('filters.helpers.icon-premium', [ 'fontawesome' => 'FontAwesome', 'example' => ' fa-solid fa-horse', 'premium' => '' . __('concept.premium-campaign') . '', ]) !!} @endif

    {!! __('entities/links.helpers.parent') !!}


    @if (auth()->check())
    @endif
    ================================================ FILE: resources/views/front/_campaign.blade.php ================================================
    {{ $campaign->name }} @if ($campaign->is_prioritised) {{ __('campaigns/applications.setup.prioritised') }} @endif

    {!! $campaign->name !!}

    @if ($campaign->spotlight) View spotlight @endif
    {{ \Illuminate\Support\Number::format($campaign->visible_entity_count) }} {{ \Illuminate\Support\Number::format($campaign->follower) }} @if ($campaign->locale) {{ $campaign->locale }} @endif @if ($campaign->systems->isNotEmpty()) {{ $campaign->getSystems() }} @endif
    ================================================ FILE: resources/views/front/boosters.blade.php ================================================ ================================================ FILE: resources/views/front/features.blade.php ================================================ ================================================ FILE: resources/views/front/footer.blade.php ================================================ ================================================ FILE: resources/views/front/home.blade.php ================================================ ================================================ FILE: resources/views/front/sitemap.blade.php ================================================ @if (!empty($urls)) @foreach ($urls as $link) {{ $link }} @endforeach @endif @if (!empty($sitemaps)) @foreach ($sitemaps as $link) {{ $link }} @endforeach @endif ================================================ FILE: resources/views/gallery/_image.blade.php ================================================
  • is_folder) data-folder="{{ route('gallery', [$campaign, 'folder_id' => $image->id]) }}" @endif title="{{ $image->name }}"> @if ($image->isFolder())
    @if ($image->visibility_id != \App\Enums\Visibility::All) @include('icons.visibility', ['icon' => $image->visibilityIcon()]) @endif {{ $image->name }}
    @else @if ($image->hasThumbnail()) @else
    @endif
    @include('icons.visibility', ['icon' => $image->visibilityIcon()]) {{ $image->name }}
    @endif
  • ================================================ FILE: resources/views/gallery/file/_actions.blade.php ================================================ @can('edit', [$image, $campaign]) @if ($image->hasThumbnail()) @endif @endcan @if (!$image->isFolder() ) @endif @can('edit', [$image, $campaign])
    @endcan ================================================ FILE: resources/views/gallery/file/_form.blade.php ================================================
    @if($image->isFolder())
    @else @if ($image->hasThumbnail())
    {{ $image->name }}
    @else

    This file can't be previewed.

    @endif
    @if (!$image->isFont())

    {{ trans_choice('campaigns/gallery.fields.image_used_in', $image->inEntitiesCount(), ['count' => $image->inEntitiesCount()]) }}

    @if($image->inEntitiesCount() > 0)
    @foreach($image->inEntities() as $entity) {{ $entity->name }} @endforeach
    @endif @endif @if($image->mentions->count() > 0)

    {{ trans_choice('campaigns/gallery.fields.image_mentioned_in', $image->mentions->count(), ['count' => $image->mentions->count()]) }}

    @foreach($image->mentions as $mention) @if($mention->isPost()) {{ $mention->post->name }} @else {{ $mention->entity->name }} @endif @endforeach
    @endif
    @endif
    @if(!$image->isFolder()) {{ strtoupper($image->ext) }} {{ $image->niceSize() }} @endif
    {{ $image->user ? $image->user->name : __('crud.users.unknown') }}
    @can('edit', [$image, $campaign]) @if(!$image->isFolder()) @endif @include('cruds.fields.visibility_id', ['model' => $image]) @endcan
    ================================================ FILE: resources/views/gallery/file/edit.blade.php ================================================ @can('edit', [$image, $campaign]) @include('partials.forms._dialog', [ 'title' => $image->name, 'content' => 'gallery.file._form', 'submit' => __('campaigns/gallery.actions.save'), 'actions' => 'gallery.file._actions', 'deleteID' => auth()->user()->can('delete', [$image, $campaign]) && ($image->isFolder() || $image->hasNoFolders()) ? '#delete-confirm-form' : null, ]) @if(!$image->isFolder() || $image->hasNoFolders()) @endif @else @include('partials.forms._dialog', [ 'title' => $image->name, 'content' => 'gallery.file._form', 'submit' => __('campaigns/gallery.actions.save'), 'actions' => 'gallery.file._actions', 'deleteID' => auth()->user()->can('delete', [$image, $campaign]) && ($image->isFolder() || $image->hasNoFolders()) ? '#delete-confirm-form' : null, ]) @endcan ================================================ FILE: resources/views/gallery/file/focus/_actions.blade.php ================================================
    ================================================ FILE: resources/views/gallery/file/focus/_form.blade.php ================================================

    {{ __('entities/image.focus.helper') }}

    img
    ================================================ FILE: resources/views/gallery/file/focus/edit.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => $image->name, 'content' => 'gallery.file.focus._form', 'articleClass' => 'container', 'actions' => 'gallery.file.focus._actions', ]) ================================================ FILE: resources/views/gallery/file/visibility/_form.blade.php ================================================

    {{ __('entities/image.visibility.helper') }}

    @include('cruds.fields.visibility', ['model' => $image])
    ================================================ FILE: resources/views/gallery/file/visibility/edit.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => $image->name, 'content' => 'gallery.file.visibility._form', 'articleClass' => 'container', #'actions' => 'gallery.file.visibility._actions', ]) ================================================ FILE: resources/views/gallery/folders/_form.blade.php ================================================ @include('cruds.fields.visibility_id', ['model' => null]) ================================================ FILE: resources/views/gallery/folders/create.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('campaigns/gallery.new_folder.title'), 'content' => 'gallery.folders._form', 'submit' => __('crud.create'), ]) @if(!empty($folder)) @endif ================================================ FILE: resources/views/gallery/images.blade.php ================================================ @if(!empty($folder))
  • @if (empty($folder->folder_id)) {{ __('crud.actions.back') }} @else {{ $folder->imageFolder->name }} @endif
  • @endif @foreach ($images as $image) @include('gallery._image') @endforeach
    ================================================ FILE: resources/views/gallery/index.blade.php ================================================ route('gallery', $campaign), 'label' => __('campaigns/gallery.breadcrumb')]; if ($folder) { if (!empty($folder->folder_id)) { if (!empty($folder->imageFolder->folder_id)) { $breadcrumbs[] = '...'; } $breadcrumbs[] = ['url' => route('gallery', [$campaign, 'folder_id' => $folder->folder_id]), 'label' => e($folder->imageFolder->name)]; } $breadcrumbs[] = e($folder->name); } ?> @extends('layouts.app', [ 'title' => __('campaigns/gallery.breadcrumb') . ' - ' . $campaign->name, 'breadcrumbs' => $breadcrumbs, 'bodyClass' => 'campaign-gallery', 'mainTitle' => false, 'centered' => true, ]) @section('content') @endsection @section('scripts') @parent @vite('resources/js/gallery/gallery.js') @endsection ================================================ FILE: resources/views/health.blade.php ================================================ {{ config('app.name', 'Laravel') }}

    Application {{ $exception ? 'experiencing problems' : 'up' }}

    HTTP request received. @if (defined('LARAVEL_START')) Response rendered in {{ round((microtime(true) - LARAVEL_START) * 1000) }}ms. @endif

    ================================================ FILE: resources/views/helpers/api-filters.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('helpers.api-filters.title'), 'breadcrumbs' => false, ]) @section('content') @if (request()->ajax())

    {{ __('helpers.api-filters.title') }}

    @endif

    {{ __('helpers.api-filters.description', ['name' => $type]) }}

      @foreach ($filters as $filter)
    • {{ $filter }}
    • @endforeach
    @endsection ================================================ FILE: resources/views/helpers/troubleshooting/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('assistance.title'), 'breadcrumbs' => false, ]) @section('content')

    {{ __('helpers.troubleshooting.subtitle') }}

    @if($token)

    {!! __('assistance.success.opening', [ 'discord' => 'Discord' ]) !!}

    {{ __('assistance.success.token') }}
    {{ $token }}

    {{ __('assistance.success.secret') }}

    @else

    {{ __('assistance.opening') }}

    {{ __('assistance.select') }}

    @endif @if(!$token)
    @endif
    @endsection ================================================ FILE: resources/views/history/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('history.title'), 'seoTitle' => __('history.title') . ' - ' . $campaign->name, 'breadcrumbs' => [['url' => route('history.index', $campaign), 'label' => __('history.title')]], 'bodyClass' => 'campaign-history', 'mainTitle' => false, 'centered' => true, ]) @section('content') @if (!$superboosted)

    {{ __('history.cta') }}

    @else

    {!! __('history.helpers.base', ['amount' => config('entities.logs_delete')]) !!}

    @endif @if ($superboosted)
    @if (count($filters) > 0) @endif
    @endif @if ($models->count() > 0) @php $count = 0; @endphp @foreach ($models as $log) @if ($log->day() !== $previous) @if ($previous !== null)
    @endif
    {{ $log->created_at->format('M d, Y') }}
    @endif
    @if ($superboosted || $count === 0) @php $postLink = null; /** @var \App\Models\Entity $logEntity */ $logEntity = $log->isPost() ? $log->parent?->entity : $log->parent; if (!$logEntity || $logEntity->trashed()) { $entityLink = '' . ($logEntity ? $logEntity->name : __('history.unknown.entity')) . ''; } else { $entityLink = \Illuminate\Support\Facades\Blade::renderComponent( new \App\View\Components\EntityLink($logEntity, $campaign) ); } @endphp {!! __('history.log.' . $log->actionCode(), [ 'user' => $log->userLink(), 'entity' => $entityLink, ]) !!} @if ($log->isPost() && $log->parent) - @if ($log->parent->trashed() || $logEntity?->trashed()) {!! $log->parent->name !!} @else {!! $log->parent->name !!} @endif @endif @if ($log->impersonator) {{ __('entities/logs.impersonated', ['name' => $log->impersonator->name]) }} @endif @else {{ \Illuminate\Support\Str::random(30) }} @endif
    @if(!empty($log->changes))
    {{ __('entities/logs.actions.reveal') }}
    @endif
    @if ($superboosted || $count === 0) @else {{ \Illuminate\Support\Str::random(12) }} @endif
    @if (!empty($log->changes) && $superboosted)

    {{ __('history.helpers.changes') }}

    @foreach ($log->changes as $attribute => $value) @if (is_array($value)) @continue @endif
    {!! $log->attributeKey($logEntity->entityType->pluralCode(), $attribute) !!}
    @if (\Illuminate\Support\Str::contains($attribute, ['has_', 'is_'])) @if ($value) {{ __('general.yes') }} @else {{ __('general.no') }} @endif @elseif (empty($value)) {{ __('history.empty') }} @else {!! $value !!} @endif
    @endforeach
    @endif
    @php $previous = $log->day(); $count++; @endphp @endforeach
    @else {{ __('history.filters.no-results') }} @endif @if ($superboosted)
    {!! $models->appends($filters)->onEachSide(0)->links() !!}
    @endif
    @endsection @section('scripts') @vite('resources/js/history.js') @endsection ================================================ FILE: resources/views/home.blade.php ================================================ @php use App\Enums\Widget; $position = 0; $seoTitle = (!empty($dashboard) ? $dashboard->name : __('sidebar.dashboard')) . ' - ' . $campaign->name; $row = 0; @endphp @extends('layouts.app', [ 'title' => __('dashboard.title') . ' ' . (!empty($dashboard) ? $dashboard->name : $campaign->name), 'seoTitle' => $seoTitle, 'breadcrumbs' => false, 'canonical' => true, 'contentId' => 'campaign-dashboard' ]) @section('og') @if ($campaign->image)@endif @endsection @section('content')
    @if (empty($dashboard)) @include('dashboard.widgets._campaign') @endif @include('ads.top') @if (isset($onboarding) && $onboarding && auth()->check())

    {!! __('dashboards/onboarding.splash', ['name' => auth()->user()->name]) !!}

    @endif
    {{-- --}} {{--

    Dashboards are currently unavailable. We are working on bringing them back as soon as possible.

    --}} {{--
    --}} @if (!$hasCampaignHeader)
    @include('dashboard.widgets._actions')
    @endif @foreach ($widgets as $widget) @if($widget->widget === Widget::Campaign) @include('dashboard.widgets._campaign') @continue; @endif @if ($widget->missingEntity()) @continue @elseif ($widget->widget === Widget::Preview && !$widget->entity) @continue @elseif (!$widget->visible()) @continue @elseif ($widget->noGuest() && auth()->guest()) @continue @endif
    @include('dashboard.widgets._' . $widget->widget->value)
    @endforeach
    @can('dashboard', $campaign) @endcan
    @endsection @section('scripts') @vite('resources/js/dashboard.js') @if ($hasMap) @endif @endsection @section('styles') @if ($hasMap) @endif @vite([ 'resources/css/dashboard.css', 'resources/css/maps/maps.css' ]) @endsection @section('modals') @can('apply', $campaign) @endif @includeWhen($onboarding, 'dashboard.dialogs.onboarding') @endsection ================================================ FILE: resources/views/icons/kanka-svg.blade.php ================================================ ================================================ FILE: resources/views/icons/svg/cog.blade.php ================================================ ================================================ FILE: resources/views/icons/visibility.blade.php ================================================ @if (!isset($icon['skip'])) @endif ================================================ FILE: resources/views/items/entities.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . __('items.show.tabs.inventories'), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('content') @include('entities.pages.subpage', [ 'active' => 'inventories', 'view' => 'items.panels.entities', ]) @endsection ================================================ FILE: resources/views/items/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Item::class, 'trans' => 'items']) @include('cruds.fields.parent') @include('cruds.fields.price', ['trans' => 'items']) @include('cruds.fields.size', ['trans' => 'items']) @include('cruds.fields.weight', ['trans' => 'items']) @include('cruds.fields.location') @include('cruds.fields.creators') @include('cruds.fields.entry2') @include('cruds.fields.status') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/items/inventories.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . __('items.show.tabs.inventories'), 'breadcrumbs' => false, 'mainTitle' => false, ]) @dd('why') @section('content') @include('entities.pages.subpage', [ 'active' => 'inventories', 'view' => 'items.panels.inventories', ]) @endsection ================================================ FILE: resources/views/items/items.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'items', 'view' => 'items.panels.items', ]) @endsection ================================================ FILE: resources/views/items/panels/entities.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/items/panels/inventories.blade.php ================================================ inventories()->orderBy('entity_id', 'ASC')->with(['entity'])->has('entity')->paginate(); ?> @foreach ($r as $inventory) @if ($inventory->entity->child) @endif @endforeach

    {{ __('fields.entry.label') }}
    @if ($inventory->entity->is_private) @endif
    @if ($r->hasPages())
    {{ $r->links() }}
    @endif ================================================ FILE: resources/views/items/panels/items.blade.php ================================================ child, 'init' => 1 ]; $datagridOptions = Datagrid::initOptions($datagridOptions); ?>
    @include('layouts.datagrid._table', ['datagridUrl' => route('items.items', $datagridOptions)])
    ================================================ FILE: resources/views/items/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true]) @includeWhen($entity->children()->count() > 0, 'items.panels.items')
    @include('entities.components.pins')
    ================================================ FILE: resources/views/journals/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Journal::class, 'trans' => 'journals']) @include('cruds.fields.parent') @include('cruds.fields.author') @include('cruds.fields.location') @include('cruds.fields.date')
    @include('cruds.forms._calendar', ['source' => $source])
    @include('cruds.fields.entry2') @include('cruds.fields.tags') @include('cruds.fields.image')
    ================================================ FILE: resources/views/journals/journals.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'journals', 'view' => 'journals.panels.journals', ]) @endsection ================================================ FILE: resources/views/journals/panels/journals.blade.php ================================================
    @include('layouts.datagrid._table')
    @section('modals') @parent @include('partials.helper-modal', [ 'id' => 'help-modal', 'title' => __('crud.actions.help'), 'textes' => [ __('journals.helpers.journals') ] ]) @endsection ================================================ FILE: resources/views/layouts/_breadcrumbs.blade.php ================================================ ================================================ FILE: resources/views/layouts/_lang-switcher.blade.php ================================================ ================================================ FILE: resources/views/layouts/_socials.blade.php ================================================ ================================================ FILE: resources/views/layouts/_theme.blade.php ================================================ @if($campaign->boosted() && $campaign->hasPluginTheme() && request()->get('_plugins') !== '0') @endif @if ($campaign->boosted() && request()->get('_styles') !== '0') @endif ================================================ FILE: resources/views/layouts/ajax.blade.php ================================================ @yield('content') ================================================ FILE: resources/views/layouts/app.blade.php ================================================ get('_theme'); $specificTheme = null; $seoTitle = isset($seoTitle) ? $seoTitle : (isset($title) ? $title : null); $showSidebar = (!empty($sidebar) && $sidebar === 'settings') || !empty($campaign); $sidebarCollapsed = Cookie::get('toggleState') === 'collapsed'; $cleanCanonical = \Illuminate\Support\Str::before(request()->fullUrl(), '%3'); ?> @include('layouts.tracking.tracking') {!! $seoTitle !!} @yield('og') @include('layouts.links.icons') @if (config('app.asset_url')) @endif @vite([ 'resources/css/vendor.css', 'resources/css/app.css', ]) @includeWhen(!config('fontawesome.kit'), 'layouts.styles.fontawesome') @includeWhen (config('ads.nitro.enabled'), 'ads.nitro.styles') @yield('styles') @if (!empty($themeOverride) && in_array($themeOverride, ['dark', 'midnight', 'base'])) @php $specificTheme = $themeOverride; @endphp @if($themeOverride != 'base') @vite('resources/css/themes/' . $themeOverride . '.css') @endif @else @if (!empty($campaign) && $campaign->boosted() && !empty($campaign->theme_id)) @if ($campaign->theme_id !== 1) @vite('resources/css/themes/' . ($campaign->theme_id === 2 ? 'dark' : 'midnight') . '.css') @php $specificTheme = ($campaign->theme_id === 2 ? 'dark' : 'midnight') @endphp @endif @elseif (auth()->check() && !empty(auth()->user()->theme)) @vite('resources/css/themes/' . auth()->user()->theme . '.css') @php $specificTheme = auth()->user()->theme @endphp @endif @endif @includeWhen(!empty($campaign), 'layouts._theme') @livewireStyles {{-- Hide the sidebar if the there is no current campaign --}} {{ __('crud.navigation.skip_to_content') }}
    @include('layouts.header', ['toggle' => $showSidebar]) @includeWhen(isset($campaign) || (isset($sidebar) && $sidebar === 'settings'), 'layouts.sidebars.' . ($sidebar ?? 'app'))
    @includeWhen(!isset($skipBanners), 'layouts.banner') @if(!view()->hasSection('content-header') && (isset($breadcrumbs) && $breadcrumbs !== false))
    @includeWhen(!isset($breadcrumbs) || $breadcrumbs !== false, 'layouts._breadcrumbs') @if (!view()->hasSection('entity-header')) @if (isset($mainTitle)) @else

    {!! $title ?? "Page Title" !!}

    @endif @endif
    @endif @yield('content-header')
    @includeWhen (auth()->check() && \App\Facades\Identity::isImpersonating(), 'partials.impersonate') @include('partials.success') @yield('entity-header') @yield('content')
    @include('ads.video')
    @include('layouts.footer')
    @include('layouts.dialogs.languages') @yield('modals')
    @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @vite(['resources/js/vendor-final.js', 'resources/js/app.js']) @yield('scripts') @includeWhen(config('tracking.consent'), 'partials.cookieconsent') @include('ads.anchor') @livewireScripts ================================================ FILE: resources/views/layouts/banner.blade.php ================================================ {{--@if (auth()->check() && auth()->user()->created_at->isBefore(\Carbon\Carbon::create(2024, 12, 25)))--}} {{-- --}} {{--

    --}} {{-- We thrive on your feedback! Take a moment to fill out our 2025 Satisfaction Survey and help us improve Kanka.--}} {{--

    --}} {{--
    --}} {{--@endif--}} {{----}} {{--

    --}} {{-- We will be performing server maintenance work on Tuesday 8th of July 2025. As a result, Kanka will be completely unavailable from 14:30 UTC to 15:30 UTC. This impacts Kanka, Plugins, and the API.

    --}} {{--

    --}} {{-- Join us on Discord to get live updates.--}} {{--

    --}} {{--
    --}} {{----}} {{--

    --}} {{-- We are releasing a big update on Wednesday 19th of February 2025. As a result, Kanka will be unavailable from 14:30 UTC to 15:30 UTC. Join us on Discord to get updates.--}} {{--

    --}} {{--
    --}} {{--@if (auth()->check() && auth()->user()->subscriptions()->count() === 0 && \Carbon\Carbon::now()->between(\Carbon\Carbon::create('2024-11-29'), \Carbon\Carbon::create('2024-12-02')))--}} {{-- --}} {{--

    --}} {{-- --}} {{-- {!! __('banners.blackfriday24', ['code' => 'BF2024']) !!}--}} {{-- --}} {{--

    --}} {{--
    --}} {{--@endif--}} @can('freeTrial', auth()->user())

    {!! __('subscriptions/free-trial.pitch.title') !!}
    {!! __('subscriptions/free-trial.actions.accept') !!}

    @endif ================================================ FILE: resources/views/layouts/callouts/recoverable.blade.php ================================================ @if ($campaign->boosted())

    {!! __('confirm.delete.recover', [ 'recover' => '' . __('campaigns.show.tabs.recovery') . '', 'day' => '' . config('entities.hard_delete') . '' ])!!}

    @else

    {!! __('confirm.delete.recoverable', [ 'premium-campaign' => '' . __('concept.premium-campaign') . '', 'day' => '' . config('entities.hard_delete') . '' ])!!}

    @endif
    ================================================ FILE: resources/views/layouts/datagrid/_column.blade.php ================================================ {!! $column !!} ================================================ FILE: resources/views/layouts/datagrid/_head.blade.php ================================================ {!! $head->label() !!} ================================================ FILE: resources/views/layouts/datagrid/_header.blade.php ================================================ @if ($header->bulk()) @else {!! $header !!} @endif ================================================ FILE: resources/views/layouts/datagrid/_table.blade.php ================================================ @if (!empty($datagridUrl))
    @endif
    @foreach (Datagrid::headers() as $header) @include('layouts.datagrid._header') @endforeach @forelse ($rows as $row) @if ($row instanceof \App\Models\MiscModel && empty($row->entity)) @continue @endif @foreach (Datagrid::columns($row) as $column) @include('layouts.datagrid._column') @endforeach @empty @endforelse
    {{ __('crud.datagrid.empty') }}
    @if ($rows->hasPages() || Datagrid::hasBulks() )
    @includeWhen(Datagrid::hasBulks(), 'layouts.datagrid.bulks') {!! $rows->appends(Datagrid::paginationFilters())->onEachSide(0)->links() !!}
    @endif
    ================================================ FILE: resources/views/layouts/datagrid/_togglers.blade.php ================================================ @if (isset($model) && $mode === 'grid' && auth()->check() && $entityType->isStandard()) @endif @if (empty($forceMode)) @if (!isset($mode) || $mode === 'grid') {{ __('datagrids.modes.table') }} @else {{ __('datagrids.modes.grid') }} @endif @endif @if ((isset($nestable) && empty($forceMode)) || (isset($entityType) && $entityType->isCustom())) @if ($nestable) {{ __('datagrids.modes.flatten') }} @else {{ __('datagrids.modes.nested') }} @endif @endif ================================================ FILE: resources/views/layouts/datagrid/actions.blade.php ================================================ @if (empty($actions)) @php return; @endphp @endif ================================================ FILE: resources/views/layouts/datagrid/bulks/_calendar-event.blade.php ================================================ @include('cruds.fields.colour_picker', ['dropdownParent' => request()->ajax() ? '#primary-dialog' : null]) ================================================ FILE: resources/views/layouts/datagrid/bulks/_map-group.blade.php ================================================ null, 0 => __('general.no'), 1 => __('general.yes'), ]; ?> @include('cruds.fields.visibility_id', ['bulk' => true]) ================================================ FILE: resources/views/layouts/datagrid/bulks/_map-layer.blade.php ================================================ null, 0 => __('maps/layers.types.standard'), 1 => __('maps/layers.types.overlay'), 2 => __('maps/layers.types.overlay_shown'), ]; ?> @include('cruds.fields.visibility_id', ['bulk' => true]) ================================================ FILE: resources/views/layouts/datagrid/bulks/_map-marker.blade.php ================================================ null, 1 => __('maps/markers.icons.marker'), 2 => __('maps/markers.icons.question'), 3 => __('maps/markers.icons.exclamation'), 4 => __('maps/markers.icons.entity'), ]; $sizeOptions = [ '' => null, 1 => __('maps/markers.circle_sizes.tiny'), 2 => __('maps/markers.circle_sizes.small'), 3 => __('maps/markers.circle_sizes.standard'), 4 => __('maps/markers.circle_sizes.large'), 5 => __('maps/markers.circle_sizes.huge'), 6 => __('maps/markers.circle_sizes.custom'), ]; $groups = $model->groupOptions(); $groups[-1] = __('crud.filters.options.none'); ?> @if ($campaign->boosted()) '"ra ra-sword"']) }}" autocomplete="off" list="map-marker-icon-list" /> @endif @include('maps.markers.fields.font_colour', ['dropdownParent' => '#primary-dialog']) @include('cruds.fields.draggable_choice') @include('cruds.fields.tooltip_choice') @include('maps.markers.fields.background_colour', ['dropdownParent' => '#primary-dialog']) @include('cruds.fields.visibility_id', ['bulk' => true]) ================================================ FILE: resources/views/layouts/datagrid/bulks/update.blade.php ================================================
    @csrf @include('partials.forms._dialog', [ 'title' => __('crud.bulk.edit.title'), 'content' => 'layouts.datagrid.bulks.' . $view, 'submit' => __('crud.actions.apply'), ]) @foreach ($models as $model) @endforeach
    ================================================ FILE: resources/views/layouts/datagrid/bulks.blade.php ================================================ @section('modals') @parent

    {{ __('confirm.delete.bulk') }}

    {{ __('crud.delete_modal.permanent') }}

    @endsection ================================================ FILE: resources/views/layouts/datagrid/delete-forms.blade.php ================================================ @if (!request()->ajax())
    @endif @foreach($models as $model) @if ($model instanceof \App\Models\Relation) @endif @endforeach @if (!request()->ajax())
    @endif ================================================ FILE: resources/views/layouts/datagrid/fulltext_search.blade.php ================================================ ================================================ FILE: resources/views/layouts/datagrid/rows/character.blade.php ================================================ @if ($model instanceof \App\Models\Character) @if ($model->entity->is_private) @endif @if ($model->entity->status) @endif
    {!! $model->title !!} @endif @if ($model->character->entity->is_private) @endif @if ($model->status) @endif
    {!! $model->character->title !!} ================================================ FILE: resources/views/layouts/datagrid/rows/date.blade.php ================================================ @if (!empty($model->date)) @endif ================================================ FILE: resources/views/layouts/datagrid/rows/entitylink.blade.php ================================================ @if ($model instanceof \App\Models\Entity) @if ($model->is_private) @endif @elseif (!empty($with)) @if ($model->{$with} instanceof \App\Models\Entity) @if ($model->{$with}->is_private) @endif @elseif (!empty($model->{$with}) && $model->{$with} && $model->{$with}->entity) @if ($model->{$with}->entity->is_private) @endif @endif @elseif($model instanceof \App\Models\Post) @if ($model->entity->is_private) @endif {!! $model->name !!} ({!! $model->entity->name !!}) @elseif($model->entity) @if ($model->entity->is_private) @endif @elseif($model->remindable) @if ($model->remindable->is_private) @endif @if ($model instanceof \App\Models\Reminder && $model->isPost()) {!! $model->remindable->name !!} ({!! $model->remindable->entity->name !!}) @else @endif @endif ================================================ FILE: resources/views/layouts/datagrid/rows/entitylist.blade.php ================================================
    @if (is_array($with)) @foreach ($model->{$with[0]} as $rel) @if (!$rel->{$with[1]}->entity) @continue @endif @endforeach @else @foreach ($model->$with as $rel) @if (!$rel->entity) @continue @endif @endforeach @endif
    ================================================ FILE: resources/views/layouts/datagrid/rows/image.blade.php ================================================ @if ($model instanceof \App\Models\Entity) @elseif ($model instanceof \App\Models\MiscModel || $model instanceof \App\Models\Post) @elseif ($model instanceof \App\Models\Reminder && $model->isPost()) @elseif ($model instanceof \App\Models\Reminder && $model->isEntity()) @elseif ($model instanceof \App\Models\MapLayer && $model->hasImage()) @elseif (!empty($with)) @php $target = \Illuminate\Support\Arr::get($with, 'target', false); @endphp @endif ================================================ FILE: resources/views/layouts/datagrid/rows/location.blade.php ================================================ @if (empty($with) && $model->location) @elseif ($model->{$with} && $model->{$with}->location) @endif ================================================ FILE: resources/views/layouts/datagrid/rows/locations.blade.php ================================================ @php $target = empty($with) ? $model->entity : data_get($model, $with); @endphp @if ($target?->locations->isNotEmpty()) @foreach ($target->locations as $location) @endforeach @endif ================================================ FILE: resources/views/layouts/datagrid/rows/mention-link.blade.php ================================================ @php /** @var \App\Models\EntityMention $model */ @endphp @if ($model->entity_id && !$model->entity) {{ __('crud.hidden') }} @php return @endphp @endif @if ($model->entity) @if ($model->entity->is_private) @endif @endif @if ($model->isQuestElement()) @if ($model->questElement && $model->entity) - @include('icons.visibility', ['icon' => $model->questElement->skipAllIcon()->visibilityIcon()]) {!! $model->questElement->name() !!} @endif @elseif ($model->isTimelineElement()) @if ($model->timelineElement && $model->entity) - @include('icons.visibility', ['icon' => $model->timelineElement->skipAllIcon()->visibilityIcon()]) {!! $model->timelineElement->elementName() !!} @endif @elseif ($model->isPost()) @if ($model->post && $model->entity) - @include('icons.visibility', ['icon' => $model->post->skipAllIcon()->visibilityIcon()]) {!! $model->post->name !!} @endif @elseif ($model->isCampaign()) {!! $campaign->name !!} @endif ================================================ FILE: resources/views/layouts/datagrid/rows/parentlink.blade.php ================================================ @if ($model->parent ) @endif ================================================ FILE: resources/views/layouts/datagrid/rows/since.blade.php ================================================ @if (!empty($model->{$key})) @endif ================================================ FILE: resources/views/layouts/datagrid/rows/tags.blade.php ================================================ @php $target = empty($with) ? $model : data_get($model, $with) @endphp
    @if ($target instanceof \App\Models\MiscModel) @foreach ($target->entity->visibleTags() as $tag) @if (!$tag->entity) @continue @endif @endforeach @elseif ($target instanceof \App\Models\Entity || $model instanceof \App\Models\Post) @foreach ($target->visibleTags() as $tag) @if (!$tag->entity) @continue @endif @endforeach @elseif ($target instanceof \App\Models\CharacterRace || $target instanceof \App\Models\CharacterFamily) @foreach ($target->character->entity->visibleTags() as $tag) @if (!$tag->entity) @continue @endif @endforeach @else @foreach ($target->tags() as $tag) @if (!$tag->entity) @continue @endif @endforeach @endif
    ================================================ FILE: resources/views/layouts/datagrid/rows/view.blade.php ================================================ @include($with) ================================================ FILE: resources/views/layouts/datagrid/rows/visibility.blade.php ================================================ @includeWhen(auth()->check(), 'icons.visibility', ['icon' => $model->visibilityIcon()]) ================================================ FILE: resources/views/layouts/datagrid/rows/visibility_pivot.blade.php ================================================ @if (auth()->check()) @php /** @var \App\Models\Visibility $pivot */ $pivot = new \App\Models\EntityAbility(); $pivot->visibility_id = $model->pivot->visibility_id; @endphp @include('icons.visibility', ['icon' => $pivot->visibilityIcon()]) @endif ================================================ FILE: resources/views/layouts/datagrid/search.blade.php ================================================ ================================================ FILE: resources/views/layouts/dialogs/languages.blade.php ================================================ @php $currentUrl = url()->full(); if (\Illuminate\Support\Str::contains($currentUrl, '?')) { $currentUrl .= '&lang='; } else { $currentUrl .= '?lang='; } @endphp
    {{ __('footer.language-switcher.other') }}
    ================================================ FILE: resources/views/layouts/dialogs/subscription.blade.php ================================================ {{ $title ?? __('concept.premium-feature') }}

    {{ __('settings/premium.create.pitch_2026') }}

    ================================================ FILE: resources/views/layouts/error.blade.php ================================================ @php AdCache::adless() @endphp @include('layouts.tracking.tracking') {{ $error }} {{ __('errors.' . $error . '.title') }} @include('layouts.links.icons') @includeWhen(!config('fontawesome.kit'), 'layouts.styles.fontawesome') @if (config('app.asset_url')) @endif @vite('resources/css/front.css') @include('layouts.front.nav', ['minimal' => $error === 503])
    @hasSection('content') @yield('content') @else

    {{ __('errors.' . $error . '.title') }}

    @if (is_array(__('errors.' . $error . '.body'))) @foreach (__('errors.' . $error . '.body') as $text)

    {{ $text }}

    @endforeach @else

    {{ __('errors.' . $error . '.body') }}

    @endif @guest

    {{ __('errors.log-in') }}

    @endguest @endif

    {!! __('errors.footer', [ 'discord' => 'Discord', 'email' => '' . config('app.email') . '', ]) !!}

    @if ($error !== 503 && auth()->check() && !\App\Facades\Identity::isImpersonating())

    {{ __('errors.back-to-campaigns') }}

    @foreach (auth()->user()->campaigns as $campaign) {!! $campaign->name !!} @endforeach
    @endif
    @includeWhen(Route::has('home'), 'front.footer') @vite('resources/js/front.js') @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @includeWhen(config('tracking.consent'), 'partials.cookieconsent') ================================================ FILE: resources/views/layouts/footer.blade.php ================================================ ================================================ FILE: resources/views/layouts/front/nav.blade.php ================================================ ================================================ FILE: resources/views/layouts/front.blade.php ================================================ @php $cleanCanonical = \Illuminate\Support\Str::before(request()->fullUrl(), '%3'); @endphp @if(config('services.facebook.client_id')) @endif @yield('og') @if(config('app.admin')) @if (!isset($ogImage) || !$ogImage) @endif @endif {{ $title }} - {{ config('app.name') }} @include('layouts.links.icons') @vite('resources/css/front.css') @livewireStyles @yield('styles') @include('layouts.tracking.tracking', ['frontLayout' => true])
    @include('layouts.front.nav') @yield('content') @include('front.footer') @vite(['resources/js/front.js']) @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @includeWhen(config('tracking.consent'), 'partials.cookieconsent') @yield('modals') @yield('scripts') @livewireScripts ================================================ FILE: resources/views/layouts/header/qq.blade.php ================================================ ================================================ FILE: resources/views/layouts/header.blade.php ================================================ ================================================ FILE: resources/views/layouts/links/icons.blade.php ================================================ ================================================ FILE: resources/views/layouts/login.blade.php ================================================ @include('layouts.tracking.tracking') {{ $title ?? __('default.page_title') }} - {{ config('app.name', 'Laravel') }} @include('layouts.links.icons') @vite('resources/css/auth.css') @includeWhen(!config('fontawesome.kit'), 'layouts.styles.fontawesome') @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @vite(['resources/js/auth.js']) @yield('scripts') @includeWhen(config('tracking.consent'), 'partials.cookieconsent') ================================================ FILE: resources/views/layouts/map.blade.php ================================================ get('_theme'); $specificTheme = null; ?> @include('layouts.tracking.tracking') {{ $title ?? __('default.page_title') }} - {{ config('app.name') }} @yield('og') @include('layouts.links.icons') @vite([ 'resources/css/vendor.css', 'resources/css/app.css', 'resources/css/maps/maps.css' ]) @includeWhen(!config('fontawesome.kit'), 'layouts.styles.fontawesome') @if (!empty($themeOverride) && in_array($themeOverride, ['dark', 'midnight', 'base'])) @php $specificTheme = $themeOverride; @endphp @if($themeOverride != 'base') @vite('resources/css/themes/' . request()->get('_theme') . '.css') @endif @else @if (!empty($campaign) && $campaign->boosted() && !empty($campaign->theme_id)) @if ($campaign->theme_id !== 1) @vite('resources/css/themes/' . ($campaign->theme_id === 2 ? 'dark' : 'midnight') . '.css') @php $specificTheme = ($campaign->theme_id === 2 ? 'dark' : 'midnight') @endphp @endif @elseif (auth()->check() && !empty(auth()->user()->theme)) @vite('resources/css/themes/' . auth()->user()->theme . '.css') @php $specificTheme = auth()->user()->theme @endphp @endif @endif @includeWhen(!empty($campaign), 'layouts._theme') @yield('styles') @if (!isset($noHeader))
    @includeWhen(!isset($noHeader), 'layouts.header', ['toggle' => true])
    @yield('content')
    @else @yield('content') @endif @vite(['resources/js/vendor-final.js', 'resources/js/app.js']) @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @if ($map->isReal()) @else @endif @vite('resources/js/location/map-v3.js') @yield('scripts') @livewireScripts @yield('modals') @includeWhen(config('tracking.consent'), 'partials.cookieconsent') ================================================ FILE: resources/views/layouts/print.blade.php ================================================ get('_theme', 'base'); $specificTheme = null; ?> {!! $title ?? '' !!} @include('layouts.links.icons') @vite([ 'resources/css/vendor.css', 'resources/css/app.css', ]) @includeWhen(!config('fontawesome.kit'), 'layouts.styles.fontawesome') @yield('styles') @if (!empty($themeOverride) && in_array($themeOverride, ['dark', 'midnight', 'base'])) @php $specificTheme = $themeOverride; @endphp @if($themeOverride != 'base') @vite('resources/css/themes/' . request()->get('_theme') . '.css') @endif @else @if (!empty($campaign) && $campaign->boosted() && !empty($campaign->theme_id)) @if ($campaign->theme_id !== 1) @vite('resources/css/themes/' . ($campaign->theme_id === 2 ? 'dark' : 'midnight') . '.css') @php $specificTheme = ($campaign->theme_id === 2 ? 'dark' : 'midnight') @endphp @endif @elseif (auth()->check() && !empty(auth()->user()->theme)) @vite('resources/css/themes/' . auth()->user()->theme . '.css') @php $specificTheme = auth()->user()->theme @endphp @endif @endif @includeWhen(!empty($campaign), 'layouts._theme') @vite([ 'resources/css/print/print.css', ])
    @if(!view()->hasSection('content-header'))
    @includeWhen(!isset($breadcrumbs) || $breadcrumbs !== false, 'layouts._breadcrumbs') @if (!View::hasSection('entity-header')) @if (isset($mainTitle)) @else

    {!! $title ?? "Page Title" !!}

    @endif @endif
    @endif @yield('content-header')
    @include('partials.success') @yield('entity-header') @yield('content')
    @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @vite(['resources/js/vendor-final.js', 'resources/js/app.js']) @yield('scripts') ================================================ FILE: resources/views/layouts/rich.blade.php ================================================ get('_theme'); $specificTheme = null; ?> @include('layouts.tracking.tracking') {{ $title ?? __('default.page_title') }} - {{ config('app.name') }} @yield('og') @include('layouts.links.icons') @vite([ 'resources/css/vendor.css', 'resources/css/app.css', ]) @includeWhen(!config('fontawesome.kit'), 'layouts.styles.fontawesome') @if (!empty($themeOverride) && in_array($themeOverride, ['dark', 'midnight', 'base'])) @php $specificTheme = $themeOverride; @endphp @if($themeOverride != 'base') @vite('resources/css/themes/' . request()->get('_theme') . '.css') @endif @else @if (!empty($campaign) && $campaign->boosted() && !empty($campaign->theme_id)) @if ($campaign->theme_id !== 1) @vite('resources/css/themes/' . ($campaign->theme_id === 2 ? 'dark' : 'midnight') . '.css') @php $specificTheme = ($campaign->theme_id === 2 ? 'dark' : 'midnight') @endphp @endif @elseif (auth()->check() && !empty(auth()->user()->theme)) @vite('resources/css/themes/' . auth()->user()->theme . '.css') @php $specificTheme = auth()->user()->theme @endphp @endif @endif @includeWhen(!empty($campaign), 'layouts._theme') @yield('styles') @yield('content')
    @vite(['resources/js/vendor-final.js', 'resources/js/app.js']) @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @yield('scripts') @livewireScripts @yield('modals') @includeWhen(config('tracking.consent'), 'partials.cookieconsent') ================================================ FILE: resources/views/layouts/scripts/fontawesome.blade.php ================================================ ================================================ FILE: resources/views/layouts/sidebars/_campaign.blade.php ================================================ ================================================ FILE: resources/views/layouts/sidebars/app.blade.php ================================================ @if (!empty($campaign)) @endif ================================================ FILE: resources/views/layouts/sidebars/bookmark.blade.php ================================================ setRelation('campaign', $campaign); ?> @auth @php \App\Facades\Dashboard::user(auth()->user()); @endphp @endif @if ($bookmark->dashboard && $campaign->boosted() && $bookmark->isValidDashboard($campaign)) @elseif ($bookmark->target) @elseif ($bookmark->entityType) @elseif ($bookmark->isRandom()) @endif ================================================ FILE: resources/views/layouts/sidebars/bookmarks.blade.php ================================================ @foreach ($links as $bookmark) @include('layouts.sidebars.bookmark', ['bookmark' => $bookmark]) @endforeach ================================================ FILE: resources/views/layouts/sidebars/campaign.blade.php ================================================ ================================================ FILE: resources/views/layouts/sidebars/settings.blade.php ================================================ ================================================ FILE: resources/views/layouts/styles/fontawesome.blade.php ================================================ ================================================ FILE: resources/views/layouts/tracking/fallback.blade.php ================================================ @if (!empty(config('tracking.gtm'))) @endif ================================================ FILE: resources/views/layouts/tracking/tracking.blade.php ================================================ @if (!empty(config('tracking.ga'))) @php isset($campaign) ? \App\Facades\DataLayer::campaign($campaign) : null @endphp @php if (auth()->check()) { \App\Facades\DataLayer::user(auth()->user()); } @endphp @if (isset($gaTrackingEvent) && !empty($gaTrackingEvent)) @endif @if (isset($gaPurchase) && !empty($gaPurchase)) @endif @endif ================================================ FILE: resources/views/layouts/whiteboard.blade.php ================================================ get('_theme'); $specificTheme = null; ?> @include('layouts.tracking.tracking') {{ $title ?? __('default.page_title') }} - {{ config('app.name') }} @yield('og') @include('layouts.links.icons') @vite([ 'resources/css/vendor.css', 'resources/css/app.css', ]) @includeWhen(!config('fontawesome.kit'), 'layouts.styles.fontawesome') @if (!empty($themeOverride) && in_array($themeOverride, ['dark', 'midnight', 'base'])) @php $specificTheme = $themeOverride; @endphp @if($themeOverride != 'base') @vite('resources/css/themes/' . request()->get('_theme') . '.css') @endif @else @if (!empty($campaign) && $campaign->boosted() && !empty($campaign->theme_id)) @if ($campaign->theme_id !== 1) @vite('resources/css/themes/' . ($campaign->theme_id === 2 ? 'dark' : 'midnight') . '.css') @php $specificTheme = ($campaign->theme_id === 2 ? 'dark' : 'midnight') @endphp @endif @elseif (auth()->check() && !empty(auth()->user()->theme)) @vite('resources/css/themes/' . auth()->user()->theme . '.css') @php $specificTheme = auth()->user()->theme @endphp @endif @endif @includeWhen(!empty($campaign), 'layouts._theme') @yield('styles') @yield('content')
    @vite(['resources/js/vendor-final.js', 'resources/js/app.js', 'resources/js/whiteboards.js']) @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @yield('scripts') @yield('modals') @includeWhen(config('tracking.consent'), 'partials.cookieconsent') ================================================ FILE: resources/views/layouts/widget.blade.php ================================================ get('_theme'); $specificTheme = null; $seoTitle = isset($seoTitle) ? $seoTitle : (isset($title) ? $title : null); $showSidebar = (!empty($sidebar) && $sidebar === 'settings') || !empty($campaign); ?> {!! $seoTitle !!} @vite([ 'resources/css/vendor.css', 'resources/css/app.css', ]) @includeWhen(!config('fontawesome.kit'), 'layouts.styles.fontawesome') @yield('styles') @if (!empty($themeOverride) && in_array($themeOverride, ['dark', 'midnight', 'base'])) @php $specificTheme = $themeOverride; @endphp @if($themeOverride != 'base') @vite('resources/css/themes/' . request()->get('_theme') . '.css') @endif @else @if (!empty($campaign) && $campaign->boosted() && !empty($campaign->theme_id)) @if ($campaign->theme_id !== 1) @vite('resources/css/themes/' . ($campaign->theme_id === 2 ? 'dark' : 'midnight') . '.css') @php $specificTheme = ($campaign->theme_id === 2 ? 'dark' : 'midnight') @endphp @endif @elseif (auth()->check() && !empty(auth()->user()->theme)) @vite('resources/css/themes/' . auth()->user()->theme . '.css') @php $specificTheme = auth()->user()->theme @endphp @endif @endif @includeWhen(!empty($campaign), 'layouts._theme') {{-- Hide the sidebar if the there is no current campaign --}}
    @yield('content')
    @yield('modals') @includeWhen(config('fontawesome.kit'), 'layouts.scripts.fontawesome') @vite(['resources/js/vendor-final.js', 'resources/js/app.js']) @yield('scripts') ================================================ FILE: resources/views/layouts/widgets/editor.blade.php ================================================ @if (Auth::user()->editor != 'legacy') @include('layouts.widgets.summernote') @else @include('layouts.widgets.tinymce') @endif ================================================ FILE: resources/views/layouts/widgets/summernote.blade.php ================================================ @section('scripts') @vite(['resources/js/summernote.js']) @endsection @section('styles') @endsection ================================================ FILE: resources/views/livewire/campaigns/csv-import.blade.php ================================================ @if (!$canAssign)

    {{ __('campaigns/import.csv.select_module') }}

    {{ __('campaigns/import.csv.type_helper') }}

    @else

    {{ __('campaigns/import.csv.set_fields') }}

    {{ __('campaigns/import.csv.fields_helper') }}

    @foreach ($fillableFields as $index => $field)
    @endforeach @if ($type->id == config('entities.ids.character'))
    @foreach($personalities as $index => $selection)
    @endforeach
    @foreach($appearances as $index => $selection)
    @endforeach
    @endif

    {{ $tagLabel }}

    @if (count($columnMap) != 0)

    {{ __('campaigns/import.csv.preview') }}

    @foreach ($columnMap as $field => $columnIndex) @endforeach @if (!empty($tags)) @endif @foreach (array_slice($preview, 1) as $row) @foreach ($columnMap as $field => $columnIndex) @endforeach @if (!empty($tags)) @endif @endforeach
    {{ $this->fieldName($field) }} {{ $tagLabel }}
    {{ $row[$columnIndex] ?? '' }} @foreach ($tags as $tag) {{ $tag['label']}} @endforeach
    @else

    {{ __('campaigns/import.csv.no_preview') }}

    @endif
    @endif
    ================================================ FILE: resources/views/livewire/campaigns/exports-table.blade.php ================================================
    @forelse ($campaignExports as $campaignExport) @empty @endforelse
    @if ($sortColumn === 'status') {!! $this->sortIcon() !!} @endif {{ __('campaigns/plugins.fields.status') }} @if ($sortColumn === 'created_by') {!! $this->sortIcon() !!} @endif {{ __('campaigns.members.fields.name') }} @if ($sortColumn === 'created_at') {!! $this->sortIcon() !!} @endif {{ __('campaigns.invites.fields.created') }} {{ __('campaigns/export.type') }} {{ __('campaigns/export.progress') }} {{ __('campaigns/export.size') }} {{ __('campaigns/export.actions.download') }}
    {{ $this->status($campaignExport) }} @if ($campaignExport->created_by) {{ $campaignExport->user->name }} @endif {!! $this->type($campaignExport) !!} {!! $this->progress($campaignExport) !!} {!! $this->size($campaignExport) !!} {!! $this->download($campaignExport) !!}
    {{ __('crud.datagrid.empty') }}
    {{ $campaignExports->links() }}
    ================================================ FILE: resources/views/livewire/campaigns/tags.blade.php ================================================
    @foreach ($selected as $tag) {{ $tag['label'] }} @endforeach
    @if ($open && count($options))
      @foreach ($options as $option)
    • {{ $option['label'] }}
    • @endforeach
    @endif
    ================================================ FILE: resources/views/livewire/dashboards/entity-listing.blade.php ================================================
    @foreach ($entities as $entity) @if ($entity->entityType->isStandard() && empty($entity->entity_id)) @continue @endif
    @if ($entity->status?->icon) @endif @if ($entity->is_private) @endif
    @if (empty($entity->updated_by) && !empty($entity->created_by)) {{ \App\Facades\UserCache::name($entity->created_by) }} @elseif (!empty($entity->updated_by)) {{ \App\Facades\UserCache::name($entity->updated_by) }} @else {{ __('crud.history.unknown') }} @endif @can('history', [$entity, $campaign]) @if (!empty($entity->updated_at)) {{ $entity->updated_at->diffForHumans() }} @endif @endcan
    @endforeach @if($hasMorePages) @endif
    ================================================ FILE: resources/views/livewire/front-pagination.blade.php ================================================ @php if (! isset($scrollTo)) { $scrollTo = 'body'; } $scrollIntoViewJsSnippet = ($scrollTo !== false) ? <<hasPages()) @endif ================================================ FILE: resources/views/livewire/roadmap/form.blade.php ================================================

    Share your ideas

    Have an idea to improve Kanka? Share it with our development team.

    @if ($success)
    Your idea has been submitted! Once approved, you will receive a notification and others will be able to upvote it.
    @endif @auth() @cannot ('create', \App\Models\Feature::class)

    You have reached the daily limit of 10 submitted ideas! Please try again tomorrow.

    @else
    @error('title') {{ $message }} @enderror
    @if (isset($duplicates) && !empty($duplicates) && !$duplicates->isEmpty())

    This idea might already exist. Here are some similar named ideas already being voted on.

      @foreach ($duplicates as $dup)
    • {!! $dup->name !!}
    • @endforeach
    @endif
    @error('description') {{ $message }} @enderror
    @error('file') {{ $message }} @enderror

    Once reviewed, your idea will show up in the ideas section. If we have questions, we'll contact you on the Discord.

    @endif @else Log in to submit ideas @endauth
    ================================================ FILE: resources/views/livewire/roadmap/ideas.blade.php ================================================
    @foreach ($ideas as $idea)
    @livewire('roadmap.upvote', ['feature' => $idea], key("idea-{$idea->id}"))

    {{ $idea->name }}

    {!! $idea->cleanDescription() !!}
    @endforeach {{ $ideas->links('livewire.front-pagination', data: ['scrollTo' => '#ideas']) }}
    ================================================ FILE: resources/views/livewire/roadmap/upvote.blade.php ================================================
    @if (auth()->check()) @if ($feature->uservote) @else @endif @else @endif {{ \Illuminate\Support\Number::format($count) }}
    @if ($isGuest) Log in to upvote @elseif ($isUnsubbed) Subscribe to upvote @endif
    ================================================ FILE: resources/views/livewire/roadmap.blade.php ================================================
    In progress Ideas Done
    @if (!isset($status) || $status === 'in-progress')
    @php /** @var \App\Models\FeatureCategory $category **/ @endphp @foreach ($categories as $category) @if ($category->nothingPlanned()) @continue @endif

    {{ $category->name }}

    Now

    @foreach ($category->now as $feat) @include('roadmap.feature._progress', ['feature' => $feat]) @endforeach

    Next

    @foreach ($category->next as $feat) @include('roadmap.feature._progress', ['feature' => $feat]) @endforeach

    Later

    @foreach ($category->later as $feat) @include('roadmap.feature._progress', ['feature' => $feat]) @endforeach
    @endforeach
    @elseif ($status === 'ideas')
    @livewire('roadmap.ideas')
    @livewire('roadmap.form')
    @elseif ($status === 'done')
    @php /** @var \App\Models\FeatureCategory $category **/ @endphp @foreach ($categories as $category) @if ($category->nothingDone()) @continue @endif

    {{ $category->name }}

    @foreach ($category->done as $feat) @include('roadmap.feature._progress', ['feature' => $feat]) @endforeach
    @endforeach
    @endif @script @endscript
    ================================================ FILE: resources/views/livewire/users/otp.blade.php ================================================
    {{ __('settings.account.2fa.title') }} @if (session()->has('disable-success')) {{ session('disable-success') }} @endif @if ($user->passwordSecurity?->google2fa_enable)

    {{ __('settings.account.2fa.enabled') }}

    @else @if(auth()->user()->isSocialLogin())

    {{ __('settings.account.2fa.social') }}

    @elseif(empty($user->passwordSecurity))

    {{ __('settings.account.2fa.helper') }} {{ __('settings.account.2fa.learn_more') }}

    {!! __('settings.account.2fa.enable_instructions', [ 'android' => 'Android', 'ios' => 'iOS', ]) !!}

    @elseif(!$user->passwordSecurity->google2fa_enable)

    {{ __('settings.account.2fa.activation_helper') }}

    {!! $user->passwordSecurity->getGoogleQR() !!}
    @error('otp') {{ $message }} @enderror
    @if (session()->has('otp-error')) {{ session('otp-error') }} @endif
    @endif @endif
    ================================================ FILE: resources/views/livewire/widgets/gallery-carousel.blade.php ================================================
    @if (!$readyToLoad) @elseif (count($images) > 0)
    @if (count($images) > 1)
    @endif @if ($showName)

    @endif
    @else

    {{ __('Nothing to show') }}

    @endif
    ================================================ FILE: resources/views/livewire/widgets/random-entity.blade.php ================================================
    @if (!$readyToLoad) @elseif ($entity) @if(view()->exists($specificPreview)) @include($specificPreview, ['entity' => $entity, 'customName' => $customName]) @else @endif @else

    {{ __('Nothing to show') }}

    @endif
    ================================================ FILE: resources/views/locations/characters.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' ' . \App\Facades\Module::plural(config('entities.ids.character'), __('entities.characters')), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'characters', 'view' => 'locations.panels.characters', ]) @endsection ================================================ FILE: resources/views/locations/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.title') @include('cruds.fields.type', ['base' => \App\Models\Location::class, 'trans' => 'locations']) @include('cruds.fields.parent') @include('cruds.fields.entry2') @include('cruds.fields.status') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/locations/locations.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'canonical' => true, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'locations', 'view' => 'locations.panels.locations', ]) @endsection ================================================ FILE: resources/views/locations/panels/characters.blade.php ================================================ @isset($init) @php $routeOptions = [ $campaign, 'location' => $entity->child, 'init' => 1 ]; $routeOptions = Datagrid::initOptions($routeOptions); $datagridOptions = ['datagridUrl' => route('locations.characters', $routeOptions)] ; @endphp @endif
    @include('layouts.datagrid._table', $datagridOptions ?? [])
    @section('modals') @parent @include('partials.helper-modal', [ 'id' => 'help-modal', 'title' => __('crud.actions.help'), 'textes' => [ __('locations.helpers.characters') ] ]) @endsection ================================================ FILE: resources/views/locations/panels/events.blade.php ================================================ @isset($init) @php $routeOptions = [ $campaign, 'location' => $entity->child, 'init' => 1 ]; $routeOptions = Datagrid::initOptions($routeOptions); $datagridOptions = ['datagridUrl' => route('locations.events', $routeOptions)] ; @endphp @endif
    @include('layouts.datagrid._table', $datagridOptions ?? null)
    ================================================ FILE: resources/views/locations/panels/locations.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/locations/panels/quests.blade.php ================================================ @isset($init) @php $routeOptions = [ $campaign, 'location' => $entity->child, 'init' => 1 ]; $routeOptions = Datagrid::initOptions($routeOptions); $datagridOptions = ['datagridUrl' => route('locations.quests', $routeOptions)] ; @endphp @endif
    @include('layouts.datagrid._table', $datagridOptions ?? [])
    ================================================ FILE: resources/views/maps/_explore-link.blade.php ================================================ @php /** @var \App\Models\Map $map */ if (!$map->explorable()) { return ''; } if ($map->isChunked()) { if ($map->chunkingError()) { return ''; } elseif ($map->chunkingRunning()) { return ''; } } @endphp ================================================ FILE: resources/views/maps/_marker.blade.php ================================================
    ================================================ FILE: resources/views/maps/_preview.blade.php ================================================ @section('content') @endsection @section('scripts') @parent @include('maps._setup') @endsection @section('styles') @parent @endsection ================================================ FILE: resources/views/maps/_setup.blade.php ================================================ centerFocus(); if (isset($single) && $single) { $focus = "$model->latitude, $model->longitude"; } elseif (request()->has('lat') && request()->has('lng')) { $focus = ((float) request()->get('lat')) . ', ' . ((float) request()->get('lng')); } elseif (request()->has('focus')) { /** @var \App\Models\MapMarker $pin */ $pin = $map->markers->where('id', request()->get('focus', 0))->first(); if ($pin) { $focus = "$pin->latitude, $pin->longitude"; } } ?> ================================================ FILE: resources/views/maps/_ticker.blade.php ================================================ ================================================ FILE: resources/views/maps/bulk/modals/_copy_to_campaign.blade.php ================================================ ================================================ FILE: resources/views/maps/explore/legend.blade.php ================================================

    {{ __('maps.panels.legend') }}

    ================================================ FILE: resources/views/maps/explore.blade.php ================================================ @extends('layouts.map', [ 'title' => $map->name, 'map' => $map, ]) @section('content') @can('update', $map->entity)
    @endif
    @endsection @section('scripts') @parent @include('maps._setup', ['editable' => true]) @endsection @section('styles') @parent @endsection @section('modals') @can('update', $map->entity) @include('partials.errors') @include('maps.markers._form', ['model' => null, 'map' => $map, 'activeTab' => 1, 'dropdownParent' => '#marker-modal', 'from' => 'explore']) @endcan @endsection ================================================ FILE: resources/views/maps/form/_copy.blade.php ================================================ ================================================ FILE: resources/views/maps/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Map::class, 'trans' => 'maps']) @include('cruds.fields.parent') @include('cruds.fields.location') @include('cruds.fields.entry2') @include('cruds.fields.tags') @include('cruds.fields.image', ['size' => 'map']) ================================================ FILE: resources/views/maps/form/_groups_max.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/groups.create.title', ['name' => $map->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), ['url' => route('maps.map_groups.index', [$campaign, $map]), 'label' => __('maps.panels.groups')], __('maps/groups.create.title') ] ]) @section('content') @if ($campaign->boosted()) {{ __('maps/groups.pitch.max.limit') }} @else {!! __('maps/groups.pitch.upgrade.limit', ['limit' => config('limits.campaigns.groups.standard')]) !!} @endif @if ($campaign->boosted())

    {{ __('maps/groups.pitch.max.helper', ['limit' => $max]) }}

    @else

    {{ __('maps/groups.pitch.upgrade.upgrade', ['limit' => $max]) }}

    @endif
    @endsection ================================================ FILE: resources/views/maps/form/_layers_max.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/layers.create.title', ['name' => $map->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), ['url' => route('maps.map_layers.index', [$campaign, $map]), 'label' => __('maps.panels.layers')], __('maps/layers.create.title') ] ]) @section('content') @if ($campaign->boosted())

    {{ __('maps/groups.pitch.max.limit') }}

    {{ __('maps/layers.pitch.max.helper', ['limit' => $max]) }}

    @else

    {{ __('maps/layers.pitch.upgrade.upgrade', ['limit' => $max]) }}

    @endif @endsection ================================================ FILE: resources/views/maps/form/_markers.blade.php ================================================ @if (!isset($model) || !$model->explorable())

    {{ __('maps.helpers.missing_image') }}

    @else

    {{ __('maps/markers.helpers.base') }}

    @section('scripts') @parent @vite([ 'resources/js/location/map-v3.js', ]) @include('maps._setup', ['map' => $model, 'editable' => true]) @endsection @section('styles') @parent @vite('resources/css/maps/maps.css') @endsection @section('modals') @parent @include('maps.markers._form', ['model' => null, 'map' => $model, 'activeTab' => 1, 'dropdownParent' => '#marker-modal', 'from' => base64_encode('maps.map_markers.index:' . $model->id)]) @endsection @endif ================================================ FILE: resources/views/maps/form/_panes.blade.php ================================================
    @include('maps.form._settings')
    ================================================ FILE: resources/views/maps/form/_settings.blade.php ================================================ isChunked()) { $minInitial = Map::MIN_ZOOM_CHUNK; $maxInitial = Map::MAX_ZOOM_CHUNK; $defaultInitial = $minInitial; } ?> is_real ?? false)) checked="checked" @endif /> has_clustering ?? true)) checked="checked" @endif />
    @if (isset($model) && $model->isChunked())

    {{ __('maps.helpers.chunked_zoom') }}

    @else @endif

    {{ __('maps.helpers.centering') }}

    ================================================ FILE: resources/views/maps/form/_tabs.blade.php ================================================ ================================================ FILE: resources/views/maps/groups/_actions.blade.php ================================================
    ================================================ FILE: resources/views/maps/groups/_form.blade.php ================================================ @empty($model)

    {!! __('maps/groups.create.helper', ['name' => $map->name]) !!}

    @endif is_shown ?? true)) checked="checked" @endif /> @php $options = $map->groupPositionOptions(!empty($model->position) ? $model->position : null); $last = array_key_last($options); @endphp @include('cruds.fields.visibility_id')
    ================================================ FILE: resources/views/maps/groups/_reorder.blade.php ================================================

    {{ __('maps/groups.reorder.title') }}

    @foreach($groups as $group)
    {!! $group->name !!}
    @endforeach
    ================================================ FILE: resources/views/maps/groups/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/groups.create.title', ['name' => $map->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), ['url' => route('maps.map_groups.index', [$campaign, $map]), 'label' => __('maps.panels.groups')], __('maps/groups.create.title') ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('maps/groups.create.title', ['name' => $map->name]), 'content' => 'maps.groups._form', 'formParams' => ['model' => null, 'map' => $map], 'actions' => 'maps.groups._actions', ]) @endsection ================================================ FILE: resources/views/maps/groups/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/groups.edit.title', ['name' => $model->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), ['url' => route('maps.map_groups.index', [$campaign, $map]), 'label' => __('maps.panels.groups')], __('maps/groups.edit.title', ['name' => $model->name]) ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('maps/groups.edit.title', ['name' => $map->name]), 'content' => 'maps.groups._form', 'actions' => 'maps.groups._actions', ]) @endsection ================================================ FILE: resources/views/maps/groups/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('maps/groups.index.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @can('update', $entity) @if ($model->explorable()) @endif @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'groups', 'view' => 'maps.panels.groups', ]) @endsection ================================================ FILE: resources/views/maps/layers/_form.blade.php ================================================ __('maps/layers.types.standard'), 1 => __('maps/layers.types.overlay'), 2 => __('maps/layers.types.overlay_shown'), ]; ?> @php $options = $map->layerPositionOptions(!empty($model->position) ? $model->position : null); $last = array_key_last($options); @endphp @include('cruds.fields.entry', ['model' => $model]) @include('cruds.fields.visibility_id') @if (!$model || empty($model->image_path))
    @include('cruds.fields.image', ['fieldname' => 'image_uuid', 'size' => 'map'])
    @endif
    @include('editors.editor') ================================================ FILE: resources/views/maps/layers/_reorder.blade.php ================================================

    {{ __('maps/layers.reorder.title') }}

    @foreach($layers as $layer)
    {!! $layer->name !!} ({{ __('maps/layers.short_types.' . $layer->typeName()) }})
    @endforeach
    ================================================ FILE: resources/views/maps/layers/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/layers.create.title', ['name' => $map->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), ['url' => route('maps.map_layers.index', [$campaign, $map]), 'label' => __('maps.panels.layers')], __('maps/layers.create.title') ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('maps.layers._form', ['model' => null])
    @include('partials.footer_cancel')
    @include('maps.groups._actions')
    @endsection ================================================ FILE: resources/views/maps/layers/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/layers.edit.title', ['name' => $model->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), ['url' => route('maps.map_layers.index', [$campaign, $map]), 'label' => __('maps.panels.layers')], __('maps/layers.edit.title', ['name' => $model->name]) ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('maps.layers._form')
    @include('partials.footer_cancel')
    @include('maps.groups._actions')
    @endsection ================================================ FILE: resources/views/maps/layers/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('maps/layers.index.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @can('update', $entity) @if ($model->explorable()) @endif @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'layers', 'view' => 'maps.panels.layers', ]) @endsection ================================================ FILE: resources/views/maps/layers/migrate.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/layers.migrate.title', ['name' => $layer->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), ['url' => route('maps.map_layers.index', [$campaign, $map]), 'label' => __('maps.panels.layers')], __('maps/layers.edit.title', ['name' => $layer->name]) ], 'centered' => true, ]) @section('content') @include('partials.errors')

    This layer's image needs to be migrated to the campaign gallery before it can be edited.

    @include('partials.footer_cancel')
    @endsection ================================================ FILE: resources/views/maps/maps.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' ' . $entity->entityType->plural(), 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'maps', 'view' => 'maps.panels.maps', ]) @endsection ================================================ FILE: resources/views/maps/markers/_actions.blade.php ================================================
    ================================================ FILE: resources/views/maps/markers/_form.blade.php ================================================ __('maps/markers.circle_sizes.tiny'), 2 => __('maps/markers.circle_sizes.small'), 3 => __('maps/markers.circle_sizes.standard'), 4 => __('maps/markers.circle_sizes.large'), 5 => __('maps/markers.circle_sizes.huge'), 6 => __('maps/markers.circle_sizes.custom'), ]; ?> @endif

    {!! __('maps/markers.presets.helper') !!}

    @can('mapPresets', $campaign) {{ __('presets.actions.create') }} @endcan
    @include('cruds.fields.entity') @if (!isset($model))
    {{ __('maps/markers.hints.entry') }}
    @else
    @include('cruds.fields.entry', ['model' => $model])
    @endif

    {{ __('maps/markers.helpers.css') }}

    @include('maps.markers.fields.opacity')
    isLabel()) || (isset($source) && $source->isLabel())) style="display: none;"@endif> @include('maps.markers.fields.background_colour')
    is_popupless ?? old('is_popupless', $model->is_popupless ?? false)) checked="checked" @endif /> @include('cruds.fields.visibility_id')
    @if (isset($from)) @endif @includeWhen(isset($model), 'editors.editor') ================================================ FILE: resources/views/maps/markers/_new-footer.blade.php ================================================ @include('maps.markers._actions') ================================================ FILE: resources/views/maps/markers/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/markers.create.title', ['name' => $map->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), __('maps/markers.create.title') ], 'centered' => true, ]) @section('content') @if (request()->ajax()) @endif
    @include('partials.errors') @include('maps.markers._form', ['model' => null])
    @include('partials.footer_cancel', ['ajax' => null])
    @include('maps.markers._actions')
    @endsection @section('scripts') @parent @vite([ 'resources/js/location/map-v3.js', ]) @if (!request()->ajax() && !empty($source)) @include('maps._setup', ['single' => true, 'model' => $source, 'editable' => true]) @endif @endsection @section('styles') @parent @vite('resources/css/maps/maps.css') @endsection ================================================ FILE: resources/views/maps/markers/details.blade.php ================================================ entity && $marker->entity->hasImage(); if ($hasImage) { $class = 'with-image cover-background'; $backgroundImage = "background-image: url('" . \App\Facades\Avatar::entity($marker->entity)->size(400, 200)->thumbnail(); } ?> @if (!request()->has('mobile'))
    @if($marker->entity && $marker->entity->isMap()) @endif @can('update', $marker->map->entity) @endcan
    @endif @if ($marker->entity) @if(!$hasImage && $marker->entity->isMap()) @endif @if($marker->entity->isLocation() && !$marker->entity->child->maps->isEmpty()) @endif @endif
    @if ($marker->entity && $marker->entity->tags->isNotEmpty())
    @foreach ($marker->entity->visibleTags() as $tag) @if (!$tag->entity) @continue @endif @include ('tags._badge') @endforeach
    @endif @if ($marker->hasEntry())
    {!! \App\Facades\Mentions::mapAny($marker) !!}
    @endif @if ($marker->entity && $marker->entity->hasEntry()) @if ($marker->hasEntry()) {{ __('maps/markers.details.from-entity') }} @endif
    {!! $marker->entity->parsedEntry() !!}
    @endif @can('update', $marker->map->entity)
    @endcan
    ================================================ FILE: resources/views/maps/markers/dialog_details.blade.php ================================================ {!! $name !!}
    @if ($marker->hasEntry())
    {!! \App\Facades\Mentions::mapAny($marker) !!}
    @endif @if ($marker->entity && $marker->entity->hasEntry())
    {!! $marker->entity->parsedEntry() !!}
    @endif @can('update', $marker->map->entity) {{ __('maps/markers.actions.update') }} @endcan
    ================================================ FILE: resources/views/maps/markers/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('maps/markers.edit.title', ['name' => $model->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($map->entity)->list(), Breadcrumb::show(), __('maps/markers.edit.title', ['name' => $model->name]) ], 'centered' => true, ]) @section('content') @if (request()->ajax()) @endif @if (!$map->explorable())

    {{ __('maps.helpers.missing_image') }}

    @else
    @include('partials.errors') @include('maps.markers._form')
    @include('partials.footer_cancel', ['ajax' => null]) @include('maps.markers._actions')
    @if (isset($from) && $from === 'explore') @endif
    @endif
    @endsection @if ($map->explorable()) @section('scripts') @parent @vite([ 'resources/js/location/map-v3.js', ]) @include('maps._setup', ['single' => true, 'editable' => true]) @endsection @section('styles') @parent @vite('resources/css/maps/maps.css') @endsection @endif ================================================ FILE: resources/views/maps/markers/fields/background_colour.blade.php ================================================ @php $fieldname = $fieldname ?? 'colour'; @endphp ================================================ FILE: resources/views/maps/markers/fields/custom_icon.blade.php ================================================ @php $helper = __('maps/markers.helpers.custom_icon_v2', [ 'rpgawesome' => 'RPG Awesome', 'fontawesome' => 'Font Awesome', 'docs' => '' . __('footer.documentation') . '', ]); $fieldname = $fieldname ?? 'custom_icon'; @endphp '"ra ra-aura"']) }}" list="map-marker-icon-list" autocomplete="off" data-paste="fontawesome" @if (!$campaign->boosted()) disabled="disabled" @endif /> @if (!$campaign->boosted()) @can('boost', auth()->user())

    {!! __('crud.errors.boosted_campaigns', ['boosted' => '' . __('concept.premium-campaign') . '']) !!}

    @else

    {!! __('crud.errors.boosted_campaigns', ['boosted' => '' . __('concepts.premium-campaign') . '']) !!}

    @endif @endif
    ================================================ FILE: resources/views/maps/markers/fields/font_colour.blade.php ================================================ @php $fieldname = $fieldname ?? 'font_colour'; @endphp ================================================ FILE: resources/views/maps/markers/fields/icon.blade.php ================================================ @php $default = isset($fieldname) ? [null => ''] : []; $iconOptions = [ 1 => __('maps/markers.icons.marker'), 2 => __('maps/markers.icons.question'), 3 => __('maps/markers.icons.exclamation'), 4 => __('maps/markers.icons.entity'), ]; $iconOptions = $default + $iconOptions; @endphp ================================================ FILE: resources/views/maps/markers/fields/opacity.blade.php ================================================ ================================================ FILE: resources/views/maps/markers/fields/pin_size.blade.php ================================================ ================================================ FILE: resources/views/maps/markers/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('maps/markers.index.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @can('update', $entity) @if ($model->explorable()) @endif @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'markers', 'view' => 'maps.panels.markers', ]) @endsection ================================================ FILE: resources/views/maps/panels/groups.blade.php ================================================

    {{ __('maps/groups.helper.amount_v3') }}

    {{ __('maps.panels.groups') }}

    @if(Datagrid::hasBulks())
    @include('layouts.datagrid._table')
    @else
    @include('layouts.datagrid._table')
    @endif
    @includeWhen($groups->count() > 1, 'maps.groups._reorder') ================================================ FILE: resources/views/maps/panels/layers.blade.php ================================================

    {{ __('maps/layers.helper.amount_v2') }}

    {{ __('maps.panels.layers') }}

    @if(Datagrid::hasBulks())
    @include('layouts.datagrid._table')
    @else
    @include('layouts.datagrid._table')
    @endif
    @includeWhen($layers->count() > 1, 'maps.layers._reorder') ================================================ FILE: resources/views/maps/panels/maps.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/maps/panels/markers.blade.php ================================================ @include('maps.form._markers', ['source' => null])

    {{ __('maps.panels.markers') }}

    @if(Datagrid::hasBulks())
    @include('layouts.datagrid._table')
    @else
    @include('layouts.datagrid._table')
    @endif
    @section('modals') @parent @include('layouts.datagrid.delete-forms', ['models' => Datagrid::deleteForms(), 'params' => []]) @endsection ================================================ FILE: resources/views/maps/preview.blade.php ================================================ child; ?> @extends('layouts.map', [ 'title' => $entity->name, 'map' => $entity->child, 'noHeader' => true, ]) @section('content') @include('maps._preview') @endsection ================================================ FILE: resources/views/maps/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @if ($entity->child->explorable()) @if ($entity->child->isChunked() && $entity->child->chunkingError()) {!! __('maps.errors.chunking.error', ['discord' => 'Discord']) !!} @elseif ($entity->child->isChunked() && !$entity->child->chunkingReady()) {{ __('maps.errors.chunking.running.explore') }} {{ __('maps.errors.chunking.running.time') }} @else

    {{ __('maps.actions.explore') }}

    @endif @endif @include('entities.components.posts', ['withEntry' => true])
    @include('entities.components.pins')
    ================================================ FILE: resources/views/notes/_subnotes.blade.php ================================================

    {!! \App\Facades\Module::plural(config('entities.ids.note'), __('entities.notes')) !!}

    @foreach ($entity->children->sortBy('name') as $childEntity) @if($childEntity->is_private) @endif @endforeach
    ================================================ FILE: resources/views/notes/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Note::class, 'trans' => 'notes']) @include('cruds.fields.parent') @include('cruds.fields.entry2') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/notes/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true]) @includeWhen(!$entity->children->isEmpty(), 'notes._subnotes')
    @include('entities.components.pins')
    ================================================ FILE: resources/views/notifications/_notification.blade.php ================================================ @if (!empty($notification->data['icon'])) @if(Arr::has($notification->data['params'], 'link')) @php $url = $notification->data['params']['link']; if (!Str::startsWith($url, 'http')) { $url = url(app()->getLocale() . '/' . $url); } // Fix to new links? //$url = \Illuminate\Support\Str::replace(['/campaign/'], ['/w/'], $url); @endphp {!! __('notifications.' . $notification->data['key'], $notification->data['params']) !!} @elseif (Arr::has($notification->data['params'], 'route')) {!! __('notifications.' . $notification->data['key'], $notification->data['params']) !!} @else {!! __('notifications.' . $notification->data['key'], $notification->data['params']) !!} @endif @else

    {!! __('notifications.' . $notification->data['key'] . '.body')!!}

    @endif ================================================ FILE: resources/views/notifications/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('notifications.index.title'), 'breadcrumbs' => false, ]) @section('content')

    {{ __('notifications.index.title') }}

    @if ($notifications->count() >= 0) {{ __('notifications.clear.action') }} @endif
    @if ($notifications->count() === 0)

    {{ __('notifications.no_notifications') }}

    @else
    @foreach ($notifications as $notification) @include('notifications._notification') @endforeach
    @endif
    @if ($notifications->hasPages()) {!! $notifications->links() !!} @endif
    @endsection @section('modals')

    {{ __('crud.delete_modal.permanent') }}

    {{ __('crud.cancel') }} {{ __('crud.remove') }}
    @endsection ================================================ FILE: resources/views/organisations/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Organisation::class, 'trans' => 'organisations']) @include('cruds.fields.parent') @include('cruds.fields.locations', ['from' => $model ?? null, 'quickCreator' => true]) @include('cruds.fields.entry2') @if ($campaign->enabled('characters')) @include('components.form.members', ['options' => [ 'model' => $model ?? FormCopy::model(), 'source' => $source ?? null ]]) @endif @include('cruds.fields.status') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/organisations/members/_form.blade.php ================================================ @php $options = [ '' => __('organisations.members.pinned.none'), App\Enums\OrganisationMemberPin::character->value => App\Facades\Module::singular( config('entities.ids.character'), __('entities.character') ), App\Enums\OrganisationMemberPin::organisation->value => App\Facades\Module::singular( config('entities.ids.organisation'), __('entities.organisation') ), App\Enums\OrganisationMemberPin::both->value => __('organisations.members.pinned.both'), ]; $statuses = [ App\Enums\OrganisationMemberStatus::active->value => __('organisations.members.status.active'), App\Enums\OrganisationMemberStatus::inactive->value => __('organisations.members.status.inactive'), App\Enums\OrganisationMemberStatus::unknown->value => __('organisations.members.status.unknown'), ]; @endphp @empty($member)

    {{ __('organisations.members.create.helper', ['name' => $model->name]) }}

    @endif @include('cruds.fields.characters', ['quickCreator' => false, 'required' => true])
    @include('cruds.fields.character', [ 'name' => 'parent_id', 'label' => __('organisations.members.fields.parent'), 'placeholder' => __('organisations.members.placeholders.parent'), 'route' => 'search.organisation-member', 'dropdownParent' => request()->ajax() ? '#primary-dialog' : null, 'allowNew' => false, 'allowClear' => false, ])
    @includeWhen(auth()->user()->isAdmin(), 'cruds.fields.privacy_callout', ['model' => !empty($member) ? $member : null])
    ================================================ FILE: resources/views/organisations/members/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('families.members.create.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($model->entity)->list(), Breadcrumb::show() ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('families.members.create.title', ['name' => $model->name]), 'content' => 'organisations.members._form', 'submit' => __('organisations.members.actions.add_multiple'), ]) @endsection ================================================ FILE: resources/views/organisations/members/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('organisations.members.edit.title', ['name' => $model->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($model->entity)->list(), Breadcrumb::show() ] ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('organisations.members.edit.title', ['name' => $model->name]), 'content' => 'organisations.members._form', ]) @endsection ================================================ FILE: resources/views/organisations/members.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' ' . __('organisations.fields.members'), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('content') @include('entities.pages.subpage', [ 'active' => 'members', 'view' => 'organisations.panels.members', ]) @endsection ================================================ FILE: resources/views/organisations/organisations.blade.php ================================================ @extends('layouts.app', [ 'title' => $model->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'organisations', 'view' => 'organisations.panels.organisations', ]) @endsection ================================================ FILE: resources/views/organisations/panels/members.blade.php ================================================ child; $datagridOptions = [ $campaign, $model->id, 'init' => 1, ]; if (request()->get('m') == \App\Enums\Descendants::All->value || (!request()->has('m') && $campaign->defaultDescendantsMode() === \App\Enums\Descendants::All)) { $datagridOptions['m'] = \App\Enums\Descendants::All; $allMembers = true; } $datagridOptions = Datagrid::initOptions($datagridOptions); $datagridCall = ['datagridUrl' => route('organisations.members', $datagridOptions)]; if (!empty($rows)) { $datagridCall = []; } $direct = \Illuminate\Support\Number::format($model->members()->has('character')->count()); $all = \Illuminate\Support\Number::format($model->allMembersCount()); ?>
    @if ($direct === 0 && !$allMembers)

    {{ __('organisations.members.helpers.' . ($allMembers ? 'all_' : null) . 'members') }}

    @else
    @include('layouts.datagrid._table', $datagridCall)
    @endif
    @section('modals') @parent
    @endsection ================================================ FILE: resources/views/organisations/panels/organisations.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/organisations/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true]) @include('organisations.panels.members', ['from' => 'overview'])
    @include('entities.components.pins')
    ================================================ FILE: resources/views/partials/boost_icon.blade.php ================================================
    ================================================ FILE: resources/views/partials/cookieconsent.blade.php ================================================ @inject('countryService', 'App\Services\CountryService') @php $cookieconsent = [ 'header' => __('cookieconsent.header'), 'message' => __('cookieconsent.message'), 'dismiss' => __('cookieconsent.dismiss'), 'allow' => __('cookieconsent.allow'), 'deny' => __('cookieconsent.reject'), 'link' => __('cookieconsent.link'), 'href' => 'https://kanka.io/privacy-policy', 'close' => '❌', 'policy' => __('cookieconsent.policy'), 'target' => '_blank', ]; @endphp @vite(['resources/js/cookieconsent.js']) ================================================ FILE: resources/views/partials/errors.blade.php ================================================ @if (count($errors) > 0) {{ __('partials.errors.title') }} {{ __('partials.errors.description') }}
      @foreach ($errors as $error)
    • {!! $error !!}
    • @endforeach
    @endif ================================================ FILE: resources/views/partials/footer_cancel.blade.php ================================================ @if(request()->ajax() || (isset($ajax) && $ajax)) @else {{ __('crud.cancel') }} @endif ================================================ FILE: resources/views/partials/forms/_dialog.blade.php ================================================ @if (isset($titleIcon) && !empty($titleIcon)) {!! $titleIcon !!} @endif {!! $title !!}
    @include('partials.errors') @include($content)
    @if (isset($footer)) @include($footer) @else @include('partials.forms.dialog.footer') @endif
    ================================================ FILE: resources/views/partials/forms/_panel.blade.php ================================================
    @include('partials.errors') @include($content)
    @include('partials.footer_cancel') @if (isset($actions)) @include($actions) @endif @if (isset($deleteID) && !empty($deleteID)) @endif
    ================================================ FILE: resources/views/partials/forms/dialog/footer.blade.php ================================================ @if (!isset($skipCancel)) @endif @if (isset($deleteID) && !empty($deleteID)) @endif @if (isset($actions)) @includeWhen(!empty($actions), $actions) @else
    @endif
    ================================================ FILE: resources/views/partials/forms/form.blade.php ================================================ @includeWhen(request()->ajax(), 'partials.forms._dialog') @includeWhen(!request()->ajax(), 'partials.forms._panel') ================================================ FILE: resources/views/partials/helper-modal.blade.php ================================================ @foreach ($textes as $text)

    {!! $text !!}

    @endforeach
    ================================================ FILE: resources/views/partials/images/boosted-image.blade.php ================================================ Premium campaign CTA ================================================ FILE: resources/views/partials/impersonate.blade.php ================================================
    {{ __('campaigns.members.impersonating.title', ['name' => auth()->user()->name]) }}

    {{ __('campaigns.members.impersonating.message', ['name' => auth()->user()->name, 'campaign' => $campaign->name]) }}

    {{ __('campaigns.members.actions.switch-back') }}
    ================================================ FILE: resources/views/partials/koinks.blade.php ================================================ ({{ $cost }} koinks) ================================================ FILE: resources/views/partials/modals/close.blade.php ================================================ ================================================ FILE: resources/views/partials/newsletter.blade.php ================================================
    @if (!isset($onlyForm))

    {{ __('front/newsletter.title') }}

    {{ __('front/newsletter.headline', ['kanka' => config('app.name')]) }}
    @endif
    ================================================ FILE: resources/views/partials/or_cancel.blade.php ================================================ {!! __('crud.navigation.or_cancel', ['cancel' => '' . __('crud.navigation.cancel') . '']) !!} ================================================ FILE: resources/views/partials/success.blade.php ================================================ @if (session('success') or session('success_raw')) @if (session('success_raw')) {!! session('success_raw') !!} @else {{ session('success') }} @endif @if (session('success_docs')) @endif @endif @if (session('warning')) {{ session('warning') }} @endif @if (session('error') or session('error_raw')) @if (session('error_raw')) {!! session('error_raw') !!} @else {{ session('error') }} @endif @endif @if (session('tiptap_survey')) {!! __('tiptap.survey', [ 'share' => '' . __('tiptap.share'). '' ]) !!} @endif ================================================ FILE: resources/views/partials/superboosted.blade.php ================================================ @if(isset($callout) && $callout)

    {{ __('crud.errors.unavailable_feature') }}

    {!! __('crud.errors.boosted_campaigns', ['boosted' => '' . __('concept.premium-campaigns') . '']) !!}

    @else

    {!! __('crud.errors.boosted_campaigns', ['boosted' => '' . __('concept.premium-campaigns') . '']) !!}

    @endif ================================================ FILE: resources/views/presets/forms/_form.blade.php ================================================ ================================================ FILE: resources/views/presets/forms/_marker.blade.php ================================================ @php $types = [ 1 => __('maps/markers.tabs.marker'), 2 => __('maps/markers.tabs.label'), 3 => __('maps/markers.tabs.circle'), ]; @endphp @include('maps.markers.fields.icon', ['fieldname' => 'config[icon]']) @include('maps.markers.fields.custom_icon', ['fieldname' => 'config[custom_icon]']) @include('maps.markers.fields.pin_size', ['fieldname' => 'config[pin_size]']) @include('maps.markers.fields.font_colour', ['fieldname' => 'config[font_colour]']) @include('maps.markers.fields.opacity', ['fieldname' => 'config[opacity]']) @include('maps.markers.fields.background_colour', ['fieldname' => 'config[colour]']) @include('cruds.fields.visibility_id', ['model' => $preset ?? null]) ================================================ FILE: resources/views/presets/forms/create.blade.php ================================================ @extends('layouts.app', [ 'title' => __('presets.create.title'), 'centered' => true, ]) @section('content')
    @include('partials.forms._panel', [ 'title' => __('presets.create.title'), 'content' => 'presets.forms._' . $presetType->code, ]) @csrf
    @endsection ================================================ FILE: resources/views/presets/forms/edit.blade.php ================================================ @extends('layouts.app', [ 'title' => __('presets.edit.title', ['name' => $preset->name]), 'centered' => true, ]) @section('content') @include('partials.forms._panel', [ 'title' => __('presets.edit.title', ['name' => $preset->name]), 'content' => 'presets.forms._' . $presetType->code, 'deleteID' => '#delete-form-preset-' . $preset->id, ]) @endsection @section('modals') @parent @endsection ================================================ FILE: resources/views/presets/list.blade.php ================================================ @if (!$presets->isEmpty())
    @foreach ($presets as $preset)
    {{ $preset->name }}
    @endforeach
    @else {!! __('presets.lists.empty') !!} @endif ================================================ FILE: resources/views/quests/elements/_actions.blade.php ================================================ {{ __('crud.edit') }} @php $url = route('confirm-delete', [$campaign, 'route' => route('quests.quest_elements.destroy', [$campaign, $model, $element]), 'name' => $element->name(), 'permanent' => true]); @endphp {{ __('crud.remove') }} ================================================ FILE: resources/views/quests/elements/_buttons.blade.php ================================================
    @can('update', $entity) @include('cruds.datagrids.sorters.simple-sorter', ['target' => '#entity-main-block']) @endcan @include('entities.headers.actions', ['edit' => false])
    ================================================ FILE: resources/views/quests/elements/_element.blade.php ================================================
    entity)data-entity-id="{{ $element->entity->id }}" data-entity-type="{{ $element->entity->entityType->code }}"@endif data-word-count="{{ $element->words }}">
    @if ($element->entity && $element->entity->hasImage()) {{ $element->entity->name }} @endif

    @if($element->entity) @if ($element->entity->is_private) @endif {!! $element->name !!} @else {!! $element->name !!} @endif

    @if (!empty($element->role))
    {!! $element->role !!}
    @endif
    @can('update', $entity) @endcan
    @if ($element->entity && $element->copy_entity_entry) {!! $element->entity->parsedEntry() !!} @else {!! $element->parsedEntry() !!} @endif
    @include('icons.visibility', ['icon' => $element->visibilityIcon()])
    ================================================ FILE: resources/views/quests/elements/_elements.blade.php ================================================ @php $count = 0; @endphp
    @foreach ($elements as $element) @if ($element->entity_id && !$element->entity) @continue @endif @php $count++; @endphp @include('quests.elements._element') @endforeach
    {!! $elements->fragment('quest-elements')->links() !!} ================================================ FILE: resources/views/quests/elements/_form.blade.php ================================================

    {{ __('quests.elements.fields.entity_or_name') }}

    @include('cruds.fields.entity')
    @include('cruds.fields.entry', ['model' => $model ?? null]) copy_entity_entry ?? false)) checked="checked" @endif /> @include('cruds.fields.colour') @include('cruds.fields.visibility_id')
    ================================================ FILE: resources/views/quests/elements/_post.blade.php ================================================ @php $datagridSorter = new \App\Datagrids\Sorters\QuestElementSorter(); $elements = $entity->child ->elements() ->simpleSort($datagridSorter) ->paginate(); $elements->withPath(route('quests.quest_elements.index', [$campaign, $entity->child])); $model = $entity->child; @endphp @include('quests.elements._elements', ['elements' => $elements]) ================================================ FILE: resources/views/quests/elements/create.blade.php ================================================ @extends('layouts.app', [ 'title' => __('quests.elements.create.title', ['name' => $quest->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($quest->entity)->list(), Breadcrumb::show(), ['url' => route('quests.quest_elements.index', [$campaign, $quest->id]), 'label' => __('quests.show.tabs.elements')], __('crud.create'), ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('quests.elements._form')
    @endsection @include('editors.editor') ================================================ FILE: resources/views/quests/elements/index.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . __('quests.show.tabs.elements'), 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions') @include('quests.elements._buttons') @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'elements', 'view' => 'quests.elements._elements', ]) @endsection ================================================ FILE: resources/views/quests/elements/update.blade.php ================================================ @extends('layouts.app', [ 'title' => __('quests.elements.edit.title', ['name' => $quest->name]), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($quest->entity)->list(), Breadcrumb::show(), ['url' => route('quests.quest_elements.index', [$campaign, $quest->id]), 'label' => __('quests.show.tabs.elements')], __('crud.update'), ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('quests.elements._form')
    @if(!empty($model) && $campaign->hasEditingWarning()) @endif @endsection @include('editors.editor') @section('modals') @parent @includeWhen(!empty($editingUsers) && !empty($model), 'cruds.forms.edit_warning', ['model' => $model]) @endsection ================================================ FILE: resources/views/quests/form/_copy.blade.php ================================================ ================================================ FILE: resources/views/quests/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Quest::class, 'trans' => 'quests']) @include('cruds.fields.parent') @include('cruds.fields.instigator') @include('cruds.fields.location') @include('cruds.fields.date')
    @include('cruds.forms._calendar', ['source' => $source])
    @include('cruds.fields.status') @include('cruds.fields.entry2') @include('cruds.fields.tags') @include('cruds.fields.image')
    ================================================ FILE: resources/views/quests/panels/quests.blade.php ================================================ child, 'init' => 1 ]; if (request()->has('parent_id')) { $datagridOptions['parent_id'] = (int) request()->get('parent_id'); } $datagridOptions = Datagrid::initOptions($datagridOptions); $direct = $entity->children()->count(); $all = $entity->descendants()->count(); ?>

    {!! \App\Facades\Module::plural(config('entities.ids.quest'), __('entities.quests')) !!}

    @include('layouts.datagrid._table', ['datagridUrl' => route('quests.quests', $datagridOptions)])
    ================================================ FILE: resources/views/quests/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true]) @includeWhen($entity->children()->count() > 0, 'quests.panels.quests')
    @include('entities.components.pins')
    ================================================ FILE: resources/views/races/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Race::class, 'trans' => 'races']) @include('cruds.fields.parent') @include('cruds.fields.locations', ['from' => $model ?? null, 'quickCreator' => true]) @include('cruds.fields.entry2') @include('cruds.fields.status') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/races/members/_form.blade.php ================================================

    {!! __('races.members.create.helper', ['name' => $model->name]) !!}

    ================================================ FILE: resources/views/races/members/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('races.members.create.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($model->entity)->list(), Breadcrumb::show(), ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('races.members.create.title', ['name' => $model->name]), 'content' => 'races.members._form', 'submit' => __('races.members.create.submit'), ]) @endsection ================================================ FILE: resources/views/races/panels/characters.blade.php ================================================ child, 'init' => 1 ]; if (request()->get('m') == \App\Enums\Descendants::All->value || (!request()->has('m') && $campaign->defaultDescendantsMode() === \App\Enums\Descendants::All)) { $datagridOptions['m'] = \App\Enums\Descendants::All; $allMembers = true; } $datagridOptions = Datagrid::initOptions($datagridOptions); ?>

    {!! \App\Facades\Module::plural(config('entities.ids.character'), __('entities.characters')) !!}

    @include('layouts.datagrid._table', ['datagridUrl' => route('races.characters', $datagridOptions)])
    ================================================ FILE: resources/views/races/panels/races.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/races/races.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'races', 'view' => 'races.panels.races', ]) @endsection ================================================ FILE: resources/views/races/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true]) @include('races.panels.characters')
    @include('entities.components.pins')
    ================================================ FILE: resources/views/relations/datagrid.blade.php ================================================ {!! $datagrid ->columns([ [ 'field' => 'owner.name', 'label' => __('entities/relations.fields.owner'), 'class' => null, 'render' => function(\App\Models\Relation $model) use ($campaign) { return \Illuminate\Support\Facades\Blade::renderComponent( new \App\View\Components\EntityLink($model->owner, $campaign) ); } ], [ 'field' => 'target.name', 'label' => __('entities/relations.fields.target'), 'class' => null, 'render' => function(\App\Models\Relation $model) use($campaign) { return \Illuminate\Support\Facades\Blade::renderComponent( new \App\View\Components\EntityLink($model->target, $campaign) ); } ], [ 'field' => 'relation', 'label' => __('entities/relations.fields.role'), 'render' => function(\App\Models\Relation $model) { if (empty($model->colour)) { return $model->relation; } $html = '
    '; $colour = '
    '; return $html . $colour . '
    ' . $model->relation . '
    '; } ], [ 'field' => 'mirror_id', 'label' => '' . __('entities/relations.hints.mirrored.title') . '', 'render' => function (\App\Models\Relation $model) { return $model->isMirrored() ? '' : null; } ], [ 'field' => 'is_pinned', 'label' => '' . __('entities/relations.fields.is_star') . '', 'render' => function (\App\Models\Relation $model) { return $model->isPinned() ? '' . __('entities/relations.fields.is_star') . '' : null; } ], [ 'field' => 'attitude', 'label' => __('entities/relations.fields.attitude'), ], [ 'field' => 'visibility_id', 'label' => '' . __('crud.fields.visibility') . '', 'render' => function (\App\Models\Relation $model) { return view('icons.visibility', ['icon' => $model->visibilityIcon()]); } ], ]) ->options( [ 'route' => 'relations.index', 'baseRoute' => 'relations', 'trans' => 'relations.fields.', 'disableEntity' => true, ] ) !!} ================================================ FILE: resources/views/roadmap/feature/_form.blade.php ================================================
    {{ csrf_field() }}

    Share your ideas

    Have an idea to improve Kanka? Share it with our development team.

    @cannot ('create', \App\Models\Feature::class)

    You have reached the daily limit of 10 submitted ideas! Please try again tomorrow.

    @else

    Once reviewed, your idea will show up in the ideas section. If we have questions, we'll contact you on the Discord.

    @endif
    ================================================ FILE: resources/views/roadmap/feature/_idea.blade.php ================================================
    @livewire('roadmap.upvote', ['feature' => $feature], key("idea-{$feature->id}"))

    {{ $feature->name }}

    {!! $feature->cleanDescription() !!}
    ================================================ FILE: resources/views/roadmap/feature/_progress.blade.php ================================================
    {!! $feature->name !!}
    @include('roadmap.feature._upvote')
    ================================================ FILE: resources/views/roadmap/feature/_upvote.blade.php ================================================ @if (auth()->check() && $feature->uservote) @else @endif {{ \Illuminate\Support\Number::format($feature->upvote_count) }} ================================================ FILE: resources/views/roadmap/feature/show.blade.php ================================================

    {!! $feature->name !!}

    @if ($feature->category){!! $feature->category->name !!}@endif

    @livewire('roadmap.upvote', ['feature' => $feature, 'col' => false])
    {!! $feature->cleanDescription() !!}
    ================================================ FILE: resources/views/roadmap/index.blade.php ================================================ @extends('layouts.front', [ 'title' => __('footer.roadmap'), 'skipPerf' => true, ]) @section('content')

    Public roadmap

    Welcome to Kanka's public roadmap, where you'll find features we are working on and upvote on features that you want to see added to Kanka.

    Learn more about the roadmap

    @livewire('roadmap')
    @endsection @section('modals') @endsection ================================================ FILE: resources/views/search/fulltext.blade.php ================================================ @extends('layouts.app', [ 'title' => __('search/fulltext.title'), 'seoTitle' => __('search/fulltext.title') . ' - ' . $campaign->name, 'breadcrumbs' => false, 'canonical' => true, 'bodyClass' => 'kanka-fulltext', ]) @section('content') @include('partials.errors')

    {{ __('search/fulltext.title') }}

    @include('layouts.datagrid.fulltext_search', ['route' => route('search.fulltext', $campaign), 'term' => $term]) @include('ads.top') @if (!empty($term))

    {!! __('search/fulltext.searching', ['term' => '' . $term . '']) !!}

    @endif @include('cruds.datagrids.explore', ['flat' => true, 'skipPaginationHelper' => true])
    @endsection ================================================ FILE: resources/views/settings/_tfa.blade.php ================================================ {{ __('settings.account.2fa.title') }} @if ($user->passwordSecurity?->google2fa_enable)

    {{ __('settings.account.2fa.enabled') }}

    {{ __('settings.account.2fa.actions.disable') }}
    @else @if(auth()->user()->isSocialLogin())

    {{ __('settings.account.2fa.social') }}

    @elseif(empty($user->passwordSecurity))

    {{ __('settings.account.2fa.helper') }} {{ __('settings.account.2fa.learn_more') }}

    {!! __('settings.account.2fa.enable_instructions', [ 'android' => 'Android', 'ios' => 'iOS', ]) !!}

    {{ __('settings.account.2fa.generate_qr') }}
    @elseif(!$user->passwordSecurity->google2fa_enable)

    {{ __('settings.account.2fa.activation_helper') }}

    {!! $user->passwordSecurity->getGoogleQR() !!}
    {{ __('settings.account.2fa.actions.finish') }}
    @endif @endif
    @section('modals') @parent @if($user->passwordSecurity?->google2fa_enable)

    {{ __('settings.account.2fa.disable.helper') }}

    {{ __('crud.actions.confirm') }}
    @endif @endsection ================================================ FILE: resources/views/settings/account.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.account.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') {{ __('settings/account.title') }}
    {{ __('auth.login.fields.email') }}
    {{ auth()->user()->email }}

    {{ __('auth.login.fields.password') }}
    @if (!$user->isSocialLogin())
    *********
    @else
    {!! __('account/social.info', ['provider' => '' . ucfirst($user->provider ?? 'debug') . '']) !!}
    @endif
    @if (config('google2fa.enabled')) @livewire('users.otp') @endif {{ __('profiles.sections.dangerzone') }}

    {{ __('profiles.sections.delete.helper') }}

    @if (auth()->user()->subscribed('kanka') && !auth()->user()->subscription('kanka')->canceled())

    {!! __('profiles.sections.delete.subscribed', [ 'subscription' => '' . __('settings.menu.subscription') . '' ]) !!}

    @endif @if (!auth()->user()->subscribed('kanka') || auth()->user()->subscription('kanka')->canceled())
    {{ __('profiles.sections.delete.delete') }}
    @endif
    @endsection @section('modals') @parent

    {{ __('profiles.sections.delete.helper') }}

    {{ __('profiles.sections.delete.warning') }}

    {!! __('profiles.sections.delete.goodbye', ['code' => 'goodbye']) !!}

    {{ __('profiles.sections.delete.confirm') }}
    @endsection ================================================ FILE: resources/views/settings/api/_form.blade.php ================================================ ================================================ FILE: resources/views/settings/api/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('settings.api.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('settings/api.tokens.new'), 'content' => 'settings.api._form', 'submit' => __('crud.save'), ]) @endsection ================================================ FILE: resources/views/settings/api.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.api.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') @include('partials.errors')
    {{ __('settings.api.title') }} {{ __('settings.api.helper') }} {{ __('front.features.api.link') }}
    {{ __('settings/api.tokens.title') }}
    @if (session('new_token'))

    {{ __('settings/api.new.title') }}

    {{ session('new_token') }}
    @endif @if (empty($tokens))

    {{ __('settings/api.tokens.empty') }}

    @else
    @foreach ($tokens as $token) @endforeach
    {{ __('crud.fields.name') }} {{ __('crud.actions.actions') }}
    {{ $token['name'] }}
    @endif @if ($tokens->hasPages()) {!! $tokens->links() !!} @endif @if($applications->count() > 0)
    {{ __('settings/api.applications.title') }}
    @foreach ($applications as $application) @endforeach
    {{ __('crud.fields.name') }} {{ __('settings/api.fields.scopes') }} {{ __('crud.actions.actions') }}
    {{ $application->client['name'] }} {{implode(", ", $application['scopes'])}}
    @endif @if (request()->has('clients'))
    {{ __('settings/api.clients.title') }}
    @if ($clients->count() == 0)

    {{ __('settings/api.clients.empty') }}

    @else
    @foreach ($clients as $client) @endforeach
    {{ __('settings/api.fields.client') }} {{ __('crud.fields.name') }} {{ __('settings/api.fields.secret') }} {{ __('crud.actions.actions') }}
    {{ $client['id'] }} {{ $client['name'] }} ******
    @endif @if ($clients->hasPages()) {!! $clients->links() !!} @endif @endif @endsection ================================================ FILE: resources/views/settings/appearance.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.layout.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @php $boxClass = "rounded-2xl p-4 bg-box flex flex-col gap-2 hover:shadow-xs"; $highlightClass = 'shadow-xs border-primary border-solid border-2'; @endphp @section('content') {{ __('settings.menu.appearance') }} {{ __('settings/appearance.dismissible.main') }}
    {{ __('settings/appearance.fields.theme') }}
    {{ __('general.learn-more') }}

    {{ __('settings/appearance.helpers.theme')}}

    {{ __('settings/appearance.helpers.overridable')}}

    {{ __('settings/appearance.fields.date-format') }}
    {{ __('general.learn-more') }}

    {{ __('settings/appearance.helpers.date-format')}}

    {{ __('settings/appearance.fields.campaign-order') }}
    {{ __('general.learn-more') }}

    {{ __('settings/appearance.helpers.campaign-order')}}

    @if (!empty($editorOptions))
    {{ __('settings/appearance.fields.editor') }}
    {{ __('general.learn-more') }}

    {{ __('settings/appearance.helpers.editors') }}

    {{ __('settings/appearance.editors.helpers.legacy') }}

    {{ __('settings/appearance.editors.helpers.tiptap') }}

    {{ __('settings/appearance.editors.helpers.feedback') }}

    @endif
    {{ __('settings/appearance.fields.mentions') }}
    {{ __('general.learn-more') }}

    {!! __('settings/appearance.helpers.advanced-mentions') !!}

    {{ __('settings/appearance.actions.save') }}
    @if (!empty($from)) @endif
    @endsection ================================================ FILE: resources/views/settings/apps.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.apps.title'), 'description' => '', 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') {{ __('settings.apps.title') }} {{ __('settings.apps.benefits') }} @include('partials.errors')
    Discord
    @if($discord = auth()->user()->apps()->app('discord')->first()) {{ __('settings.apps.actions.remove') }} @if (!empty($discord->settings)) {{ $discord->settings['username'] }} @endif @else {{ __('settings.apps.actions.connect') }} @endif

    {{ __('settings.apps.discord.text') }}

    @endsection @section('modals') @parent

    {{ __('settings.apps.discord.confirm') }}

    {{ __('crud.cancel') }} {{ __('crud.actions.confirm') }}
    @endsection ================================================ FILE: resources/views/settings/boosters/_campaign.blade.php ================================================ boosts->first();?>
    @if ($campaign->image) {{ $campaign->name }} @else {{ $campaign->name }} @endif
    {!! \Illuminate\Support\Str::limit($campaign->name, 28) !!}

    @if ($campaign->premium()) {!! __('settings/boosters.campaign.premium', [ 'user' => '' . $boost->user->displayName() . '', 'time' => $boost->created_at->format('M Y') ]) !!} @elseif ($campaign->superboosted()) {!! __('settings/boosters.campaign.superboosted', [ 'user' => '' . $boost->user->displayName() . '', 'time' => $boost->created_at->format('M Y') ]) !!} @elseif ($campaign->boosted()) {!! __('settings/boosters.campaign.boosted', [ 'user' => '' . $boost->user->displayName() . '', 'time' => $boost->created_at->format('M Y') ]) !!} @else {{ __('settings/boosters.campaign.standard') }} @endif

    @if (auth()->user()->hasBoosterNomenclature()) @else @if (!$campaign->premium()) {!! __('settings/premium.actions.unlock', ['campaign' => \Illuminate\Support\Str::limit($campaign->name, 25)]) !!} @elseif (auth()->user()->can('destroy', $boost)) {{ __('crud.remove') }} @endif @endif
    ================================================ FILE: resources/views/settings/boosters/create/_actions.blade.php ================================================ @if ($campaign->boosted()) @elseif (auth()->user()->availableBoosts() < $cost) @endif @if (isset($canSuperboost) && $canSuperboost) @endif ================================================ FILE: resources/views/settings/boosters/create/_footer.blade.php ================================================ ASAS ================================================ FILE: resources/views/settings/boosters/create/_form.blade.php ================================================ @if ($campaign->boosted())

    {!! __('settings/boosters.boost.errors.boosted', ['campaign' => $campaign->name])!!}

    @elseif(auth()->user()->availableBoosts() < $cost) @can('boost', auth()->user())

    {!! __('settings/boosters.boost.errors.out-of-boosters', [ 'upgrade' => '' . __('settings/boosters.boost.upgrade') . '', 'cost' => '' . $cost . '', 'available' => '' . auth()->user()->availableBoosts() . '' ]) !!}

    @else

    {{ __('settings/boosters.boost.pitch') }}

    @endif @else

    {!! __('settings/boosters.' . ($superboost ? 'superboost' : 'boost') . '.confirm', [ 'cost' => '' . $cost . '', 'campaign' => '' . $campaign->name . '' ])!!}

    {{ __('settings/boosters.boost.duration') }}

    @endif
    ================================================ FILE: resources/views/settings/boosters/create.blade.php ================================================
    @include('settings.boosters.create._form')
    @include('settings.boosters.create._actions')
    ================================================ FILE: resources/views/settings/boosters/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings/boosters.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', ]) @section('content') @include('partials.errors')

    {{ __('settings/boosters.title') }}

    @if (auth()->user()->hasBoosterNomenclature())

    Legacy boosters

    Dear user, you are still using our legacy campaign boosters concept. Switching to premium campaigns will unboost your campaigns and give you a number of premium campaigns based on your subscription.

    As a reminder, premium campaigns are just a renamed superboosted campaign, plus a bunch of new features like family trees and custom modules. Owlbears get 1, Wyverns 3, and Elementals 7.

    This action is permanent and cannot be reverted.

    @endif

    {{ __('settings/boosters.pitch.title') }}

    {{ __('settings/boosters.pitch.description') }}

    {{ __('settings/boosters.pitch.benefits.title') }}

    {{ __('settings/boosters.pitch.benefits.customisable') }}
    {{ __('settings/boosters.pitch.benefits.entities') }}
    {{ __('settings/boosters.pitch.benefits.backup', ['amount' => config('entities.hard_delete')]) }}
    {{ __('settings/boosters.pitch.benefits.icons') }}
    {{ __('settings/boosters.pitch.benefits.upload') }}
    {{ __('settings/boosters.pitch.benefits.relations') }}

    {!! __('settings/boosters.pitch.more', ['boosters' => '' . __('concept.premium-campaigns') . '']) !!}

    {{ __('settings/boosters.ready.title') }} @can('boost', auth()->user())
    {{ auth()->user()->availableBoosts() }}
    @endif

    @if (!auth()->user()->isGoblin())

    {!! __('settings/boosters.ready.pricing', [ 'amount' => '' . __('settings/boosters.ready.pricing-amount', [ 'currency' => auth()->user()->currencySymbol(), 'amount' => '5.00' ]) . '' ]) !!}

    @endif @if ($focus) @include('settings.boosters.create', [ 'campaign' => $focus, 'superboost' => $superboost, 'cost' => $superboost ? 3 : 1, 'canSuperboost' => auth()->user()->availableBoosts() >= 3 ]) @endif
    @foreach ($boosts as $boost) @include('settings.boosters._campaign', ['campaign' => $boost->campaign]) @endforeach @foreach ($campaigns as $c) @include('settings.boosters._campaign', ['campaign' => $c]) @endforeach
    @endsection @section('modals') @parent

    Are you sure you want to switch to premium campaigns? This will unboost your campaigns and give you a number of premium campaigns based on your subscription.

    This action cannot be reverted.

    {{ __('crud.cancel') }}
    Yes, switch to premium @csrf
    @endsection @section('scripts') @parent @vite('resources/js/settings.js') @endsection ================================================ FILE: resources/views/settings/boosters/unboost/_actions.blade.php ================================================ @if (!$boost->inCooldown()) @endif ================================================ FILE: resources/views/settings/boosters/unboost/_form.blade.php ================================================

    @if ($boost->inCooldown()) {!! __('settings/premium.remove.cooldown', [ 'campaign' => '' . $campaign->name . '', 'date' => $boost->created_at->addDays(7)->diffForHumans() ])!!} @else {!! __('settings/boosters.unboost.warning', [ 'action' => $campaign->superboosted() ? __('settings/boosters.unboost.status.superboosting') : __('settings/boosters.unboost.status.boosting'), 'campaign' => '' . $campaign->name . ''])!!} @endif

    ================================================ FILE: resources/views/settings/boosters/unboost.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('settings/boosters.unboost.title'), 'content' => 'settings.boosters.unboost._form', 'actions' => 'settings.boosters.unboost._actions', ]) ================================================ FILE: resources/views/settings/boosters/update/_actions.blade.php ================================================ @if ($campaign->premium() || ($campaign->superboosted() && $boost->inCooldown())) @elseif (auth()->user()->availableBoosts() < $cost) @endif ================================================ FILE: resources/views/settings/boosters/update/_form.blade.php ================================================ @if ($campaign->superboosted())

    {!! __('settings/boosters.superboost.errors.boosted', ['campaign' => $campaign->name])!!}

    @elseif(auth()->user()->availableBoosts() < $cost) @can('boost', auth()->user())

    {!! __('settings/boosters.boost.errors.out-of-boosters', [ 'upgrade' => '' . __('settings/boosters.boost.upgrade') . '', 'cost' => '' . $cost . '', 'available' => '' . auth()->user()->availableBoosts() . '' ]) !!}

    @else

    {{ __('settings/boosters.boost.pitch') }}

    @endif @else

    {!! __('settings/boosters.superboost.upgrade', [ 'cost' => '' . $cost . '', 'campaign' => '' . $campaign->name . '' ])!!}

    {{ __('settings/boosters.boost.duration') }}

    @endif
    ================================================ FILE: resources/views/settings/boosters/update.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('settings/boosters.superboost.title', ['campaign' => $campaign->name]), 'content' => 'settings.boosters.update._form', 'actions' => 'settings.boosters.update._actions', ]) ================================================ FILE: resources/views/settings/bragi.blade.php ================================================ @extends('layouts.app', [ 'title' => 'Bragi', 'description' => '', 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') {{ __('Bragi Logs') }} {{ __('Find info about your available Bragi tokens.') }} @include('partials.errors')

    Tokens {{ auth()->user()->availableTokens() }} / {{ auth()->user()->maxTokens() }}

    Your tokens refill on {{ auth()->user()->tokenRenewalDate() }}.

    @foreach ($logs as $log)
    {{ $log->prompt }}
    {{ $log->created_at->diffForHumans() }}
    {!! $log->result !!}
    @if ($isAdmin)
    @foreach($log->data as $key => $value)
    {{ $key }}: {{ $value }}
    @endforeach
    @endif
    @endforeach @if ($logs->hasPages())
    {{ $logs->links() }}
    @endif
    @endsection ================================================ FILE: resources/views/settings/client/_form.blade.php ================================================ {{ __('settings/api.clients.form.name_helper') }} {{ __('settings/api.clients.form.redirect_helper') }} ================================================ FILE: resources/views/settings/client/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('settings.api.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('settings/api.clients.new'), 'content' => 'settings.client._form', 'submit' => __('crud.save'), ]) @endsection ================================================ FILE: resources/views/settings/client/update.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('settings.api.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') @include('partials.forms.form', [ 'title' => __('settings/api.clients.update'), 'content' => 'settings.client._form', 'submit' => __('crud.save'), ]) @endsection ================================================ FILE: resources/views/settings/notifications.blade.php ================================================ @extends('layouts.app', [ 'title' => __('profiles.newsletter.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') {{ __('profiles.newsletter.title') }} {{ __('profiles.newsletter.helpers.header') }} @include('partials.errors') mail_release ?? false)) checked="checked" @endif /> @endsection @section('scripts') @parent @vite('resources/js/profile.js') @endsection ================================================ FILE: resources/views/settings/patreon.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.patreon.title'), 'description' => __('settings.patreon.description'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') {{ __('settings.patreon.title') }} @include('partials.errors') @if(!auth()->user()->isLegacyPatron()) @includeIf('settings.tiers._' . strtolower(auth()->user()->pledge ?: 'kobold'))
    {{ __('settings.patreon.remove.button') }}
    @else

    {!! __('settings.patreon.deprecated', ['subscription' => '' . __('settings.menu.subscription') . '']) !!}

    @endif
    @endsection @section('modals')

    {{ __('settings.patreon.remove.text') }}

    {{ __('crud.actions.confirm') }}
    @endsection ================================================ FILE: resources/views/settings/premium/create/_actions.blade.php ================================================ @if ($campaign->boosted()) @elseif (auth()->user()->availableBoosts() < 1) @endif ================================================ FILE: resources/views/settings/premium/create/_form.blade.php ================================================ @if ($campaign->premium())

    {!! __('settings/premium.create.errors.boosted', ['campaign' => $campaign->name])!!}

    @elseif(auth()->user()->availableBoosts() < 1) @can('boost', auth()->user())

    {!! __('settings/boosters.boost.errors.out-of-boosters', [ 'upgrade' => '' . __('settings/boosters.boost.upgrade') . '', 'cost' => '' . 1 . '', 'available' => '' . auth()->user()->availableBoosts() . '' ]) !!}

    @else

    error

    @endif @else

    {!! __('settings/premium.create.confirm', [ 'campaign' => '' . $campaign->name . '' ])!!}

    {{ __('settings/premium.create.duration') }}

    @endif
    ================================================ FILE: resources/views/settings/premium/create.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('settings/premium.actions.unlock'), 'content' => 'settings.premium.create._form', 'actions' => 'settings.premium.create._actions', ]) ================================================ FILE: resources/views/settings/premium/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.menu.premium'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') @include('partials.errors')

    {{ __('settings.menu.premium') }}

    @if (config('app.debug')) Switch to legacy @endif

    {{ __('settings/boosters.pitch.title') }}

    {{ __('settings/premium.pitch.description') }}

    {{ __('settings/premium.pitch.title') }}

    {{ __('settings/boosters.pitch.benefits.customisable') }}
    {{ __('settings/boosters.pitch.benefits.plugins') }}
    {{ __('settings/boosters.pitch.benefits.backup', ['amount' => config('entities.hard_delete')]) }}
    {{ __('settings/boosters.pitch.benefits.icons') }}
    {{ __('settings/boosters.pitch.benefits.upload') }}
    {{ __('settings/boosters.pitch.benefits.visual') }}

    {!! __('callouts.premium.learn-more') !!}

    {{ __('settings/premium.ready.title') }}

    @can('boost', auth()->user())
    {{ auth()->user()->availableBoosts() }} / {{ auth()->user()->maxBoosts() }}
    @endif
    @if (!auth()->user()->isGoblin())

    {!! __('settings/premium.ready.pricing', [ 'amount' => '' . __('settings/boosters.ready.pricing-amount', [ 'currency' => auth()->user()->currencySymbol(), 'amount' => '5.00' ]) . '' ]) !!}

    @endif
    @foreach ($premiums as $premium) @include('settings.boosters._campaign', ['campaign' => $premium->campaign]) @endforeach @foreach ($campaigns as $userCampaign) @include('settings.boosters._campaign', ['campaign' => $userCampaign]) @endforeach
    @endsection @section('modals') @parent @if ($focus) @endif @endsection @section('scripts') @parent @vite('resources/js/settings.js') @endsection ================================================ FILE: resources/views/settings/premium/remove/_actions.blade.php ================================================ @if ($boost->inCooldown()) @endif ================================================ FILE: resources/views/settings/premium/remove/_form.blade.php ================================================

    @if ($boost->inCooldown()) {!! __('settings/premium.remove.cooldown', [ 'campaign' => '' . $campaign->name . '', 'date' => $boost->created_at->addDays(7)->diffForHumans() ])!!} @else {!! __('settings/premium.remove.warning', [ 'campaign' => '' . $campaign->name . '']) !!} @endif

    ================================================ FILE: resources/views/settings/premium/remove.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('settings/premium.remove.title'), 'content' => 'settings.premium.remove._form', 'actions' => 'settings.premium.remove._actions', ]) ================================================ FILE: resources/views/settings/profile.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.profile.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') @include('partials.errors') {{ __('settings.profile.title') }}
    @php $helper = __('profiles.helpers.profile-name', [ 'marketplace' => '' . __('footer.plugins'). '', 'profile' => '' . __('profiles.settings.helpers.profile') . '']) @endphp @php $helper = __('profiles.helpers.pronouns', [ 'marketplace' => '' . __('footer.plugins'). '', 'profile' => '' . __('profiles.settings.helpers.profile') . '']) @endphp @php $helper = __('profiles.settings.helpers.bio', [ 'link' => '' . __('profiles.settings.helpers.profile') . '' ]); @endphp @php $helper = __('profiles.helpers.link', [ 'marketplace' => '' . __('footer.plugins'). '', 'profile' => '' . __('profiles.settings.helpers.profile') . '']) @endphp user()->has_last_login_sharing ?? false)) checked="checked" @endif /> @if (auth()->user()->isSubscriber()) user()->settings['hide_subscription'] ?? false)) checked="checked" @endif /> @endif
    @if (!empty(auth()->user()->avatar) && auth()->user()->hasAvatar())
    {{ auth()->user()->name }}
    @endif
    {{ __('settings.profile.actions.update_profile') }}
    @if (!app()->isProduction()) Reset Tutorials

    This will reset all tutorials, and make all the dismissible helper texts reappear.

    Reset all tutorials
    @endif @endsection ================================================ FILE: resources/views/settings/referrals/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.referrals.title'), 'description' => '', 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') {{ __('referrals.title') }} {{ __('referrals.benefits') }} @include('partials.errors') {{ __('referrals.fields.link') }}
    {{ route('referrals', $referral) }} {{ __('referrals.actions.copy') }}
    @if (empty($users))

    {{ __('referrals.stats.empty') }}

    @else

    {{ __('referrals.stats.invited') }} {{ trans_choice('referrals.stats.users', $users, ['amount' => \Illuminate\Support\Number::format($users)]) }}

    {{ __('referrals.stats.subscribers', ['amount' => \Illuminate\Support\Number::format($subscribers)]) }}

    {{ __('referrals.stats.badge', ['level' => $badge]) }}

    @endif
    @endsection ================================================ FILE: resources/views/settings/subscription/_cancel.blade.php ================================================ @php dd('who is calling dis'); $endDate = date($user->date_format, $user->upcomingInvoice()?->period_end); @endphp

    {!! __('settings.subscription.cancel.text', ['date' => $endDate])!!}

    @php $reasons = [ '' => __('crud.select'), 'financial' => __('settings.subscription.cancel.options.financial'), 'not_for' => __('settings.subscription.cancel.options.not_for'), 'not_using' => __('settings.subscription.cancel.options.not_using'), 'not_playing' => __('settings.subscription.cancel.options.not_playing'), 'missing_features' => __('settings.subscription.cancel.options.missing_features'), 'competitor' => __('settings.subscription.cancel.options.competitor'), 'custom' => __('settings.subscription.cancel.options.other') ]; @endphp
    ================================================ FILE: resources/views/settings/subscription/_promo.blade.php ================================================ @if ($isYearly) @if ($user->subscribed('kanka')) {{-- --}} {{-- Looking for the promotion? Unfortunately it is only available for new and returning subscribers.--}} {{-- --}} @else
    {{ __('settings.subscription.coupon.invalid') }}
    @endif @else {{-- --}} {{-- Psst! Our yearly subscriptions are 20% of during black friday!--}} {{-- --}} @endif ================================================ FILE: resources/views/settings/subscription/_recap.blade.php ================================================ @php $cols = 1; $colClass = 'lg:grid-cols-1'; if ($user->isLegacyPatron()) { $cols = 3; $colClass = 'lg:grid-cols-3'; } else { $colClass = 'lg:grid-cols-4'; $cols = 4; if ($user->subscribed('kanka')) { $colClass = 'lg:grid-cols-5'; $cols = 5; if ($status == \App\Services\SubscriptionService::STATUS_GRACE) { $cols = 6; $colClass = 'lg:grid-cols-6'; } } } $box = 'rounded-2xl p-2 lg:p-3 bg-box shadow-xs hover:shadow-md flex flex-col items-center justify-center gap-2'; @endphp
    @if ($user->isLegacyPatron())
    {{ empty($user->pledge) ? 'Kobold' : $user->pledge }}
    {{ __('settings.subscription.fields.plan') }}
    Patreon
    {{ __('settings.subscription.fields.billing') }}
    @else
    @if ($user->hasPayPal() || $user->hasManualSubscription()) {{ $user->pledge }} @else {{ $current->tier->name ?? \App\Models\Pledge::KOBOLD }} @endif
    {{ __('settings.subscription.fields.plan') }}
    @if (!$user->hasPayPal())
    @if (!empty($current)) @if ($current->isYearly()) {{ __('settings.subscription.plans.cost_yearly', ['amount' => \Illuminate\Support\Number::format($current->cost, 2), 'currency' => \Illuminate\Support\Str::upper($current->currency)]) }} @else {{ __('settings.subscription.plans.cost_monthly', ['amount' => \Illuminate\Support\Number::format($current->cost, 2), 'currency' => \Illuminate\Support\Str::upper($current->currency)]) }} @endif @else {{ __('front.pricing.tier.free') }} @endif
    {{ __('settings.subscription.fields.billing') }}
    @endif
    {{ $user->currency() }} {{ __('crud.edit') }}
    {{ __('settings.subscription.fields.currency') }}
    @if ($user->subscribed('kanka'))
    {{ $user->subscription('kanka')->created_at->isoFormat('MMMM D, Y') }}
    {{ __('settings.subscription.fields.active_since') }}
    @if ($status == \App\Services\SubscriptionService::STATUS_GRACE)
    {{ $user->subscription('kanka')->ends_at->isoFormat('MMMM D, Y') }}
    {{ __('settings.subscription.fields.active_until') }}
    @endif @endif @endif @if ($user->hasDefaultPaymentMethod())
    @php $method = $user->defaultPaymentMethod(); @endphp {{ __('settings.subscription.payment_method.saved', ['brand' => ucfirst($method->card?->brand), 'last4' => $method->card?->last4]) }}
    {{ __('settings.subscription.fields.payment_method') }}
    @else
    {{ __('settings.subscription.payment_method.actions.add' ) }}
    {{ __('settings.subscription.fields.payment_method') }}
    @endif
    ================================================ FILE: resources/views/settings/subscription/cancellation/form.blade.php ================================================ @php $endDate = $user->subscription('kanka')->upcomingInvoice()?->date()->isoFormat('MMMM D, Y'); $secondaryKeys = [ 'financial' => ['lower_price', 'not_often', 'forgot'], 'not_for' => ['expected_different', 'terminology', 'too_complex', 'better_fit'], 'not_using' => ['on_break', 'too_busy', 'lost_motivation'], 'not_playing' => ['campaign_finished', 'group_fell_apart', 'on_break'], 'competitor' => ['world_anvil', 'legend_keeper', 'notion_obsidian', 'other'], ]; $secondaryOptions = []; foreach ($secondaryKeys as $primaryReason => $keys) { foreach ($keys as $key) { $secondaryOptions[$primaryReason][$key] = __('subscriptions/cancellation.secondary.' . $primaryReason . '.' . $key); } } @endphp {{ __('settings.subscription.cancel.title') }}

    {!! __('subscriptions/cancellation.intro', ['date' => $endDate])!!}

    {{ __('subscriptions/cancellation.loss.title') }} @if ($premiumCampaign)
    {{ trans_choice('subscriptions/cancellation.loss.premium.title', $premiumCampaigns->count() - 1, ['campaign' => $premiumCampaign->name, 'count' => $premiumCampaigns->count() - 1 ]) }}
    @if ($players > 0)
    {{ trans_choice('subscriptions/cancellation.loss.premium.players', $players, ['count' => $players]) }}
    @endif @if ($plugins > 0)
    {{ trans_choice('subscriptions/cancellation.loss.premium.plugins', $plugins, ['count' => $plugins]) }}
    @endif
    @endif
    {{ __('subscriptions/cancellation.loss.ads.title') }}
    @if ($discord)
    {{ __('subscriptions/cancellation.loss.discord.title', ['role' => auth()->user()->pledge]) }}
    @endif
    @php $reasons = [ 'financial' => __('settings.subscription.cancel.options.financial'), 'not_for' => __('settings.subscription.cancel.options.not_for'), 'not_using' => __('settings.subscription.cancel.options.not_using'), 'not_playing' => __('settings.subscription.cancel.options.not_playing'), 'missing_features' => __('settings.subscription.cancel.options.missing_features'), 'competitor' => __('settings.subscription.cancel.options.competitor'), ]; if ($user->subscription('kanka') && $user->subscription('kanka')->created_at->greaterThanOrEqualTo(\Carbon\Carbon::now()->subHour())) { $reasons['testing'] = __('settings.subscription.cancel.options.testing'); } $reasons['custom'] = __('settings.subscription.cancel.options.other'); @endphp
    @foreach($reasons as $value => $label)
    @endforeach
    {!! __('subscriptions/cancellation.loss.roadmap', ['roadmap' => '' . __('footer.roadmap') . '']) !!}
    {{ __('subscriptions/cancellation.loss.downgrade') }}

    {{ __('subscriptions/cancellation.select_reason') }}

    ================================================ FILE: resources/views/settings/subscription/cancellation/grace.blade.php ================================================ @php $endDate = $user->subscription('kanka')->ends_at->isoFormat('MMMM D, Y'); @endphp {{ __('settings.subscription.cancel.grace.title') }}

    {!! __('settings.subscription.cancel.grace.text', ['date' => '' . $endDate . ''])!!}

    ================================================ FILE: resources/views/settings/subscription/cancelled.blade.php ================================================ @extends('layouts.app', [ 'title' => __('subscriptions/cancelled.seo_title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') 💔 {!! __('subscriptions/cancelled.title', ['name' => $user->name]) !!} {{ __('subscriptions/cancelled.subtitle') }}
    ⏳ {{ __('subscriptions/cancelled.active.title') }}

    {!! __('subscriptions/cancelled.active.helper', [ 'date' => '' . $endDate . '' ]) !!}

    • {!! __('subscriptions/cancelled.active.premium' ) !!}
    • {!! __('subscriptions/cancelled.active.discord', ['discord' => 'Discord'] ) !!}
    • {!! __('subscriptions/cancelled.active.adfree' ) !!}
    • {!! __('subscriptions/cancelled.active.limit' ) !!}
    • {!! __('subscriptions/cancelled.active.more' ) !!}
    ❌ {{ __('subscriptions/cancelled.next.title') }}

    {!! __('subscriptions/cancelled.next.helper', [ ]) !!}

    • {!! __('subscriptions/cancelled.next.premium' ) !!}
    • {!! __('subscriptions/cancelled.next.discord', ['discord' => 'Discord'] ) !!}
    • {!! __('subscriptions/cancelled.next.data' ) !!}
    💡 {{ __('subscriptions/cancelled.change.title') }}

    {!! __('subscriptions/cancelled.change.helper', [ ]) !!}

    {{ __('subscriptions/cancelled.change.action') }}

    👋 {{ __('subscriptions/cancelled.contact.title') }}

    {!! __('subscriptions/cancelled.contact.helper', []) !!}

    {!! __('subscriptions/cancelled.contact.feedback', []) !!} {{ __('subscriptions/cancelled.contact.send') }}

    @endsection @section('scripts') @parent @vite('resources/js/subscription.js') @endsection @section('styles') @vite('resources/css/subscription.css') @endsection ================================================ FILE: resources/views/settings/subscription/change.blade.php ================================================ {{ __('subscriptions/confirm.title', ['name' => $tier->name]) }}
    @if ($user->isFrauding()) {{ __('emails/validation.modal') }}
    @endif
    {{ $tier->name }}
    @if ($user->hasManualSubscription()) You currently have a manual subscription managed by our team. Please contact us at {{ config('app.email') }} for assistance. @elseif ($user->hasPayPal()) {!! __('settings.subscription.change.text.upgrade_paypal', [ 'upgrade' => "$currency" . \Illuminate\Support\Number::format($upgrade, 2) . "", 'tier' => "$tier->name", 'amount' => "$currency$amount", 'date' => $user->subscription('kanka')->ends_at->isoFormat('MMMM D, Y') ]) !!} @else @if ($isDowngrading) {!! __('settings.subscription.change.text.downgrade_' . ($period->isYearly() ? 'yearly' : 'monthly'), [ 'downgrade' => "$currency" . \Illuminate\Support\Number::format($upgrade, 2) . "", 'tier' => "$tier->name", 'amount' => "$currency$amount" ]) !!} @else {!! __('settings.subscription.change.text.upgrade_' . ($period->isYearly() ? 'yearly' : 'monthly'), [ 'upgrade' => "$currency" . \Illuminate\Support\Number::format($upgrade, 2) . "", 'tier' => "$tier->name", 'amount' => "$currency$amount" ]) !!} @endif @endif
    @if (!$isDowngrading)
    {{ __('subscriptions/confirm.helpers.tiny') }}
    @endif
    @if ($user->hasManualSubscription()) @php return @endphp @endif
    @if (! $limited)
    @if (!$card)
    @else
    {{ __('settings.subscription.fields.payment_method') }}
    **** {{ $card->card->last4 }} {{ $card->card->exp_month }}/{{ $card->card->exp_year }}

    {{ __('settings.subscription.payment_method.actions.change') }}

    @if ($isDowngrading)

    {!! __('settings.subscription.upgrade_downgrade.downgrade.provide_reason')!!}

    @php $reasons = [ '' => __('crud.select'), 'financial' => __('settings.subscription.cancel.options.financial'), 'not_using' => __('settings.subscription.cancel.options.not_using'), 'missing_features' => __('settings.subscription.cancel.options.missing_features'), 'custom' => __('settings.subscription.cancel.options.other') ]; @endphp
    @endif @endif @includeWhen($hasPromo, 'settings.subscription._promo')

    @if ($isYearly) {!! __('subscriptions/confirm.helpers.auto-renew.yearly', ['date' => $nextBillingDate->isoFormat('MMMM D, Y')]) !!} @else {!! __('subscriptions/confirm.helpers.auto-renew.monthly', ['date' => $nextBillingDate->isoFormat('MMMM D, Y')]) !!} @endif

    {!! __('settings.subscription.helpers.stripe', ['stripe' => 'Stripe']) !!}

    @if($isYearly)

    {!! __('subscriptions/confirm.helpers.refund', ['email' => '' . config('app.email') . '']) !!}

    @endif
    @endif
    @if (!$period->isYearly()) {{ __('settings.subscription.helpers.alternatives_yearly', ['method' => 'PayPal']) }} @else @if ($user->subscribed('kanka') && !$user->hasPayPal()) {{ __('settings.subscription.helpers.alternatives_warning') }} @else @if ($hasPromo) @endif

    {{ __('settings.subscription.helpers.paypal_v3') }}

    {!! __('subscriptions/confirm.helpers.auto-renew.none', ['date' => $nextBillingDate->isoFormat('MMMM D, Y')]) !!}

    {!! __('subscriptions/confirm.helpers.paypal') !!}

    @if($isYearly)

    {!! __('subscriptions/confirm.helpers.refund', ['email' => '' . config('app.email') . '']) !!}

    @endif
    @endif @endif
    ================================================ FILE: resources/views/settings/subscription/change_blocked.blade.php ================================================ {{ __('settings.subscription.change.title') }}
    {{ __('subscription.errors.grace', ['date' => $user->subscription('kanka')->ends_at->isoFormat('MMMM D, Y')]) }}
    ================================================ FILE: resources/views/settings/subscription/currency/_blocked.blade.php ================================================

    {!! __('settings.subscription.helpers.currency_block', ['email' => '' . config('app.email') . ''])!!}

    ================================================ FILE: resources/views/settings/subscription/currency/_form.blade.php ================================================ @if (auth()->user()->subscription('kanka')?->ended()) @include('settings.subscription.currency._reset') @endif @if (auth()->user()->subscription('kanka')?->ended()) @endif ================================================ FILE: resources/views/settings/subscription/currency/_reset.blade.php ================================================

    {!! __('settings.subscription.helpers.currency_reset') !!}

    ================================================ FILE: resources/views/settings/subscription/currency/edit.blade.php ================================================ @include('partials.forms._dialog', [ 'title' => __('settings.subscription.currency.title'), 'content' => 'settings.subscription.currency.' . $content, 'submit' => __('settings.subscription.actions.update_currency'), ]) ================================================ FILE: resources/views/settings/subscription/faq.blade.php ================================================

    {{ __('subscriptions/faq.title') }}

    @foreach (['why', 'help', 'cost', 'methods', 'cancellation', 'renewal', 'trial', 'data' , 'downgrade', 'refund', 'discount', 'sharing', 'security', 'update', 'fail'] as $key) {{ __('subscriptions/faq.' . $key . '.question') }}

    {!! __('subscriptions/faq.' . $key . '.answer', [ 'billing' => '' . __('billing/menu.payment-method') . '', 'stripe' => 'Stripe', 'email' => '' . config('app.email') . '' ]) !!}

    @endforeach
    ================================================ FILE: resources/views/settings/subscription/finish.blade.php ================================================ @extends('layouts.app', [ 'title' => __('subscriptions/finish.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content') {{ $isTrial ? __('subscriptions/free-trial.started.title') : __('subscriptions/finish.title') }} {{ $isTrial ? __('subscriptions/free-trial.started.header') : __('subscriptions/finish.header') }}

    {{ __('subscriptions/finish.next') }}

    {{ __('subscriptions/finish.premium.title') }}

    {!! __('subscriptions/finish.premium.helper', [ 'plugins' => '' . __('footer.plugins') . '' ]) !!}

    @foreach ($availableCampaigns as $availableCampaign)
    @if ($availableCampaign->image) {{ $availableCampaign->name }} @else {{ $availableCampaign->name }} @endif
    @endforeach

    {{ __('subscriptions/finish.discord.title') }}

    {!! __('subscriptions/finish.discord.helper', [ ]) !!}

    @if (!$user->discord()) {{ __('subscriptions/finish.discord.action') }} @else {{ __('subscriptions/finish.discord.enjoy') }} @endif

    {{ __('subscriptions/finish.roadmap.title') }}

    {!! __('subscriptions/finish.roadmap.helper', [ ]) !!}

    {{ __('subscriptions/finish.roadmap.action') }}

    {{ __('subscriptions/finish.help.title') }}

    {!! __('subscriptions/finish.help.helper', [ 'contact-us' => '' . __('subscriptions/finish.help.contact-us') . '', 'docs' => '' . __('footer.documentation') . '' ]) !!}

    @endsection @section('scripts') @parent @vite('resources/js/subscription.js') @if($tracking == 'subscribed') @endif @endsection @section('styles') @vite('resources/css/subscription.css') @endsection ================================================ FILE: resources/views/settings/subscription/free-trial.blade.php ================================================ @extends('layouts.app', [ 'title' => __('subscriptions/free-trial.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, 'skipBanners' => true ]) @section('content') {{ __('subscriptions/free-trial.title') }} {!! __('subscriptions/free-trial.header', [ 'what' => '' . __('subscriptions/free-trial.what', [ 'amount' => 15, 'tier' => 'Owlbear' ]) . '' ]) !!} {{ __('subscriptions/free-trial.actions.magic') }}

    {{ __('subscriptions/free-trial.included.title') }}

    @include('settings.subscription.tiers.benefits._owlbear')

    {!! __('subscriptions/free-trial.included.upsell.pitch', [ 'tier' => 'Owlbear', ]) !!} {{ __('subscriptions/free-trial.included.upsell.action') }}

    {{ __('subscriptions/free-trial.why.title') }}

    {!! __('subscriptions/free-trial.why.helper', [ 'plugins' => '' . __('footer.plugins') . '' ]) !!}

    {{ __('subscriptions/free-trial.tease.title') }}

    {!! __('subscriptions/free-trial.tease.helper', [ 'subscription' => '' . __('billing/menu.overview') . '' ]) !!}

    {{ __('subscriptions/free-trial.final.magic') }}
    @endsection @section('scripts') @parent @vite('resources/js/subscription.js') @endsection @section('styles') @vite('resources/css/subscription.css') @endsection ================================================ FILE: resources/views/settings/subscription/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('settings.subscription.manage_subscription'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content')

    {{ __('settings.subscription.manage_subscription') }}

    {!! __('subscription.benefits.main', [ 'more' => '' . __('subscription.benefits.more') . '', 'boosters' => '' . __('concept.premium-campaigns') . '', 'stripe' => 'Stripe' ]) !!}

    @include('partials.errors') @can('renewPaypalSubscription', $user)

    {!! __('settings.subscription.paypal_expiring', [ 'date' => '' . $user->subscription('kanka')->ends_at->isoFormat('MMMM D, Y') . '', ]) !!} {{ __('subscriptions/renew.actions.renew') }}

    @endcan @include('settings.subscription._recap')

    {{ __('settings.subscription.tiers') }}

    {!! __('tiers.why', ['tiny' => '' . __('tiers.tiny') . '']) !!}

    @if (!$isPayPal && !$hasManual)
    {{ __('tiers.actions.pay.monthly') }}
    {{ __('tiers.actions.pay.yearly') }} {{ __('tiers.actions.pay.save') }}
    @endif
    @php /** @var \App\Models\Tier $tier **/ @endphp @foreach ($tiers as $tier) @if ($tier->isFree() && $user->isSubscriber()) @continue @endif
    {{ $tier->name }}
    {{ $tier->name }}
    @if ($tier->isFree())
    {{ __('front.features.patreon.free') }}
    @else
    {{ $user->currencySymbol() }} {{ \Illuminate\Support\Number::format($tier->price($user->currency(), \App\Enums\PricingPeriod::Monthly), 2) }}
    {{ __('tiers.periods.billed_monthly') }}
    {{ $user->currencySymbol() }} {{ \Illuminate\Support\Number::format($tier->price($user->currency(), \App\Enums\PricingPeriod::Yearly), 2) }}
    {{ __('tiers.periods.billed_yearly') }}
    @endif @if ($tier->code === 'owlbear')

    {{ __('tiers.target.owlbear') }}

    @elseif ($tier->isWyvern())

    {{ __('tiers.target.wyvern') }}

    @elseif ($tier->code === 'elemental')

    {{ __('tiers.target.elemental') }}

    @endif
    @if (!$user->isLegacyPatron() && !$user->hasIncompletePayment('kanka'))
    @include('settings.subscription.tiers.actions._' . $tier->code)
    @endif
    @includeIf('settings.subscription.tiers.benefits._' . $tier->code)
    @if (!$tier->isFree() && $tier->isCurrent($user) && $user->subscribed('kanka') && !$hasManual && !$user->hasPayPal()) @endif
    @endforeach
    @include('settings.subscription.faq')
    @endsection @section('modals') @parent @endsection @section('scripts') @parent @vite('resources/js/subscription.js') @if (!$user->isSubscriber()) @endif @endsection @section('styles') @vite('resources/css/subscription.css') @endsection ================================================ FILE: resources/views/settings/subscription/paypal-renew.blade.php ================================================ @extends('layouts.app', [ 'title' => __('subscriptions/renew.title'), 'breadcrumbs' => false, 'sidebar' => 'settings', 'centered' => true, ]) @section('content')

    {{ __('subscriptions/renew.title') }}

    {!! __('subscriptions/paypal-renew.intro', [ 'date' => '' . $user->subscription('kanka')->ends_at->isoFormat('MMMM D, Y') . '', ]) !!}

    @foreach ($tiers as $tier)
    {{ $tier->name }}
    {{ $tier->name }}
    @if ($tier->isFree())
    {{ __('front.features.patreon.free') }}
    @else
    {{ $user->currencySymbol() }} {{ \Illuminate\Support\Number::format($tier->price($user->currency(), \App\Enums\PricingPeriod::Yearly), 2) }}
    {{ __('tiers.periods.billed_yearly') }}
    @endif @if ($tier->code === 'owlbear')

    {{ __('tiers.target.owlbear') }}

    @elseif ($tier->isWyvern())

    {{ __('tiers.target.wyvern') }}

    @elseif ($tier->code === 'elemental')

    {{ __('tiers.target.elemental') }}

    @endif
    @php $isDowngrade = match($user->pledge) { 'Elemental' => in_array($tier->name, ['Owlbear', 'Wyvern']), 'Wyvern' => $tier->name === 'Owlbear', default => false, }; @endphp @if (!$isDowngrade) @endif
    @include('settings.subscription.tiers.benefits._' . strtolower($tier->name))
    @endforeach
    @endsection ================================================ FILE: resources/views/settings/subscription/renew.blade.php ================================================ @php $endDate = $user->subscription('kanka')->ends_at->isoFormat('MMMM D, Y'); @endphp {{ __('subscriptions/renew.title') }}

    {!! __('settings.subscription.cancel.grace.text', ['date' => '' . $endDate . ''])!!}

    {!! __('subscriptions/renew.helper')!!}

    {{ __('subscriptions/renew.actions.renew') }}
    ================================================ FILE: resources/views/settings/subscription/tiers/actions/_elemental.blade.php ================================================ @php /** * @var \App\Models\User $user * @var \App\Models\Tier $tier */ @endphp @if ($user->hasPayPal()) @if (!$user->isElemental()) {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @else @if($user->subscribedToPrice($tier->monthlyPlans(), 'kanka')) {{ __('tiers.current') }} @elseif ($user->subscribedToPrice($tier->plans(), 'kanka')) {{ __('settings.subscription.subscription.actions.downgrading') }} @else @if (in_array($tier->id, $upgrades)) {{ __('tiers.actions.subscribe.upgrade', ['tier' => $tier->name]) }} @elseif (in_array($tier->id, $downgrades)) {{ __('tiers.actions.subscribe.downgrade', ['tier' => $tier->name]) }} @else {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @endif @if($user->subscribedToPrice($tier->yearlyPlans(), 'kanka')) {{ __('tiers.current') }} @else @if (in_array($tier->id, $upgrades)) {{ __('tiers.actions.subscribe.upgrade', ['tier' => $tier->name]) }} @elseif (in_array($tier->id, $downgrades)) {{ __('tiers.actions.subscribe.downgrade', ['tier' => $tier->name]) }} @else {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @endif @endif ================================================ FILE: resources/views/settings/subscription/tiers/actions/_kobold.blade.php ================================================ @if (!$user->hasPayPal()) @if(empty($current)) {{ __('tiers.current') }} @else {{ __('settings.subscription.subscription.actions.cancel') }} @endif @endif ================================================ FILE: resources/views/settings/subscription/tiers/actions/_owlbear.blade.php ================================================ @php /** * @var \App\Models\User $user * @var \App\Models\Tier $tier */ @endphp @if ($user->hasPayPal()) @if (!$user->isWyvern() && !$user->isElemental()) {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @else @if($user->subscribedToPrice($tier->monthlyPlans(), 'kanka')) {{ __('tiers.current') }} @else @if (in_array($tier->id, $downgrades)) {{ __('tiers.actions.subscribe.downgrade', ['tier' => $tier->name]) }} @else {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @if($user->subscribedToPrice($tier->yearlyPlans(), 'kanka')) {{ __('tiers.current') }} @else @if (in_array($tier->id, $upgrades)) {{ __('tiers.actions.subscribe.upgrade', ['tier' => $tier->name]) }} @elseif (in_array($tier->id, $downgrades)) {{ __('tiers.actions.subscribe.downgrade', ['tier' => $tier->name]) }} @else {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @endif @endif @endif ================================================ FILE: resources/views/settings/subscription/tiers/actions/_wyvern.blade.php ================================================ @php /** * @var \App\Models\User $user * @var \App\Models\Tier $tier */ @endphp @if ($user->hasPayPal()) @if (!$user->isWyvern() && !$user->isElemental()) {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @else @if($user->subscribedToPrice($tier->monthlyPlans(), 'kanka')) {{ __('tiers.current') }} @elseif ($user->subscribedToPrice($tier->plans(), 'kanka')) {{ __('settings.subscription.subscription.actions.downgrading') }} @else @if (in_array($tier->id, $upgrades)) {{ __('tiers.actions.subscribe.upgrade', ['tier' => $tier->name]) }} @elseif (in_array($tier->id, $downgrades)) {{ __('tiers.actions.subscribe.downgrade', ['tier' => $tier->name]) }} @else {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @endif @if($user->subscribedToPrice($tier->yearlyPlans(), 'kanka')) {{ __('tiers.current') }} @else @if (in_array($tier->id, $upgrades)) {{ __('tiers.actions.subscribe.upgrade', ['tier' => $tier->name]) }} @elseif (in_array($tier->id, $downgrades)) {{ __('tiers.actions.subscribe.downgrade', ['tier' => $tier->name]) }} @else {{ __('tiers.actions.subscribe.choose', ['tier' => $tier->name]) }} @endif @endif @endif ================================================ FILE: resources/views/settings/subscription/tiers/benefits/_elemental.blade.php ================================================
    @if (auth()->user()->hasBoosterNomenclature()) @else @endif
    @if (auth()->user()->hasBoosterNomenclature()) 10 {{ __('tiers.features.boosters') }} @else 7 {{ __('concept.premium-campaigns') }} @endif {{ __('tiers.features.premium', ['storage' => config('limits.gallery.elemental') / 1024 / 1024, 'modules' => config('limits.campaigns.modules.elemental')]) }}
    {{ __('tiers.features.file_size', ['size' => config('limits.filesize.image.elemental') . ' MiB']) }}
    {{ __('tiers.features.no_ads') }}

    {{ __('tiers.features.nice_image') }}
    {!! __('tiers.features.discord', ['discord' => 'Discord',]) !!}
    {{ __('tiers.features.feature_influence') }}
    ================================================ FILE: resources/views/settings/subscription/tiers/benefits/_kobold.blade.php ================================================
    {{ __('tiers.features.file_size', ['size' => '1 MiB']) }}
    ================================================ FILE: resources/views/settings/subscription/tiers/benefits/_owlbear.blade.php ================================================
    @if (auth()->user()->hasBoosterNomenclature()) @else @endif
    @if (auth()->user()->hasBoosterNomenclature()) 3 {{ __('tiers.features.boosters') }} @else 1 {{ __('concept.premium-campaign') }}
    @endif
    {{ __('tiers.features.premium', ['storage' => config('limits.gallery.premium') / 1024 / 1024, 'modules' => config('limits.campaigns.modules.premium')]) }}
    {{ __('tiers.features.file_size', ['size' => config('limits.filesize.image.owlbear') . ' MiB']) }}
    {{ __('tiers.features.no_ads') }}

    {{ __('tiers.features.nice_image') }}
    {!! __('tiers.features.discord', ['discord' => 'Discord',]) !!}
    ================================================ FILE: resources/views/settings/subscription/tiers/benefits/_wyvern.blade.php ================================================
    @if (auth()->user()->hasBoosterNomenclature()) @else @endif
    @if (auth()->user()->hasBoosterNomenclature()) 6 {{ __('tiers.features.boosters') }} @else 3 {{ __('concept.premium-campaigns') }} @endif {{ __('tiers.features.premium', ['storage' => config('limits.gallery.wyvern') / 1024 / 1024, 'modules' => config('limits.campaigns.modules.wyvern')]) }}
    {{ __('tiers.features.file_size', ['size' => config('limits.filesize.image.wyvern') . ' MiB']) }}
    {{ __('tiers.features.no_ads') }}

    {{ __('tiers.features.nice_image') }}
    {!! __('tiers.features.discord', ['discord' => 'Discord',]) !!}
    ================================================ FILE: resources/views/settings/tiers/_elemental.blade.php ================================================
    Elemental

    Elemental

    {{ auth()->user()->currencySymbol() }}25 / {{ __('front.pricing.tier.month') }}
    {{ __('front.features.patreon.upload_limit') }}
    25 MiB
    {{ __('front.features.patreon.upload_limit_map') }}
    50 MiB
    {!! __('front.features.patreon.discord', ['discord' => 'Discord']) !!}
    {{ __('front.features.patreon.default_image') }}
    {!! __('front.features.patreon.hall_of_fame', ['link' => '' . __('front/hall-of-fame.title') . '']) !!}
    {{ __('front.features.patreon.api_calls') }}
    {{ __('front.features.patreon.pagination') }}
    {{ __('front.features.patreon.monthly_vote') }}
    {{ __('front.features.patreon.boosts') }}
    10
    {{ __('front.features.patreon.curation') }}
    {{ __('front.features.patreon.impact') }}
    ================================================ FILE: resources/views/settings/tiers/_goblin.blade.php ================================================
    Goblin

    Goblin

    {{ auth()->user()->currencySymbol() }}3 / {{ __('front.pricing.tier.month') }}
    {{ __('front.features.patreon.upload_limit') }}
    8 MiB
    {{ __('front.features.patreon.upload_limit_map') }}
    10 MiB
    {!! __('front.features.patreon.discord', ['discord' => 'Discord']) !!}
    {{ __('front.features.patreon.default_image') }}
    {!! __('front.features.patreon.hall_of_fame', ['link' => '' . __('front/hall-of-fame.title') . '']) !!}
    {{ __('front.features.patreon.api_calls') }}
    {{ __('front.features.patreon.pagination') }}
    {{ __('front.features.patreon.monthly_vote') }}
    {{ __('front.features.patreon.boosts') }}
    1
    ================================================ FILE: resources/views/settings/tiers/_kobold.blade.php ================================================
    Kobold

    Kobold

    {{ auth()->user()->currencySymbol() }}1 / {{ __('front.pricing.tier.month') }}
    {{ __('front.features.patreon.upload_limit') }}
    8 MiB
    {{ __('front.features.patreon.upload_limit_map') }}
    10 MiB
    {!! __('front.features.patreon.discord', ['discord' => 'Discord']) !!}
    ================================================ FILE: resources/views/settings/tiers/_owlbear.blade.php ================================================
    Owlbear

    Owlbear

    {{ auth()->user()->currencySymbol() }}5 / {{ __('front.pricing.tier.month') }}
    {{ __('front.features.patreon.upload_limit') }}
    8 MiB
    {{ __('front.features.patreon.upload_limit_map') }}
    10 MiB
    {!! __('front.features.patreon.discord', ['discord' => 'Discord']) !!}
    {{ __('front.features.patreon.default_image') }}
    {!! __('front.features.patreon.hall_of_fame', ['link' => '' . __('front/hall-of-fame.title') . '']) !!}
    {{ __('front.features.patreon.api_calls') }}
    {{ __('front.features.patreon.pagination') }}
    {{ __('front.features.patreon.monthly_vote') }}
    {{ __('front.features.patreon.boosts') }}
    3
    ================================================ FILE: resources/views/setup.blade.php ================================================ ================================================ FILE: resources/views/spotlights/field.blade.php ================================================
    @if ($content?->isApproved() || $content?->isApplied()) {!! \Illuminate\Support\Arr::get($content->content_json, $field) !!} @else @error($field) {{ $message }} @enderror @endif @if ($field === 'kanka')
    @endif
    ================================================ FILE: resources/views/spotlights/form.blade.php ================================================ @extends('layouts.front', [ 'title' => __('spotlights.form.title') . ' - ' . $campaign->name, 'skipPerf' => true, ]) @section('content')

    {{ __('spotlights.form.title') }}

    {!! __('spotlights.form.preset', ['campaign' => '' . $campaign->name . '']) !!}

    @if (!$campaign->isPublic())

    {{ __('spotlights.form.not-public') }}

    @else @if ($content?->isApplied())

    {{ __('spotlights.applied.title') }}

    {{ __('spotlights.applied.description') }}

    @csrf
    @elseif ($content?->isApproved())

    {{ __('spotlights.approved.title') }}

    {!! __('spotlights.approved.description', ['spotlight' => 'Spotlight']) !!}

    @elseif ($content?->isRejected())

    {{ __('spotlights.rejected.title') }}

    {{ __('spotlights.rejected.description') }}

    @endif
    @csrf @if ($errors->any() || session('error'))
    {{ __('partials.errors.title') }} {{ __('partials.errors.description') }} @if (session('error')) {!! session('error') !!} @endif
    @endif @include('spotlights.field', ['field' => 'time']) @include('spotlights.field', ['field' => 'world']) @include('spotlights.field', ['field' => 'proud']) @include('spotlights.field', ['field' => 'inspiration']) @include('spotlights.field', ['field' => 'stories']) @include('spotlights.field', ['field' => 'kanka']) @if (empty($content) || $content->isDraft())

    {{ __('spotlights.form.draft') }}

    @if ($content) @endif
    @endif
    @endif
    @endsection ================================================ FILE: resources/views/spotlights/index.blade.php ================================================ @extends('layouts.front', [ 'title' => __('spotlights.title'), 'skipPerf' => true, ]) @section('content')

    {{ __('spotlights.title') }}

    {!! __('spotlights.rules', [ 'showcase' => '' . __('footer.showcase') . '' ]) !!}

    @if (!empty($campaign)) @if ($campaign->isPublic()) @else

    {!! __('spotlights.overview.campaign-not-public', ['name' => $campaign->name]) !!}

    @endif @else

    {{ __('spotlights.started') }}

    @foreach ($campaigns as $world) {{ $world->name }}

    {!! $world->name !!}

    @endforeach
    @endif
    {{ __('spotlights.faq.what.q') }}

    {{ __('spotlights.faq.what.a') }}

    {{ __('spotlights.faq.who.q') }}

    {{ __('spotlights.faq.who.a.lead') }}

    {{ __('spotlights.faq.who.a.requirements') }}

    • {{ __('spotlights.faq.who.a.req1') }}
    • {{ __('spotlights.faq.who.a.req2') }}
    • {{ __('spotlights.faq.who.a.req3') }}
    {{ __('spotlights.faq.how.q') }}

    {{ __('spotlights.faq.how.a.lead') }}

    {{ __('spotlights.faq.how.a.requirements') }}

    • {{ __('spotlights.faq.how.a.req1') }}
    • {{ __('spotlights.faq.how.a.req2') }}
    • {{ __('spotlights.faq.how.a.req3') }}
    {{ __('spotlights.faq.selected.q') }}

    {{ __('spotlights.faq.selected.a.lead') }}

    • {{ __('spotlights.faq.selected.a.req1') }}
    • {!! __('spotlights.faq.selected.a.req2', [ 'blog' => 'Blog', 'showcase' => '' . __('footer.showcase') . '' ]) !!}
    • {{ __('spotlights.faq.selected.a.req3') }}

    {{ __('spotlights.faq.selected.a.end') }}

    {{ __('spotlights.faq.reapply.q') }}

    {{ __('spotlights.faq.reapply.a') }}

    {{ __('spotlights.faq.finisher') }}

    @endsection ================================================ FILE: resources/views/tags/_badge.blade.php ================================================ hasColour()) style="{{ $tag->colourStyle() }}" @endif> @if ($tag->hasIcon()) @else {!! $tag->name !!} @endif ================================================ FILE: resources/views/tags/children.blade.php ================================================ @extends('layouts.app', [ 'title' => __('tags.children.title', ['name' => $entity->name]), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('content') @include('entities.pages.subpage', [ 'active' => 'entities', 'view' => 'tags.panels.children', ]) @endsection ================================================ FILE: resources/views/tags/entities/_form.blade.php ================================================

    {!! __('tags.children.create.helper', ['name' => $model->name]) !!}

    ================================================ FILE: resources/views/tags/entities/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('tags.children.create.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($model->entity)->list(), Breadcrumb::show(), ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('tags.children.create.title', ['name' => $model->name]), 'content' => 'tags.entities._form', 'submit' => __('tags.children.actions.add'), ]) @endsection ================================================ FILE: resources/views/tags/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Tag::class, 'trans' => 'tags']) @include('cruds.fields.parent') @include('cruds.fields.colour_picker') @php $iconHelper = __('tags.helpers.icon', [ 'fontawesome' => 'Font Awesome', 'rpgawesome' => 'RPG Awesome', ]) @endphp '"ra ra-aura"']) }}" autocomplete="off" data-paste="fontawesome" @if (!$campaign->boosted()) disabled="disabled" @endif /> @if (!$campaign->boosted()) @can('boost', auth()->user())

    {!! __('crud.errors.boosted_campaigns', ['boosted' => '' . __('concept.premium-campaign') . '']) !!}

    @else

    {!! __('crud.errors.boosted_campaigns', ['boosted' => '' . __('concepts.premium-campaign') . '']) !!}

    @endif @endif
    @include('cruds.fields.entry2') child->is_auto_applied ?? $model->is_auto_applied ?? false)) checked="checked" @endif /> child->is_hidden ?? $model->is_hidden ?? false)) checked="checked" @endif /> @include('cruds.fields.tags') @include('cruds.fields.image')
    ================================================ FILE: resources/views/tags/panels/children.blade.php ================================================ child]); $datagridOptions = []; if (!empty($onload)) { $routeOptions = [ $campaign, $entity->child, 'init' => 1, ]; if (request()->get('m') == \App\Enums\Descendants::All->value || (!request()->has('m') && $campaign->defaultDescendantsMode() === \App\Enums\Descendants::All)) { $routeOptions['m'] = \App\Enums\Descendants::All; $allMembers = true; } $routeOptions = Datagrid::initOptions($routeOptions); $datagridOptions = ['datagridUrl' => route('tags.children', $routeOptions)] ; } $all = $entity->child->allChildren()->count(); $direct = $entity->child->entities()->count(); ?>

    {{ __('tags.show.tabs.children') }}

    {{-- Actions dropdown --}} @if ($all > 0) @can('update', $entity) @endcan @endif
    @if ($all === 0)

    {{ __('tags.helpers.no_children') }}

    @can('update', $entity) @endcan
    @else
    @include('layouts.datagrid._table', $datagridOptions)
    @endif ================================================ FILE: resources/views/tags/panels/posts.blade.php ================================================ child; $datagridOptions = []; if (!empty($onload)) { $routeOptions = [ $campaign, $model, 'init' => 1, ]; $routeOptions = Datagrid::initOptions($routeOptions); $datagridOptions = ['datagridUrl' => route('tags.posts', $routeOptions)] ; } $all = $model->posts()->count(); ?>

    {{ __('entities.articles') }}

    @if ($all > 0) @can('update', $entity) @endcan @endif
    @if ($all === 0)

    {{ __('tags.helpers.no_posts') }}

    @else
    @include('layouts.datagrid._table', $datagridOptions)
    @endif ================================================ FILE: resources/views/tags/panels/tags.blade.php ================================================ has('tag_id')) { $filters['tag_id'] = request()->get('tag_id'); } ?>
    @include('layouts.datagrid._table')
    @section('modals') @parent @include('partials.helper-modal', [ 'id' => 'help-modal', 'title' => __('crud.actions.help'), 'textes' => [ __('tags.hints.tag') ] ]) @endsection ================================================ FILE: resources/views/tags/show.blade.php ================================================
    @include('entities.components.header')
    @include('entities.components.menu_v2', ['active' => 'story'])
    @include('entities.components.posts', ['withEntry' => true]) @include('tags.panels.children', ['onload' => true]) @include('tags.panels.posts', ['onload' => true])
    @include('entities.components.pins')
    ================================================ FILE: resources/views/tags/tags.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    {{ __('crud.actions.help') }} @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'tags', 'view' => 'tags.panels.tags', ]) @endsection ================================================ FILE: resources/views/tags/transfer/entities/form.blade.php ================================================

    {!! __('tags.transfer.entities.helper', ['name' => $tag->name]) !!}

    @include('cruds.fields.tag', ['model' => $tag->entity, 'allowNew' => false])
    ================================================ FILE: resources/views/tags/transfer/entities/transfer.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('tags.transfer.entities.title'), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($tag->entity)->list(), Breadcrumb::show(), __('tags.transfer.transfer'), ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('tags.transfer.entities.title'), 'content' => 'tags.transfer.entities.form', 'submit' => __('tags.transfer.transfer'), ]) @endsection ================================================ FILE: resources/views/tags/transfer/posts/form.blade.php ================================================

    {!! __('tags.transfer.posts.helper', ['name' => $tag->name]) !!}

    @include('cruds.fields.tag', ['model' => $tag->entity, 'allowNew' => false])
    ================================================ FILE: resources/views/tags/transfer/posts/transfer.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('tags.transfer.posts.title'), 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($tag->entity)->list(), Breadcrumb::show(), __('tags.transfer.transfer'), ], 'centered' => true, ]) @section('content') @include('partials.forms._dialog', [ 'title' => __('tags.transfer.posts.title'), 'content' => 'tags.transfer.posts.form', 'submit' => __('tags.transfer.transfer'), ]) @endsection ================================================ FILE: resources/views/timelines/_element.blade.php ================================================
  • @include('timelines.elements._icon')

    @if ($element->entity) {!! $element->name !!} @if($element->entity && $element->entity->is_private) @endif @else {!! $element->name !!} @endif

    @if (isset($element->date) || $element->use_event_date && isset($element->entity->event->date)) {{isset($element->entity->event->date) && $element->use_event_date ? $element->entity->event->date : $element->date}} @endif
    @includeWhen(auth()->check(), 'icons.visibility', ['icon' => $element->visibilityIcon('')]) @can('update', $timeline->entity) @endcan
  • ================================================ FILE: resources/views/timelines/_timeline.blade.php ================================================ eras()->with(['orderedElements', 'orderedElements.entity', 'orderedElements.entity.event'])->ordered()->get(); $loadedElements = []; ?> @forelse ($eras as $era) @php $position = 1; @endphp

    {!! $era->name !!} @if(!empty($era->abbreviation)) ({{ $era->abbreviation }}) @endif {!! $era->ages()!!}

    @can('update', $timeline->entity) {{ __('crud.edit') }} {{ __('crud.remove') }} @endcan
    {!! \App\Facades\Mentions::mapAny($era) !!}
    @can('update', $timeline->entity) @endcan @empty

    {{ __('timelines.helpers.no_era_v2') }}

    @can('update', $timeline->entity) @endcan
    @endforelse @if (!$timeline->eras->isEmpty()) @can('update', $timeline->entity) @endcan @endif ================================================ FILE: resources/views/timelines/elements/_form.blade.php ================================================ position) ? $model->position : null; $positions = []; if (!empty($era)) { $positions = $era->positionOptions(null, true); $oldPosition = count($positions); } elseif (!empty($model)) { $positions = $model->era->positionOptions($oldPosition); } ?> @include('cruds.fields.entity') @include('cruds.fields.entry', ['model' => $model]) use_entity_entry ?? false)) checked="checked" @endif /> use_event_date ?? false)) checked="checked" @endif /> @include('cruds.fields.colour', ['default' => 'grey']) boosted()) disabled="disabled" @endif />

    {!! __('timelines/elements.helpers.icon', [ 'rpgawesome' => 'RPG Awesome', 'fontawesome' => 'Font Awesome' ]) !!}

    @if (!$campaign->boosted()) @can('boost', auth()->user())

    {!! __('crud.errors.boosted_campaigns', ['boosted' => '' . __('concept.premium-campaign') . '']) !!}

    @else

    {!! __('crud.errors.boosted_campaigns', ['boosted' => '' . __('concept.premium-campaign') . '']) !!}

    @endif @endif
    @include('cruds.fields.visibility_id') is_collapsed ?? false)) checked="checked" @endif />
    @include('editors.editor') @if (request()->ajax()) @endif ================================================ FILE: resources/views/timelines/elements/_icon.blade.php ================================================ @if (!empty($element->icon)) @if (Illuminate\Support\Str::startsWith($element->icon, 'colour . ' ' . $min . ' ', $element->icon) !!} @else @endif @else @endif ================================================ FILE: resources/views/timelines/elements/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('timelines/elements.create.title', ['name' => $timeline->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($timeline->entity)->list(), Breadcrumb::show(), __('timelines/elements.create.title') ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('timelines.elements._form', ['model' => null])
    @endsection ================================================ FILE: resources/views/timelines/elements/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('timelines/elements.edit.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($timeline->entity)->list(), Breadcrumb::show(), __('timelines/elements.edit.title', ['name' => $model->name]) ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('timelines.elements._form')
    @if(!empty($model) && $campaign->hasEditingWarning()) @endif @endsection @section('scripts') @parent @endsection @section('modals') @parent @includeWhen(!empty($editingUsers) && !empty($model), 'cruds.forms.edit_warning', ['model' => $model]) @endsection ================================================ FILE: resources/views/timelines/eras/_form.blade.php ================================================ @include('cruds.fields.entry', ['model' => $model]) is_collapsed ?? false)) checked="checked" @endif /> @include('editors.editor', (request()->ajax() ? ['dialogsInBody' => true] : [])) @if (request()->ajax()) @endif ================================================ FILE: resources/views/timelines/eras/create.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('timelines/eras.create.title', ['name' => $timeline->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($timeline->entity)->list(), Breadcrumb::show(), __('timelines/eras.create.title') ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('timelines.eras._form', ['model' => null])
    @if (!empty($from)) @endif
    @endsection ================================================ FILE: resources/views/timelines/eras/edit.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('timelines/eras.edit.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => [ Breadcrumb::campaign($campaign)->entity($timeline->entity)->list(), Breadcrumb::show(), __('timelines/eras.edit.title', ['name' => $model->name]) ], 'centered' => true, ]) @section('content') @include('partials.errors') @include('timelines.eras._form')
    @if (!empty($from)) @endif
    @endsection ================================================ FILE: resources/views/timelines/eras/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('timelines/eras.index.title', ['name' => $model->name]), 'description' => '', 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @can('update', $entity) @endcan @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'eras', 'view' => 'timelines.panels.eras', ]) @endsection ================================================ FILE: resources/views/timelines/form/_copy.blade.php ================================================ ================================================ FILE: resources/views/timelines/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Timeline::class, 'trans' => 'timelines']) @include('cruds.fields.parent') @include('cruds.fields.entry2') @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/timelines/panels/eras.blade.php ================================================
    @if(Datagrid::hasBulks())
    @include('layouts.datagrid._table')
    @else
    @include('layouts.datagrid._table')
    @endif
    @section('modals') @parent @include('layouts.datagrid.delete-forms', ['models' => Datagrid::deleteForms(), 'params' => []]) @endsection ================================================ FILE: resources/views/timelines/panels/timelines.blade.php ================================================
    @include('layouts.datagrid._table')
    ================================================ FILE: resources/views/timelines/reorder/_reorder.blade.php ================================================ @if ($eras->isEmpty())

    {{ __('timelines.reorder.empty') }}

    @endif
    @foreach($eras as $era)
    {!! $era->name !!} {!! $era->ages()!!}
    @if (!$era->orderedElements->isEmpty())
    @foreach ($era->orderedElements as $element) @if ($element->invisibleEntity()) @continue @endif
    @include('timelines.elements._icon', ['absolute' => false])
    @if ($element->entity) {!! $element->name !!} @else {!! $element->name !!} @endif @if (isset($element->date)) ({{ $element->date }}) @endif
    @endforeach
    @endif
    @endforeach
    ================================================ FILE: resources/views/timelines/reorder/index.blade.php ================================================ @extends('layouts.' . (request()->ajax() ? 'ajax' : 'app'), [ 'title' => __('timelines.reorder.title', ['name' => $timeline->name]), 'breadcrumbs' => false, 'mainTitle' => false, 'bodyClass' => 'timeline-eras-reorder' ]) @section('content') @include('entities.pages.subpage', [ 'active' => 'reorder', 'model' => $timeline, 'view' => 'timelines.reorder._reorder', 'entity' => $timeline->entity, ]) @endsection ================================================ FILE: resources/views/timelines/show.blade.php ================================================ @section('entity-header-actions-override')
    @include('entities.headers.toggle') @include('entities.headers.actions')
    @endsection
    @include('entities.components.header', [ 'entityHeaderActions' => 'entity-header-actions-override', ])
    @include('entities.components.menu_v2', ['active' => 'story'])
    We know timelines need work. Tell us what you'd actually want. 5 minute survey @include('entities.components.posts', ['withEntry' => true]) @include('timelines._timeline', ['timeline' => $entity->child])
    @include('entities.components.pins')
    ================================================ FILE: resources/views/timelines/timelines.blade.php ================================================ @extends('layouts.app', [ 'title' => $entity->name . ' - ' . $entity->entityType->plural(), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('entity-header-actions')
    @if ($mode === \App\Enums\Descendants::Direct) @else @endif @include('entities.headers.actions', ['edit' => false])
    @endsection @section('content') @include('entities.pages.subpage', [ 'active' => 'timelines', 'view' => 'timelines.panels.timelines', ]) @endsection ================================================ FILE: resources/views/tutorials/content.blade.php ================================================ @if (view()->hasSection('footer')) @endif ================================================ FILE: resources/views/tutorials/dashboard_1.blade.php ================================================ @extends('tutorials.content') @section('title') {{ __('tutorials/home.dashboard_1.title') }} @endsection @section('body')

    {{ __('tutorials/home.dashboard_1.first') }}

    {{ __('tutorials/home.dashboard_1.second') }}

    {{ __('tutorials/home.dashboard_1.third') }}

    @endsection @section('footer') @endsection ================================================ FILE: resources/views/tutorials/modal.blade.php ================================================ ================================================ FILE: resources/views/tw.blade.php ================================================ This file is never loaded, but needed to trick tailwind to include some classes which aren't clearly written in the code. For example `bg-{{ $colour }}` won't be detected. For this, we have this file.
    ================================================ FILE: resources/views/users/_badges.blade.php ================================================
    {{ __('users/profile.fields.achievements') }}
    @if($user->isWordsmith()) @endif
    ================================================ FILE: resources/views/users/profile.blade.php ================================================ @extends('layouts.front', [ 'title' => __('users/profile.title', ['name' => $user->displayName()]), 'skipPerf' => true, 'ogImage' => (bool) $user->avatar, ]) @section('og') @if (!empty($user->profile['bio']))@endif @if ($user->hasAvatar()) @endif @endsection @section('content')

    {!! $user->displayName() !!} @if(isset($user->settings['pronouns'])) ({{ $user->settings['pronouns']}}) @endif

    @if ($user->isBanned()) {{__('users/profile.fields.banned')}} @endif @if (!empty($user->profile['bio']))

    {!! nl2br($user->profile['bio']) !!}

    @endif
    @if ($discord = $user->discord()) {{ $discord->settings['username'] }} @endif @if ($user->hasPlugins()) {{ __('footer.plugins') }} @endif @isset($user->settings['link']) @endif @if (auth()->check() && !\App\Facades\Identity::isImpersonating() && auth()->user()->id === $user->id) {{ __('settings.profile.actions.update_profile') }} @endif
    @if ($user->isElemental())
    Elemental
    @elseif ($user->isWyvern())
    Wyvern
    @elseif ($user->isOwlbear())
    Owlbear
    @elseif ($user->hasRole('admin'))
    Kanka Team
    @endif
    @if (!$campaigns->isEmpty())

    {{ __('users/profile.fields.public_campaigns') }}

    @foreach ($campaigns as $campaign)
    @include('front._campaign', ['campaign' => $campaign, 'featured' => false])
    @endforeach
    @endif

    {!! __('users/profile.fields.member_since', ['date' => '

    ' . $user->created_at->format('M d, Y')]) !!}

    @if ($user->subscribed('kanka'))

    {!! __('users/profile.fields.subscriber_since', ['date' => '

    ' . $user->subscription('kanka')->created_at->format('M d, Y')]) !!}

    @endif

    {!! __('users/profile.fields.entities_created', [ 'count' => '

    ' . $user->createdEntitiesCount(), 'help' => '', ]) !!}

    @includeWhen($user->hasAchievements(), 'users._badges')
    @endsection ================================================ FILE: resources/views/vendor/cashier/invoice.blade.php ================================================ Invoice
    Receipt

    @isset ($product) Product: {{ $product }}
    @endisset Date: {{ $invoice->date()->toFormattedDateString() }}
    @if ($dueDate = $invoice->dueDate()) Due date: {{ $dueDate->toFormattedDateString() }}
    @endif @if ($invoiceId = $id ?? $invoice->number) Invoice Number: {{ $invoiceId }}
    @endif

    {{ $header ?? $vendor ?? $invoice->account_name }}
    {{ $vendor ?? $invoice->account_name }}
    @isset($street) {{ $street }}
    @endisset @isset($location) {{ $location }}
    @endisset @isset($country) {{ $country }}
    @endisset @isset($phone) {{ $phone }}
    @endisset @isset($email) {{ $email }}
    @endisset @isset($url) {{ $url }}
    @endisset @isset($vendorVat) {{ $vendorVat }}
    @else @foreach ($invoice->accountTaxIds() as $taxId) {{ $taxId->value }}
    @endforeach @endisset
    Recipient
    {{ $invoice->customer_name ?? $invoice->customer_email }}
    @if ($address = $invoice->customer_address) @if ($address->line1) {{ $address->line1 }}
    @endif @if ($address->line2) {{ $address->line2 }}
    @endif @if ($address->city) {{ $address->city }}
    @endif @if ($address->state || $address->postal_code) {{ implode(' ', [$address->state, $address->postal_code]) }}
    @endif @if ($address->country) {{ $address->country }}
    @endif @endif @if ($invoice->customer_phone) {{ $invoice->customer_phone }}
    @endif @if ($invoice->customer_name) {{ $invoice->customer_email }}
    @endif @foreach ($invoice->customerTaxIds() as $taxId) {{ $taxId->value }}
    @endforeach {{ $billing }}
    @if ($invoice->description)

    {{ $invoice->description }}

    @endif @if (isset($vat))

    {{ $vat }}

    @endif
    @if ($invoice->hasTax()) @endif @foreach ($invoice->invoiceLineItems() as $item) @if ($invoice->hasTax()) @endif @endforeach @if ($invoice->hasDiscount() || $invoice->hasTax() || $invoice->hasStartingBalance()) @endif @if ($invoice->hasDiscount()) @foreach ($invoice->discounts() as $discount) @php($coupon = $discount->coupon()) @endforeach @endif @unless ($invoice->isNotTaxExempt()) @else @foreach ($invoice->taxes() as $tax) @endforeach @endunless @if ($invoice->hasAppliedBalance()) @endif
    Description Qty Unit priceTaxAmount
    {{ $item->description }} @if ($item->hasPeriod() && ! $item->periodStartAndEndAreEqual())
    {{ $item->startDate() }} - {{ $item->endDate() }} @endif
    {{ $item->quantity }} {{ $item->unitAmountExcludingTax() }} @if ($inclusiveTaxPercentage = $item->inclusiveTaxPercentage()) {{ $inclusiveTaxPercentage }}% incl. @endif @if ($item->hasBothInclusiveAndExclusiveTax()) + @endif @if ($exclusiveTaxPercentage = $item->exclusiveTaxPercentage()) {{ $exclusiveTaxPercentage }}% @endif {{ $item->total() }}
    Subtotal {{ $invoice->subtotal() }}
    @if ($coupon->isPercentage()) {{ $coupon->name() }} ({{ $coupon->percentOff() }}% Off) @else {{ $coupon->name() }} ({{ $coupon->amountOff() }} Off) @endif -{{ $invoice->discountFor($discount) }}
    @if ($invoice->isTaxExempt()) Tax is exempted @else Tax to be paid on reverse charge basis @endif
    {{ $tax->display_name }} {{ $tax->jurisdiction ? ' - '.$tax->jurisdiction : '' }} ({{ $tax->percentage }}%{{ $tax->isInclusive() ? ' incl.' : '' }}) {{ $tax->amount() }}
    Total {{ $invoice->realTotal() }}
    Applied balance {{ $invoice->appliedBalance() }}
    Amount due {{ $invoice->amountDue() }}
    ================================================ FILE: resources/views/vendor/larecipe/default.blade.php ================================================ {{ isset($title) ? $title . ' | ' : null }}{{ config('app.name') }} @if (isset($canonical) && $canonical) @endif @if($openGraph = config('larecipe.seo.og')) @foreach($openGraph as $key => $value) @if($value) @endif @endforeach @endif @if (config('larecipe.ui.fav')) @endif @if (config('larecipe.ui.fa_v4_shims', true)) @endif @include('larecipe::style') @foreach(LaRecipe::allStyles() as $name => $path) @if (preg_match('/^https?:\/\//', $path)) @else @endif @endforeach
    @include('larecipe::partials.nav') @include('larecipe::plugins.search') @yield('content') @include('layouts.tracking.tracking')
    @if(config('larecipe.settings.ga_id')) @endif @foreach (LaRecipe::allScripts() as $name => $path) @if (preg_match('/^https?:\/\//', $path)) @else @endif @endforeach @includeWhen(config('tracking.consent'), 'partials.cookieconsent') ================================================ FILE: resources/views/vendor/larecipe/partials/logo.blade.php ================================================ ================================================ FILE: resources/views/vendor/livewire/tailwind.blade.php ================================================ @php if (! isset($scrollTo)) { $scrollTo = 'body'; } $scrollIntoViewJsSnippet = ($scrollTo !== false) ? << @if ($paginator->hasPages()) @endif ================================================ FILE: resources/views/vendor/mail/html/button.blade.php ================================================ @props([ 'url', 'color' => 'primary', 'align' => 'center', ]) ================================================ FILE: resources/views/vendor/mail/html/footer.blade.php ================================================ ================================================ FILE: resources/views/vendor/mail/html/header.blade.php ================================================ @props(['url']) Kanka logo ================================================ FILE: resources/views/vendor/mail/html/layout.blade.php ================================================ @props([ 'layout' => 'user', ]) {{ config('app.name') }}
    @include('emails.2024.header') {{ Illuminate\Mail\Markdown::parse($slot) }} {{ $subcopy ?? '' }} @includeWhen($layout != 'admin', 'emails.2024.footer', ['layout' => $layout])
    ================================================ FILE: resources/views/vendor/mail/html/message.blade.php ================================================ @props([ 'layout' => 'user', ]) {{-- Body --}} {{ $slot }} ================================================ FILE: resources/views/vendor/mail/html/panel.blade.php ================================================ ================================================ FILE: resources/views/vendor/mail/html/subcopy.blade.php ================================================ ================================================ FILE: resources/views/vendor/mail/html/table.blade.php ================================================
    {{ Illuminate\Mail\Markdown::parse($slot) }}
    ================================================ FILE: resources/views/vendor/mail/html/themes/default.css ================================================ /* Base */ body, body *:not(html):not(style):not(br):not(tr):not(code) { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; position: relative; } body { -webkit-text-size-adjust: none; background-color: #ffffff; color: #718096; height: 100%; line-height: 1.4; margin: 0; padding: 0; width: 100% !important; } p, ul, ol, blockquote { line-height: 1.4; text-align: left; } a { color: #2CB191; } a img { border: none; } /* Typography */ h1 { color: #000000; font-size: 28px; font-weight: bold; margin-top: 0; margin-bottom: 20px; padding: 20px; text-align: center; line-height: 1.4; } h2 { color: #ffffff; font-size: 28px; font-weight: bold; margin-top: 0; margin-bottom: 20px; padding: 20px; text-align: center; line-height: 1.4; } h3 { font-size: 14px; font-weight: bold; margin-top: 0; text-align: left; } p { font-size: 16px; line-height: 1.5em; margin-top: 0; margin-bottom: 1.2rem; text-align: left; } p.sub { font-size: 12px; } img { max-width: 100%; } /* Layout */ .wrapper { -premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 100%; background-color: #edf2f7; margin: 0; padding: 0; width: 100%; } .content { -premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 100%; margin: 0; padding: 0; width: 100%; } /* Header */ .header { padding: 25px 0; text-align: center; } .header a { color: #3d4852; font-size: 19px; font-weight: bold; text-decoration: none; } /* Logo */ .logo { height: 75px; max-height: 75px; width: 75px; } /* Body */ .body { -premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 100%; background-color: #edf2f7; border-bottom: 1px solid #edf2f7; border-top: 1px solid #edf2f7; margin: 0; padding: 0; width: 100%; } .inner-body { -premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 570px; background-color: #ffffff; border-color: #e8e5ef; border-radius: 2px; border-width: 1px; box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); margin: 0 auto; padding: 0; width: 570px; } .inner-body a { word-break: break-all; } /* Subcopy */ .subcopy { border-top: 1px solid #e8e5ef; margin-top: 25px; padding-top: 25px; } .subcopy p { font-size: 14px; } /* Footer */ .footer { -premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 570px; margin: 0 auto; padding: 0; text-align: center; width: 570px; } .footer p { color: #b0adc5; font-size: 12px; text-align: center; } .footer a { color: #b0adc5; text-decoration: underline; } /* Tables */ .table table { -premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 100%; margin: 30px auto; width: 100%; } .table th { border-bottom: 1px solid #edeff2; margin: 0; padding-bottom: 8px; } .table td { color: #74787e; font-size: 15px; line-height: 18px; margin: 0; padding: 10px 0; } .content-cell { max-width: 100vw; padding: 32px; } table.social-links { border-spacing: 5px; border-collapse: separate; } /* Buttons */ .action { -premailer-cellpadding: 0; -premailer-cellspacing: 0; -premailer-width: 100%; margin: 30px auto; padding: 0; text-align: center; width: 100%; float: unset; } .button { -webkit-text-size-adjust: none; border-radius: 4px; color: #fff; display: inline-block; overflow: hidden; text-decoration: none; } .button-blue { padding: 10px 20px; border-radius: 20px; background-color: #1919ad; color: #efefef; text-decoration: none; margin: 20px 0; display: inline-block; } .button-primary { background-color: #2d3748; border-bottom: 8px solid #2d3748; border-left: 18px solid #2d3748; border-right: 18px solid #2d3748; border-top: 8px solid #2d3748; } .button-green, .button-success { background-color: #48bb78; border-bottom: 8px solid #48bb78; border-left: 18px solid #48bb78; border-right: 18px solid #48bb78; border-top: 8px solid #48bb78; } .button-red, .button-error { background-color: #e53e3e; border-bottom: 8px solid #e53e3e; border-left: 18px solid #e53e3e; border-right: 18px solid #e53e3e; border-top: 8px solid #e53e3e; } /* Panels */ .panel { /* border-left: #2d3748 solid 4px; */ width: calc(100% + 64px); margin: 21px; margin-left: -32px; } .panel-content { background-color: #404790; color: #718096; padding: 32px; } .panel-content p { color: #ffffff; } .panel-item { padding: 0; } .panel-item p:last-of-type { margin-bottom: 0; padding-bottom: 0; } /* Utilities */ .break-all { word-break: break-all; } ================================================ FILE: resources/views/vendor/mail/text/button.blade.php ================================================ {{ $slot }}: {{ $url }} ================================================ FILE: resources/views/vendor/mail/text/footer.blade.php ================================================ {{ $slot }} ================================================ FILE: resources/views/vendor/mail/text/header.blade.php ================================================ {{ $slot }}: {{ $url }} ================================================ FILE: resources/views/vendor/mail/text/layout.blade.php ================================================ {!! strip_tags($header ?? '') !!} {!! strip_tags($slot) !!} @isset($subcopy) {!! strip_tags($subcopy) !!} @endisset {!! strip_tags($footer ?? '') !!} ================================================ FILE: resources/views/vendor/mail/text/message.blade.php ================================================ {{-- Header --}} {{ config('app.name') }} {{-- Body --}} {{ $slot }} {{-- Subcopy --}} @isset($subcopy) {{ $subcopy }} @endisset {{-- Footer --}} © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') ================================================ FILE: resources/views/vendor/mail/text/panel.blade.php ================================================ {{ $slot }} ================================================ FILE: resources/views/vendor/mail/text/subcopy.blade.php ================================================ {{ $slot }} ================================================ FILE: resources/views/vendor/mail/text/table.blade.php ================================================ {{ $slot }} ================================================ FILE: resources/views/vendor/pagination/tailwind.blade.php ================================================ @if ($paginator->hasPages()) @endif ================================================ FILE: resources/views/vendor/passport/authorize.blade.php ================================================ @extends('layouts.login', [ 'title' => 'Kanka oAuth Authorization', ]) @section('content')

    {{ __('Authorization Request') }}

    The application {{ $client->name }} is requesting permission to access your Kanka account.

    @if (count($scopes) > 0)

    This application will be able to:

      @foreach ($scopes as $scope)
    • {{ $scope->description }}
    • @endforeach
    @else

    This application will have full access to your account and campaigns.

    @endif
    @csrf
    @csrf @method('DELETE')

    If you are unsure why you are seeing this, contact us at {{ config('app.email') }} or reach out directly on Discord.

    @endsection ================================================ FILE: resources/views/whiteboards/_draw-link.blade.php ================================================ @php /** @var \App\Models\Whiteboard $model */ @endphp ================================================ FILE: resources/views/whiteboards/cta.blade.php ================================================ @extends('layouts.app', [ 'title' => __('entities.whiteboards'), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('content')

    {{ __('whiteboards.cta.title') }}

    {!! __('whiteboards.cta.text', ['wyvern' => 'Wyvern', 'elemental' => 'Elemental']) !!}

    @endsection ================================================ FILE: resources/views/whiteboards/draw.blade.php ================================================ @extends('layouts.rich', [ 'title' => $whiteboard->name, 'breadcrumbs' => false, 'mainTitle' => false, 'pageClass' => 'whiteboard-page' ]) @section('content')
    @endsection @section('scripts') @parent @vite('resources/js/whiteboards.js') @endsection ================================================ FILE: resources/views/whiteboards/form/_copy.blade.php ================================================ ================================================ FILE: resources/views/whiteboards/form/_entry.blade.php ================================================ @include('cruds.fields.entity-name') @include('cruds.fields.type', ['base' => \App\Models\Whiteboard::class, 'trans' => 'whiteboards']) @include('cruds.fields.tags') @include('cruds.fields.image') ================================================ FILE: resources/views/whiteboards/index.blade.php ================================================ @extends('layouts.app', [ 'title' => __('entities.whiteboards'), 'breadcrumbs' => false, 'mainTitle' => false, ]) @section('content')

    {{ __('entities.whiteboards') }}

    {{ __('entities.whiteboard') }}
    {!! $models->links() !!} @endsection ================================================ FILE: resources/views/whiteboards/show.blade.php ================================================ @section('entity-header-actions-override')
    @include('entities.headers.toggle') @include('entities.headers.actions')
    @endsection
    @include('entities.components.header', [ 'entityHeaderActions' => 'entity-header-actions-override', ])
    @include('entities.components.menu_v2', ['active' => 'story'])
    {{ __('whiteboards.actions.draw') }} @include('entities.components.posts', ['withEntry' => true])
    @include('entities.components.pins')
    ================================================ FILE: routes/api-public.php ================================================ ['auth:api', 'throttle:rate_limit,1'], 'namespace' => 'Api\v1', 'prefix' => 'v1', ], function() { require base_path('routes/api.v1.php'); });*/ Route::group([ 'middleware' => ['auth:api', 'throttle:rate_limit,1'], 'namespace' => 'Api\v1', 'prefix' => '1.0', ], function () { require base_path('routes/api.v1.php'); }); Route::group([ 'namespace' => 'Api\Public', 'prefix' => 'public', ], function () { require base_path('routes/api-public.php'); }); ================================================ FILE: routes/api.v1.php ================================================ 'CampaignApiController', 'campaigns.abilities' => 'AbilityApiController', 'campaigns.attribute_templates' => 'AttributeTemplateApiController', 'campaigns.bookmarks' => 'BookmarkApiController', // 'campaigns.campaign_users' => 'CampaignUserApiController', 'campaigns.calendars' => 'CalendarApiController', 'campaigns.calendars.calendar_weather' => 'CalendarWeatherApiController', 'campaigns.characters' => 'CharacterApiController', 'campaigns.creatures' => 'CreatureApiController', 'campaigns.dice_rolls' => 'DiceRollApiController', 'campaigns.events' => 'EventApiController', 'campaigns.families' => 'FamilyApiController', 'campaigns.items' => 'ItemApiController', 'campaigns.journals' => 'JournalApiController', 'campaigns.locations' => 'LocationApiController', 'campaigns.maps' => 'MapApiController', 'campaigns.maps.map_layers' => 'MapLayerApiController', 'campaigns.maps.map_groups' => 'MapGroupApiController', 'campaigns.maps.map_markers' => 'MapMarkerApiController', 'campaigns.notes' => 'NoteApiController', 'campaigns.organisations' => 'OrganisationApiController', 'campaigns.organisations.organisation_members' => 'OrganisationMemberApiController', 'campaigns.quests' => 'QuestApiController', 'campaigns.quests.quest_elements' => 'QuestElementApiController', 'campaigns.races' => 'RaceApiController', 'campaigns.tags' => 'TagApiController', 'campaigns.timelines' => 'TimelineApiController', 'campaigns.timelines.timeline_eras' => 'TimelineEraApiController', 'campaigns.timelines.timeline_elements' => 'TimelineElementApiController', 'campaigns.conversations' => 'ConversationApiController', 'campaigns.conversations.conversation_participants' => 'ConversationParticipantApiController', 'campaigns.conversations.conversation_messages' => 'ConversationMessageApiController', // 'campaigns.' => 'ApiController', // Entity elements 'campaigns.entities.attributes' => 'EntityAttributeApiController', 'campaigns.entities.posts' => 'PostApiController', 'campaigns.entities.reminders' => 'ReminderApiController', 'campaigns.entities.relations' => 'EntityRelationApiController', 'campaigns.entities.entity_tags' => 'EntityTagApiController', 'campaigns.entities.inventory' => 'EntityInventoryApiController', 'campaigns.entities.entity_abilities' => 'EntityAbilityApiController', 'campaigns.entities.entity_assets' => 'EntityAssetApiController', 'campaigns.entities.entity_permissions' => 'EntityPermissionApiController', 'campaigns.campaign_dashboard_widgets' => 'CampaignDashboardWidgetApiController', 'campaigns.campaign_styles' => 'CampaignStyleApiController', 'campaigns.images' => 'CampaignImageApiController', 'campaigns.entity_types' => 'Campaigns\EntityTypeApiController', ]); Route::get('campaigns/{campaign}/entities/{entity}/image', [EntityImageApiController::class, 'show']); Route::post('campaigns/{campaign}/entities/{entity}/image', [EntityImageApiController::class, 'put']); Route::delete('campaigns/{campaign}/entities/{entity}/image', [EntityImageApiController::class, 'destroy']); Route::get('campaigns/{campaign}/roles', 'CampaignRoleApiController@index'); Route::get('campaigns/{campaign}/category_statuses', [CategoryStatusController::class, 'index']); Route::get('campaigns/{campaign}/relations', [RelationApiController::class, 'index']); Route::get('campaigns/{campaign}/search/{query}', [SearchApiController::class, 'index']); Route::get('campaigns/{campaign}/entities/templates', [EntityTemplateApiController::class, 'index']); Route::post('campaigns/{campaign}/entities/templates/{entity}/switch', [EntityTemplateApiController::class, 'switch']); Route::get('campaigns/{campaign}/entities/archived', [EntityArchiveApiController::class, 'index']); Route::post('campaigns/{campaign}/entities/{entity}/archive', [EntityArchiveApiController::class, 'switch']); Route::get('campaigns/{campaign}/entities', [EntityApiController::class, 'index']); Route::get('campaigns/{campaign}/entities/recent', [RecentEntityApiController::class, 'index']); Route::post('campaigns/{campaign}/entities/{entity_type}', [EntityApiController::class, 'put']); Route::get('campaigns/{campaign}/entities/{entity}', [EntityApiController::class, 'show']); Route::put('campaigns/{campaign}/entities/{entity}', [EntityApiController::class, 'edit']); Route::patch('campaigns/{campaign}/entities/{entity}', [EntityApiController::class, 'patch']); Route::delete('campaigns/{campaign}/entities/{entity}', [EntityApiController::class, 'destroy']); Route::get('campaigns/{campaign}/entities/{entity}/mentions', [EntityMentionApiController::class, 'index']); Route::get('campaigns/{campaign}/users', 'Campaigns\UserApiController@index'); Route::get('campaigns/{campaign}/users/{user}', 'Campaigns\UserApiController@show'); Route::post('campaigns/{campaign}/users', 'Campaigns\UserApiController@add'); Route::delete('campaigns/{campaign}/users', 'Campaigns\UserApiController@remove'); Route::get('campaigns/{campaign}/users', [UserApiController::class, 'index']); Route::get('campaigns/{campaign}/users/{user}', [UserApiController::class, 'show']); Route::post('campaigns/{campaign}/users', [UserApiController::class, 'add']); Route::delete('campaigns/{campaign}/users', [UserApiController::class, 'remove']); Route::post('campaigns/{campaign}/permissions/test', [EntityPermissionApiController::class, 'test']); Route::get('campaigns/{campaign}/calendars/{calendar}/reminders', [CalendarEventApiController::class, 'index']); Route::post('campaigns/{campaign}/calendars/{calendar}/advance', [AdvancerApiController::class, 'advance']); Route::post('campaigns/{campaign}/calendars/{calendar}/retreat', [AdvancerApiController::class, 'retreat']); Route::get('visibilities', [VisibilityController::class, 'index']); Route::get('post-layouts', [PostLayoutApiController::class, 'index']); Route::get('campaigns/{campaign}/recovery', [EntityRecoveryApiController::class, 'index']); Route::post('campaigns/{campaign}/recover', [EntityRecoveryApiController::class, 'recover']); Route::get('campaigns/{campaign}/recovery/posts', [PostRecoveryApiController::class, 'index']); Route::post('campaigns/{campaign}/recover/posts', [PostRecoveryApiController::class, 'recover']); Route::post('campaigns/{campaign}/transform', [EntityTransformApiController::class, 'transform']); Route::post('campaigns/{campaign}/transfer', [EntityMoveApiController::class, 'transfer']); Route::get('campaigns/{campaign}/default-thumbnails', [DefaultThumbnailApiController::class, 'index']); Route::post('campaigns/{campaign}/default-thumbnails', [DefaultThumbnailApiController::class, 'upload']); Route::delete('campaigns/{campaign}/default-thumbnails', [DefaultThumbnailApiController::class, 'delete']); Route::get('campaigns/{campaign}/fulltext-search', [FullTextSearchApiController::class, 'index']); Route::get('campaigns/{campaign}/families/{family}/tree', [FamilyTreeApiController::class, 'show']); Route::post('campaigns/{campaign}/families/{family}/tree', [FamilyTreeApiController::class, 'store']); Route::put('campaigns/{campaign}/families/{family}/tree', [FamilyTreeApiController::class, 'store']); Route::delete('campaigns/{campaign}/families/{family}/tree', [FamilyTreeApiController::class, 'destroy']); Route::get('profile', [ProfileApiController::class, 'index']); Route::get('version', function () { return config('app.version'); }); Route::get('health', [HealthController::class, 'index']); Route::get('entity-types', [EntityTypeApiController::class, 'index']); Route::get('filters', [FilterApiController::class, 'index']); Route::get('filters/{entityType}', [FilterApiController::class, 'show']); Route::get('campaigns/{campaign}/applications', [ApplicationApiController::class, 'index']); Route::get('campaigns/{campaign}/applications/{application}', [ApplicationApiController::class, 'show']); Route::post('campaigns/{campaign}/applications/{application}/approve', [ApplicationApiController::class, 'approve']); Route::post('campaigns/{campaign}/applications/{application}/reject', [ApplicationApiController::class, 'reject']); // Bulk entity attributes Route::put('campaigns/{campaign}/entities/{entity}/attributes', [PutController::class, 'put']); Route::patch('campaigns/{campaign}/entities/{entity}/attributes', [PatchController::class, 'patch']); ================================================ FILE: routes/auth.php ================================================ config('auth.register_enabled')]); Route::post('/logout', [AuthController::class, 'logout'])->name('logout'); Route::get('/login-as-user/{user}', [LoginController::class, 'loginAsUser'])->name('login-as-user'); Route::get('/login-as', [LoginController::class, 'loginAs'])->name('login-as'); // OAuth Routes Route::get('auth/{provider}', [AuthController::class, 'redirectToProvider'])->name('auth.provider'); // Password Reset Routes... Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email'); include 'oauth.php'; /* Route::get('login', 'Auth\LoginController@showLoginForm')->name('login'); Route::post('login', 'Auth\LoginController@login'); Route::post('logout', 'Auth\LoginController@logout')->name('logout'); // Registration Routes... if (config('auth.register_enabled')) { Route::get('register', 'Auth\LoginController@showRegistrationForm')->name('register'); Route::post('register', 'Auth\LoginController@register'); } // Password Confirmation Routes... Route::get('password/confirm', 'Auth\ConfirmPasswordController@showConfirmForm')->name('password.confirm'); Route::post('password/confirm', 'Auth\ConfirmPasswordController@confirm'); // Email Verification Routes... Route::get('email/verify', 'Auth\VerificationController@show')->name('verification.notice'); Route::get('email/verify/{id}/{hash}', 'Auth\VerificationController@verify')->name('verification.verify'); Route::post('email/resend', 'Auth\VerificationController@resend')->name('verification.resend'); */ ================================================ FILE: routes/campaigns/bulks.php ================================================ name('bulk.batch'); Route::post('/w/{campaign}/bulk/{entity_type}/batch', [BatchController::class, 'apply'])->name('bulk.batch.apply'); Route::get('/w/{campaign}/bulk/{entity_type}/templates', [TemplateController::class, 'index'])->name('bulk.templates'); Route::post('/w/{campaign}/bulk/{entity_type}/templates', [TemplateController::class, 'apply'])->name('bulk.templates.apply'); Route::get('/w/{campaign}/bulk/{entity_type}/transform', [TransformController::class, 'index'])->name('bulk.transform'); Route::post('/w/{campaign}/bulk/{entity_type}/transform', [TransformController::class, 'apply'])->name('bulk.transform.apply'); Route::get('/w/{campaign}/bulk/{entity_type}/permissions', [PermissionController::class, 'index'])->name('bulk.permissions'); Route::post('/w/{campaign}/bulk/{entity_type}/permissions', [PermissionController::class, 'apply'])->name('bulk.permissions.apply'); Route::get('/w/{campaign}/bulk/{entity_type}/copy-to-campaign', [CopyController::class, 'index'])->name('bulk.copy-to-campaign'); Route::post('/w/{campaign}/bulk/{entity_type}/copy-to-campaign', [CopyController::class, 'apply'])->name('bulk.copy-to-campaign.apply'); Route::get('/w/{campaign}/bulk/{entity_type}/delete', [DeleteController::class, 'index'])->name('bulk.delete'); Route::post('/w/{campaign}/bulk/{entity_type}/delete', [DeleteController::class, 'apply'])->name('bulk.delete.apply'); Route::get('/w/{campaign}/bulk/relations-delete', [DeleteRelationController::class, 'index'])->name('bulk.delete-relations'); Route::post('/w/{campaign}/bulk/relations-delete', [DeleteRelationController::class, 'apply'])->name('bulk.delete-relations.apply'); Route::post('/w/{campaign}/bulk/{entity_type}/print', [PrintController::class, 'index'])->name('bulk.print'); ================================================ FILE: routes/campaigns/campaign.php ================================================ name('dashboard'); Route::post('/w/{campaign}/follow', 'Campaign\FollowController@update')->name('campaign.follow'); Route::get('/w/{campaign}/apply', 'Campaign\ApplyController@index')->name('campaign.apply'); Route::post('/w/{campaign}/apply', 'Campaign\ApplyController@save')->name('campaign.apply.save'); Route::delete('/w/{campaign}/remove', 'Campaign\ApplyController@remove')->name('campaign.apply.remove'); Route::get('/w/{campaign}/gallery', 'GalleryController@index')->name('gallery'); Route::get('/w/{campaign}/gallery-index', 'GalleryController@index')->name('campaign.gallery.index'); Route::post('/w/{campaign}/gallery/ajax-upload', 'Summernote\GalleryController@upload')->name('campaign.gallery.ajax-upload'); Route::get('/w/{campaign}/gallery/ajax-gallery', 'Summernote\GalleryController@index')->name('campaign.gallery.summernote'); Route::post('/w/{campaign}/gallery/upload/file', [UploadController::class, 'file'])->name('gallery.upload.file'); Route::post('/w/{campaign}/gallery/upload/files', [UploadController::class, 'files'])->name('gallery.upload.files'); Route::post('/w/{campaign}/gallery/upload/url', [UploadController::class, 'url'])->name('gallery.upload.url'); Route::get('/w/{campaign}/gallery/browse', [BrowseController::class, 'index'])->name('gallery.browse'); Route::get('/w/{campaign}/gallery/tiptap', [TiptapController::class, 'index'])->name('gallery.tiptap'); Route::post('/w/{campaign}/gallery/tiptap', [UploadController::class, 'file'])->name('gallery.tiptap.save'); Route::get('/w/{campaign}/gallery/setup', [SetupController::class, 'index'])->name('gallery.setup'); Route::get('/w/{campaign}/gallery/open/{image}', [ImageController::class, 'show'])->name('gallery.show'); Route::get('/w/{campaign}/gallery/search/{term?}', [SearchController::class, 'index'])->name('gallery.search'); Route::post('/w/{campaign}/gallery/delete', [DeleteController::class, 'destroy'])->name('gallery.delete'); Route::post('/w/{campaign}/gallery/create', [CreateController::class, 'index'])->name('gallery.create'); Route::post('/w/{campaign}/gallery/update/bulk', [App\Http\Controllers\Gallery\UpdateController::class, 'bulk'])->name('gallery.update'); Route::get('/w/{campaign}/gallery/{image}', [ShowController::class, 'show'])->name('gallery.file.show'); Route::post('/w/{campaign}/gallery/{image}/update', [App\Http\Controllers\Gallery\UpdateController::class, 'process'])->name('gallery.file.update'); Route::delete('/w/{campaign}/gallery/{image}/delete', [DeleteController::class, 'file'])->name('gallery.file.delete'); Route::post('/w/{campaign}/gallery/{image}/update-focus', [App\Http\Controllers\Gallery\UpdateController::class, 'focus'])->name('gallery.file.update-focus'); Route::get('/w/{campaign}/gallery/{image}/visibility', [VisibilityController::class, 'index'])->name('gallery.file.visibility'); Route::patch('/w/{campaign}/gallery/{image}/visibility', [VisibilityController::class, 'save'])->name('gallery.file.visibility-save'); // Campaign Route::get('/w/{campaign}/editing-warning', [EditingController::class, 'index'])->name('campaign.editing-warning'); Route::post('/w/{campaign}/editing/confirm-editing', 'EditingController@confirmCampaign')->name('campaigns.confirm-editing'); Route::post('/w/{campaign}/editing/keep-alive', 'EditingController@keepAliveCampaign')->name('campaigns.keep-alive'); // Permission save Route::post('/w/{campaign}/campaign_roles/{campaign_role}/savePermissions', 'Campaign\RoleController@savePermissions')->name('campaign_roles.savePermissions'); Route::post('/w/{campaign}/campaign_roles/{campaign_role}/toggle/{entityType}/{action}', 'Campaign\RoleController@toggle')->name('campaign_roles.toggle'); Route::post('/w/{campaign}/campaign_roles/bulk', 'Campaign\RoleController@bulk')->name('campaign_roles.bulk'); // Impersonator Route::get('/w/{campaign}/members/switch/{campaign_user}', 'Campaign\MemberController@switch')->name('identity.switch'); Route::get('/w/{campaign}/members/back', 'Campaign\MemberController@back')->name('identity.back'); Route::get('/w/{campaign}/members/switch/{campaign_user}/{entity}', 'Campaign\MemberController@switch')->name('identity.switch-entity'); Route::get('/w/{campaign}/campaign_users/{campaign_user}/delete', [MemberController::class, 'delete'])->name('campaign_users.delete'); Route::get('/w/{campaign}/campaign_user_roles/{campaign_user}', [RoleController::class, 'index'])->name('campaign.members.roles'); Route::post('/w/{campaign}/campaign_user_roles/{campaign_user}', [RoleController::class, 'save'])->name('campaign_users.update-roles'); // Recovery Route::get('/w/{campaign}/recovery', 'Campaign\RecoveryController@index')->name('recovery'); Route::post('/w/{campaign}/recovery', 'Campaign\RecoveryController@recover')->name('recovery.save'); Route::get('/w/{campaign}/recovery-setup', 'Campaign\RecoveryController@setup')->name('recovery.setup'); // Stats Route::get('/w/{campaign}/achievements', 'Campaign\AchievementController@index')->name('campaign.achievements'); // User search Route::get('/w/{campaign}/users/search', 'Campaign\UserController@search')->name('users.find'); Route::get('/w/{campaign}/roles/search', 'Campaign\RoleController@search')->name('roles.find'); Route::get('/w/{campaign}/placeholder-images', 'Campaign\DefaultImageController@index') ->name('campaign.default-images'); Route::get('/w/{campaign}/placeholder-images/create', 'Campaign\DefaultImageController@create') ->name('campaign.default-images.create'); Route::post('/w/{campaign}/placeholder-images/create', 'Campaign\DefaultImageController@store') ->name('campaign.default-images.store'); Route::delete('/w/{campaign}/placeholder-images', 'Campaign\DefaultImageController@destroy') ->name('campaign.default-images.delete'); Route::delete('/w/{campaign}/placeholder-images/reset', 'Campaign\DefaultImageController@reset') ->name('campaign.default-images.reset'); Route::resources([ '/w/{campaign}/campaign_users' => 'Campaign\UserController', '/w/{campaign}/applications' => 'Campaign\ApplicationController', // Permission manager '/w/{campaign}/campaign_roles' => 'Campaign\RoleController', '/w/{campaign}/campaign_roles.campaign_role_users' => 'Campaign\RoleUserController', '/w/{campaign}/campaign_styles' => 'Campaign\StyleController', '/w/{campaign}/campaign_invites' => 'Campaign\InviteController', '/w/{campaign}/campaign_dashboards' => 'Campaign\DashboardController', '/w/{campaign}/campaign_dashboard_widgets' => 'Campaign\DashboardWidgetController', '/w/{campaign}/preset_types.presets' => 'PresetController', '/w/{campaign}/webhooks' => 'Campaign\WebhookController', '/w/{campaign}/entity_types' => 'Campaign\EntityTypeController', ]); Route::get('/w/{campaign}/leave', 'Campaign\LeaveController@index')->name('campaign.leave'); Route::post('/w/{campaign}/leave-for-real', 'Campaign\LeaveController@process')->name('campaign.leave-process'); // Campaign CRUD Route::get('/w/{campaign}/edit', [CampaignController::class, 'edit'])->name('campaigns.edit'); Route::patch('/w/{campaign}/update', [CampaignController::class, 'update'])->name('campaigns.update'); Route::post('/w/{campaign}/campaign_styles/bulk', 'Campaign\StyleController@bulk')->name('campaign_styles.bulk'); Route::get('/w/{campaign}/campaign_styles/{campaign_style}/toggle', [StyleController::class, 'toggle'])->name('campaign_styles.toggle'); Route::post('/w/{campaign}/campaign_styles/reorder', 'Campaign\StyleController@reorder')->name('campaign_styles.reorder-save'); Route::get('/w/{campaign}/theme-builder', [ThemeBuilderController::class, 'index'])->name('campaign_styles.builder'); Route::post('/w/{campaign}/theme-builder', [ThemeBuilderController::class, 'save'])->name('campaign_styles.builder-save'); Route::delete('/w/{campaign}/theme-builder', [ThemeBuilderController::class, 'reset'])->name('campaign_styles.builder-reset'); Route::get('/w/{campaign}/dashboard-header/{campaignDashboardWidget?}', 'Campaign\DashboardHeaderController@edit')->name('campaigns.dashboard-header.edit'); Route::patch('/w/{campaign}/dashboard-header', 'Campaign\DashboardHeaderController@update')->name('campaigns.dashboard-header.update'); // Helper links Route::get('/w/{campaign}/campaign-roles/admin', 'Campaign\RoleController@admin')->name('campaigns.campaign_roles.admin'); Route::get('/w/{campaign}/campaign-roles/public', 'Campaign\RoleController@public')->name('campaigns.campaign_roles.public'); Route::get('/w/{campaign}/campaign-roles/{campaign_role}/duplicate', 'Campaign\RoleController@duplicate')->name('campaign_roles.duplicate'); // Marketplace plugin route if (config('marketplace.enabled')) { Route::get('/w/{campaign}/plugins', 'Campaign\PluginController@index')->name('campaign_plugins.index'); Route::delete('/w/{campaign}/plugins/{plugin}/delete', 'Campaign\PluginController@delete')->name('campaign_plugins.destroy'); Route::get('/w/{campaign}/plugins/{plugin}/enable', [ToggleController::class, 'enable'])->name('campaign_plugins.enable'); Route::get('/w/{campaign}/plugins/{plugin}/disable', [ToggleController::class, 'disable'])->name('campaign_plugins.disable'); Route::get('/w/{campaign}/plugins/{plugin}/confirm-import', [ImportController::class, 'index'])->name('campaign_plugins.confirm-import'); Route::post('/w/{campaign}/plugins/{plugin}/import', [ImportController::class, 'process'])->name('campaign_plugins.import'); Route::get('/w/{campaign}/plugins/{plugin}/update', [UpdateController::class, 'index'])->name('campaign_plugins.update-info'); Route::post('/w/{campaign}/plugins/{plugin}/update', [UpdateController::class, 'update'])->name('campaign_plugins.update'); Route::post('/w/{campaign}/plugins/bulk', [BulkController::class, 'index'])->name('campaign_plugins.bulk'); } // Campaign Dashboard Widgets Route::get('/w/{campaign}/dashboard-setup', [App\Http\Controllers\Dashboards\SetupController::class, 'index'])->name('dashboard.setup'); Route::post('/w/{campaign}/dashboard-setup/reorder', [App\Http\Controllers\Dashboards\SetupController::class, 'save'])->name('dashboard.reorder'); Route::get('/w/{campaign}/dashboard/widgets/recent/{id}', 'DashboardController@recent')->name('dashboard.recent'); Route::get('/w/{campaign}/dashboard/widgets/unmentioned/{id}', 'DashboardController@unmentioned')->name('dashboard.unmentioned'); Route::post('/w/{campaign}/dashboard/widgets/calendar/{campaignDashboardWidget}/add', [CalendarWidgetController::class, 'add'])->name('dashboard.calendar.add'); Route::post('/w/{campaign}/dashboard/widgets/calendar/{campaignDashboardWidget}/sub', [CalendarWidgetController::class, 'sub'])->name('dashboard.calendar.sub'); Route::get('/w/{campaign}/dashboard/widgets/{campaignDashboardWidget}/render', [CalendarWidgetController::class, 'render'])->name('dashboard.calendar.render'); // The campaign management subpages Route::get('/w/{campaign}/overview', 'Crud\CampaignController@show')->name('overview'); Route::get('/w/{campaign}/modules', 'Campaign\ModuleController@index')->name('campaign.modules'); Route::post('/w/{campaign}/modules/toggle/{entity_type}', [ModuleController::class, 'toggle'])->name('campaign.modules.toggle'); Route::post('/w/{campaign}/features/toggle/{module}', [ModuleController::class, 'toggleFeature'])->name('campaign.features.toggle'); // Route::get('/w/{campaign}/entity_types/create', [\App\Http\Controllers\Campaign\EntityTypeController::class, 'create'])->name('campaign.entity_types.create'); // Route::post('/w/{campaign}/entity_types/create', [\App\Http\Controllers\Campaign\EntityTypeController::class, 'store'])->name('campaign.entity_types.store'); // // Route::get('/w/{campaign}/entity_types/{entity_type}/edit', [\App\Http\Controllers\Campaign\EntityTypeController::class, 'edit'])->name('campaign.entity_types.edit'); // Route::patch('/w/{campaign}/entity_types/{entity_type}/update', [\App\Http\Controllers\Campaign\EntityTypeController::class, 'update'])->name('campaign.entity_types.update'); Route::post('/w/{campaign}/entity_types/{entity_type}/toggle', [EntityTypeController::class, 'toggle'])->name('entity_types.toggle'); // Route::delete('/w/{campaign}/entity_types/{entity_type}/delete', [\App\Http\Controllers\Campaign\EntityTypeController::class, 'delete'])->name('campaign.entity_types.destroy'); Route::get('/w/{campaign}/entity_types/{entity_type}/confirm', [EntityTypeController::class, 'confirm'])->name('entity_types.confirm'); Route::get('/w/{campaign}/campaign-theme', 'Campaign\StyleController@theme')->name('campaign-theme'); Route::post('/w/{campaign}/campaign-theme', 'Campaign\StyleController@themeSave')->name('campaign-theme.save'); Route::get('/w/{campaign}/campaign-export', 'Campaign\ExportController@index')->name('campaign.export'); Route::post('/w/{campaign}/campaign-export', 'Campaign\ExportController@export')->name('campaign.export-process'); Route::get('/w/{campaign}/campaign-import', 'Campaign\ImportController@index')->name('campaign.import'); Route::post('/w/{campaign}/campaign-import', 'Campaign\ImportController@store')->name('campaign.import-process'); Route::get('/w/{campaign}/campaign-import/{campaign_import}/csv', 'Campaign\ImportController@csv')->name('campaign.import.csv'); Route::get('/w/{campaign}/campaign-{ts}.styles', [CssController::class, 'index'])->name('campaign.css'); Route::get('/w/{campaign}/campaign_plugin-{ts}.styles', 'Campaign\Plugins\CssController@index')->name('campaign_plugins.css'); Route::get('/w/{campaign}/campaign-visibility', 'Campaign\VisibilityController@edit')->name('campaign-visibility'); Route::post('/w/{campaign}/campaign-visibility', 'Campaign\VisibilityController@save')->name('campaign-visibility.save'); Route::get('/w/{campaign}/share', [ShareController::class, 'setup'])->name('campaign.share.setup'); Route::post('/w/{campaign}/share', [ShareController::class, 'save'])->name('campaign.share.save'); Route::get('/w/{campaign}/modules/{entity_type}/edit', [ModuleController::class, 'edit'])->name('modules.edit'); Route::patch('/w/{campaign}/modules/{entity_type}/update', [ModuleController::class, 'update'])->name('modules.update'); Route::delete('/w/{campaign}/modules/reset', [ModuleController::class, 'reset'])->name('modules.reset'); Route::get('/w/{campaign}/campaign-applications', 'Campaign\ApplicationController@toggle')->name('campaign-applications'); Route::get('/w/{campaign}/campaign-applications/setup', 'Campaign\ApplicationSetupController@setup')->name('campaign-applications.setup'); Route::post('/w/{campaign}/campaign-applications/setup', 'Campaign\ApplicationSetupController@saveSetup')->name('campaign-applications.setup.save'); Route::get('/w/{campaign}/campaign-applications/dashboard-widget', 'Campaign\ApplicationDashboardController@index')->name('campaign-applications.dashboard-widget'); Route::post('/w/{campaign}/campaign-applications/dashboard-widget', 'Campaign\ApplicationDashboardController@store')->name('campaign-applications.dashboard-widget.store'); Route::post('/w/{campaign}/campaign-applications', 'Campaign\ApplicationController@toggleSave')->name('campaign-applications.save'); // Campaign sidebar setup Route::get('/w/{campaign}/sidebar-setup', 'Campaign\SidebarController@index')->name('campaign-sidebar'); Route::post('/w/{campaign}/sidebar-setup', 'Campaign\SidebarController@save')->name('campaign-sidebar-save'); Route::delete('/w/{campaign}/sidebar-setup/reset', 'Campaign\SidebarController@reset')->name('campaign-sidebar-reset'); Route::get('/w/{campaign}/campaign-defaults', 'Campaign\DefaultsController@index')->name('campaign-defaults'); Route::post('/w/{campaign}/campaign-defaults', 'Campaign\DefaultsController@save')->name('campaign-defaults-save'); Route::get('/w/{campaign}/presets/type/{preset_type}/list', [PresetController::class, 'presets'])->name('presets.list'); Route::get('/w/{campaign}/presets/type/{preset_type}/create', [PresetController::class, 'create'])->name('presets.create'); Route::post('/w/{campaign}/presets/type/{preset_type}/store', [PresetController::class, 'store'])->name('presets.store'); Route::post('/w/{campaign}/presets/{preset}/load', [PresetController::class, 'load'])->name('presets.show'); Route::model('preset_type', PresetType::class); Route::get('/w/{campaign}/history', [HistoryController::class, 'index'])->name('history.index'); Route::get('/w/{campaign}/bragi', [BragiController::class, 'index'])->name('bragi'); Route::post('/w/{campaign}/bragi', [BragiController::class, 'generate'])->name('bragi.generate'); Route::get('/w/{campaign}/confirm-delete', [ConfirmController::class, 'index'])->name('confirm-delete'); Route::post('/w/{campaign}/vanity-validate', [VanityController::class, 'index'])->name('campaign.vanity-validate'); // Permission save Route::get('/w/{campaign}/webhooks/{webhook}/toggle', [WebhookController::class, 'toggle'])->name('webhooks.toggle'); Route::get('/w/{campaign}/webhooks/{webhook}/status', [WebhookController::class, 'status'])->name('webhooks.status'); Route::post('/w/{campaign}/webhooks/bulk', [WebhookController::class, 'bulk'])->name('webhooks.bulk'); Route::get('/w/{campaign}/webhooks/{webhook}/test', [WebhookController::class, 'test'])->name('webhooks.test'); Route::get('/w/{campaign}/attributes/api/type/{entity_type}', [ApiController::class, 'index'])->name('attributes.api'); Route::get('/w/{campaign}/attributes/api/entity/{entity}', [ApiController::class, 'entity'])->name('attributes.api-entity'); Route::get('/w/{campaign}/templates/load', [LoadController::class, 'index'])->name('templates.load-attributes'); Route::get('/w/{campaign}/deletion', [App\Http\Controllers\Campaign\DeleteController::class, 'show'])->name('campaign.delete'); Route::delete('/w/{campaign}/destroy', [App\Http\Controllers\Campaign\DeleteController::class, 'destroy'])->name('campaigns.destroy'); Route::get('/w/{campaign}/sidebar/image', [App\Http\Controllers\Campaign\ImageController::class, 'index'])->name('campaign.sidebar.image'); Route::post('/w/{campaign}/sidebar/image', [App\Http\Controllers\Campaign\ImageController::class, 'save'])->name('campaign.sidebar.image-save'); Route::get('/w/{campaign}/stats', [StatController::class, 'index'])->name('campaign.stats'); Route::get('/w/{campaign}/logs', [LogController::class, 'index'])->name('campaign.logs'); Route::post('/w/{campaign}/onboarding/initial', [ InitialController::class, 'save', ])->name('campaign.onboarding.initial'); Route::post('/w/{campaign}/onboarding/initial-skip', [ InitialController::class, 'skip', ])->name('campaign.onboarding.initial-skip'); Route::get('/w/{campaign}/widgets/getting-started', [ GettingStartedController::class, 'index', ])->name('campaign.widgets.getting-started'); Route::get('/w/{campaign}/connections/web', [WebController::class, 'index'])->name('connections.web'); Route::get('/w/{campaign}/connections/web/api', [WebController::class, 'api'])->name('connections.web.api'); ================================================ FILE: routes/campaigns/entities.php ================================================ name('entities.show')->where(['entity' => '[0-9]+']); Route::get('/w/{campaign}/entities/{entity}-{slug}', [ShowController::class, 'index'])->name('entities.show-slug'); Route::get('/w/{campaign}/t/{entityType}', [IndexController::class, 'index'])->name('entities.index'); Route::get('/w/{campaign}/t/{entityType}/api', [IndexController::class, 'api'])->name('entities.index-api'); Route::patch('/w/{campaign}/t/{entityType}/preferences', [ListingPreferenceController::class, 'update']) ->name('entities.listing-preferences.update'); Route::delete('/w/{campaign}/t/{entityType}/preferences', [ListingPreferenceController::class, 'destroy']) ->name('entities.listing-preferences.destroy'); Route::get('/w/{campaign}/t/{entityType}/create', [CreateController::class, 'index'])->name('entities.create'); Route::post('/w/{campaign}/t/{entity_type}/create', [CreateController::class, 'store'])->name('entities.store'); Route::get('/w/{campaign}/entities/{entity}/edit', [EditController::class, 'index'])->name('entities.edit'); Route::patch('/w/{campaign}/entities/{entity}/save', [EditController::class, 'save'])->name('entities.update'); Route::delete('/w/{campaign}/entities/{entity}/delete', [DeleteController::class, 'index'])->name('entities.destroy'); Route::get('/w/{campaign}/entities/{entity}/children', [ChildrenController::class, 'index'])->name('entities.children'); // Abilities Route::get('/w/{campaign}/abilities/{ability}/abilities', 'Abilities\AbilityController@index')->name('abilities.abilities'); Route::get('/w/{campaign}/abilities/{ability}/entities', 'Abilities\EntityController@index')->name('abilities.entities'); Route::get('/w/{campaign}/abilities/{ability}/entity-add', 'Abilities\EntityController@create')->name('abilities.entity-add'); Route::post('/w/{campaign}/abilities/{ability}/entity-add', 'Abilities\EntityController@store')->name('abilities.entity-add.save'); // Ability reorder Route::get('/w/{campaign}/entities/{entity}/entity_abilities/reorder', [App\Http\Controllers\Entity\Abilities\ReorderController::class, 'index']) ->name('entities.entity_abilities.reorder'); Route::post('/w/{campaign}/entities/{entity}/entity_abilities/reorder', [App\Http\Controllers\Entity\Abilities\ReorderController::class, 'save']) ->name('entities.entity_abilities.reorder-save'); // Maps Route::get('/w/{campaign}/maps/{map}/maps', 'Maps\MapController@index')->name('maps.maps'); Route::get('/w/{campaign}/maps/{map}/explore', 'Maps\ExploreController@index')->name('maps.explore'); Route::get('/w/{campaign}/maps/{map}/chunks/', 'Maps\ExploreController@chunks')->name('maps.chunks'); Route::get('/w/{campaign}/maps/{map}/ticker', 'Maps\ExploreController@ticker')->name('maps.ticker'); Route::get('/w/{campaign}/maps/{map}/preview', 'Maps\PreviewController@index')->name('maps.preview'); Route::get('/w/{campaign}/maps/{map}/{map_marker}/details', 'Maps\Markers\DetailController@index')->name('maps.markers.details'); Route::post('/w/{campaign}/maps/{map}/{map_marker}/move', 'Maps\Markers\MoveController@index')->name('maps.markers.move'); Route::post('/w/{campaign}/maps/{map}/groups/bulk', 'Maps\Bulks\GroupController@index')->name('maps.groups.bulk'); Route::post('/w/{campaign}/maps/{map}/groups/reorder', 'Maps\Reorders\GroupController@index')->name('maps.groups.reorder-save'); Route::post('/w/{campaign}/maps/{map}/layers/bulk', 'Maps\Bulks\LayerController@index')->name('maps.layers.bulk'); Route::post('/w/{campaign}/maps/{map}/layers/reorder', 'Maps\Reorders\LayerController@index')->name('maps.layers.reorder-save'); Route::post('/w/{campaign}/maps/{map}/layers/{map_layer}/migrate', 'Maps\Layers\MigrateController@index')->name('maps.layers.migrate'); Route::post('/w/{campaign}/maps/{map}/markers/bulk', 'Maps\Bulks\MarkerController@index')->name('maps.markers.bulk'); // Character Route::get('/w/{campaign}/characters/{character}/organisations', [MemberController::class, 'index'])->name('characters.organisations'); Route::get('/w/{campaign}/characters/{character}/races/management', 'Characters\Races\ManagementController@index')->name('characters.races.management'); Route::post('/w/{campaign}/characters/{character}/races/save', 'Characters\Races\ManagementController@save')->name('characters.races.save'); Route::get('/w/{campaign}/characters/{character}/families/management', 'Characters\Families\ManagementController@index')->name('characters.families.management'); Route::post('/w/{campaign}/characters/{character}/families/save', 'Characters\Families\ManagementController@save')->name('characters.families.save'); Route::get('/w/{campaign}/dice_rolls/{dice_roll}/roll', 'Crud\DiceRollController@roll')->name('dice_rolls.roll'); Route::delete('/w/{campaign}/dice_rolls/{dice_roll}/roll/{dice_roll_result}/destroy', 'Crud\DiceRollController@destroyRoll')->name('dice_rolls.destroy_roll'); Route::get('/w/{campaign}/dice_rolls/results', [ResultsController::class, 'index'])->name('dice_rolls.results'); // Locations Route::get('/w/{campaign}/locations/{location}/characters', 'Locations\CharacterController@index')->name('locations.characters'); Route::get('/w/{campaign}/locations/{location}/locations', 'Locations\LocationController@index')->name('locations.locations'); Route::get('/w/{campaign}/locations/{location}/events', 'Locations\EventController@index')->name('locations.events'); Route::get('/w/{campaign}/locations/{location}/quests', 'Locations\QuestController@index')->name('locations.quests'); // Organisation menu Route::get('/w/{campaign}/organisations/{organisation}/members', 'Organisation\MemberController@index')->name('organisations.members'); Route::get('/w/{campaign}/organisations/{organisation}/organisations', 'Organisation\OrganisationController@organisations')->name('organisations.organisations'); // Families menu Route::get('/w/{campaign}/families/{family}/members', 'Families\MemberController@index')->name('families.members'); Route::get('/w/{campaign}/families/{family}/families', 'Families\FamilyController@index')->name('families.families'); Route::get('/w/{campaign}/families/{family}/tree', [TreeController::class, 'index'])->name('families.family-tree'); Route::get('/w/{campaign}/families/{family}/tree/api', [App\Http\Controllers\Families\Trees\ApiController::class, 'index'])->name('families.family-tree.api'); Route::get('/w/{campaign}/families/{entity}/tree/entity-api', [App\Http\Controllers\Families\Trees\ApiController::class, 'entity'])->name('families.family-tree.entity-api'); Route::post('/w/{campaign}/families/{family}/tree/api', [App\Http\Controllers\Families\Trees\ApiController::class, 'save'])->name('families.family-tree.api-save'); Route::post('/w/{campaign}/families/{family}/store-member', 'Families\MemberController@store')->name('families.members.store'); Route::get('/w/{campaign}/families/{family}/add-member', 'Families\MemberController@create')->name('families.members.create'); // Items menu Route::get('/w/{campaign}/items/{item}/inventories', 'Items\EntityController@index')->name('items.inventories'); Route::get('/w/{campaign}/items/{item}/items', 'Items\ItemController@index')->name('items.items'); // Quest menus Route::get('/w/{campaign}/quests/{quest}/quests', 'Quests\QuestController@index')->name('quests.quests'); // Notes menus Route::get('/w/{campaign}/notes/{note}/notes', 'Notes\NoteController@index')->name('notes.notes'); // Races Route::get('/w/{campaign}/races/{race}/characters', 'Races\MemberController@index')->name('races.characters'); Route::get('/w/{campaign}/races/{race}/races', 'Races\RaceController@index')->name('races.races'); Route::get('/w/{campaign}/races/{race}/member', 'Races\MemberController@create')->name('races.members.create'); Route::post('/w/{campaign}/races/{race}/member', 'Races\MemberController@store')->name('races.members.store'); // Creatures Route::get('/w/{campaign}/creatures/{creature}/creatures', 'Creatures\CreatureController@index')->name('creatures.creatures'); // Journal Route::get('/w/{campaign}/journals/{journal}/journals', 'Journals\JournalController@index')->name('journals.journals'); Route::get('/w/{campaign}/events/{event}/events', 'Events\EventController@index')->name('events.events'); Route::get('/w/{campaign}/timelines/{timeline}/timelines', 'Timelines\TimelineController@index')->name('timelines.timelines'); // Tag menus Route::get('/w/{campaign}/tags/{tag}/tags', 'Tags\TagController@index')->name('tags.tags'); Route::get('/w/{campaign}/tags/{tag}/transfer', 'Tags\TransferController@index')->name('tags.transfer'); Route::get('/w/{campaign}/tags/{tag}/transfer-posts', 'Tags\TransferController@postIndex')->name('tags.transfer.posts'); Route::post('/w/{campaign}/tags/{tag}/transfer', 'Tags\TransferController@process')->name('tags.transfer-process'); Route::post('/w/{campaign}/tags/{tag}/transfer-posts', 'Tags\TransferController@processPosts')->name('tags.transfer.posts-process'); // Tags Quick Add Route::get('/w/{campaign}/tags/{tag}/children', 'Tags\ChildController@index')->name('tags.children'); Route::get('/w/{campaign}/tags/{tag}/posts', 'Tags\PostController@index')->name('tags.posts'); Route::get('/w/{campaign}/tags/{tag}/entity-add', 'Tags\ChildController@create')->name('tags.entity-add'); Route::post('/w/{campaign}/tags/{tag}/entity-add', 'Tags\ChildController@store')->name('tags.entity-add.save'); Route::get('/w/{campaign}/entities/{entity}/tags/add', 'Entity\TagController@create')->name('entity.tags-add'); Route::post('/w/{campaign}/entities/{entity}/tags/add', 'Entity\TagController@store')->name('entity.tags-add.save'); // Multi-delete for cruds Route::post('/w/{campaign}/bulk/process', 'BulkController@index')->name('bulk.process'); Route::get('/w/{campaign}/bulk/modal', 'BulkController@modal')->name('bulk.modal'); // Calendar Route::get('/w/{campaign}/calendars/{calendar}/event', 'Calendars\EventController@create')->name('calendars.event.create'); Route::post('/w/{campaign}/calendars/{calendar}/event', 'Calendars\EventController@store')->name('calendars.event.store'); Route::get('/w/{campaign}/calendars/{calendar}/month-list', 'Crud\CalendarController@monthList')->name('calendars.month-list'); Route::get('/w/{campaign}/calendars/{calendar}/events', 'Calendars\EventController@index')->name('calendars.events'); Route::get('/w/{campaign}/calendars/{calendar}/today', 'Crud\CalendarController@today')->name('calendars.today'); Route::get('/w/{campaign}/calendars/{calendar}/validate-length', [EventController::class, 'eventLength'])->name('calendars.event-length'); Route::post('/w/{campaign}/calendars/{calendar}/calendar-events/bulk', 'Calendars\Bulks\EntityEventController@index')->name('calendars.entity-events.bulk'); // Route::get('/w/{campaign}/calendars/{calendar}/weather', 'Calendar\CalendarWeatherController@form')->name('calendars.weather.create'); // Route::post('/w/{campaign}/calendars/{calendar}/weather', 'Calendar\CalendarWeatherController@store')->name('calendars.weather.store'); // Attribute multi-save Route::get('/w/{campaign}/entities/{entity}/attributes', [AttributeController::class, 'index'])->name('entities.attributes'); Route::get('/w/{campaign}/entities/{entity}/attributes-dashboard', [AttributeController::class, 'dashboard'])->name('entities.attributes-dashboard'); Route::get('/w/{campaign}/entities/{entity}/attributes/edit', [AttributeController::class, 'edit'])->name('entities.attributes.edit'); Route::post('/w/{campaign}/entities/{entity}/attributes/save', [AttributeController::class, 'save'])->name('entities.attributes.save'); Route::get('/w/{campaign}/entities/{entity}/attributes/live-edit/', [AttributeController::class, 'liveEdit']) ->name('entities.attributes.live.edit'); Route::get('/w/{campaign}/entities/{entity}/attributes/live-edit/{attribute}', [LiveController::class, 'index']) ->name('entities.attributes.live.edit2'); Route::post('/w/{campaign}/entities/{entity}/attributes/live-edit/{attribute}/save', [LiveController::class, 'save']) ->name('entities.attributes.live.save'); Route::get('/w/{campaign}/entities/{entity}/attributes/live-api', [LiveApiController::class, 'index']) ->name('entities.attributes.live-api.index'); Route::post('/w/{campaign}/entities/{entity}/attributes/live-api', [LiveApiController::class, 'store']) ->name('entities.attributes.live-api.create'); Route::post('/w/{campaign}/entities/{entity}/attributes/live-api/{attribute}', [LiveApiController::class, 'update']) ->name('entities.attributes.live-api.update'); Route::post('/w/{campaign}/entities/{entity}/attributes/live-api/{attribute}/delete', [LiveApiController::class, 'destroy']) ->name('entities.attributes.live-api.delete'); Route::model('attribute', Attribute::class); Route::get('/w/{campaign}/entities/{entity}/story-reorder', [StoryController::class, 'edit'])->name('entities.story.reorder'); Route::post('/w/{campaign}/entities/{entity}/story-reorder', [StoryController::class, 'save'])->name('entities.story.reorder-save'); Route::get('/w/{campaign}/entities/{entity}/story-more', [StoryController::class, 'more'])->name('entities.story.load-more'); // Image of entities Route::get('/w/{campaign}/entities/{entity}/image-focus', [ImageController::class, 'focus'])->name('entities.image.focus'); Route::post('/w/{campaign}/entities/{entity}/image-focus', [ImageController::class, 'saveFocus'])->name('entities.image.save-focus'); Route::get('/w/{campaign}/entities/{entity}/image-replace', [ImageController::class, 'replace'])->name('entities.image.replace'); Route::post('/w/{campaign}/entities/{entity}/image-replace', [ImageController::class, 'update'])->name('entities.image.replace.save'); // Quick privacy toggle Route::get('/w/{campaign}/entities/{entity}/privacy', [PrivacyController::class, 'index'])->name('entities.quick-privacy'); Route::post('/w/{campaign}/entities/{entity}/privacy', [PrivacyController::class, 'toggle'])->name('entities.quick-privacy.toggle'); // Route::post('/w/{campaign}/entities/{entity}/toggle-privacy', [\App\Http\Controllers\Entity\PrivacyController::class, 'toggle'])->name('entities.privacy.toggle'); // Entity update entry Route::get('/w/{campaign}/entities/{entity}/entry', [EntryController::class, 'edit'])->name('entities.entry.edit'); Route::patch('w/{campaign}/entities/{entity}/entry', [EntryController::class, 'update'])->name('entities.entry.update'); Route::get('/w/{campaign}/entities/{entity}/connection/map', 'Entity\Connections\MapController@index')->name('entities.relations_map'); Route::get('/w/{campaign}/entities/{entity}/connection/table', 'Entity\Connections\TableController@index')->name('entities.relations_table'); // Entity Route::post('/w/{campaign}/entities/{entity}/confirm-editing', 'EditingController@confirm')->name('entities.confirm-editing'); Route::post('/w/{campaign}/entities/{entity}/keep-alive', 'EditingController@keepAlive')->name('entities.keep-alive'); // Posts Route::post('/w/{campaign}/editing/posts/{entity}/{post}/confirm-editing', 'EditingController@confirmPost')->name('posts.confirm-editing'); Route::post('/w/{campaign}/editing/posts/{entity}/{post}/keep-alive', 'EditingController@keepAlivePost')->name('posts.keep-alive'); Route::get('/w/{campaign}/posts/{entity}/{post}/visibility', [VisibilityController::class, 'index'])->name('posts.edit.visibility'); Route::post('/w/{campaign}/posts/{entity}/{post}/visibility/update', [VisibilityController::class, 'update'])->name('posts.update.visibility'); // Quest Elements Route::post('/w/{campaign}/editing/quest-elements/{quest_element}/confirm-editing', 'EditingController@confirmQuestElement')->name('quest-elements.confirm-editing'); Route::post('/w/{campaign}/editing/quest-elements/{quest_element}/keep-alive', 'EditingController@keepAliveQuestElement')->name('quest-elements.keep-alive'); // Timeline Elements Route::post('/w/{campaign}/editing/timeline-elements/{timeline_element}/confirm-editing', 'EditingController@confirmTimelineElement')->name('timeline-elements.confirm-editing'); Route::post('/w/{campaign}/editing/timeline-elements/{timeline_element}/keep-alive', 'EditingController@keepAliveTimelineElement')->name('timeline-elements.keep-alive'); Route::get('/w/{campaign}/timeline/{timeline}/era/{timeline_era}/list', 'Timelines\TimelineEraController@positionList')->name('timelines.era-list'); Route::get('/w/{campaign}/bookmarks/{bookmark}/random', 'Bookmarks\RandomController@index') ->name('bookmarks.random'); Route::get('/w/{campaign}/timelines/{timeline}/reorder', [TimelineReorderController::class, 'index']) ->name('timelines.reorder'); Route::post('/w/{campaign}/timelines/{timeline}/reorder', [TimelineReorderController::class, 'save']) ->name('timelines.reorder-save'); Route::post('/w/{campaign}/timelines/{timeline}/eras/bulk', 'Timelines\TimelineEraController@bulk')->name('timelines.eras.bulk'); Route::get('/w/{campaign}/bookmarks/reorder', [ReorderController::class, 'index']) ->name('bookmarks.reorder'); Route::post('/w/{campaign}/bookmarks/reorder', [ReorderController::class, 'save']) ->name('bookmarks.reorder-save'); Route::get('/w/{campaign}/entity_types/{entity_type}/bookmark-form', [SaveController::class, 'render'])->name('filters.modal_form'); // Entity Abilities API Route::get('/w/{campaign}/entities/{entity}/abilities', 'Entity\AbilityController@index')->name('entities.abilities'); Route::get('/w/{campaign}/entities/{entity}/entity_abilities/api', 'Entity\Abilities\ApiController@index')->name('entities.entity_abilities.api'); Route::get('/w/{campaign}/entities/{entity}/entity_abilities/import', 'Entity\Abilities\ImportController@import')->name('entities.entity_abilities.import'); Route::get('/w/{campaign}/entities/{entity}/entity_abilities/import-confirm', 'Entity\Abilities\ImportController@index')->name('entities.entity_abilities.import.confirm'); Route::post('/w/{campaign}/entities/{entity}/entity_abilities/{entity_ability}/use', 'Entity\Abilities\ChargeController@use')->name('entities.entity_abilities.use'); Route::get('/w/{campaign}/entities/{entity}/entity_abilities/reset', 'Entity\Abilities\ChargeController@reset')->name('entities.entity_abilities.reset'); Route::get('/w/{campaign}/entities/{entity}/entity_assets/{entity_asset}/go', 'Entity\AssetController@go')->name('entities.entity_assets.go'); Route::get('/w/{campaign}/entities/{entity}/profile', 'Entity\ProfileController@index') ->name('entities.profile'); Route::get('/w/{campaign}/entity_types/{entity_type}/filter-form', [FormController::class, 'index'])->name('filters.form'); Route::get('/w/{campaign}/connection/filter-form', [FormController::class, 'connection'])->name('filters.form-connection'); Route::get('/w/{campaign}/filters/{entity_type}/save', [SaveController::class, 'save'])->name('save-filters'); // Redirect standard entity type index routes to the unified listing $standardEntityTypes = [ 'abilities', 'calendars', 'characters', 'creatures', 'events', 'families', 'items', 'journals', 'locations', 'maps', 'notes', 'organisations', 'quests', 'races', 'tags', 'timelines', ]; foreach ($standardEntityTypes as $entityTypeCode) { Route::get("/w/{campaign}/{$entityTypeCode}", function (Campaign $campaign) use ($entityTypeCode) { $entityType = EntityType::where('code', $entityTypeCode)->first(); if ($entityType) { return redirect()->route('entities.index', array_merge( [$campaign, $entityType], request()->query() )); } abort(404); })->name("{$entityTypeCode}.index"); } // Route::get('/w/{campaign}/my-campaigns', 'CampaignController@index')->name('campaign'); Route::resources([ '/w/{campaign}/abilities' => 'Crud\AbilityController', '/w/{campaign}/calendars' => 'Crud\CalendarController', '/w/{campaign}/calendars.calendar_weather' => 'Calendar\CalendarWeatherController', '/w/{campaign}/characters' => 'Crud\CharacterController', '/w/{campaign}/characters.character_organisations' => 'Characters\MembershipController', '/w/{campaign}/conversations' => 'Crud\ConversationController', '/w/{campaign}/conversations.conversation_participants' => 'ConversationParticipantController', '/w/{campaign}/conversations.conversation_messages' => 'ConversationMessageController', '/w/{campaign}/dice_rolls' => 'Crud\DiceRollController', '/w/{campaign}/events' => 'Crud\EventController', '/w/{campaign}/locations' => 'Crud\LocationController', '/w/{campaign}/families' => 'Crud\FamilyController', '/w/{campaign}/items' => 'Crud\ItemController', '/w/{campaign}/journals' => 'Crud\JournalController', '/w/{campaign}/maps' => 'Crud\MapController', '/w/{campaign}/maps.map_layers' => 'Maps\LayerController', '/w/{campaign}/maps.map_groups' => 'Maps\GroupController', '/w/{campaign}/maps.map_markers' => 'Maps\MarkerController', '/w/{campaign}/bookmarks' => 'Crud\BookmarkController', '/w/{campaign}/organisations' => 'Crud\OrganisationController', '/w/{campaign}/organisations.organisation_members' => 'Organisation\MemberController', '/w/{campaign}/notes' => 'Crud\NoteController', '/w/{campaign}/quests' => 'Crud\QuestController', '/w/{campaign}/quests.quest_elements' => 'Quests\ElementController', '/w/{campaign}/tags' => 'Crud\TagController', '/w/{campaign}/timelines' => 'Crud\TimelineController', '/w/{campaign}/timelines.timeline_eras' => 'Timelines\TimelineEraController', '/w/{campaign}/timelines.timeline_elements' => 'Timelines\TimelineElementController', '/w/{campaign}/races' => 'Crud\RaceController', '/w/{campaign}/creatures' => 'Crud\CreatureController', '/w/{campaign}/relations' => 'RelationController', '/w/{campaign}/reminders' => 'ReminderUpdateController', // Entities // 'entities.attributes' => 'AttributeController', '/w/{campaign}/entities.entity_abilities' => 'Entity\AbilityController', '/w/{campaign}/entities.posts' => 'Entity\PostController', '/w/{campaign}/entities.reminders' => 'Entity\ReminderController', '/w/{campaign}/entities.entity_assets' => 'Entity\AssetController', '/w/{campaign}/entities.inventories' => 'Entity\InventoryController', '/w/{campaign}/entities.relations' => 'Entity\RelationController', '/w/{campaign}/attribute_templates' => 'Crud\AttributeTemplateController', '/w/{campaign}/whiteboards' => 'Whiteboards\CrudController', // 'presets' => 'PresetController', ]); // Move Route::get('/w/{campaign}/entities/{entity}/move', 'Entity\MoveController@index')->name('entities.move'); Route::post('/w/{campaign}/entities/{entity}/move', 'Entity\MoveController@move')->name('entities.move-process'); Route::get('/w/{campaign}/entities/{entity}/posts/{post}/move', 'Entity\Posts\MoveController@index')->name('posts.move'); Route::post('/w/{campaign}/entities/{entity}/posts/{post}/move', 'Entity\Posts\MoveController@move')->name('posts.move-process'); Route::get('/w/{campaign}/entities/{entity}/post-layouts', [LayoutController::class, 'index'])->name('posts.layouts'); // Transform Route::get('/w/{campaign}/entities/{entity}/transform', 'Entity\TransformController@index')->name('entities.transform'); Route::post('/w/{campaign}/entities/{entity}/transform', 'Entity\TransformController@transform')->name('entities.transform-process'); Route::get('/w/{campaign}/entities/{entity}/tooltip', 'Entity\TooltipController@show')->name('entities.tooltip'); // Entity files Route::get('/w/{campaign}/entities/{entity}/logs', 'Entity\LogController@index')->name('entities.logs'); Route::get('/w/{campaign}/entities/{entity}/post/{post}/logs', 'Entity\Posts\LogController@index')->name('entities.posts.logs'); Route::get('/w/{campaign}/entities/{entity}/mentions', 'Entity\MentionController@index')->name('entities.mentions'); // Inventory Route::get('/w/{campaign}/entities/{entity}/inventory', 'Entity\InventoryController@index') ->name('entities.inventory'); Route::post('/w/{campaign}/entities/{entity}/inventory/generate/store', 'Entity\GenerateInventoryController@store') ->name('entities.inventory.generate.store'); Route::get('/w/{campaign}/entities/{entity}/inventory/generate', 'Entity\GenerateInventoryController@index') ->name('entities.inventory.generate'); Route::post('/w/{campaign}/entities/{entity}/inventory/copy_from', 'Entity\CopyInventoryController@store') ->name('entities.inventory.copy.store'); Route::get('/w/{campaign}/entities/{entity}/inventory/copy', 'Entity\CopyInventoryController@index') ->name('entities.inventory.copy'); Route::get('/w/{campaign}/entities/{entity}/inventory/{inventory}/details', 'Entity\Inventory\DetailController@index') ->name('entities.inventory.details'); Route::delete('/w/{campaign}/entities/{entity}/inventory/{inventory}/delete_section', 'Entity\InventorySectionController@delete') ->name('entities.inventory.delete.section'); // Export Route::get('/w/{campaign}/entities/{entity}/html-export', 'Entity\ExportController@html')->name('entities.html-export'); Route::get('/w/{campaign}/entities/{entity}.json', 'Entity\ExportController@json')->name('entities.json.export'); Route::get('/w/{campaign}/entities/{entity}.md', 'Entity\ExportController@markdown')->name('entities.markdown.export'); // Share Route::get('/w/{campaign}/entities/{entity}/share', 'Entity\ShareController@setup')->name('entities.share.setup'); Route::post('/w/{campaign}/entities/{entity}/share', 'Entity\ShareController@save')->name('entities.share.save'); Route::get('/w/{campaign}/entities/{entity}/template', 'Entity\TemplateController@update')->name('entities.template'); Route::get('/w/{campaign}/posts/{post}/template', 'Entity\Posts\TemplateController@update')->name('posts.template'); // Archive Route::get('/w/{campaign}/entities/{entity}/archive', 'Entity\ArchiveController@update')->name('entities.archive'); // Attribute template Route::get('/w/{campaign}/entities/{entity}/attribute-template', 'Entity\AttributeTemplateController@index')->name('entities.attributes.template'); Route::post('/w/{campaign}/entities/{entity}/attribute-template', 'Entity\AttributeTemplateController@process')->name('entities.attributes.template-process'); Route::get('/w/{campaign}/entities/{entity}/permissions', 'Entity\PermissionController@view')->name('entities.permissions'); Route::post('/w/{campaign}/entities/{entity}/permissions', 'Entity\PermissionController@store')->name('entities.permissions-process'); Route::get('/w/{campaign}/entities/{entity}/preview', 'Entity\PreviewController@index')->name('entities.preview'); // Entity quick creator Route::get('/w/{campaign}/entity-creator', [EntityCreatorController::class, 'selection'])->name('entity-creator.selection'); Route::get('/w/{campaign}/entity-creator/{entity_type}', [EntityCreatorController::class, 'form'])->name('entity-creator.form'); Route::get('/w/{campaign}/entity-creator-post', [EntityCreatorController::class, 'post'])->name('entity-creator.post'); Route::post('/w/{campaign}/entity-creator/{entity_type}', [EntityCreatorController::class, 'store'])->name('entity-creator.store'); Route::post('/w/{campaign}/entity-creator-post', [EntityCreatorController::class, 'storePost'])->name('entity-creator.store-post'); // Whiteboards Route::get('/w/{campaign}/whiteboards/{whiteboard}/draw', [DrawController::class, 'show'])->name('whiteboards.draw'); Route::get('/w/{campaign}/whiteboards/{whiteboard}/api', [ApiController::class, 'index'])->name('whiteboards.api'); Route::put('/w/{campaign}/whiteboards/{whiteboard}/api', [ApiController::class, 'store'])->name('whiteboards.shapes.store'); Route::patch('/w/{campaign}/whiteboards/{whiteboard}/api/{whiteboard_shape}', [ApiController::class, 'update'])->name('whiteboards.shapes.update'); Route::delete('/w/{campaign}/whiteboards/{whiteboard}/api/{whiteboard_shape}', [ApiController::class, 'destroy'])->name('whiteboards.shapes.delete'); Route::post('/w/{campaign}/whiteboards/{whiteboard}/api/{whiteboard_shape}/stroke', [ApiController::class, 'stroke'])->name('whiteboards.shapes.stroke'); Route::get('/w/{campaign}/entities/{entity}/api/document', [DocumentController::class, 'index'])->name('entities.api.document'); ================================================ FILE: routes/campaigns/search.php ================================================ name('search'); Route::get('/w/{campaign}/search/markers', [MarkerController::class, 'index'])->name('markers.find'); Route::get('/w/{campaign}/search/images', [ImageController::class, 'index'])->name('images.find'); Route::get('/w/{campaign}/search/members', [CampaignController::class, 'members'])->name('find.members'); Route::get('/w/{campaign}/search/roles', [CampaignController::class, 'roles'])->name('find.campaign.roles'); Route::get('/w/{campaign}/search/entity-calendars', [CalendarController::class, 'index'])->name('search.calendars'); Route::get('/w/{campaign}/search/months', [CalendarController::class, 'months'])->name('search.calendar-months'); Route::get('/w/{campaign}/search/entities/{entity}/attributes', [AttributeController::class, 'index'])->name('search.attributes'); // Global Entity Search Route::get('/w/{campaign}/search/reminder-entities', [LiveController::class, 'reminderEntities'])->name('search.entities-with-reminders'); Route::get('/w/{campaign}/search/relation-entities', [LiveController::class, 'relationEntities'])->name('search.entities-with-relations'); Route::get('/w/{campaign}/search/tag-children', [LiveController::class, 'tagChildren'])->name('search.tag-children'); Route::get('/w/{campaign}/search/ability-entities', [LiveController::class, 'abilityEntities'])->name('search.ability-entities'); Route::get('/w/{campaign}/search/organisation-member', [LiveController::class, 'organisationMembers'])->name('search.organisation-member'); Route::get('/w/{campaign}/search/type/{entity_type}', [ListController::class, 'index'])->name('search-list'); Route::get('/w/{campaign}/search/{map}/map_groups', [MapGroupController::class, 'index'])->name('map-groups-list'); Route::get('/w/{campaign}/search/type/{entity_type}/templates', [TemplateController::class, 'index'])->name('search.templates'); Route::get('/w/{campaign}/search/live', [LiveController::class, 'index'])->name('search.live'); Route::get('/w/{campaign}/search/recent', [RecentController::class, 'index'])->name('search.recent'); Route::get('/w/{campaign}/search/fulltext', [FullTextController::class, 'index'])->name('search.fulltext'); Route::get('/w/{campaign}/search/mention', [MentionController::class, 'index'])->name('search.mention'); Route::post('/w/{campaign}/search/mention', [MentionController::class, 'load'])->name('search.mention.load'); ================================================ FILE: routes/channels.php ================================================ id === (int) $id; // }); Broadcast::channel('whiteboard.{id}', function (User $user, $id) { $whiteboard = Whiteboard::withInvisible()->findOrFail($id); $entity = $whiteboard->entity()->withInvisible()->firstOrFail(); EntityPermission::campaign($entity->campaign); if ($user->can('member', $entity->campaign) && $user->can('view', $entity)) { return [ 'id' => $user->id, 'name' => $user->name, 'image' => $user->hasAvatar() ? $user->getAvatarUrl() : null, 'url' => route('users.profile', [$user]), 'role' => $user->can('update', $entity) ? 'edit' : 'view', ]; } return false; }); ================================================ FILE: routes/console.php ================================================ comment(Inspiring::quote()); })->describe('Display an inspiring quote'); ================================================ FILE: routes/oauth.php ================================================ group(function () { Route::get('/tokens', [ 'uses' => '\Laravel\Passport\Http\Controllers\AuthorizedAccessTokenController@forUser', 'as' => 'tokens.index', ]); Route::delete('/tokens/{token_id}', [ 'uses' => '\Laravel\Passport\Http\Controllers\AuthorizedAccessTokenController@destroy', 'as' => 'tokens.destroy', ]); Route::get('/clients', [ 'uses' => '\Laravel\Passport\Http\Controllers\ClientController@forUser', 'as' => 'clients.index', ]); Route::post('/clients', [ 'uses' => '\App\Http\Controllers\Passport\ClientController@store', 'as' => 'clients.store', ]); Route::put('/clients/{client_id}', [ 'uses' => '\Laravel\Passport\Http\Controllers\ClientController@update', 'as' => 'clients.update', ]); Route::delete('/clients/{client_id}', [ 'uses' => '\Laravel\Passport\Http\Controllers\ClientController@destroy', 'as' => 'clients.destroy', ]); Route::get('/scopes', [ 'uses' => '\Laravel\Passport\Http\Controllers\ScopeController@all', 'as' => 'scopes.index', ]); Route::get('/personal-access-tokens', [ 'uses' => '\Laravel\Passport\Http\Controllers\PersonalAccessTokenController@forUser', 'as' => 'personal.tokens.index', ]); Route::post('/personal-access-tokens', [ 'uses' => '\Laravel\Passport\Http\Controllers\PersonalAccessTokenController@store', 'as' => 'personal.tokens.store', ]); Route::delete('/personal-access-tokens/{token_id}', [ 'uses' => '\Laravel\Passport\Http\Controllers\PersonalAccessTokenController@destroy', 'as' => 'personal.tokens.destroy', ]); }); ================================================ FILE: routes/settings.php ================================================ name('settings'); Route::get('/profile', [ProfileController::class, 'index'])->name('settings.profile'); Route::patch('/profile', [ProfileController::class, 'update'])->name('settings.profile-process'); Route::get('/account/billing/info', [InformationController::class, 'index'])->name('account.billing.info'); Route::patch('/account/billing/info', [InformationController::class, 'save'])->name('account.billing.info-save'); Route::get('/boosters', [BoostController::class, 'index'])->name('settings.boost'); Route::get('/boosters/boost/{campaign}', [BoostController::class, 'boost'])->name('settings.campaign-boost'); Route::get('/boosters/unboost/{campaign}', [BoostController::class, 'unboost'])->name('settings.campaign-unboost'); Route::post('/switch-to-premium', [PremiumController::class, 'migrate']) ->name('settings.switch-to-premium'); Route::get('/switch-back', [PremiumController::class, 'back']) ->name('settings.switch-back'); Route::get('/premium', [PremiumController::class, 'index'])->name('settings.premium'); Route::get('/boosters/premium/{campaign}', [PremiumController::class, 'premium'])->name('settings.campaign-premium'); Route::get('/boosters/unpremium/{campaign}', [PremiumController::class, 'unpremium'])->name('settings.campaign-unpremium'); Route::post('/release/{app_release}', [ReleaseController::class, 'read'])->name('settings.release'); Route::get('/account', [AccountController::class, 'index'])->name('settings.account'); Route::get('/account/password', [PasswordController::class, 'index'])->name('account.password'); Route::patch('/account/password', [PasswordController::class, 'save'])->name('account.password-save'); Route::get('/account/email', [EmailController::class, 'index'])->name('account.email'); Route::patch('/account/email', [EmailController::class, 'save'])->name('account.email-save'); Route::patch('/account/destroy', [DeleteController::class, 'destroy'])->name('settings.account.destroy'); Route::get('/account/social', [SocialController::class, 'index'])->name('account.social'); Route::patch('/account/social', [SocialController::class, 'save'])->name('account.social-save'); Route::get('/patreon', [PatreonController::class, 'index'])->name('settings.patreon'); Route::delete('/patreon-unlink', [PatreonController::class, 'unlink'])->name('settings.patreon.unlink'); Route::get('/api', [ApiController::class, 'index'])->name('settings.api'); Route::get('/api/create', [ApiController::class, 'create'])->name('settings.api.create'); Route::post('/api/store', [ApiController::class, 'store'])->name('settings.api.store'); Route::delete('/api/revoke/{token}', [ApiController::class, 'revoke'])->name('settings.api.revoke'); Route::put('/client/update/{client}', [ClientController::class, 'update'])->name('settings.client.update'); Route::get('/client/create', [ClientController::class, 'create'])->name('settings.client.create'); Route::post('/client/store', [ClientController::class, 'store'])->name('settings.client.store'); Route::get('/client/edit/{client}', [ClientController::class, 'edit'])->name('settings.client.edit'); Route::delete('/client/revoke/{client}', [ClientController::class, 'revoke'])->name('settings.client.revoke'); Route::get('/appearance', [AppearanceController::class, 'index'])->name('settings.appearance'); Route::patch('/appearance', [AppearanceController::class, 'update'])->name('settings.appearance.update'); Route::get('/newsletter', [NewsletterController::class, 'index'])->name('settings.newsletter'); Route::patch('/newsletter', [NewsletterController::class, 'update'])->name('settings.newsletter.save'); Route::get('/subscription', [SubscriptionController::class, 'index'])->name('settings.subscription'); Route::get('/subscription/change/{tier}', [SubscriptionController::class, 'change'])->name('settings.subscription.change'); Route::post('/subscription/renew', [SubscriptionController::class, 'renew'])->name('settings.subscription.renew'); Route::get('/subscription/finish', [FinishController::class, 'index'])->name('settings.subscription.finish'); Route::get('/subscription/callback', [SubscriptionController::class, 'callback'])->name('settings.subscription.callback'); Route::post('/subscription/change/{tier}', [SubscriptionController::class, 'subscribe'])->name('settings.subscription.subscribe'); Route::get('/subscription/unsubscribe', [CancellationController::class, 'index'])->name('settings.subscription.unsubscribe'); Route::post('/subscription/cancel', [CancellationController::class, 'save'])->name('settings.subscription.cancel'); Route::get('/subscription/cancelled', [CancelledController::class, 'index'])->name('settings.subscription.cancelled'); Route::get('/billing/payment-method', [PaymentMethodController::class, 'index'])->name('billing.payment-method'); Route::patch('/billing/payment-method', [PaymentMethodController::class, 'save'])->name('billing.payment-method.save'); Route::get('/billing/currency', [PaymentMethodController::class, 'currency'])->name('billing.currency'); Route::get('/subscription/free-trial', [FreeTrialController::class, 'index'])->name('settings.free-trial'); Route::post('/subscription/free-trial/accept', [FreeTrialController::class, 'accept'])->name('settings.free-trial.accept'); Route::get('/billing/history', [HistoryController::class, 'index'])->name('billing.history'); Route::get('/billing/history/download/{invoice}', [HistoryController::class, 'download'])->name('billing.history.download'); Route::get('/bragi', 'Settings\BragiController@index') ->name('settings.bragi'); Route::get('/apps', [AppsController::class, 'index']) ->name('settings.apps'); Route::get('/discord-me', [DiscordController::class, 'me']); Route::delete('/discord', [DiscordController::class, 'destroy']) ->name('settings.discord.destroy'); Route::get('/discord-callback', [DiscordController::class, 'callback']) ->name('settings.discord.callback'); Route::get('/discord-setup', [DiscordController::class, 'seup']); Route::post('newsletter-api', [NewsletterApiController::class, 'update']) ->name('settings.newsletter-api'); /*Route::get('/marketplace', 'Settings\MarketplaceController@index') ->name('settings.marketplace'); Route::post('/marketplace', 'Settings\MarketplaceController@save') ->name('settings.marketplace.save');*/ // Tutorial // Route::get('/tutorial/{tutorial}/done/{next?}', 'Settings\TutorialController@done') // ->name('settings.tutorial.done'); // Route::get('/tutorial/disable', 'Settings\TutorialController@disable') // ->name('settings.tutorial.disable'); // Route::get('/tutorial/reset', 'Settings\TutorialController@reset') // ->name('settings.tutorial.reset'); Route::post('/tutorials/{code}/dismiss', [TutorialController::class, 'dismiss'])->name('tutorials.dismiss'); Route::patch('/tutorials/reset', [TutorialController::class, 'reset'])->name('tutorials.reset'); // Campaign boosters Route::resources([ 'campaign_boosts' => CampaignBoostController::class, ]); Route::get( 'campaign_boosts/{campaign_boost}/confirm', [CampaignBoostController::class, 'confirm'] )->name('campaign_boost.confirm-destroy'); /* -------------------------------------------------------------------------- Google2FA -------------------------------------------------------------------------- */ Route::post('/security/cancel2fa', [PasswordSecurityController::class, 'cancel2FA'])->name('auth.cancel-2fa'); // Generate a new Google2FA code if a User does not already have one Route::post('/security/generate2faSecret', [PasswordSecurityController::class, 'generate2faSecretCode']) ->name('settings.security.generate-2fa'); // Verify 2FA if User has it enabled Route::post('/security/verify2fa', function () { return redirect()->route('home'); })->name('auth.verify-2fa')->middleware('2fa'); /*Route::get('/security/verify2fa', function() { return redirect(URL()->previous()); })->name('auth.verify-2fa')->middleware('2fa');*/ /* -------------------------------------------------------------------------- PayPal API -------------------------------------------------------------------------- */ Route::post('paypal/process-transaction/{tier}', [PayPalController::class, 'processTransaction']) ->name('paypal.process-transaction'); Route::get('paypal/success-transaction', [PayPalController::class, 'successTransaction']) ->name('paypal.transaction-success'); Route::get('paypal/cancel-transaction', [PayPalController::class, 'cancelTransaction']) ->name('paypal.cancel-transaction'); /* -------------------------------------------------------------------------- PayPal Renewal -------------------------------------------------------------------------- */ Route::get('subscription/paypal/renew', [RenewalController::class, 'index']) ->name('paypal.renew'); Route::post('subscription/paypal/renew/{tier}', [RenewalController::class, 'process']) ->name('paypal.renew-process'); Route::get('subscription/paypal/renew/success', [RenewalController::class, 'success']) ->name('paypal.renew-success'); Route::get('subscription/paypal/renew/cancel', [RenewalController::class, 'cancel']) ->name('paypal.renew-cancel'); /* -------------------------------------------------------------------------- Notifications -------------------------------------------------------------------------- */ Route::get('/notifications', [NotificationController::class, 'index'])->name('notifications'); Route::get('/notifications/refresh', [NotificationController::class, 'refresh'])->name('notifications.refresh'); Route::post('/notifications/read/{id}', [NotificationController::class, 'read'])->name('notifications.read'); Route::post('/notifications/clear-all', [NotificationController::class, 'clearAll'])->name('notifications.clear-all'); Route::get('/layout/navigation', [NavigationController::class, 'index'])->name('layout.navigation'); Route::get('/referrals', [ReferralController::class, 'index'])->name('settings.referrals'); ================================================ FILE: routes/vendor.php ================================================ config('larecipe.docs.route'), 'domain' => config('larecipe.domain', null), 'as' => 'larecipe.', 'middleware' => 'web', ], function () { Route::get('/', '\BinaryTorch\LaRecipe\Http\Controllers\DocumentationController@index')->name('index'); Route::get('/{version}/{page?}', '\BinaryTorch\LaRecipe\Http\Controllers\DocumentationController@show')->where('page', '(.*)')->name('show'); }); // 3rd party Route::group(['middleware' => ['auth', 'translator'], 'prefix' => 'translations'], function () { Translator::routes(); }); ================================================ FILE: routes/web-i18n.php ================================================ name('home'); Route::get('/new-campaign', [CreateController::class, 'index'])->name('start'); Route::post('/new-campaign', [CreateController::class, 'store'])->name('create-campaign'); // Invitation's campaign comes from the token. Route::get('/invitation/join/{token}', [InvitationController::class, 'join'])->name('campaigns.join'); Route::get('/assistance', [TroubleshootingController::class, 'index'])->name('troubleshooting'); Route::post('/assistance', [TroubleshootingController::class, 'store'])->name('troubleshooting.generate'); ================================================ FILE: routes/web.php ================================================ name('auth.provider.callback'); Route::group(['prefix' => 'subscription-api'], function () { Route::get('setup-intent', 'Settings\SubscriptionApiController@setupIntent'); Route::post('payments', 'Settings\SubscriptionApiController@paymentMethods'); Route::get('payment-methods', 'Settings\SubscriptionApiController@getPaymentMethods'); Route::post('remove-payment', 'Settings\SubscriptionApiController@removePaymentMethod'); Route::get('check-coupon/{tier}', [SubscriptionApiController::class, 'checkCoupon']) ->name('subscription.check-coupon'); }); Route::get('users/{user}', [ProfileController::class, 'show'])->name('users.profile'); Route::get('/_ccapi/country', [CookieConsentController::class, 'index']) ->name('cookieconsent.country'); Route::get('/frontend-prepare', [FrontendPrepareController::class, 'index']); Route::get('/_setup', [SetupController::class, 'index']); Route::get('/up', [HealthController::class, 'index']); Route::model('feature', Feature::class); Route::get('roadmap', [RoadmapController::class, 'index'])->name('roadmap'); Route::get('roadmap/{feature}', [FeatureController::class, 'show'])->name('roadmap.feature.show'); Route::post('roadmap/{feature}/upvote', [FeatureController::class, 'upvote'])->name('roadmap.upvote'); Route::post('roadmap/submit', [FeatureController::class, 'store'])->name('roadmap.store'); Route::get('spotlights', [ApplicationController::class, 'index'])->name('spotlights.application'); Route::get('spotlights/{campaign}', [ApplicationController::class, 'form'])->name('spotlights.form'); Route::post('spotlights/{campaign}/save', [ApplicationController::class, 'save'])->name('spotlights.save'); Route::post('spotlights/retract/{campaign}', [ApplicationController::class, 'retract'])->name('spotlights.retract'); Route::get('/validation/{userValidation}', [EmailValidationController::class, 'validateEmail'])->name('validation.email'); // Game System Search Route::get('/search/systems', [GameSystemSearchController::class, 'index'])->name('search.systems'); Route::get('/r/{referral}', [ReferralController::class, 'index'])->name('referrals'); Route::get('/datagrids/subscription', [SubscriptionController::class, 'index'])->name('datagrids.subscription'); ================================================ FILE: routes/webhooks.php ================================================ name('cashier.webhook'); Route::post('/facebook/data-deletion', [DeletionController::class, 'handle']); Route::get('/facebook/data-deletion/status', [DeletionController::class, 'status']); Route::get('/facebook/data-deletion/generate', [DeletionController::class, 'generate']); ================================================ FILE: server.php ================================================ */ $uri = urldecode( parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) ); // This file allows us to emulate Apache's "mod_rewrite" functionality from the // built-in PHP web server. This provides a convenient way to test a Laravel // application without having installed a "real" web server software here. if ($uri !== '/' && file_exists(__DIR__ . '/public' . $uri)) { return false; } require_once __DIR__ . '/public/index.php'; ================================================ FILE: sonar-project.properties ================================================ sonar.exclusions=test/**, node_modules/**, vendor/**, bower_components/**, .docker/**, database/**, public/**, VagrantFile, routes/**, hooks/**, docs/**, resources/**, storage/**, .idea/** ================================================ FILE: tailwind.config.js ================================================ import defaultTheme from 'tailwindcss/defaultTheme'; /** @type {import('tailwindcss').Config} */ export default { content: [ './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', './storage/framework/views/*.php', './resources/**/*.blade.php', './resources/**/*.js', './resources/**/*.vue', "./app/Models/*.php", "./app/View/Components/**/*.php", ], theme: { extend: { fontFamily: { sans: ['Figtree', ...defaultTheme.fontFamily.sans], }, }, }, plugins: [], }; ================================================ FILE: tests/CreatesApplication.php ================================================ make(Kernel::class)->bootstrap(); return $app; } } ================================================ FILE: tests/Feature/AuthTest.php ================================================ get('/api/1.0/profile') ->assertStatus(401); it('rejects invalid token') ->get('/api/1.0/profile', ['Authorization' => 'Bearer: FAKE']) ->assertStatus(401); it('approves a valid token') ->asUser() ->get('/api/1.0/profile') ->assertStatus(200); ================================================ FILE: tests/Feature/CampaignTest.php ================================================ asUser() ->postJson('/api/1.0/campaigns', []) ->assertStatus(422); it('campaigns POST valid') ->asUser() ->postJson('/api/1.0/campaigns', [ 'name' => fake()->name(), 'entry' => fake()->text(500), 'excerpt' => fake()->text(100), ]) ->assertStatus(201); it('campaigns GET') ->asUser() ->withCampaign() ->get('/api/1.0/campaigns') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'name', 'boosted', 'superboosted', 'premium', 'members', ], ], ]); it('campaign GET') ->asUser() ->withCampaign() ->get('/api/1.0/campaigns/1') ->assertStatus(200) ->assertJson([ 'data' => [ 'id' => 1, ], ]); it('campaign PATCH') ->asUser() ->withCampaign() ->patchJson('/api/1.0/campaigns/1', [ 'name' => 'New name', ]) ->assertJson([ 'data' => [ 'id' => 1, 'name' => 'New name', ], ]); ================================================ FILE: tests/Feature/Entities/AbilityTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/abilities', [ 'name' => fake()->name(), ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all abilities') ->asUser() ->withCampaign() ->withAbilities() ->get('/api/1.0/campaigns/1/abilities') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific ability') ->asUser() ->withCampaign() ->withAbilities() ->get('/api/1.0/campaigns/1/abilities/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid ability') ->asUser() ->withCampaign() ->withAbilities() ->putJson('/api/1.0/campaigns/1/abilities/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('UPDATES a valid ability without a name') ->asUser() ->withCampaign() ->withAbilities() ->putJson('/api/1.0/campaigns/1/abilities/1', ['type' => 'Magic']) ->assertStatus(200) ->assertJsonFragment(['type' => 'Magic']); it('DELETES a ability') ->asUser() ->withCampaign() ->withAbilities() ->delete('/api/1.0/campaigns/1/abilities/1') ->assertStatus(204); it('DELETES an invalid ability') ->asUser() ->withCampaign() ->withAbilities() ->delete('/api/1.0/campaigns/1/abilities/100') ->assertStatus(404); it('can GET a ability as a player') ->asUser() ->withCampaign() ->withAbilities() ->asPlayer() ->get('/api/1.0/campaigns/1/abilities/1') ->assertStatus(200); /** * This example showcases building a custom function in the test to avoid polluting the TestCase file with lots of * on-off function calls. */ it('can\'t GET a private ability as a player', function () { $this->asUser() ->withCampaign(); Ability::factory() ->count(5) ->create(['campaign_id' => 1, 'is_private' => true]); $this->asPlayer(); $response = $this->get('/api/1.0/campaigns/1/abilities/1'); expect($response->status()) ->toBe(403); }); ================================================ FILE: tests/Feature/Entities/BookmarkTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/bookmarks', []) ->assertStatus(422); it('POSTS a new bookmark') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/bookmarks', [ 'name' => fake()->name(), 'entity_id' => 1, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all bookmarks') ->asUser() ->withCampaign() ->withBookmarks() ->get('/api/1.0/campaigns/1/bookmarks') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific bookmark') ->asUser() ->withCampaign() ->withBookmarks() ->get('/api/1.0/campaigns/1/bookmarks/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid bookmark') ->asUser() ->withCampaign() ->withCharacters() ->withBookmarks() ->putJson('/api/1.0/campaigns/1/bookmarks/1', ['name' => 'Bob', 'entity_id' => 1]) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('DELETES a bookmark') ->asUser() ->withCampaign() ->withBookmarks() ->delete('/api/1.0/campaigns/1/bookmarks/1') ->assertStatus(204); it('DELETES an invalid bookmark') ->asUser() ->withCampaign() ->withBookmarks() ->delete('/api/1.0/campaigns/1/bookmarks/100') ->assertStatus(404); ================================================ FILE: tests/Feature/Entities/CalendarTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/calendars', []) ->assertStatus(422); it('POSTS a new calendar') ->asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/calendars', [ 'name' => 'Gregorian', 'months' => '[{"name":"January","length":31,"type":"standard","alias":""},{"name":"February","length":28,"type":"standard","alias":""},{"name":"March","length":31,"type":"standard","alias":""},{"name":"April","length":30,"type":"standard","alias":""},{"name":"Mai","length":31,"type":"standard","alias":""},{"name":"June","length":30,"type":"standard","alias":""},{"name":"July","length":31,"type":"standard","alias":""},{"name":"August","length":31,"type":"standard","alias":""},{"name":"September","length":30,"type":"standard","alias":""},{"name":"October","length":31,"type":"standard","alias":""},{"name":"November","length":30,"type":"standard","alias":""},{"name":"December","length":31,"type":"standard","alias":""}]', 'weekdays' => '["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]', 'seasons' => '[{"name":"Spring","month":3,"day":21},{"name":"Summer","month":6,"day":21},{"name":"Autumn","month":9,"day":21},{"name":"Winter","month":12,"day":21}]', 'month_name' => ['first', 'last'], 'month_length' => [1, 2], 'weekday' => ['monday', 'sunday'], 'suffix' => 'AD', 'has_leap_year' => 1, 'leap_year_amount' => 1, 'leap_year_month' => 2, 'leap_year_offset' => 4, 'leap_year_start' => 4, 'skip_year_zero' => 1, 'start_offset' => 5, 'is_incrementing' => 1, 'date' => Carbon::now()->toDateString(), ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all calendars') ->asUser() ->withCampaign() ->withCalendars() ->get('/api/1.0/campaigns/1/calendars') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific calendar') ->asUser() ->withCampaign() ->withCalendars() ->get('/api/1.0/campaigns/1/calendars/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid calendar') ->asUser() ->withCampaign() ->withCalendars() ->putJson('/api/1.0/campaigns/1/calendars/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('UPDATES a valid calendar without a name') ->asUser() ->withCampaign() ->withCalendars() ->putJson('/api/1.0/campaigns/1/calendars/1', ['type' => 'Magic']) ->assertStatus(200) ->assertJsonFragment(['type' => 'Magic']); it('DELETES a calendar') ->asUser() ->withCampaign() ->withCalendars() ->delete('/api/1.0/campaigns/1/calendars/1') ->assertStatus(204); it('DELETES an invalid calendar') ->asUser() ->withCampaign() ->withCalendars() ->delete('/api/1.0/campaigns/1/calendars/100') ->assertStatus(404); it('can GET a calendar as a player') ->asUser() ->withCampaign() ->withCalendars() ->asPlayer() ->get('/api/1.0/campaigns/1/calendars/1') ->assertStatus(200); /** * This example showcases building a custom function in the test to avoid polluting the TestCase file with lots of * on-off function calls. */ it('can\'t GET a private calendar as a player', function () { $this->asUser() ->withCampaign(); Calendar::factory() ->count(5) ->create(['campaign_id' => 1, 'is_private' => true]); $this->asPlayer(); $response = $this->get('/api/1.0/campaigns/1/calendars/1'); expect($response->status()) ->toBe(403); }); ================================================ FILE: tests/Feature/Entities/CampaignDashboardWidgetTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/campaign_dashboard_widgets', []) ->assertStatus(422); it('POSTS a new widget') ->asUser() ->withCampaign() ->withMember() ->postJson('/api/1.0/campaigns/1/campaign_dashboard_widgets', [ 'widget' => 'header', ]) ->assertStatus(201); it('GETS all dashboard widgets') ->asUser() ->withCampaign() ->withDashboardWidgets() ->get('/api/1.0/campaigns/1/campaign_dashboard_widgets') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ '*' => [ 'id', 'widget', ], ], ]); it('GETS a specific dashboard widget') ->asUser() ->withCampaign() ->withDashboardWidgets() ->get('/api/1.0/campaigns/1/campaign_dashboard_widgets/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'widget', ], ]); it('DELETES a dashboard widget') ->asUser() ->withCampaign() ->withDashboardWidgets() ->delete('/api/1.0/campaigns/1/campaign_dashboard_widgets/1') ->assertStatus(204); it('DELETES an invalid user role') ->asUser() ->withCampaign() ->withDashboardWidgets() ->delete('/api/1.0/campaigns/1/campaign_dashboard_widgets/100') ->assertStatus(404); it('UPDATES a valid dashboard widget') ->asUser() ->withCampaign() ->withDashboardWidgets() ->putJson('/api/1.0/campaigns/1/campaign_dashboard_widgets/1', ['position' => 2, 'widget' => 'header']) ->assertStatus(200) ->assertJsonFragment(['position' => 2]); ================================================ FILE: tests/Feature/Entities/CampaignImageTest.php ================================================ asUser(true) ->withCampaign() ->postJson('/api/1.0/campaigns/1/images', [ // 'folder_id' => 1, 'file' => [ UploadedFile::fake()->image('avatar.jpg'), ], ]) ->assertStatus(200) ->assertJsonStructure([ 'data' => [ '*' => [ 'id', 'name', 'path', ], ], ]); it('GETS all images') ->asUser(true) ->withCampaign() ->withImages() ->get('/api/1.0/campaigns/1/images') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'name', ], ], ]); it('GETS a specific image') ->asUser(true) ->withCampaign() ->withImages() ->get('/api/1.0/campaigns/1/images/16598f1b-7d93-36d9-bea5-212bfa1e354b') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', ], ]); it('UPDATES a valid image') ->asUser(true) ->withCampaign() ->withImages() ->putJson('/api/1.0/campaigns/1/images/16598f1b-7d93-36d9-bea5-212bfa1e354b', ['name' => 'bob', 'content' => 'content', 'is_enabled' => true]) ->assertStatus(200) ->assertJsonFragment(['name' => 'bob']); it('DELETES a image') ->asUser(true) ->withCampaign() ->withImages() ->delete('/api/1.0/campaigns/1/images/16598f1b-7d93-36d9-bea5-212bfa1e354b') ->assertStatus(204); it('DELETES an invalid image') ->asUser(true) ->withCampaign() ->withImages() ->delete('/api/1.0/campaigns/1/images/100') ->assertStatus(404); it('cant GET a image as a player') ->asUser(true) ->withCampaign() ->withImages() ->asPlayer() ->get('/api/1.0/campaigns/1/images/16598f1b-7d93-36d9-bea5-212bfa1e354b') ->assertStatus(403); ================================================ FILE: tests/Feature/Entities/CampaignMemberTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/users', []) ->assertStatus(422); it('POSTS a new user role') ->asUser() ->withCampaign() ->withMember() ->postJson('/api/1.0/campaigns/1/users', [ 'user_id' => 2, 'role_id' => 1, ]) ->assertJsonFragment([ 'role successfully added to user', ]) ->assertStatus(200); it('GETS all campaign members') ->asUser() ->withCampaign() ->withMember() ->get('/api/1.0/campaigns/1/users') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'name', ], ], ]); it('GETS a specific campaign member') ->asUser() ->withCampaign() ->withMember() ->get('/api/1.0/campaigns/1/users/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'name', ], ], ]); it('DELETES a user role') ->asUser() ->withCampaign() ->withMember() ->delete('/api/1.0/campaigns/1/users', [ 'user_id' => 2, 'role_id' => 3, ]) ->assertJsonFragment([ 'role successfully removed from the user', ]) ->assertStatus(200); it('DELETES an invalid user role') ->asUser() ->withCampaign() ->withMember() ->delete('/api/1.0/campaigns/1/users') ->assertStatus(422); it('GETS all campaign roles') ->asUser() ->withCampaign() ->withMember() ->get('/api/1.0/campaigns/1/roles') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'name', ], ], ]); ================================================ FILE: tests/Feature/Entities/CampaignStyleTest.php ================================================ asUser(true) ->withCampaign(['boost_count' => 4]) ->postJson('/api/1.0/campaigns/1/campaign_styles', [ 'name' => fake()->name(), 'content' => fake()->text(50), 'is_enabled' => false, 'is_theme' => false, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'campaign_id', 'name', ], ]); it('GETS all campaign_styles') ->asUser(true) ->withCampaign(['boost_count' => 4]) ->withCampaignStyles() ->get('/api/1.0/campaigns/1/campaign_styles') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'campaign_id', 'name', ], ], ]); it('GETS a specific campaign_style') ->asUser(true) ->withCampaign(['boost_count' => 4]) ->withCampaignStyles() ->get('/api/1.0/campaigns/1/campaign_styles/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'campaign_id', 'name', ], ]); it('UPDATES a valid campaign_style') ->asUser(true) ->withCampaign(['boost_count' => 4]) ->withCampaignStyles() ->putJson('/api/1.0/campaigns/1/campaign_styles/1', ['name' => 'bob', 'content' => 'content', 'is_enabled' => true]) ->assertStatus(200) ->assertJsonFragment(['name' => 'bob']); it('DELETES a campaign_style') ->asUser(true) ->withCampaign(['boost_count' => 4]) ->withCampaignStyles() ->delete('/api/1.0/campaigns/1/campaign_styles/1') ->assertStatus(204); it('DELETES an invalid campaign_style') ->asUser(true) ->withCampaign(['boost_count' => 4]) ->withCampaignStyles() ->delete('/api/1.0/campaigns/1/campaign_styles/100') ->assertStatus(404); it('cant GET a campaign_style as a player') ->asUser(true) ->withCampaign(['boost_count' => 4]) ->withCampaignStyles() ->asPlayer() ->get('/api/1.0/campaigns/1/campaign_styles/1') ->assertStatus(403); ================================================ FILE: tests/Feature/Entities/CharacterTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/characters', []) ->assertStatus(422); it('POSTS a new character') ->asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/characters', [ 'name' => fake()->name(), ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all characters') ->asUser() ->withCampaign() ->withCharacters() ->get('/api/1.0/campaigns/1/characters') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific character') ->asUser() ->withCampaign() ->withCharacters() ->get('/api/1.0/campaigns/1/characters/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid character') ->asUser() ->withCampaign() ->withCharacters() ->putJson('/api/1.0/campaigns/1/characters/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('UPDATES a valid character without a name') ->asUser() ->withCampaign() ->withCharacters() ->putJson('/api/1.0/campaigns/1/characters/1', ['type' => 'character']) ->assertStatus(200) ->assertJsonFragment(['type' => 'character']); it('DELETES a character') ->asUser() ->withCampaign() ->withCharacters() ->delete('/api/1.0/campaigns/1/characters/1') ->assertStatus(204); it('DELETES an invalid character') ->asUser() ->withCampaign() ->withCharacters() ->delete('/api/1.0/campaigns/1/characters/100') ->assertStatus(404); it('can GET a character as a player') ->asUser() ->withCampaign() ->withCharacters() ->asPlayer() ->get('/api/1.0/campaigns/1/characters/1') ->assertStatus(200); /** * This example showcases building a custom function in the test to avoid polluting the TestCase file with lots of * on-off function calls. */ it('can\'t GET a private character as a player', function () { $this->asUser() ->withCampaign(); Character::factory() ->count(5) ->create(['campaign_id' => 1, 'is_private' => true]); $this->asPlayer(); $response = $this->get('/api/1.0/campaigns/1/characters/1'); expect($response->status()) ->toBe(403); }); ================================================ FILE: tests/Feature/Entities/ConversationMessageTest.php ================================================ asUser() ->withCampaign() ->withConversations() ->postJson('/api/1.0/campaigns/1/conversations/1/conversation_messages', [ ]) ->assertStatus(422); it('POSTS a new conversation message') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->postJson('/api/1.0/campaigns/1/conversations/1/conversation_messages', [ 'character_id' => 1, 'conversation_id' => 1, 'message' => 'cookies', ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'character_id', 'conversation_id', 'message', ], ]); it('GETS all conversation messages') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationMessages() ->get('/api/1.0/campaigns/1/conversations/1/conversation_messages') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'character_id', 'conversation_id', ], ], ]); it('GETS a specific conversation message') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationMessages() ->get('/api/1.0/campaigns/1/conversations/1/conversation_messages/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'character_id', 'conversation_id', ], ]); it('UPDATES a valid conversation message') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationMessages() ->putJson('/api/1.0/campaigns/1/conversations/1/conversation_messages/1', ['message' => 'cookies']) ->assertStatus(200) ->assertJsonFragment(['message' => 'cookies']); it('DELETES a conversation message') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationMessages() ->delete('/api/1.0/campaigns/1/conversations/1/conversation_messages/1') ->assertStatus(204); it('DELETES an invalid conversation message') ->asUser() ->withCampaign() ->withConversations() ->delete('/api/1.0/campaigns/1/conversations/1/conversation_messages/100') ->assertStatus(404); it('can GET a conversation as a player') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationMessages() ->asPlayer() ->get('/api/1.0/campaigns/1/conversations/1/conversation_messages/1') ->assertStatus(200); ================================================ FILE: tests/Feature/Entities/ConversationParticipantTest.php ================================================ asUser() ->withCampaign() ->withConversations() ->postJson('/api/1.0/campaigns/1/conversations/1/conversation_participants', [ ]) ->assertStatus(422); it('POSTS a new conversation participant') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->postJson('/api/1.0/campaigns/1/conversations/1/conversation_participants', [ 'character_id' => 1, 'conversation_id' => 1, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'character_id', 'conversation_id', ], ]); it('GETS all conversation participants') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationParticipants() ->get('/api/1.0/campaigns/1/conversations/1/conversation_participants') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'character_id', 'conversation_id', ], ], ]); it('GETS a specific conversation participant') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationParticipants() ->get('/api/1.0/campaigns/1/conversations/1/conversation_participants/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'character_id', 'conversation_id', ], ]); it('UPDATES a valid conversation participant') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationParticipants() ->putJson('/api/1.0/campaigns/1/conversations/1/conversation_participants/1', ['character_id' => 2]) ->assertStatus(200) ->assertJsonFragment(['character_id' => 2]); it('DELETES a conversation participant') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationParticipants() ->delete('/api/1.0/campaigns/1/conversations/1/conversation_participants/1') ->assertStatus(204); it('DELETES an invalid conversation participant') ->asUser() ->withCampaign() ->withConversations() ->delete('/api/1.0/campaigns/1/conversations/1/conversation_participants/100') ->assertStatus(404); it('can GET a conversation as a player') ->asUser() ->withCampaign() ->withConversations() ->withCharacters() ->withConversationParticipants() ->asPlayer() ->get('/api/1.0/campaigns/1/conversations/1/conversation_participants/1') ->assertStatus(200); ================================================ FILE: tests/Feature/Entities/ConversationTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/conversations', []) ->assertStatus(422); it('POSTS a new conversation') ->asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/conversations', [ 'name' => fake()->name(), 'target_id' => 2, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all conversations') ->asUser() ->withCampaign() ->withConversations() ->get('/api/1.0/campaigns/1/conversations') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific conversation') ->asUser() ->withCampaign() ->withConversations() ->get('/api/1.0/campaigns/1/conversations/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid conversation') ->asUser() ->withCampaign() ->withConversations() ->putJson('/api/1.0/campaigns/1/conversations/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('UPDATES a valid conversation without a name') ->asUser() ->withCampaign() ->withConversations() ->putJson('/api/1.0/campaigns/1/conversations/1', ['type' => 'Magic']) ->assertStatus(200) ->assertJsonFragment(['type' => 'Magic']); it('DELETES a conversation') ->asUser() ->withCampaign() ->withConversations() ->delete('/api/1.0/campaigns/1/conversations/1') ->assertStatus(204); it('DELETES an invalid conversation') ->asUser() ->withCampaign() ->withConversations() ->delete('/api/1.0/campaigns/1/conversations/100') ->assertStatus(404); it('can GET a conversation as a player') ->asUser() ->withCampaign() ->withConversations() ->asPlayer() ->get('/api/1.0/campaigns/1/conversations/1') ->assertStatus(200); /** * This example showcases building a custom function in the test to avoid polluting the TestCase file with lots of * on-off function calls. */ it('can\'t GET a private conversation as a player', function () { $this->asUser() ->withCampaign(); Conversation::factory() ->count(5) ->create(['campaign_id' => 1, 'is_private' => true]); $this->asPlayer(); $response = $this->get('/api/1.0/campaigns/1/conversations/1'); expect($response->status()) ->toBe(403); }); ================================================ FILE: tests/Feature/Entities/CreatureTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/creatures', []) ->assertStatus(422); it('POSTS a new creature') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/creatures', [ 'name' => fake()->name(), 'entry' => 'Entity: [entity:2]', ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all creatures') ->asUser() ->withCampaign() ->withCreatures() ->get('/api/1.0/campaigns/1/creatures') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific creature') ->asUser() ->withCampaign() ->withCreatures() ->get('/api/1.0/campaigns/1/creatures/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid creature') ->asUser() ->withCampaign() ->withCreatures() ->putJson('/api/1.0/campaigns/1/creatures/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('UPDATES a valid creature without a name') ->asUser() ->withCampaign() ->withCreatures() ->putJson('/api/1.0/campaigns/1/creatures/1', ['type' => 'Magic']) ->assertStatus(200) ->assertJsonFragment(['type' => 'Magic']); it('DELETES a creature') ->asUser() ->withCampaign() ->withCreatures() ->delete('/api/1.0/campaigns/1/creatures/1') ->assertStatus(204); it('DELETES an invalid creature') ->asUser() ->withCampaign() ->withCreatures() ->delete('/api/1.0/campaigns/1/creatures/100') ->assertStatus(404); it('can GET a creature as a player') ->asUser() ->withCampaign() ->withCreatures() ->asPlayer() ->get('/api/1.0/campaigns/1/creatures/1') ->assertStatus(200); /** * This example showcases building a custom function in the test to avoid polluting the TestCase file with lots of * on-off function calls. */ it('can\'t GET a private creature as a player', function () { $this->asUser() ->withCampaign(); Creature::factory() ->count(5) ->create(['campaign_id' => 1, 'is_private' => true]); $this->asPlayer(); $response = $this->get('/api/1.0/campaigns/1/creatures/1'); expect($response->status()) ->toBe(403); }); ================================================ FILE: tests/Feature/Entities/DiceRollTest.php ================================================ asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/dice_rolls', []) ->assertStatus(422); it('POSTS a new dice_roll') ->asUser() ->withCampaign() ->postJson('/api/1.0/campaigns/1/dice_rolls', [ 'name' => fake()->name(), 'parameters' => '2d2', ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all dice_rolls') ->asUser() ->withCampaign() ->withDiceRolls() ->get('/api/1.0/campaigns/1/dice_rolls') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific dice_roll') ->asUser() ->withCampaign() ->withDiceRolls() ->get('/api/1.0/campaigns/1/dice_rolls/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid dice_roll') ->asUser() ->withCampaign() ->withDiceRolls() ->putJson('/api/1.0/campaigns/1/dice_rolls/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('UPDATES a valid dice_roll without a name') ->asUser() ->withCampaign() ->withDiceRolls() ->putJson('/api/1.0/campaigns/1/dice_rolls/1', ['parameters' => '1d2']) ->assertStatus(200) ->assertJsonFragment(['parameters' => '1d2']); it('DELETES a dice_roll') ->asUser() ->withCampaign() ->withDiceRolls() ->delete('/api/1.0/campaigns/1/dice_rolls/1') ->assertStatus(204); it('DELETES an invalid dice_roll') ->asUser() ->withCampaign() ->withDiceRolls() ->delete('/api/1.0/campaigns/1/dice_rolls/100') ->assertStatus(404); it('can GET a dice_roll as a player') ->asUser() ->withCampaign() ->withDiceRolls() ->asPlayer() ->get('/api/1.0/campaigns/1/dice_rolls/1') ->assertStatus(200); /** * This example showcases building a custom function in the test to avoid polluting the TestCase file with lots of * on-off function calls. */ it('can\'t GET a private dice_roll as a player', function () { $this->asUser() ->withCampaign(); DiceRoll::factory() ->count(5) ->create(['campaign_id' => 1, 'is_private' => true]); $this->asPlayer(); $response = $this->get('/api/1.0/campaigns/1/dice_rolls/1'); expect($response->status()) ->toBe(403); }); ================================================ FILE: tests/Feature/Entities/EntityAssetTest.php ================================================ asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/entity_assets', []) ->assertStatus(422); it('POSTS a new Alias') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/entity_assets', [ 'name' => fake()->name(), 'entity_id' => 1, 'type_id' => 3, 'visibility_id' => 1, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('POSTS a new File') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/entity_assets', [ 'name' => fake()->name(), // 'entity_id' => 1, 'type_id' => 1, 'visibility_id' => 1, 'file' => UploadedFile::fake()->image('avatar.jpg'), ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('POSTS a new Link') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/entity_assets', [ 'name' => fake()->name(), 'entity_id' => 1, 'type_id' => 2, 'visibility_id' => 1, 'metadata' => [ 'url' => 'https://www.google.com', 'icon' => 'fa-solid fa-towers', ], ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all entity_assets') ->asUser() ->withCampaign() ->withCharacters() ->withAssets() ->get('/api/1.0/campaigns/1/entities/1/entity_assets') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific asset') ->asUser() ->withCampaign() ->withCharacters() ->withAssets() ->get('/api/1.0/campaigns/1/entities/1/entity_assets/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid asset') ->asUser() ->withCampaign() ->withCharacters() ->withAssets() ->putJson('/api/1.0/campaigns/1/entities/1/entity_assets/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('DELETES an asset') ->asUser() ->withCampaign() ->withCharacters() ->withAssets() ->delete('/api/1.0/campaigns/1/entities/1/entity_assets/1') ->assertStatus(204); it('DELETES an invalid asset') ->asUser() ->withCampaign() ->withCharacters() ->withAssets() ->delete('/api/1.0/campaigns/1/entities/1/entity_assets/100') ->assertStatus(404); ================================================ FILE: tests/Feature/Entities/EntityAttributeTest.php ================================================ asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/attributes', []) ->assertStatus(422); it('POSTS a new attribute') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/attributes', [ 'name' => fake()->name(), 'type_id' => 1, 'api_key' => '1', 'value' => 'Entity: [entity:2]', 'is_hidden' => 0, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all attributes') ->asUser() ->withCampaign() ->withCharacters() ->withAttributes() ->get('/api/1.0/campaigns/1/entities/1/attributes') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific attribute') ->asUser() ->withCampaign() ->withCharacters() ->withAttributes() ->get('/api/1.0/campaigns/1/entities/1/attributes/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid attribute') ->asUser() ->withCampaign() ->withCharacters() ->withAttributes() ->putJson('/api/1.0/campaigns/1/entities/1/attributes/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('UPDATES a valid attribute without a name') ->asUser() ->withCampaign() ->withCharacters() ->withAttributes() ->putJson('/api/1.0/campaigns/1/entities/1/attributes/1', ['value' => 'Magic']) ->assertStatus(200) ->assertJsonFragment(['value' => 'Magic']); it('DELETES an attribute') ->asUser() ->withCampaign() ->withCharacters() ->withAttributes() ->delete('/api/1.0/campaigns/1/entities/1/attributes/1') ->assertStatus(204); it('DELETES an invalid attribute') ->asUser() ->withCampaign() ->withCharacters() ->withAttributes() ->delete('/api/1.0/campaigns/1/entities/1/attributes/100') ->assertStatus(404); ================================================ FILE: tests/Feature/Entities/EntityDefaultThumbnailTest.php ================================================ asUser(true) ->withCampaign(['boost_count' => 1]) ->postJson('/api/1.0/campaigns/1/default-thumbnails', [ 'entity_type_id' => 2, 'default_entity_image' => UploadedFile::fake()->image('avatar.jpg'), ]) ->assertJsonFragment(['data' => 'Default thumbnail successfully uploaded']); it('GETS all default thumbnails') ->asUser(true) ->withCampaign(['boost_count' => 1, 'default_images' => ['characters' => '16598f1b-7d93-36d9-bea5-212bfa1e354b']]) ->withImages() ->get('/api/1.0/campaigns/1/default-thumbnails') ->assertStatus(200); it('DELETES a default thumbnail') ->asUser(true) ->withCampaign(['boost_count' => 1, 'default_images' => ['characters' => '16598f1b-7d93-36d9-bea5-212bfa1e354b']]) ->withImages() ->delete('/api/1.0/campaigns/1/default-thumbnails', ['entity_type_id' => 1]) ->assertJsonFragment(['data' => 'Default thumbnail successfully deleted']); ================================================ FILE: tests/Feature/Entities/EntityEventTest.php ================================================ asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/reminders', []) ->assertStatus(422); it('POSTS a new entity event') ->asUser() ->withCampaign() ->withCharacters() ->withCalendars() ->postJson('/api/1.0/campaigns/1/entities/1/reminders', [ 'calendar_id' => 1, 'day' => 2, 'month' => 2, 'year' => 2, 'length' => 2, 'visibility_id' => 1, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'calendar_id', ], ]); it('GETS all reminders') ->asUser() ->withCampaign() ->withCharacters() ->withCalendars() ->withReminders() ->get('/api/1.0/campaigns/1/entities/1/reminders') ->assertStatus(200) ->assertJsonFragment([ 'id' => 1, ]); it('GETS a specific entity event') ->asUser() ->withCampaign() ->withCharacters() ->withCalendars() ->withReminders() ->get('/api/1.0/campaigns/1/entities/1/reminders/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'calendar_id', ], ]); it('UPDATES a valid entity event') ->asUser() ->withCampaign() ->withCharacters() ->withCalendars() ->withReminders() ->putJson('/api/1.0/campaigns/1/entities/1/reminders/1', ['length' => 2]) ->assertStatus(200) ->assertJsonFragment(['length' => 2]); it('DELETES an entity event') ->asUser() ->withCampaign() ->withCharacters() ->withCalendars() ->withReminders() ->delete('/api/1.0/campaigns/1/entities/1/reminders/1') ->assertStatus(204); it('DELETES an invalid entity event') ->asUser() ->withCampaign() ->withCharacters() ->withCalendars() ->withReminders() ->delete('/api/1.0/campaigns/1/entities/1/reminders/100') ->assertStatus(404); ================================================ FILE: tests/Feature/Entities/EntityPostTest.php ================================================ asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/posts', []) ->assertStatus(422); it('POSTS a new post') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/posts', [ 'name' => fake()->name(), 'entity_id' => 1, 'position' => 1, 'entry' => 'Entity: [entity:2]', 'is_template' => false, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', ], ]); it('GETS all posts') ->asUser() ->withCampaign() ->withCharacters() ->withPosts() ->get('/api/1.0/campaigns/1/entities/1/posts') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'entity_id', 'name', 'is_private', ], ], ]); it('GETS a specific post') ->asUser() ->withCampaign() ->withCharacters() ->withPosts() ->get('/api/1.0/campaigns/1/entities/1/posts/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'is_private', ], ]); it('UPDATES a valid post') ->asUser() ->withCampaign() ->withCharacters() ->withPosts() ->putJson('/api/1.0/campaigns/1/entities/1/posts/1', ['name' => 'Bob']) ->assertStatus(200) ->assertJsonFragment(['name' => 'Bob']); it('UPDATES a valid post without a name') ->asUser() ->withCampaign() ->withCharacters() ->withPosts() ->putJson('/api/1.0/campaigns/1/entities/1/posts/1', ['position' => 2]) ->assertStatus(200) ->assertJsonFragment(['position' => 2]); it('DELETES an post') ->asUser() ->withCampaign() ->withCharacters() ->withPosts() ->delete('/api/1.0/campaigns/1/entities/1/posts/1') ->assertStatus(204); it('DELETES an invalid post') ->asUser() ->withCampaign() ->withCharacters() ->withPosts() ->delete('/api/1.0/campaigns/1/entities/1/posts/100') ->assertStatus(404); ================================================ FILE: tests/Feature/Entities/EntityRelationTest.php ================================================ asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/relations', []) ->assertStatus(422); it('POSTS a new relation') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/relations', [ 'relation' => fake()->text(20), 'owner_id' => 1, 'target_id' => 2, 'two_way' => 0, 'is_pinned' => 0, 'visibility_id' => 1, ]) ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'owner_id', ], ], ]); it('GETS all relations') ->asUser() ->withCampaign() ->withCharacters() ->withRelations() ->get('/api/1.0/campaigns/1/entities/1/relations') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'id', 'owner_id', ], ], ]); it('GETS a specific relation') ->asUser() ->withCampaign() ->withCharacters() ->withRelations() ->get('/api/1.0/campaigns/1/entities/1/relations/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'owner_id', ], ]); it('UPDATES a valid relation') ->asUser() ->withCampaign() ->withCharacters() ->withRelations() ->putJson('/api/1.0/campaigns/1/entities/1/relations/1', ['attitude' => 100]) ->assertStatus(200) ->assertJsonFragment(['attitude' => 100]); it('DELETES a relation') ->asUser() ->withCampaign() ->withCharacters() ->withRelations() ->delete('/api/1.0/campaigns/1/entities/1/relations/1') ->assertStatus(204); it('DELETES an invalid relation') ->asUser() ->withCampaign() ->withCharacters() ->withRelations() ->delete('/api/1.0/campaigns/1/entities/1/relations/100') ->assertStatus(404); ================================================ FILE: tests/Feature/Entities/EntityTagTest.php ================================================ asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/entities/1/entity_tags', []) ->assertStatus(422); it('POSTS a new entity_tag') ->asUser() ->withCampaign() ->withCharacters() ->withTags() ->postJson('/api/1.0/campaigns/1/entities/1/entity_tags', [ // 'entity_id' => 1, 'tag_id' => 1, ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'tag_id', ], ]); it('GETS all entity_tags') ->asUser() ->withCampaign() ->withCharacters() ->withTags() ->withEntityTags() ->get('/api/1.0/campaigns/1/entities/1/entity_tags') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ [ 'tag_id', ], ], ]); it('GETS a specific entity_tag') ->asUser() ->withCampaign() ->withCharacters() ->withTags() ->withEntityTags() ->get('/api/1.0/campaigns/1/entities/1/entity_tags/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'tag_id', ], ]); it('UPDATES a valid entity_tag') ->asUser() ->withCampaign() ->withCharacters() ->withTags() ->withEntityTags() ->putJson('/api/1.0/campaigns/1/entities/1/entity_tags/1', ['tag_id' => 2]) ->assertStatus(200) ->assertJsonFragment(['tag_id' => 2]); it('DELETES an entity_tag') ->asUser() ->withCampaign() ->withCharacters() ->withTags() ->withEntityTags() ->delete('/api/1.0/campaigns/1/entities/1/entity_tags/1') ->assertStatus(204); it('DELETES an invalid entity_tag') ->asUser() ->withCampaign() ->withCharacters() ->withTags() ->withEntityTags() ->delete('/api/1.0/campaigns/1/entities/1/entity_tags/100') ->assertStatus(404); // For characters, create a character with 1 tag, and make sure it's in the returned json it('POSTS a new character with 1 tag') ->asUser() ->withCampaign() ->withTags() ->postJson('/api/1.0/campaigns/1/characters', [ 'name' => fake()->name(), 'tags' => [1], ]) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'tags', ], ]) ->assertJsonFragment([ 'tags' => [1], ]); // Create a character with two tags in the factory. // Update the character with one of those tags and a third new tag. // The result contains one of the original tags + the new tag. it('PUT the character with 2 tags') ->asUser() ->withCampaign() ->withCharacterTags() ->putJson('/api/1.0/campaigns/1/characters/1', [ 'tags' => [1, 3], 'name' => fake()->name(), ]) ->assertJsonStructure([ 'data' => [ 'id', 'entity_id', 'tags', ], ]) ->assertJsonFragment([ 'tags' => [1, 3], ]); // Create a character with two tags, // one of the tags is private. // Get the character asPlayer() and validate that the private tag isn't visible it('POSTS a new character with a private tag') ->asUser() ->withCampaign() ->withPrivateCharacterTags() ->asPlayer() ->get('/api/1.0/campaigns/1/characters/1') ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'entity_id', 'tags', ], ]) ->assertJsonFragment([ 'tags' => [1], ]); ================================================ FILE: tests/Feature/Entities/EntityTest.php ================================================ asUser() ->withCampaign() ->withCharacters() ->withCreatures() ->get('/api/1.0/campaigns/1/entities') ->assertStatus(200); it('GETS a specific entity') ->asUser() ->withCampaign() ->withCharacters() ->withCreatures() ->get('/api/1.0/campaigns/1/entities/1') ->assertStatus(200); it('GETS all creatures') ->asUser() ->withCampaign() ->withCharacters() ->withCreatures() ->get('/api/1.0/campaigns/1/entities?types=creature') ->assertStatus(200) ->assertJsonCount(5, 'data'); it('Transforms entities') ->asUser() ->withCampaign() ->withCharacters() ->postJson('/api/1.0/campaigns/1/transform', [ 'entities' => [1, 2, 3], 'entity_type' => 'organisation', ]) ->assertJsonFragment(['success' => 'Succesfully transformed 3 entities.']) ->assertStatus(200); it('POSTS a new character with a mention and checks that a new entity is created', function () { $this->asUser() ->withCampaign(); $response = $this->postJson('/api/1.0/campaigns/1/characters', [ 'name' => fake()->name(), 'entry' => '[new:item|Mega sword]', ]); $this->assertStringStartsWith('

    in('Feature'); /* |-------------------------------------------------------------------------- | Expectations |-------------------------------------------------------------------------- | | When you're writing tests, you often need to check that values meet certain conditions. The | "expect()" function gives you access to a set of "expectations" methods that you can use | to assert different things. Of course, you may extend the Expectation API at any time. | */ expect()->extend('toBeOne', function () { return $this->toBe(1); }); /* |-------------------------------------------------------------------------- | Functions |-------------------------------------------------------------------------- | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your | project that you don't want to repeat in every file. Here you can also expose helpers as | global functions to help you to reduce the number of lines of code in your test files. | */ function something() { // .. } ================================================ FILE: tests/TestCase.php ================================================ create(); Passport::actingAs( $user, ['*'] ); if ($subscribed) { // Add the subscriber role $user->roles()->syncWithoutDetaching([5]); // Add the subscription to the user level $user->pledge = 'Elemental'; $user->save(); $sub = new Subscription; $sub->user_id = $user->id; $sub->type = 'kanka'; $sub->stripe_id = 'manual_sub_' . uniqid(); $sub->stripe_status = 'canceled'; $sub->stripe_price = 'paypal_' . $user->pledge; $sub->quantity = 1; $sub->ends_at = Carbon::now()->addYear(); $sub->save(); } return $this; } public function asPlayer(): self { $user2 = User::factory()->create(); Passport::actingAs( $user2, ['*'] ); $this->isPlayer = true; CampaignUser::create([ 'campaign_id' => 1, 'user_id' => $user2->id, ]); CampaignRoleUser::create([ 'campaign_role_id' => 3, 'user_id' => $user2->id, ]); return $this; } public function withMember(): self { $user3 = User::factory()->create(); CampaignUser::create([ 'campaign_id' => 1, 'user_id' => $user3->id, ]); CampaignRoleUser::create([ 'campaign_role_id' => 3, 'user_id' => $user3->id, ]); return $this; } public function withCampaign(array $extra = []): self { $this->seed(VisibilitiesTableSeeder::class); $this->seed(EntityTypesTableSeeder::class); Storage::fake('s3'); $campaign = Campaign::factory()->create($extra + ['slug' => 'test-campaign']); CampaignLocalization::forceCampaign($campaign); CampaignUser::create([ 'campaign_id' => 1, 'user_id' => 1, ]); CampaignRole::create([ 'campaign_id' => $campaign->id, 'name' => __('campaigns.members.roles.owner'), 'is_admin' => true, ]); $readOnlyRoles = []; $readOnlyRoles[] = CampaignRole::create([ 'campaign_id' => $campaign->id, 'name' => __('campaigns.members.roles.public'), 'is_public' => true, ]); $readOnlyRoles[] = CampaignRole::create([ 'campaign_id' => $campaign->id, 'name' => __('campaigns.members.roles.player'), ]); $entityTypes = EntityType::default()->get(); foreach ($readOnlyRoles as $readOnlyRole) { foreach ($entityTypes as $entityType) { CampaignPermission::create([ 'campaign_role_id' => $readOnlyRole->id, 'access' => true, 'action' => CampaignPermission::ACTION_READ, 'entity_type_id' => $entityType->id, ]); } } CampaignRoleUser::create([ 'campaign_role_id' => 1, 'user_id' => 1, ]); $setting = new CampaignSetting([ 'campaign_id' => $campaign->id, 'dice_rolls' => 0, 'conversations' => 0, ]); $setting->save(); EntityCache::campaign($campaign); CampaignCache::campaign($campaign); UserCache::campaign($campaign); Mentions::campaign($campaign); Module::campaign($campaign); QuestCache::campaign($campaign); BookmarkCache::campaign($campaign); EntityAssetCache::campaign($campaign); TimelineElementCache::campaign($campaign); CharacterCache::campaign($campaign); MapMarkerCache::campaign($campaign); Avatar::campaign($campaign); return $this; } public function withCampaigns(array $extra = []): self { Campaign::factory()->create($extra + ['slug' => 'test-campaign-2']); CampaignUser::create([ 'campaign_id' => 2, 'user_id' => 1, ]); CampaignRole::create([ 'campaign_id' => 2, 'name' => __('campaigns.members.roles.owner'), 'is_admin' => true, ]); CampaignRoleUser::create([ 'campaign_role_id' => 4, 'user_id' => 1, ]); $setting = new CampaignSetting([ 'campaign_id' => 2, 'dice_rolls' => 0, 'conversations' => 0, ]); $setting->save(); return $this; } public function withPermissions(array $extra = []): self { /** @var RolePermissionService $service */ $service = app()->make(RolePermissionService::class); $service->role(CampaignRole::where('id', 3)->first())->entityType(EntityType::find(1))->toggle(1, 1); $service->role(CampaignRole::where('id', 3)->first())->entityType(EntityType::find(1))->toggle(1, 2); $service->role(CampaignRole::where('id', 3)->first())->entityType(EntityType::find(1))->toggle(1, 3); return $this; } public function withCampaignStyles(array $extra = []): self { CampaignStyle::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withCreatures(array $extra = []): self { Creature::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withCharacters(array $extra = []): self { Character::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withFamilies(array $extra = []): self { Family::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withLocations(array $extra = []): self { Location::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withOrganisations(array $extra = []): self { Organisation::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withItems(array $extra = []): self { Item::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withNotes(array $extra = []): self { Note::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withEvents(array $extra = []): self { Event::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withCalendars(array $extra = []): self { Calendar::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withRaces(array $extra = []): self { Race::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withQuestElements(array $extra = []): self { QuestElement::factory() ->count(5) ->create(['quest_id' => 1] + $extra); return $this; } public function withQuests(array $extra = []): self { Quest::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withJournals(array $extra = []): self { Journal::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withTags(array $extra = []): self { Tag::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withAbilities(array $extra = []): self { Ability::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withTimelines(array $extra = []): self { Timeline::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withTimelineEras(array $extra = []): self { TimelineEra::factory() ->count(5) ->create(['timeline_id' => 1] + $extra); return $this; } public function withTimelineElements(array $extra = []): self { TimelineElement::factory() ->count(5) ->create(['timeline_id' => 1, 'era_id' => 1] + $extra); return $this; } public function withDiceRolls(array $extra = []): self { DiceRoll::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withConversations(array $extra = []): self { Conversation::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withConversationParticipants(array $extra = []): self { ConversationParticipant::factory() ->count(5) ->create(['conversation_id' => 1] + $extra); return $this; } public function withConversationMessages(array $extra = []): self { ConversationMessage::factory() ->count(5) ->create(['conversation_id' => 1] + $extra); return $this; } public function withBookmarks(array $extra = []): self { Bookmark::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withMaps(array $extra = []): self { Map::factory() ->count(5) ->create(['campaign_id' => 1] + $extra); return $this; } public function withMapLayers(array $extra = []): self { MapLayer::factory() ->count(5) ->create(['map_id' => 1] + $extra); return $this; } public function withMapGroups(array $extra = []): self { MapGroup::factory() ->count(5) ->create(['map_id' => 1] + $extra); return $this; } public function withMapMarkers(array $extra = []): self { MapMarker::factory() ->count(5) ->create(['map_id' => 1] + $extra); return $this; } public function withAttributes(array $extra = []): self { Attribute::factory() ->count(5) ->create(['entity_id' => 1, 'type_id' => 1, 'api_key' => 1] + $extra); return $this; } public function withAssets(array $extra = []): self { EntityAsset::factory() ->count(5) ->create(['entity_id' => 1, 'type_id' => 3] + $extra); return $this; } public function withReminders(array $extra = []): self { Reminder::factory() ->count(5) ->create(['remindable_id' => 1, 'remindable_type' => 'App\Models\Entity'] + $extra); return $this; } public function withPosts(array $extra = []): self { Post::factory() ->count(5) ->create(['entity_id' => 1, 'is_template' => false] + $extra); return $this; } public function withRelations(array $extra = []): self { Relation::factory() ->count(5) ->create(['owner_id' => 1, 'target_id' => 2, 'campaign_id' => 1] + $extra); return $this; } public function withEntityTags(array $extra = []): self { EntityTag::factory() ->count(5) ->create($extra); return $this; } public function withDashboardWidgets(array $extra = []): self { CampaignDashboardWidget::factory() ->count(5) ->create(['campaign_id' => 1]); return $this; } public function withImages(array $extra = []): self { Image::factory() ->count(1) ->create(['campaign_id' => 1, 'id' => '16598f1b-7d93-36d9-bea5-212bfa1e354b'] + $extra); return $this; } public function withThumbnails(array $extra = []): self { Image::factory() ->count(1) ->create(['campaign_id' => 1] + $extra); return $this; } public function withCharacterTags(array $extra = []): self { Character::factory() ->count(1) ->create(['campaign_id' => 1]); Tag::factory() ->count(3) ->create(['campaign_id' => 1] + $extra); EntityTag::factory()->count(2)->state( new Sequence( ['tag_id' => 1, 'entity_id' => 1], ['tag_id' => 2, 'entity_id' => 1], ) )->create(); return $this; } public function withPrivateCharacterTags(array $extra = []): self { Character::factory() ->count(1) ->create(['campaign_id' => 1, 'is_private' => false]); Tag::factory() ->count(2) ->state( new Sequence( ['campaign_id' => 1], ['campaign_id' => 1, 'is_private' => true] ) ) ->create(); EntityTag::factory()->count(2)->state( new Sequence( ['tag_id' => 1, 'entity_id' => 1], ['tag_id' => 2, 'entity_id' => 1], ) )->create(); return $this; } } ================================================ FILE: vite.config.js ================================================ import { defineConfig } from 'vite'; import laravel from 'laravel-vite-plugin'; import vue from '@vitejs/plugin-vue'; import tailwindcss from '@tailwindcss/vite'; import { viteStaticCopy } from 'vite-plugin-static-copy'; export default defineConfig({ server: { //host: '172.18.0.6', hmr: { host: 'kanka.test', }, watch: { ignored: [ '**/bootstrap/**', '**/database/**', '**/docs/**', '**/lang/**', '**/node_modules/**', '**/public/**', '**/resources/api-docs/**', '**/resources/assets/**', '**/routes/**', '**/storage/**', '**/storage/debugbar/**', '**/tests/**', '**/vendor/**', ], }, }, plugins: [ laravel({ input: [ 'resources/js/app.js', 'resources/js/auth.js', 'resources/js/location/map-v3.js', 'resources/js/attributes.js', 'resources/js/abilities.js', 'resources/js/story.js', 'resources/js/conversation.js', 'resources/js/subscription.js', 'resources/js/billing.js', 'resources/js/forms/character.js', 'resources/js/forms/calendar.js', 'resources/js/dashboard.js', 'resources/js/front.js', 'resources/js/settings.js', 'resources/js/profile.js', 'resources/js/cookieconsent.js', 'resources/js/relations.js', 'resources/js/recovery/recovery.js', 'resources/js/gallery/gallery.js', 'resources/js/history.js', 'resources/js/whiteboards.js', 'resources/js/connections/web.js', 'resources/js/editors/summernote.js', 'resources/js/editors/tiptap/index.js', 'resources/js/family-tree-vue.js', 'resources/js/entities/explore.js', 'resources/js/attributes-manager.js', 'resources/js/campaigns/theme-builder.js', 'resources/js/campaigns/import.js', 'resources/css/app.css', 'resources/css/vendors/tinymce.css', 'resources/css/maps/maps.css', 'resources/css/subscription.css', 'resources/css/front.css', 'resources/css/auth.css', 'resources/css/relations.css', 'resources/css/dashboard.css', 'resources/css/families/tree.css', 'resources/css/vendor.css', 'resources/css/themes/dark.css', 'resources/css/themes/midnight.css', 'resources/css/print/print.css', 'resources/js/vendor-final.js', ], refresh: true, }), vue({ template: { transformAssetUrls: { // The Vue plugin will re-write asset URLs, when referenced // in Single File Components, to point to the Laravel web // server. Setting this to `null` allows the Laravel plugin // to instead re-write asset URLs to point to the Vite // server instead. base: null, // The Vue plugin will parse absolute URLs and treat them // as absolute paths to files on disk. Setting this to // `false` will leave absolute URLs un-touched so they can // reference assets in the public directory as expected. includeAbsolute: false, }, }, }), tailwindcss(), viteStaticCopy({ targets: [ { src: 'node_modules/tinymce/**/*', dest: 'js/tinymce', }, { src: 'resources/vendor/tinymce/plugins/mention', dest: 'js/tinymce/plugins', }, { src: 'resources/vendor/tinymce/langs/*', dest: 'js/tinymce/langs', }, ], }), ], build: { rollupOptions: { output: { manualChunks(id) { if (id.includes('node_modules/@tiptap') || id.includes('node_modules/prosemirror') || id.includes('node_modules/@prosemirror')) { return 'vendor-tiptap' } }, }, }, }, resolve: { alias: { 'vue': 'vue/dist/vue.esm-bundler', }, }, });