Compare commits

...

182 Commits

Author SHA1 Message Date
  欧醚 20e797a51d fix permission 8 months ago
  欧醚 9a1e8d0f4b Merge tag 'v3.4.6' into cs+3.4.6 11 months ago
  欧醚 4390b676da 加强邮箱保护 1 year ago
  欧醚 a9554a1c9b 展示转嘟,替代 1d0ddd33b6 的临时措施 1 year ago
  Claire 93a6c143af
Fix insufficient sanitization of report comments (#17430) 11 months ago
  Claire bb7b2868a0 Bump version to 3.4.6 1 year ago
  Wonderfall a06dda41d0 disable legacy XSS filtering (#17289) 1 year ago
  Claire bf005edd30 Change mastodon:webpush:generate_vapid_key task to not require functional env (#17338) 1 year ago
  Claire df68d2eab8 Fix response_to_recipient? CTE 1 year ago
  Claire b27f50da5a Fix insufficient sanitization of report comments 1 year ago
  Claire e2009ced3a Fix compacted JSON-LD possibly causing compatibility issues on forwarding 1 year ago
  Puck Meerburg fe0210074f Compact JSON-LD signed incoming activities 1 year ago
  Claire c8dbbd60eb Fix error-prone SQL queries (#15828) 1 year ago
  Claire 6d831fe274
Fix spurious errors when receiving an Add activity for a private post (#17425) 1 year ago
  TA 0b7b25d17c remove unrendered HTML b tag from welcome email 1 year ago
  欧醚 eede6ca0c5 change version suffix to fix push notification 1 year ago
  欧醚 fa687d53a6 show toot chars limit and poll limit in instance api (for mobile app) 1 year ago
  欧醚 910de600d5 修改投票选项数上限 1 year ago
  Claire 2476d4f391 Fix user email address being banned on self-deletion (#16503) 1 year ago
  欧醚 34927adb74 兼容不开启闭社树 1 year 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 2 years ago
  欧醚 fbf63eef29 Merge v3.3.0 into closed-social-v3 2 years ago
  欧醚 09c1e377a7 fix detail 2 years ago
  欧醚 d3625fa0f4 feat: 年度数据 fix some detail & 允许管理员查询其他人 2 years ago
  欧醚 fd8dd1498d feat: 年度数据统计 2 years ago
  欧醚 f0c6d72e60 配置参数说明 2 years ago
  欧醚 84d2e402d3 匿名每天刷新 2 years ago
  欧醚 a583987d76 Merge pull request 'fix bugs' (#5) from use-display-name-for-mention into closed-social-v3 2 years ago
  欧醚 c148e7a041 fix bugs 2 years ago
  欧醚 2b34c72fc4 Merge pull request 'use display_name for mention' (#4) from use-display-name-for-mention into closed-social-v3 2 years ago
  欧醚 ff0da1940b use display_name for mention 2 years ago
  欧醚 0d55e30e9e 修复界面bug 2 years ago
  欧醚 54cf68d8c6 增加前端邮箱正则限制 2 years ago
  欧醚 48cf6d253e make api/v1/instance public 2 years ago
  欧醚 04bfe40736 Merge remote-tracking branch 'origin/master' into closed-social-v3 2 years ago
  Junxi Song 3140a72fd7 Fixed the problem of fixed comments. 2 years ago
  欧醚 2cbee8feda 删除空格 2 years ago
  欧醚 53e44cc118 删除关注tag功能 2 years ago
  欧醚 eee3899b2c Merge remote-tracking branch 'origin/master' into closed-social-v3 2 years ago
  Junxi Song 4b80abced1 更新英文,台湾,香港表述# 2 years ago
  欧醚 bdb623f9eb 版本号后缀及默认仓库 2 years ago
  欧醚 98fe3af7b2 Merge branch 'master' into closed-social-v3 2 years ago
  欧醚 38632675b1 fix bugs 2 years ago
  欧醚 aa139841e5 恢复界面 2 years ago
  欧醚 b33a2295a9 Merge branch 'master' into closed-social-v3 2 years ago
  欧醚 f4e504e1fb 修复注册页面的显示 2 years ago
  欧醚 445ff0ed5f Merge branch 'master' into closed-social-v3 2 years ago
  欧醚 3b950848ca Merge branch 'master' into closed-social-v3 2 years ago
  欧醚 b6e7399e9e Merge tag 'v3.2.0' into closed-social-v3 2 years ago
  欧醚 7a9863cef7 中文搜索使用ik分词器 2 years ago
  欧醚 2a780b1118 og:image 允许不规范的 image/jpg (兼容网易云音乐) 2 years ago
  欧醚 c96e2c1d8f 跨站栏显示热门 2 years ago
  欧醚 57f5128fc6 fix bug (static variables are not thread safe) 2 years ago
  欧醚 d67cd9a573 微调界面 2 years ago
  欧醚 1b666873a2 允许whitelist_mode显示更多内容 2 years ago
  欧醚 c4897a3157 Merge branch 'master' into closed-social-v3 2 years ago
  欧醚 89bc52e250 微调about界面 2 years ago
  欧醚 bd9607f5c6 preview card 兼容有重复content-type头的情况 2 years ago
  欧醚 478efa8bf9 fix bug 2 years ago
  欧醚 20e0c1f803 重新实现了获取context 2 years ago
  欧醚 8ee92eab43 优化UI 2 years ago
  欧醚 a93e09ed9d 缩短加载评论延时,对所有嘟文加载评论 2 years ago
  欧醚 d39498f027 删除了所有关于清华邮箱延迟的提醒 2 years ago
  欧醚 0646238fed fix bug of mastodon(?): add :ssl in smtp setting 2 years ago
  欧醚 3dfac8387d fix bugs && 匿名可禁言 2 years ago
  欧醚 747ac90294 fix bug && 取消不必要的修改 2 years ago
  欧醚 57c905a689 minor change 2 years ago
  欧醚 6e1eb19ae0 删除硬编码ws地址 2 years ago
  欧醚 192657c54b 标记匿名标签刷新 超过一天未使用自动刷新 2 years ago
  欧醚 32cb3aa8d9 闭社树树根帐号可配置 对所有嘟文提供树形结构 2 years ago
  欧醚 720f8fc9d1 fix bug 2 years ago
  欧醚 95ff45bb7d 更好的匿名 2 years ago
  欧醚 cdf0e96549 优化了markdown外链图片的显示格式 2 years ago
  欧醚 24d6a043e5 删除markdown库,修改linkify实现嘟文/公告支持markdown语法的链接,彻底清理置顶栏 2 years ago
  欧醚 3149fbd706 删除置顶栏目,公告使用markdown 2 years ago
  欧醚 c247330f05 Merge branch 'master' of https://github.com/tootsuite/mastodon into closed-social-v3 2 years ago
  欧醚 3cc3992d1d 删除orig 修改细节 匿名可配置 2 years ago
  欧醚 9312eab8de fix bug && 小改动 2 years ago
  欧醚 d5cc1beb73 Merge branch 'master' into closed-social-v3 2 years 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 修改闭社树上内容转嘟的引用显示 3 years ago
  欧醚 29c865dbc8 fix bugs 3 years ago
  欧醚 aeb8046cb5 fix bugs;闭社树按时间倒序 3 years ago
  欧醚 c608897a29 Merge branch 'closed-social-v3' of github.com:closed-social/mastodon into closed-social-v3 3 years ago
  欧醚 4f1f2540a4 评论预览显示两层,微调显示界面 3 years ago
  欧醚 3197fd21e4 修复修改邮箱时不检查白名单的问题 3 years ago
  欧醚 40533e194f 调整了左右滑动的阈值 3 years ago
  欧醚 0efe1c54e9 为ws单独指定域名(解决CDN不支持ws的问题),放宽api频率 3 years ago
  欧醚 d00d6415fe Fix reuse of detailed status components 3 years ago
  欧醚 4103b2a466 重新整理了评论/回复图标及文字 3 years ago
  欧醚 e701ceb033 删除了旧的点赞动画 3 years ago
  欧醚 2bb0aaa47c Merge branch 'closed-social-v3' of https://github.com/closed-social/mastodon into closed-social-v3 3 years ago
  欧醚 e389037482 修改了喜欢的图标颜色和动画 3 years ago
  欧醚 33e1c8680e 闭社树根节点只加载一层 3 years ago
  欧醚 6bd0848a0b 修改frame规则 3 years ago
  欧醚 dd16a8f4ac 描述为https链接的的图片显示为iframe 3 years ago
  欧醚 604cffbeed fix bugs 3 years ago
  欧醚 112a3f0754 时间轴只显示对自己的转嘟 3 years ago
  欧醚 0414dccfe9 闭社树每次只加载两层 3 years ago
  欧醚 ec25fe716e 不对不可转嘟问显示转嘟数,private嘟文强制请求context 3 years ago
  欧醚 50439d7218 延时自动加载评论,不再需要鼠标经过/点击 3 years ago
  欧醚 f8a2a10284 显示引用以实现转评 3 years ago
  欧醚 78bacf9626 取消热门tag门槛,清华邮箱的额外提醒信息 3 years ago
  欧醚 49a2a2ef96 修复bug 3 years ago
  欧醚 dc6403acac 为高级web模式设置置顶栏 3 years ago
  欧醚 0dbbca12c0 修改文字,解决previewcard被反爬虫的问题,增长Timeout 3 years ago
  欧醚 56f55defc2 修改了移动应用、指南(文档)的链接,修改了消息图标 3 years ago
  欧醚 7a02ce51be 修改了评论预览的结构和显示方式(增加了wrapper) 3 years ago
  欧醚 774235c1f5 修改了评论预览的展开方式 3 years ago
  欧醚 08e12eadaf 修改默认头像,修改style 3 years ago
  欧醚 36ec3f043c 修改style,置顶栏可关闭 3 years ago
  欧醚 b9541d6adc 修复style 3 years ago
  欧醚 be62d2550f 修复背景图显示 3 years ago
  欧醚 c4ff322c3a 新增主题,并迁移部分自定义css到主题 3 years ago
  欧醚 1c90891868 置顶栏支持富文本 3 years ago
  欧醚 d2d99f86dd 仅[pub]开头的嘟文站外可见 3 years ago
  欧醚 e5c6a02ae4 修复favicon, 为所有评论显示show thread 3 years ago
  欧醚 f9fe84914a 不在时间线显示自回复 3 years ago
  欧醚 c67bf258ab 修改图标 3 years ago
  欧醚 c2dc7566c1 置顶消息,回复API限制,修改闭社树地址配置方式 3 years ago
  欧醚 9b76f5bb43 优化了闭社树的显示 3 years ago
  欧醚 eae877d063 修改了评论预览的处理逻辑,移动端第一次点击加载评论预览,第二次点击打开嘟文 3 years ago
  欧醚 1db7d22058 修改后端 赞藏 -> 喜欢 3 years ago
  欧醚 3b326e3a55 字数限制 500 -> 5000 3 years ago
  欧醚 24859251dc 本站栏显示热门tag榜 3 years ago
  欧醚 6a4bfbf621 禁止非closed.social的mastodon实例 Warning:硬编码 3 years ago
  欧醚 a31aaeef04 change email info and force to use zh-CN 3 years ago
  欧醚 8fa6fcf097 仅在鼠标移过/手机按下时加载评论预览 3 years ago
  欧醚 062308c006 放宽了非认证api频率限制 3 years ago
  欧醚 a83a3e78fc 放宽了api频率限制 3 years ago
  欧醚 a04935f5c1 树洞评论匿名 3 years ago
  欧醚 66561200b5 show comments on timeline, and show right count when unfavourite/unreblog 3 years ago
  欧醚 3142db432d set default email domain in ENV 3 years ago
  欧醚 5b9cc9c93b change 赞藏 to 喜欢, and change logo_alt 3 years ago
  欧醚 718814bfbf give up pinned statuses, and add tree to navigation/tabs_bar, with funny way 3 years ago
  欧醚 713bf5509f add 闭社树 to navigation, with horrible way 3 years ago
  欧醚 40ea60e578 show reblogs on timeline 3 years ago
  欧醚 6dd757a495 change x and y of text on tree 3 years ago
  欧醚 4f2275c1e5 click the node on tree to go to the status 3 years ago
  欧醚 0a8bcfade5 fix bug for tree animate. Use statusId as keyPro 3 years ago
  欧醚 fb82f7b6ac now you can click nodes on 闭社树 3 years ago
  欧醚 2bb6238dc8 change size of tree graph 3 years ago
  欧醚 a8e4c3f5a2 now can draw 闭社树 3 years ago
  欧醚 6be4def2ec follow tags 3 years ago
  欧醚 0101dfcdfd fix bug 3 years ago
  欧醚 a5db5d45a2 fix bug 3 years ago
  欧醚 b0aadae668 show tree in className 3 years ago
  欧醚 f0ad5eaea7 pin statuses pinned by Account(1) 3 years ago
  欧醚 353b88eba0 require login for tag api 3 years ago
  欧醚 3fd2acbacb show depth and only show direct sons for 闭社树 3 years ago
  欧醚 457daf4353 better hide to unsigned user 3 years ago
  欧醚 60a876f39a change 收藏 to 赞藏, and hide wrong active_user_count 3 years ago
  欧醚 489c93f011 show comments on timeline, with ugly way 3 years ago
  欧醚 3c25a96aad show comments on timeline, with bugs 3 years ago
  欧醚 990d264f63 show fav/reb number 3 years ago
  欧醚 5eb86661ba set max trends number to 5 3 years ago
  欧醚 207b875ded hide account info to unsigned user 3 years ago
  欧醚 3f178bbdae change all icon of fav/rep 3 years ago
  欧醚 66919577e8 change some icon of fav/rep 3 years ago
  欧醚 c2c5c22ee6 fix bugs 3 years ago
  欧醚 79f21b2d8f fix favicon.ico 3 years ago
  欧醚 1abf9c6d92 merge changes 3 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]);