Compare commits

...

182 Commits

Author SHA1 Message Date
  欧醚 20e797a51d fix permission 1 month ago
  欧醚 9a1e8d0f4b Merge tag 'v3.4.6' into cs+3.4.6 4 months ago
  欧醚 4390b676da 加强邮箱保护 4 months ago
  欧醚 a9554a1c9b 展示转嘟,替代 1d0ddd33b6 的临时措施 4 months ago
  Claire 93a6c143af
Fix insufficient sanitization of report comments (#17430) 4 months ago
  Claire bb7b2868a0 Bump version to 3.4.6 4 months ago
  Wonderfall a06dda41d0 disable legacy XSS filtering (#17289) 5 months ago
  Claire bf005edd30 Change mastodon:webpush:generate_vapid_key task to not require functional env (#17338) 5 months ago
  Claire df68d2eab8 Fix response_to_recipient? CTE 6 months ago
  Claire b27f50da5a Fix insufficient sanitization of report comments 4 months ago
  Claire e2009ced3a Fix compacted JSON-LD possibly causing compatibility issues on forwarding 4 months ago
  Puck Meerburg fe0210074f Compact JSON-LD signed incoming activities 5 months ago
  Claire c8dbbd60eb Fix error-prone SQL queries (#15828) 5 months ago
  Claire 6d831fe274
Fix spurious errors when receiving an Add activity for a private post (#17425) 4 months ago
  TA 0b7b25d17c remove unrendered HTML b tag from welcome email 10 months ago
  欧醚 eede6ca0c5 change version suffix to fix push notification 11 months ago
  欧醚 fa687d53a6 show toot chars limit and poll limit in instance api (for mobile app) 11 months ago
  欧醚 910de600d5 修改投票选项数上限 11 months ago
  Claire 2476d4f391 Fix user email address being banned on self-deletion (#16503) 11 months ago
  欧醚 34927adb74 兼容不开启闭社树 11 months ago
  欧醚 4db2290525 Merge tag 'v3.4.1' into closed-social-v3 1 year ago
  TA 5dd4f64b9f [fix] background image of THU 1 year ago
  欧醚 1d0ddd33b6 临时措施 1 year ago
  TA 3312ec5e9b strip for anonymous status 1 year ago
  TA 50101ad5f2 temporarily disable security code email 1 year ago
  TA 14630f3f48 fix hour format temporarily 1 year ago
  欧醚 fbf63eef29 Merge v3.3.0 into closed-social-v3 1 year ago
  欧醚 09c1e377a7 fix detail 1 year ago
  欧醚 d3625fa0f4 feat: 年度数据 fix some detail & 允许管理员查询其他人 1 year ago
  欧醚 fd8dd1498d feat: 年度数据统计 1 year ago
  欧醚 f0c6d72e60 配置参数说明 1 year ago
  欧醚 84d2e402d3 匿名每天刷新 1 year ago
  欧醚 a583987d76 Merge pull request 'fix bugs' (#5) from use-display-name-for-mention into closed-social-v3 1 year ago
  欧醚 c148e7a041 fix bugs 1 year ago
  欧醚 2b34c72fc4 Merge pull request 'use display_name for mention' (#4) from use-display-name-for-mention into closed-social-v3 1 year ago
  欧醚 ff0da1940b use display_name for mention 1 year ago
  欧醚 0d55e30e9e 修复界面bug 1 year ago
  欧醚 54cf68d8c6 增加前端邮箱正则限制 1 year ago
  欧醚 48cf6d253e make api/v1/instance public 1 year ago
  欧醚 04bfe40736 Merge remote-tracking branch 'origin/master' into closed-social-v3 1 year ago
  Junxi Song 3140a72fd7 Fixed the problem of fixed comments. 1 year ago
  欧醚 2cbee8feda 删除空格 1 year ago
  欧醚 53e44cc118 删除关注tag功能 1 year ago
  欧醚 eee3899b2c Merge remote-tracking branch 'origin/master' into closed-social-v3 1 year ago
  Junxi Song 4b80abced1 更新英文,台湾,香港表述# 1 year ago
  欧醚 bdb623f9eb 版本号后缀及默认仓库 1 year ago
  欧醚 98fe3af7b2 Merge branch 'master' into closed-social-v3 1 year ago
  欧醚 38632675b1 fix bugs 1 year ago
  欧醚 aa139841e5 恢复界面 1 year ago
  欧醚 b33a2295a9 Merge branch 'master' into closed-social-v3 1 year ago
  欧醚 f4e504e1fb 修复注册页面的显示 1 year ago
  欧醚 445ff0ed5f Merge branch 'master' into closed-social-v3 1 year ago
  欧醚 3b950848ca Merge branch 'master' into closed-social-v3 1 year ago
  欧醚 b6e7399e9e Merge tag 'v3.2.0' into closed-social-v3 1 year ago
  欧醚 7a9863cef7 中文搜索使用ik分词器 1 year ago
  欧醚 2a780b1118 og:image 允许不规范的 image/jpg (兼容网易云音乐) 1 year ago
  欧醚 c96e2c1d8f 跨站栏显示热门 1 year ago
  欧醚 57f5128fc6 fix bug (static variables are not thread safe) 1 year ago
  欧醚 d67cd9a573 微调界面 1 year ago
  欧醚 1b666873a2 允许whitelist_mode显示更多内容 1 year ago
  欧醚 c4897a3157 Merge branch 'master' into closed-social-v3 1 year ago
  欧醚 89bc52e250 微调about界面 1 year ago
  欧醚 bd9607f5c6 preview card 兼容有重复content-type头的情况 1 year ago
  欧醚 478efa8bf9 fix bug 1 year ago
  欧醚 20e0c1f803 重新实现了获取context 1 year ago
  欧醚 8ee92eab43 优化UI 1 year ago
  欧醚 a93e09ed9d 缩短加载评论延时,对所有嘟文加载评论 1 year ago
  欧醚 d39498f027 删除了所有关于清华邮箱延迟的提醒 1 year ago
  欧醚 0646238fed fix bug of mastodon(?): add :ssl in smtp setting 1 year ago
  欧醚 3dfac8387d fix bugs && 匿名可禁言 1 year ago
  欧醚 747ac90294 fix bug && 取消不必要的修改 1 year ago
  欧醚 57c905a689 minor change 1 year ago
  欧醚 6e1eb19ae0 删除硬编码ws地址 1 year ago
  欧醚 192657c54b 标记匿名标签刷新 超过一天未使用自动刷新 1 year ago
  欧醚 32cb3aa8d9 闭社树树根帐号可配置 对所有嘟文提供树形结构 1 year ago
  欧醚 720f8fc9d1 fix bug 1 year ago
  欧醚 95ff45bb7d 更好的匿名 1 year ago
  欧醚 cdf0e96549 优化了markdown外链图片的显示格式 1 year ago
  欧醚 24d6a043e5 删除markdown库,修改linkify实现嘟文/公告支持markdown语法的链接,彻底清理置顶栏 1 year ago
  欧醚 3149fbd706 删除置顶栏目,公告使用markdown 1 year ago
  欧醚 c247330f05 Merge branch 'master' of https://github.com/tootsuite/mastodon into closed-social-v3 1 year ago
  欧醚 3cc3992d1d 删除orig 修改细节 匿名可配置 1 year ago
  欧醚 9312eab8de fix bug && 小改动 1 year ago
  欧醚 d5cc1beb73 Merge branch 'master' into closed-social-v3 1 year ago
  欧醚 7f54b403de 细节 2 years ago
  欧醚 4eb82a685f fix bugs 2 years ago
  欧醚 2a094835e9 清华紫主题恢复显示吉祥物 2 years ago
  欧醚 0b5b741e32 jump 处理参数 2 years ago
  欧醚 a0c0005808 fix bugs 2 years ago
  欧醚 bc3df81610 修改jump跳转 2 years ago
  欧醚 09688a0535 Merge branch 'closed-social-v3' of github.com:closed-social/mastodon into closed-social-v3 2 years ago
  欧醚 d7400b4d34 /jump 登陆后跳转功能 2 years ago
  欧醚 ca716e07ef mask不同天使用不同的编码规则 2 years ago
  欧醚 b323bc8e15 默认白名单模式,修改apple icon 2 years ago
  欧醚 97cfee7886 白名单模式下仍然提供about about/more 页面 2 years ago
  欧醚 b39c079cec 匿名使用汉字编号 2 years ago
  欧醚 7848876404 每日刷新匿名编号 2 years ago
  欧醚 459942e2ea 允许任何时候匿名 2 years ago
  欧醚 bb9c5f822e 匿名评论有简单数字代号 2 years ago
  欧醚 ac744cd462 取消错误的修改(直接使用mastodon的白名单模式控制权限),针对微信内置浏览器跳转 2 years ago
  欧醚 dbd802d13b 修改闭社树上内容转嘟的引用显示 2 years ago
  欧醚 29c865dbc8 fix bugs 2 years ago
  欧醚 aeb8046cb5 fix bugs;闭社树按时间倒序 2 years ago
  欧醚 c608897a29 Merge branch 'closed-social-v3' of github.com:closed-social/mastodon into closed-social-v3 2 years ago
  欧醚 4f1f2540a4 评论预览显示两层,微调显示界面 2 years ago
  欧醚 3197fd21e4 修复修改邮箱时不检查白名单的问题 2 years ago
  欧醚 40533e194f 调整了左右滑动的阈值 2 years ago
  欧醚 0efe1c54e9 为ws单独指定域名(解决CDN不支持ws的问题),放宽api频率 2 years ago
  欧醚 d00d6415fe Fix reuse of detailed status components 2 years ago
  欧醚 4103b2a466 重新整理了评论/回复图标及文字 2 years ago
  欧醚 e701ceb033 删除了旧的点赞动画 2 years ago
  欧醚 2bb0aaa47c Merge branch 'closed-social-v3' of https://github.com/closed-social/mastodon into closed-social-v3 2 years ago
  欧醚 e389037482 修改了喜欢的图标颜色和动画 2 years ago
  欧醚 33e1c8680e 闭社树根节点只加载一层 2 years ago
  欧醚 6bd0848a0b 修改frame规则 2 years ago
  欧醚 dd16a8f4ac 描述为https链接的的图片显示为iframe 2 years ago
  欧醚 604cffbeed fix bugs 2 years ago
  欧醚 112a3f0754 时间轴只显示对自己的转嘟 2 years ago
  欧醚 0414dccfe9 闭社树每次只加载两层 2 years ago
  欧醚 ec25fe716e 不对不可转嘟问显示转嘟数,private嘟文强制请求context 2 years ago
  欧醚 50439d7218 延时自动加载评论,不再需要鼠标经过/点击 2 years ago
  欧醚 f8a2a10284 显示引用以实现转评 2 years ago
  欧醚 78bacf9626 取消热门tag门槛,清华邮箱的额外提醒信息 2 years ago
  欧醚 49a2a2ef96 修复bug 2 years ago
  欧醚 dc6403acac 为高级web模式设置置顶栏 2 years ago
  欧醚 0dbbca12c0 修改文字,解决previewcard被反爬虫的问题,增长Timeout 2 years ago
  欧醚 56f55defc2 修改了移动应用、指南(文档)的链接,修改了消息图标 2 years ago
  欧醚 7a02ce51be 修改了评论预览的结构和显示方式(增加了wrapper) 2 years ago
  欧醚 774235c1f5 修改了评论预览的展开方式 2 years ago
  欧醚 08e12eadaf 修改默认头像,修改style 2 years ago
  欧醚 36ec3f043c 修改style,置顶栏可关闭 2 years ago
  欧醚 b9541d6adc 修复style 2 years ago
  欧醚 be62d2550f 修复背景图显示 2 years ago
  欧醚 c4ff322c3a 新增主题,并迁移部分自定义css到主题 2 years ago
  欧醚 1c90891868 置顶栏支持富文本 2 years ago
  欧醚 d2d99f86dd 仅[pub]开头的嘟文站外可见 2 years ago
  欧醚 e5c6a02ae4 修复favicon, 为所有评论显示show thread 2 years ago
  欧醚 f9fe84914a 不在时间线显示自回复 2 years ago
  欧醚 c67bf258ab 修改图标 2 years ago
  欧醚 c2dc7566c1 置顶消息,回复API限制,修改闭社树地址配置方式 2 years ago
  欧醚 9b76f5bb43 优化了闭社树的显示 2 years ago
  欧醚 eae877d063 修改了评论预览的处理逻辑,移动端第一次点击加载评论预览,第二次点击打开嘟文 2 years ago
  欧醚 1db7d22058 修改后端 赞藏 -> 喜欢 2 years ago
  欧醚 3b326e3a55 字数限制 500 -> 5000 2 years ago
  欧醚 24859251dc 本站栏显示热门tag榜 2 years ago
  欧醚 6a4bfbf621 禁止非closed.social的mastodon实例 Warning:硬编码 2 years ago
  欧醚 a31aaeef04 change email info and force to use zh-CN 2 years ago
  欧醚 8fa6fcf097 仅在鼠标移过/手机按下时加载评论预览 2 years ago
  欧醚 062308c006 放宽了非认证api频率限制 2 years ago
  欧醚 a83a3e78fc 放宽了api频率限制 2 years ago
  欧醚 a04935f5c1 树洞评论匿名 2 years ago
  欧醚 66561200b5 show comments on timeline, and show right count when unfavourite/unreblog 2 years ago
  欧醚 3142db432d set default email domain in ENV 2 years ago
  欧醚 5b9cc9c93b change 赞藏 to 喜欢, and change logo_alt 2 years ago
  欧醚 718814bfbf give up pinned statuses, and add tree to navigation/tabs_bar, with funny way 2 years ago
  欧醚 713bf5509f add 闭社树 to navigation, with horrible way 2 years ago
  欧醚 40ea60e578 show reblogs on timeline 2 years ago
  欧醚 6dd757a495 change x and y of text on tree 2 years ago
  欧醚 4f2275c1e5 click the node on tree to go to the status 2 years ago
  欧醚 0a8bcfade5 fix bug for tree animate. Use statusId as keyPro 2 years ago
  欧醚 fb82f7b6ac now you can click nodes on 闭社树 2 years ago
  欧醚 2bb6238dc8 change size of tree graph 2 years ago
  欧醚 a8e4c3f5a2 now can draw 闭社树 2 years ago
  欧醚 6be4def2ec follow tags 2 years ago
  欧醚 0101dfcdfd fix bug 2 years ago
  欧醚 a5db5d45a2 fix bug 2 years ago
  欧醚 b0aadae668 show tree in className 2 years ago
  欧醚 f0ad5eaea7 pin statuses pinned by Account(1) 2 years ago
  欧醚 353b88eba0 require login for tag api 2 years ago
  欧醚 3fd2acbacb show depth and only show direct sons for 闭社树 2 years ago
  欧醚 457daf4353 better hide to unsigned user 2 years ago
  欧醚 60a876f39a change 收藏 to 赞藏, and hide wrong active_user_count 2 years ago
  欧醚 489c93f011 show comments on timeline, with ugly way 2 years ago
  欧醚 3c25a96aad show comments on timeline, with bugs 2 years ago
  欧醚 990d264f63 show fav/reb number 2 years ago
  欧醚 5eb86661ba set max trends number to 5 2 years ago
  欧醚 207b875ded hide account info to unsigned user 2 years ago
  欧醚 3f178bbdae change all icon of fav/rep 2 years ago
  欧醚 66919577e8 change some icon of fav/rep 2 years ago
  欧醚 c2c5c22ee6 fix bugs 2 years ago
  欧醚 79f21b2d8f fix favicon.ico 2 years ago
  欧醚 1abf9c6d92 merge changes 2 years ago
123 changed files with 2361 additions and 403 deletions
Split View
  1. +6
    -0
      .gitignore
  2. +12
    -0
      CHANGELOG.md
  3. +30
    -0
      CS_CONF.md
  4. +1
    -0
      app/chewy/statuses_index.rb
  5. +35
    -1
      app/controllers/about_controller.rb
  6. +2
    -2
      app/controllers/api/v1/instances/activity_controller.rb
  7. +2
    -2
      app/controllers/api/v1/instances/peers_controller.rb
  8. +1
    -1
      app/controllers/api/v1/instances_controller.rb
  9. +17
    -3
      app/controllers/api/v1/statuses_controller.rb
  10. +2
    -1
      app/controllers/api/v1/timelines/public_controller.rb
  11. +1
    -1
      app/controllers/home_controller.rb
  12. +7
    -1
      app/helpers/application_helper.rb
  13. +55
    -0
      app/helpers/context_helper.rb
  14. +10
    -2
      app/helpers/home_helper.rb
  15. +80
    -0
      app/helpers/jsonld_helper.rb
  16. +1
    -1
      app/javascript/images/logo.svg
  17. +1
    -1
      app/javascript/images/logo_alt.svg
  18. +5
    -1
      app/javascript/images/logo_full.svg
  19. +1
    -1
      app/javascript/images/logo_transparent.svg
  20. +1
    -1
      app/javascript/images/logo_transparent_black.svg
  21. BIN
     
  22. BIN
     
  23. +8
    -2
      app/javascript/mastodon/actions/interactions.js
  24. +4
    -1
      app/javascript/mastodon/actions/timelines.js
  25. +12
    -1
      app/javascript/mastodon/components/media_gallery.js
  26. +80
    -4
      app/javascript/mastodon/components/status.js
  27. +473
    -0
      app/javascript/mastodon/components/status2.js
  28. +7
    -6
      app/javascript/mastodon/components/status_action_bar.js
  29. +1
    -1
      app/javascript/mastodon/components/status_content.js
  30. +64
    -4
      app/javascript/mastodon/containers/status_container.js
  31. +168
    -0
      app/javascript/mastodon/containers/status_container2.js
  32. +2
    -2
      app/javascript/mastodon/features/compose/components/compose_form.js
  33. +1
    -1
      app/javascript/mastodon/features/compose/components/poll_form.js
  34. +16
    -1
      app/javascript/mastodon/features/compose/index.js
  35. +1
    -1
      app/javascript/mastodon/features/favourited_statuses/index.js
  36. +1
    -1
      app/javascript/mastodon/features/getting_started/components/trends.js
  37. +1
    -1
      app/javascript/mastodon/features/getting_started/index.js
  38. +2
    -2
      app/javascript/mastodon/features/notifications/components/filter_bar.js
  39. +1
    -1
      app/javascript/mastodon/features/notifications/components/notification.js
  40. +6
    -5
      app/javascript/mastodon/features/picture_in_picture/components/footer.js
  41. +9
    -6
      app/javascript/mastodon/features/status/components/action_bar.js
  42. +18
    -5
      app/javascript/mastodon/features/status/components/detailed_status.js
  43. +94
    -11
      app/javascript/mastodon/features/status/index.js
  44. +6
    -0
      app/javascript/mastodon/features/ui/components/columns_area.js
  45. +1
    -1
      app/javascript/mastodon/features/ui/components/link_footer.js
  46. +5
    -2
      app/javascript/mastodon/features/ui/components/navigation_panel.js
  47. +15
    -1
      app/javascript/mastodon/features/ui/components/tabs_bar.js
  48. +3
    -0
      app/javascript/mastodon/initial_state.js
  49. +5
    -2
      app/javascript/mastodon/locales/en.json
  50. +5
    -2
      app/javascript/mastodon/locales/zh-CN.json
  51. +11
    -8
      app/javascript/mastodon/locales/zh-HK.json
  52. +15
    -11
      app/javascript/mastodon/locales/zh-TW.json
  53. +4
    -0
      app/javascript/styles/application.scss
  54. +115
    -0
      app/javascript/styles/closed-social/global.scss
  55. +39
    -0
      app/javascript/styles/closed-social/timeline_comments.scss
  56. +65
    -0
      app/javascript/styles/closed-social/tree.scss
  57. +1
    -0
      app/javascript/styles/mastodon-light/variables.scss
  58. +2
    -1
      app/javascript/styles/mastodon/about.scss
  59. +1
    -1
      app/javascript/styles/mastodon/forms.scss
  60. +4
    -1
      app/javascript/styles/mastodon/variables.scss
  61. +3
    -0
      app/javascript/styles/thu.scss
  62. +221
    -0
      app/javascript/styles/thu/diff.scss
  63. +8
    -0
      app/javascript/styles/thu/variables.scss
  64. +1
    -1
      app/lib/activitypub/activity/add.rb
  65. +2
    -50
      app/lib/activitypub/adapter.rb
  66. +36
    -3
      app/lib/formatter.rb
  67. +1
    -1
      app/lib/search_query_transformer.rb
  68. +55
    -50
      app/models/account.rb
  69. +1
    -1
      app/models/preview_card.rb
  70. +4
    -6
      app/models/status.rb
  71. +2
    -2
      app/models/trending_tags.rb
  72. +3
    -2
      app/models/user.rb
  73. +1
    -0
      app/policies/user_policy.rb
  74. +2
    -0
      app/serializers/initial_state_serializer.rb
  75. +15
    -1
      app/serializers/rest/instance_serializer.rb
  76. +17
    -1
      app/services/activitypub/process_collection_service.rb
  77. +1
    -0
      app/services/fetch_link_card_service.rb
  78. +9
    -5
      app/services/notify_service.rb
  79. +1
    -1
      app/validators/blacklisted_email_validator.rb
  80. +1
    -1
      app/validators/poll_validator.rb
  81. +1
    -1
      app/validators/status_length_validator.rb
  82. +6
    -2
      app/views/about/_registration.html.haml
  83. +11
    -0
      app/views/about/jump.html.haml
  84. +55
    -0
      app/views/about/my_data.html.haml
  85. +2
    -5
      app/views/about/show.html.haml
  86. +3
    -5
      app/views/admin/accounts/show.html.haml
  87. +1
    -1
      app/views/admin/reports/show.html.haml
  88. +11
    -11
      app/views/admin/settings/edit.html.haml
  89. +6
    -2
      app/views/auth/registrations/new.html.haml
  90. +4
    -4
      app/views/follower_accounts/index.html.haml
  91. +4
    -4
      app/views/following_accounts/index.html.haml
  92. +1
    -1
      app/views/home/index.html.haml
  93. +0
    -0
     
  94. +2
    -2
      app/views/layouts/public.html.haml
  95. +3
    -3
      app/views/statuses/_detailed_status.html.haml
  96. +3
    -3
      app/views/statuses/_simple_status.html.haml
  97. +1
    -1
      app/views/statuses/show.html.haml
  98. +1
    -1
      chart/values.yaml
  99. +1
    -1
      config/application.rb
  100. +0
    -80
      config/brakeman.ignore

+ 6
- 0
.gitignore View File

@ -4,6 +4,8 @@
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
*.orig
# Ignore bundler config and downloaded libraries.
/.bundle
/vendor/bundle
@ -65,3 +67,7 @@ yarn-debug.log
# Ignore Docker option files
docker-compose.override.yml
# ctag
.ctags
tags

+ 12
- 0
CHANGELOG.md View File

@ -3,6 +3,18 @@ Changelog
All notable changes to this project will be documented in this file.
## [3.4.6] - 2022-02-03
### Fixed
- Fix `mastodon:webpush:generate_vapid_key` task requiring a functional environment ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17338))
- Fix spurious errors when receiving an Add activity for a private post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17425))
### Security
- Fix error-prone SQL queries ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15828))
- Fix not compacting incoming signed JSON-LD activities ([puckipedia](https://github.com/mastodon/mastodon/pull/17426), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/17428)) (CVE-2022-24307)
- Fix insufficient sanitization of report comments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17430))
- Fix stop condition of a Common Table Expression ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17427))
- Disable legacy XSS filtering ([Wonderfall](https://github.com/mastodon/mastodon/pull/17289))
## [3.4.5] - 2022-01-31
### Added
- Add more advanced migration tests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17393))

+ 30
- 0
CS_CONF.md View File

@ -0,0 +1,30 @@
# 闭社新增配置参数说明
## 匿名相关
闭社新增了匿名发言的功能,开启后,以特定标记结尾(注意不要有多余的回车或空格),发言者就会变成特定的帐号。
`ANON_TAG` 匿名标记,以这个结尾的嘟文就会成为匿名嘟文(本质只是修改了发言者)
`ANON_NAME_LIST` 匿名代号列表,一个文件名,文件内容的格式为每行一个名字。每个用户会随机取一个代号加在匿名嘟文的最前面。对应关系每日更新。
`ANON_ACC` 匿名帐号的id。匿名嘟文的发言者会变成这个帐号。
## 闭社树相关
闭社设计了一套闭社树功能。
`TREE_ADDRESS` 作为闭社树根节点的嘟文的路径(示例:`/statuses/102995298871197915`)
`TREE_ACC`建树帐号的id。跟节点发布自这个帐号的嘟文,在现实上会特殊处理。
## 邮件提示加强
考虑到一定会限制邮箱,闭社加强了邮件提示。
`EMAIL_DEFAULT_DOMAIN` 默认的邮箱后缀,会显示在注册界面并自动补全
`EMAIL_REGEX` 邮箱正则,在前端输入时检查邮箱格式是否正确,这主要是为了及时发现输错邮箱的情况,提升用户体验和减少对邮箱服务器的浪费。
* 注意: 邮箱正则只是一个纯前端的辅助校验,与后端的实际邮箱规则无关。有些时候需要用不公开的邮箱规则创建一些机器人帐号,用于匿名功能的匿名帐号/闭社树功能的建树机器人/管理员帐号/其他bot,邮箱正则会给注册带来不便,可以直接f12打开调试界面直接删除input元素中的正则限制,也可以前期先不使用邮箱正则功能,还可以直接在服务器上通过命令行创建帐号。

+ 1
- 0
app/chewy/statuses_index.rb View File

@ -58,6 +58,7 @@ class StatusesIndex < Chewy::Index
field :text, type: 'text', value: ->(status) { [status.spoiler_text, Formatter.instance.plaintext(status)].concat(status.media_attachments.map(&:description)).concat(status.preloadable_poll ? status.preloadable_poll.options : []).join("\n\n") } do
field :stemmed, type: 'text', analyzer: 'content'
field :chn , type: 'text', analyzer: 'ik_max_word', search_analyzer: 'ik_smart'
end
field :searchable_by, type: 'long', value: ->(status, crutches) { status.searchable_by(crutches) }

+ 35
- 1
app/controllers/about_controller.rb View File

@ -5,11 +5,12 @@ class AboutController < ApplicationController
layout 'public'
before_action :require_open_federation!, only: [:show, :more]
# before_action :require_open_federation!, only: [:show, :more]
before_action :set_body_classes, only: :show
before_action :set_instance_presenter
before_action :set_expires_in, only: [:more, :terms]
before_action :set_registration_form_time, only: :show
before_action :authenticate_user!, only: [:jump, :my_data]
skip_before_action :require_functional!, only: [:more, :terms]
@ -28,6 +29,39 @@ class AboutController < ApplicationController
def terms; end
def jump
@jump_url = "https://#{request.fullpath[6..-1]}"
end
def my_data
@uid = params[:user_id]
if @uid and current_account.user.admin
@account = Account.find(@uid)
else
@account = current_account
end
year = params[:year].to_i
year = nil unless year > 2000
@year_text = year or ''
y = year ? "statuses.created_at >= '#{year}-1-1' and statuses.created_at < '#{year+1}-1-1'" : nil
y2 = year ? "s2.created_at >= '#{year}-1-1' and s2.created_at < '#{year+1}-1-1'" : nil
yf = year ? "favourites.created_at >='#{year}-1-1' and favourites.created_at < '#{year+1}-1-1'" : nil
def raw_to_list(r)
r.map{|k,v| {:account => Account.find(k), :num => v.to_s}}
end
@total = @account.statuses.where(y).count
@most_times = @account.statuses.where(y).group('cast (created_at as date)').reorder('count_id desc').limit(1).count(:id).map{ |k,v| {:date => k.to_s, :num => v.to_s}}
@most_fav = @account.statuses.where(y).joins(:status_stat).reorder('status_stats.favourites_count desc').first
@like_me_most = raw_to_list(@account.statuses.where(yf).joins(:favourites).group('favourites.account_id').reorder('count_id desc').limit(5).count(:id))
@i_like_most = raw_to_list(@account.favourites.where(yf).joins(:status).group('statuses.account_id').reorder('count_id desc').limit(5).count(:id))
@communi_most = raw_to_list(@account.statuses.where(y).where(y2).joins('join statuses as s2 on statuses.account_id != s2.account_id and (statuses.in_reply_to_id = s2.id or s2.in_reply_to_id = statuses.id)').group('s2.account_id').reorder('count_id desc').limit(5).count(:id))
end
helper_method :display_blocks?
helper_method :display_blocks_rationale?
helper_method :public_fetch_mode?

+ 2
- 2
app/controllers/api/v1/instances/activity_controller.rb View File

@ -4,7 +4,7 @@ class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
skip_before_action :require_authenticated_user!#, unless: :whitelist_mode?
def show
expires_in 1.day, public: true
@ -33,6 +33,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
end
def require_enabled_api!
head 404 unless Setting.activity_api_enabled && !whitelist_mode?
head 404 unless Setting.activity_api_enabled #&& !whitelist_mode?
end
end

+ 2
- 2
app/controllers/api/v1/instances/peers_controller.rb View File

@ -4,7 +4,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
skip_before_action :require_authenticated_user!#, unless: :whitelist_mode?
def index
expires_in 1.day, public: true
@ -14,6 +14,6 @@ class Api::V1::Instances::PeersController < Api::BaseController
private
def require_enabled_api!
head 404 unless Setting.peers_api_enabled && !whitelist_mode?
head 404 unless Setting.peers_api_enabled #&& !whitelist_mode?
end
end

+ 1
- 1
app/controllers/api/v1/instances_controller.rb View File

@ -2,7 +2,7 @@
class Api::V1::InstancesController < Api::BaseController
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
skip_before_action :require_authenticated_user! #, unless: :whitelist_mode?
def show
expires_in 3.minutes, public: true

+ 17
- 3
app/controllers/api/v1/statuses_controller.rb View File

@ -24,7 +24,11 @@ class Api::V1::StatusesController < Api::BaseController
def context
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(CONTEXT_LIMIT, current_account)
descendants_results = @status.descendants(CONTEXT_LIMIT, current_account)
treeId = Rails.configuration.x.tree_address.split('/')[-1].to_i
depth = (@status.id == treeId || (!ancestors_results.empty? && ancestors_results[0].id == treeId)) ? 1 : nil
descendants_results = @status.descendants(CONTEXT_LIMIT, current_account, nil, nil, depth)
loaded_ancestors = cache_collection(ancestors_results, Status)
loaded_descendants = cache_collection(descendants_results, Status)
@ -35,8 +39,13 @@ class Api::V1::StatusesController < Api::BaseController
end
def create
@status = PostStatusService.new.call(current_user.account,
text: status_params[:status],
anon = Rails.configuration.x.anon
anon_name = anon.acc && status_params[:status].strip.end_with?(anon.tag) && generate_anon_name(current_user.account.username + anon.salt + (Time.now - 5.hours).strftime("%D"), anon.namelist, Account.find(anon.acc).note)
sender = anon_name ? Account.find(anon.acc) : current_user.account
st_text = anon_name ? ("[#{anon_name}]:\n#{status_params[:status]}"[0..5000]) : status_params[:status]
@status = PostStatusService.new.call(sender,
text: st_text,
thread: @thread,
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
@ -98,4 +107,9 @@ class Api::V1::StatusesController < Api::BaseController
def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params)
end
def generate_anon_name(k, namelist, note)
name = namelist[Digest::SHA2.hexdigest(k).to_i(16) % namelist.size]
name.in?(note) ? nil : name
end
end

+ 2
- 1
app/controllers/api/v1/timelines/public_controller.rb View File

@ -37,7 +37,8 @@ class Api::V1::Timelines::PublicController < Api::BaseController
current_account,
local: truthy_param?(:local),
remote: truthy_param?(:remote),
only_media: truthy_param?(:only_media)
only_media: truthy_param?(:only_media),
with_reblogs: true
)
end

+ 1
- 1
app/controllers/home_controller.rb View File

@ -41,7 +41,7 @@ class HomeController < ApplicationController
end
def default_redirect_path
if request.path.start_with?('/web') || whitelist_mode?
if request.path.start_with?('/web') # || whitelist_mode?
new_user_session_path
elsif single_user_mode?
short_account_path(Account.local.without_suspended.where('id > 0').first)

+ 7
- 1
app/helpers/application_helper.rb View File

@ -51,7 +51,8 @@ module ApplicationHelper
def available_sign_up_path
if closed_registrations?
'https://joinmastodon.org/#getting-started'
#'https://joinmastodon.org/#getting-started'
'https://closed.social'
else
new_user_registration_path
end
@ -87,6 +88,11 @@ module ApplicationHelper
policy(record).public_send("#{action}?")
end
def masked_email(email)
email_username = email.split('@').first
"#{email_username[0]}***#{email_username[-1]}@#{email.split('@').last}"
end
def fa_icon(icon, attributes = {})
class_names = attributes[:class]&.split(' ') || []
class_names << 'fa'

+ 55
- 0
app/helpers/context_helper.rb View File

@ -0,0 +1,55 @@
# frozen_string_literal: true
module ContextHelper
NAMED_CONTEXT_MAP = {
activitystreams: 'https://www.w3.org/ns/activitystreams',
security: 'https://w3id.org/security/v1',
}.freeze
CONTEXT_EXTENSION_MAP = {
manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' },
sensitive: { 'sensitive' => 'as:sensitive' },
hashtag: { 'Hashtag' => 'as:Hashtag' },
moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } },
also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } },
emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' },
featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' }, 'featuredTags' => { '@id' => 'toot:featuredTags', '@type' => '@id' } },
property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' },
atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' },
conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } },
identity_proof: { 'toot' => 'http://joinmastodon.org/ns#', 'IdentityProof' => 'toot:IdentityProof' },
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' },
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' },
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
}.freeze
def full_context
serialized_context(NAMED_CONTEXT_MAP, CONTEXT_EXTENSION_MAP)
end
def serialized_context(named_contexts_map, context_extensions_map)
context_array = []
named_contexts = named_contexts_map.keys
context_extensions = context_extensions_map.keys
named_contexts.each do |key|
context_array << NAMED_CONTEXT_MAP[key]
end
extensions = context_extensions.each_with_object({}) do |key, h|
h.merge!(CONTEXT_EXTENSION_MAP[key])
end
context_array << extensions unless extensions.empty?
if context_array.size == 1
context_array.first
else
context_array
end
end
end

+ 10
- 2
app/helpers/home_helper.rb View File

@ -7,7 +7,7 @@ module HomeHelper
}
end
def account_link_to(account, button = '', path: nil)
def account_link_to(account, button = '', path: nil, full: true)
content_tag(:div, class: 'account') do
content_tag(:div, class: 'account__wrapper') do
section = if account.nil?
@ -20,7 +20,7 @@ module HomeHelper
content_tag(:span, t('about.contact_unavailable'), class: 'display-name__account')
end
end
else
elsif full
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
content_tag(:div, class: 'account__avatar-wrapper') do
image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar')
@ -32,6 +32,14 @@ module HomeHelper
content_tag(:span, "@#{account.acct}", class: 'display-name__account')
end
end
else
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
content_tag(:span, class: 'display-name') do
content_tag(:bdi) do
content_tag(:strong, display_name(account, custom_emojify: true), class: 'display-name__html emojify')
end
end
end
end
section + button

+ 80
- 0
app/helpers/jsonld_helper.rb View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
module JsonLdHelper
include ContextHelper
def equals_or_includes?(haystack, needle)
haystack.is_a?(Array) ? haystack.include?(needle) : haystack == needle
end
@ -63,6 +65,84 @@ module JsonLdHelper
graph.dump(:normalize)
end
def compact(json)
compacted = JSON::LD::API.compact(json.without('signature'), full_context, documentLoader: method(:load_jsonld_context))
compacted['signature'] = json['signature']
compacted
end
# Patches a JSON-LD document to avoid compatibility issues on redistribution
#
# Since compacting a JSON-LD document against Mastodon's built-in vocabulary
# means other extension namespaces will be expanded, malformed JSON-LD
# attributes lost, and some values “unexpectedly” compacted this method
# patches the following likely sources of incompatibility:
# - 'https://www.w3.org/ns/activitystreams#Public' being compacted to
# 'as:Public' (for instance, pre-3.4.0 Mastodon does not understand
# 'as:Public')
# - single-item arrays being compacted to the item itself (`[foo]` being
# compacted to `foo`)
#
# It is not always possible for `patch_for_forwarding!` to produce a document
# deemed safe for forwarding. Use `safe_for_forwarding?` to check the status
# of the output document.
#
# @param original [Hash] The original JSON-LD document used as reference
# @param compacted [Hash] The compacted JSON-LD document to be patched
# @return [void]
def patch_for_forwarding!(original, compacted)
original.without('@context', 'signature').each do |key, value|
next if value.nil? || !compacted.key?(key)
compacted_value = compacted[key]
if value.is_a?(Hash) && compacted_value.is_a?(Hash)
patch_for_forwarding!(value, compacted_value)
elsif value.is_a?(Array)
compacted_value = [compacted_value] unless compacted_value.is_a?(Array)
return if value.size != compacted_value.size
compacted[key] = value.zip(compacted_value).map do |v, vc|
if v.is_a?(Hash) && vc.is_a?(Hash)
patch_for_forwarding!(v, vc)
vc
elsif v == 'https://www.w3.org/ns/activitystreams#Public' && vc == 'as:Public'
v
else
vc
end
end
elsif value == 'https://www.w3.org/ns/activitystreams#Public' && compacted_value == 'as:Public'
compacted[key] = value
end
end
end
# Tests whether a JSON-LD compaction is deemed safe for redistribution,
# that is, if it doesn't change its meaning to consumers that do not actually
# handle JSON-LD, but rely on values being serialized in a certain way.
#
# See `patch_for_forwarding!` for details.
#
# @param original [Hash] The original JSON-LD document used as reference
# @param compacted [Hash] The compacted JSON-LD document to be patched
# @return [Boolean] Whether the patched document is deemed safe
def safe_for_forwarding?(original, compacted)
original.without('@context', 'signature').all? do |key, value|
compacted_value = compacted[key]
return false unless value.class == compacted_value.class
if value.is_a?(Hash)
safe_for_forwarding?(value, compacted_value)
elsif value.is_a?(Array)
value.zip(compacted_value).all? do |v, vc|
v.is_a?(Hash) ? (vc.is_a?(Hash) && safe_for_forwarding?(v, vc)) : v == vc
end
else
value == compacted_value
end
end
end
def fetch_resource(uri, id, on_behalf_of = nil)
unless id
json = fetch_resource_without_id_validation(uri, on_behalf_of)

+ 1
- 1
app/javascript/images/logo.svg View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M211.80734 139.0875c-3.18125 16.36625-28.4925 34.2775-57.5625 37.74875-15.15875 1.80875-30.08375 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.39125 27.9425 21.11625.7225 39.91875-5.20625 39.91875-5.20625l.8675 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23234 213.82 1.40609 165.31125.20859 116.09125c-.365-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67234 3.45375 78.20359.2425 107.86484 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.975 14.7525 32.975 65.0825 0 0 .41375 37.13375-4.59875 62.915" fill="#3088d4"/><path d="M177.50984 80.077v60.94125h-24.14375v-59.15c0-12.46875-5.24625-18.7975-15.74-18.7975-11.6025 0-17.4175 7.5075-17.4175 22.3525v32.37625H96.20734V85.42325c0-14.845-5.81625-22.3525-17.41875-22.3525-10.49375 0-15.74 6.32875-15.74 18.7975v59.15H38.90484V80.077c0-12.455 3.17125-22.3525 9.54125-29.675 6.56875-7.3225 15.17125-11.07625 25.85-11.07625 12.355 0 21.71125 4.74875 27.8975 14.2475l6.01375 10.08125 6.015-10.08125c6.185-9.49875 15.54125-14.2475 27.8975-14.2475 10.6775 0 19.28 3.75375 25.85 11.07625 6.36875 7.3225 9.54 17.22 9.54 29.675" fill="#fff"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.484 71.644" fill="#2ea7e0"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></svg>

+ 1
- 1
app/javascript/images/logo_alt.svg View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.41507 232.00976"><path d="M211.80683 139.0875c-3.1825 16.36625-28.4925 34.2775-57.5625 37.74875-15.16 1.80875-30.0825 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.3925 27.9425 21.115.7225 39.91625-5.20625 39.91625-5.20625l.86875 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23183 213.82 1.40558 165.31125.20808 116.09125c-.36375-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67058 3.45375 78.20308.2425 107.86433 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.97625 14.7525 32.97625 65.0825 0 0 .4125 37.13375-4.6 62.915" fill="#3088d4"/><path d="M65.68743 96.45938c0 9.01375-7.3075 16.32125-16.3225 16.32125-9.01375 0-16.32-7.3075-16.32-16.32125 0-9.01375 7.30625-16.3225 16.32-16.3225 9.015 0 16.3225 7.30875 16.3225 16.3225M124.52893 96.45938c0 9.01375-7.30875 16.32125-16.3225 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.3225 7.30875 16.3225 16.3225M183.36933 96.45938c0 9.01375-7.3075 16.32125-16.32125 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.32125 7.30875 16.32125 16.3225" fill="#fff"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.484 71.644" fill="#2ea7e0"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></svg>

+ 5
- 1
app/javascript/images/logo_full.svg
File diff suppressed because it is too large
View File


+ 1
- 1
app/javascript/images/logo_transparent.svg View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg"><symbol id="mastodon-svg-logo" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" /></symbol></svg>
<svg xmlns="http://www.w3.org/2000/svg"><symbol id="mastodon-svg-logo" viewBox="0 0 57.484 71.644"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></symbol></svg>

+ 1
- 1
app/javascript/images/logo_transparent_black.svg View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M107.86523 0C78.203984.2425 49.672422 3.4535937 33.044922 11.089844c0 0-32.97656262 14.752031-32.97656262 65.082031 0 11.525-.224375 25.306175.140625 39.919925 1.19750002 49.22 9.02375002 97.72843 54.53124962 109.77343 20.9825 5.55375 38.99711 6.71547 53.505856 5.91797 26.31125-1.45875 41.08203-9.38867 41.08203-9.38867l-.86914-19.08984s-18.80171 5.92758-39.91796 5.20508c-20.921254-.7175-43.006879-2.25516-46.390629-27.94141-.3125-2.25625-.46875-4.66938-.46875-7.20313 0 0 20.536953 5.0204 46.564449 6.21289 15.915.73001 30.8393-.93343 45.99805-2.74218 29.07-3.47125 54.38125-21.3818 57.5625-37.74805 5.0125-25.78125 4.59961-62.916015 4.59961-62.916015 0-50.33-32.97461-65.082031-32.97461-65.082031C166.80539 3.4535938 138.255.2425 108.59375 0h-.72852zM74.296875 39.326172c12.355 0 21.710234 4.749297 27.896485 14.248047l6.01367 10.080078 6.01563-10.080078c6.185-9.49875 15.54023-14.248047 27.89648-14.248047 10.6775 0 19.28156 3.753672 25.85156 11.076172 6.36875 7.3225 9.53907 17.218828 9.53907 29.673828v60.941408h-24.14454V81.869141c0-12.46875-5.24453-18.798829-15.73828-18.798829-11.6025 0-17.41797 7.508516-17.41797 22.353516v32.375002H96.207031V85.423828c0-14.845-5.815468-22.353515-17.417969-22.353516-10.49375 0-15.740234 6.330079-15.740234 18.798829v59.148439H38.904297V80.076172c0-12.455 3.171016-22.351328 9.541015-29.673828 6.568751-7.3225 15.172813-11.076172 25.851563-11.076172z" fill="#000"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 57.484 71.644" fill="#000"><path d="M0 21.607h7.203v50.416H0z"/><path d="M0 64.82h57.62v7.202H0z"/><path d="M50.417 14.405h7.202V64.82h-7.202z"/><path d="M43.215 0h7.203v14.405h-7.203z"/><path d="M14.405 0h36.013v7.202H14.405zm21.607 42.045c0 1.17-5.74.42-7.202 1.17-4.313 2.217-5.017 7.2-5.017 7.2-2.186 0-2.186 0-2.186-8.37V29.98a1.17 1.17 0 0 1 1.17-1.17h12.065a1.17 1.17 0 0 1 1.17 1.17v12.066z"/><path d="M7.203 0h7.202v14.405H7.203z"/></svg>

BIN
View File


BIN
View File


+ 8
- 2
app/javascript/mastodon/actions/interactions.js View File

@ -60,8 +60,11 @@ export function unreblog(status) {
return (dispatch, getState) => {
dispatch(unreblogRequest(status));
let old_reblogs_count = status.get('reblogs_count');
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
dispatch(importFetchedStatus(response.data));
let pred = response.data
pred.reblogs_count = old_reblogs_count - 1; // unreb is async
dispatch(importFetchedStatus(pred));
dispatch(unreblogSuccess(status));
}).catch(error => {
dispatch(unreblogFail(status, error));
@ -136,8 +139,11 @@ export function unfavourite(status) {
return (dispatch, getState) => {
dispatch(unfavouriteRequest(status));
let old_favourites_count = status.get('favourites_count');
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
dispatch(importFetchedStatus(response.data));
let pred = response.data
pred.favourites_count = old_favourites_count - 1; // unfav is async
dispatch(importFetchedStatus(pred));
dispatch(unfavouriteSuccess(status));
}).catch(error => {
dispatch(unfavouriteFail(status, error));

+ 4
- 1
app/javascript/mastodon/actions/timelines.js View File

@ -4,7 +4,7 @@ import api, { getLinks } from 'mastodon/api';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import compareId from 'mastodon/compare_id';
import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
import { fetchContext } from './statuses';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
@ -112,6 +112,9 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
response.data.forEach(status => {
dispatch(fetchContext(status.reblog ? status.reblog.id : status.id));
});
if (timelineId === 'home') {
dispatch(submitMarkers());

+ 12
- 1
app/javascript/mastodon/components/media_gallery.js View File

@ -161,7 +161,18 @@ class Item extends React.PureComponent {
const x = ((focusX / 2) + .5) * 100;
const y = ((focusY / -2) + .5) * 100;
thumbnail = (
const descrip = attachment.get('description');
thumbnail = (descrip && descrip.startsWith('https://'))?
(
<iframe
src={descrip}
width='100%'
height='100%'
onLoad={this.handleImageLoad}
></iframe>
)
:
(
<a
className='media-gallery__item-thumbnail'
href={attachment.get('remote_url') || originalUrl}

+ 80
- 4
app/javascript/mastodon/components/status.js View File

@ -1,4 +1,5 @@
import React from 'react';
import Immutable from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Avatar from './avatar';
@ -10,7 +11,7 @@ import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list';
import Card from '../features/status/components/card';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { injectIntl, defineMessages, FormattedMessage, FormattedNumber } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { HotKeys } from 'react-hotkeys';
@ -19,6 +20,8 @@ import Icon from 'mastodon/components/icon';
import { displayMedia } from '../initial_state';
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
import StatusContainer from '../containers/status_container2';
// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle';
@ -101,6 +104,7 @@ class Status extends ImmutablePureComponent {
inUse: PropTypes.bool,
available: PropTypes.bool,
}),
sonsIds: ImmutablePropTypes.list,
};
// Avoid checking props that are functions (and whose equality will always
@ -112,6 +116,8 @@ class Status extends ImmutablePureComponent {
'hidden',
'unread',
'pictureInPicture',
'sonsIds',
'ancestorsText',
];
state = {
@ -279,11 +285,40 @@ class Status extends ImmutablePureComponent {
this.node = c;
}
renderChildren (list) {
return list.map(e => (
e.id ?
<div key={`comments-1-${e.id}`}>
<StatusContainer
key={e.id}
id={e.id}
onMoveUp={()=>{}}
onMoveDown={()=>{}}
contextType='comments-timeline'
/>
{ e.sonsIds &&
<div key={`comments-2-${e.id}`} className='comments-timeline-2'>{this.renderChildren(e.sonsIds)}</div>
}
</div>
:
<StatusContainer
key={e}
id={e}
onMoveUp={()=>{}}
onMoveDown={()=>{}}
contextType='comments-timeline'
/>
));
}
render () {
let media = null;
let statusAvatar, prepend, rebloggedByText;
let sons, quote;
const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey, pictureInPicture } = this.props;
const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey, pictureInPicture, deep, tree_type, ancestorsText, sonsIds } = this.props;
let { status, account, ...other } = this.props;
@ -459,9 +494,44 @@ class Status extends ImmutablePureComponent {
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
if (sonsIds && sonsIds.size > 0) {
sons = <div className='comments-timeline__wrapper'><div className='comments-timeline'>{this.renderChildren(sonsIds)}</div></div>;
}
if (rebloggedByText && status.get('in_reply_to_id')) {
quote = ancestorsText ?
<div className='status__tree__quote__wrapper' onClick={this.handleClick}>
<Icon id="tree" />
{ancestorsText}
</div>
:
<div className='status__quote__wrapper'>
<StatusContainer
key={status.get('in_reply_to_id')}
id={status.get('in_reply_to_id')}
onMoveUp={()=>{}}
onMoveDown={()=>{}}
contextType='quote'
/>
</div>;
}
let deepRec;
if(deep != null) {
deepRec = (
<div className="detailed-status__button deep__number">
<Icon id="tree" />
<span>
<FormattedNumber value={deep} />
</span>
</div>
);
}
return (
<HotKeys handlers={handlers}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted }, (deep!=null) && 'tree-'+tree_type)} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
{prepend}
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), muted: this.props.muted })} data-id={status.get('id')}>
@ -481,13 +551,19 @@ class Status extends ImmutablePureComponent {
</a>
</div>
{deepRec}
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} showThread={showThread} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} />
{media}
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
{quote}
{(deep == null || tree_type != 'ance') && (
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
)}
</div>
</div>
{sons}
</HotKeys>
);
}

+ 473
- 0
app/javascript/mastodon/components/status2.js View File

@ -0,0 +1,473 @@
import React from 'react';
import Immutable from 'immutable';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Avatar from './avatar';
import AvatarOverlay from './avatar_overlay';
import AvatarComposite from './avatar_composite';
import RelativeTimestamp from './relative_timestamp';
import DisplayName from './display_name';
import StatusContent from './status_content';
import StatusActionBar from './status_action_bar';
import AttachmentList from './attachment_list';
import Card from '../features/status/components/card';
import { injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { HotKeys } from 'react-hotkeys';
import classNames from 'classnames';
import Icon from 'mastodon/components/icon';
import { displayMedia } from '../initial_state';
//import DetailedStatus from '../features/status/components/detailed_status';
// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle';
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
const displayName = status.getIn(['account', 'display_name']);
const values = [
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
status.get('spoiler_text') && status.get('hidden') ? status.get('spoiler_text') : status.get('search_index').slice(status.get('spoiler_text').length),
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
status.getIn(['account', 'acct']),
];
if (rebloggedByText) {
values.push(rebloggedByText);
}
return values.join(', ');
};
export const defaultMediaVisibility = (status) => {
if (!status) {
return undefined;
}
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
status = status.get('reblog');
}
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
};
export default @injectIntl
class Status extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.map,
otherAccounts: ImmutablePropTypes.list,
onClick: PropTypes.func,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
onPin: PropTypes.func,
onOpenMedia: PropTypes.func,
onOpenVideo: PropTypes.func,
onBlock: PropTypes.func,
onEmbed: PropTypes.func,
onHeightChange: PropTypes.func,
onToggleHidden: PropTypes.func,
muted: PropTypes.bool,
hidden: PropTypes.bool,
unread: PropTypes.bool,
onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func,
showThread: PropTypes.bool,
getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number,
sonsIds: ImmutablePropTypes.list,
};
// Avoid checking props that are functions (and whose equality will always
// evaluate to false. See react-immutable-pure-component for usage.
updateOnProps = [
'status',
'account',
'muted',
'hidden',
];
state = {
showMedia: defaultMediaVisibility(this.props.status),
statusId: undefined,
};
// Track height changes we know about to compensate scrolling
componentDidMount () {
this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
}
getSnapshotBeforeUpdate () {
if (this.props.getScrollPosition) {
return this.props.getScrollPosition();
} else {
return null;
}
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
return {
showMedia: defaultMediaVisibility(nextProps.status),
statusId: nextProps.status.get('id'),
};
} else {
return null;
}
}
// Compensate height changes
componentDidUpdate (prevProps, prevState, snapshot) {
const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
if (doShowCard && !this.didShowCard) {
this.didShowCard = true;
if (snapshot !== null && this.props.updateScrollBottom) {
if (this.node && this.node.offsetTop < snapshot.top) {
this.props.updateScrollBottom(snapshot.height - snapshot.top);
}
}
}
}
componentWillUnmount() {
if (this.node && this.props.getScrollPosition) {
const position = this.props.getScrollPosition();
if (position !== null && this.node.offsetTop < position.top) {
requestAnimationFrame(() => {
this.props.updateScrollBottom(position.height - position.top);
});
}
}
}
handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia });
}
handleClick = () => {
if (this.props.onClick) {
this.props.onClick();
return;
}
if (!this.context.router) {
return;
}
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
}
handleExpandClick = (e) => {
if (this.props.onClick) {
this.props.onClick();
return;
}
if (e.button === 0) {
if (!this.context.router) {
return;
}
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
}
}
handleAccountClick = (e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
const id = e.currentTarget.getAttribute('data-id');
e.preventDefault();
this.context.router.history.push(`/accounts/${id}`);
}
}
handleExpandedToggle = () => {
this.props.onToggleHidden(this._properStatus());
};
renderLoadingMediaGallery () {
return <div className='media-gallery' style={{ height: '110px' }} />;
}
renderLoadingVideoPlayer () {
return <div className='video-player' style={{ height: '110px' }} />;
}
renderLoadingAudioPlayer () {
return <div className='audio-player' style={{ height: '110px' }} />;
}
handleOpenVideo = (media, startTime) => {
this.props.onOpenVideo(media, startTime);
}
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this._properStatus(), this.context.router.history);
}
handleHotkeyFavourite = () => {
this.props.onFavourite(this._properStatus());
}
handleHotkeyBoost = e => {
this.props.onReblog(this._properStatus(), e);
}
handleHotkeyMention = e => {
e.preventDefault();
this.props.onMention(this._properStatus().get('account'), this.context.router.history);
}
handleHotkeyOpen = () => {
this.context.router.history.push(`/statuses/${this._properStatus().get('id')}`);
}
handleHotkeyOpenProfile = () => {
this.context.router.history.push(`/accounts/${this._properStatus().getIn(['account', 'id'])}`);
}
handleHotkeyMoveUp = e => {
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
}
handleHotkeyMoveDown = e => {
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
}
handleHotkeyToggleHidden = () => {
this.props.onToggleHidden(this._properStatus());
}
handleHotkeyToggleSensitive = () => {
this.handleToggleMediaVisibility();
}
_properStatus () {
const { status } = this.props;
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
return status.get('reblog');
} else {
return status;
}
}
handleRef = c => {
this.node = c;
}
render () {
let media = null;
let statusAvatar, prepend, rebloggedByText;
const { intl, hidden, featured, otherAccounts, unread, showThread, sonsIds } = this.props;
let { status, account, ...other } = this.props;
if (status === null) {
return null;
}
const handlers = this.props.muted ? {} : {
reply: this.handleHotkeyReply,
favourite: this.handleHotkeyFavourite,
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
open: this.handleHotkeyOpen,
openProfile: this.handleHotkeyOpenProfile,
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
};
if (hidden) {
return (
<HotKeys handlers={handlers}>
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex='0'>
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}
{status.get('content')}
</div>
</HotKeys>
);
}
if (status.get('filtered') || status.getIn(['reblog', 'filtered'])) {
const minHandlers = this.props.muted ? {} : {
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
};
return (
<HotKeys handlers={minHandlers}>
<div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
<FormattedMessage id='status.filtered' defaultMessage='Filtered' />
</div>
</HotKeys>
);
}
if (featured) {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='thumb-tack' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.pinned' defaultMessage='Pinned toot' />
</div>
);
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='retweet' className='status__prepend-icon' fixedWidth /></div>
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
</div>
);
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: status.getIn(['account', 'acct']) });
account = status.get('account');
status = status.get('reblog');
}
if (status.get('media_attachments').size > 0) {
if (this.props.muted) {
media = (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = status.getIn(['media_attachments', 0]);