logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe git clone https://anongit.hacktivis.me/git/pleroma-fe.git/

instance.js (11646B)


  1. import apiService from '../services/api/api.service.js'
  2. import { instanceDefaultProperties } from './config.js'
  3. import { langCodeToCldrName, ensureFinalFallback } from '../i18n/languages.js'
  4. const SORTED_EMOJI_GROUP_IDS = [
  5. 'smileys-and-emotion',
  6. 'people-and-body',
  7. 'animals-and-nature',
  8. 'food-and-drink',
  9. 'travel-and-places',
  10. 'activities',
  11. 'objects',
  12. 'symbols',
  13. 'flags'
  14. ]
  15. const REGIONAL_INDICATORS = (() => {
  16. const start = 0x1F1E6
  17. const end = 0x1F1FF
  18. const A = 'A'.codePointAt(0)
  19. const res = new Array(end - start + 1)
  20. for (let i = start; i <= end; ++i) {
  21. const letter = String.fromCodePoint(A + i - start)
  22. res[i - start] = {
  23. replacement: String.fromCodePoint(i),
  24. imageUrl: false,
  25. displayText: 'regional_indicator_' + letter,
  26. displayTextI18n: {
  27. key: 'emoji.regional_indicator',
  28. args: { letter }
  29. }
  30. }
  31. }
  32. return res
  33. })()
  34. const REMOTE_INTERACTION_URL = '/main/ostatus'
  35. const defaultState = {
  36. // Stuff from apiConfig
  37. name: 'Pleroma FE',
  38. registrationOpen: true,
  39. server: 'http://localhost:4040/',
  40. textlimit: 5000,
  41. themeData: undefined, // used for theme editor v2
  42. vapidPublicKey: undefined,
  43. // Stuff from static/config.json
  44. alwaysShowSubjectInput: true,
  45. defaultAvatar: '/images/avi.png',
  46. defaultBanner: '/images/banner.png',
  47. background: '/static/aurora_borealis.jpg',
  48. collapseMessageWithSubject: false,
  49. greentext: false,
  50. useAtIcon: false,
  51. mentionLinkDisplay: 'short',
  52. mentionLinkShowTooltip: true,
  53. mentionLinkShowAvatar: false,
  54. mentionLinkFadeDomain: true,
  55. mentionLinkShowYous: false,
  56. mentionLinkBoldenYou: true,
  57. hideFilteredStatuses: false,
  58. // bad name: actually hides posts of muted USERS
  59. hideMutedPosts: false,
  60. hideMutedThreads: true,
  61. hideWordFilteredPosts: false,
  62. hidePostStats: false,
  63. hideBotIndication: false,
  64. hideSitename: false,
  65. hideUserStats: false,
  66. muteBotStatuses: false,
  67. muteSensitiveStatuses: false,
  68. modalOnRepeat: false,
  69. modalOnUnfollow: false,
  70. modalOnBlock: true,
  71. modalOnMute: false,
  72. modalOnDelete: true,
  73. modalOnLogout: true,
  74. modalOnApproveFollow: false,
  75. modalOnDenyFollow: false,
  76. modalOnRemoveUserFromFollowers: false,
  77. loginMethod: 'password',
  78. logo: '/static/logo.svg',
  79. logoMargin: '.2em',
  80. logoMask: true,
  81. logoLeft: false,
  82. disableUpdateNotification: false,
  83. minimalScopesMode: false,
  84. nsfwCensorImage: undefined,
  85. postContentType: 'text/plain',
  86. redirectRootLogin: '/main/friends',
  87. redirectRootNoLogin: '/main/all',
  88. scopeCopy: true,
  89. showFeaturesPanel: true,
  90. showInstanceSpecificPanel: false,
  91. sidebarRight: false,
  92. subjectLineBehavior: 'email',
  93. theme: 'pleroma-dark',
  94. emojiReactionsScale: 0.5,
  95. textSize: '14px',
  96. emojiSize: '2.2rem',
  97. navbarSize: '3.5rem',
  98. panelHeaderSize: '3.2rem',
  99. forcedRoundness: -1,
  100. fontsOverride: {},
  101. virtualScrolling: true,
  102. sensitiveByDefault: false,
  103. conversationDisplay: 'linear',
  104. conversationTreeAdvanced: false,
  105. conversationOtherRepliesButton: 'below',
  106. conversationTreeFadeAncestors: false,
  107. showExtraNotifications: true,
  108. showExtraNotificationsTip: true,
  109. showChatsInExtraNotifications: true,
  110. showAnnouncementsInExtraNotifications: true,
  111. showFollowRequestsInExtraNotifications: true,
  112. maxDepthInThread: 6,
  113. autocompleteSelect: false,
  114. closingDrawerMarksAsSeen: true,
  115. unseenAtTop: false,
  116. ignoreInactionableSeen: false,
  117. // Nasty stuff
  118. customEmoji: [],
  119. customEmojiFetched: false,
  120. emoji: {},
  121. emojiFetched: false,
  122. unicodeEmojiAnnotations: {},
  123. pleromaBackend: true,
  124. postFormats: [],
  125. restrictedNicknames: [],
  126. safeDM: true,
  127. knownDomains: [],
  128. birthdayRequired: false,
  129. birthdayMinAge: 0,
  130. // Feature-set, apparently, not everything here is reported...
  131. shoutAvailable: false,
  132. pleromaChatMessagesAvailable: false,
  133. pleromaCustomEmojiReactionsAvailable: false,
  134. gopherAvailable: false,
  135. mediaProxyAvailable: false,
  136. suggestionsEnabled: false,
  137. suggestionsWeb: '',
  138. quotingAvailable: false,
  139. groupActorAvailable: false,
  140. // Html stuff
  141. instanceSpecificPanelContent: '',
  142. tos: '',
  143. // Version Information
  144. backendVersion: '',
  145. backendRepository: '',
  146. frontendVersion: '',
  147. pollsAvailable: false,
  148. pollLimits: {
  149. max_options: 4,
  150. max_option_chars: 255,
  151. min_expiration: 60,
  152. max_expiration: 60 * 60 * 24
  153. }
  154. }
  155. const loadAnnotations = (lang) => {
  156. return import(
  157. /* webpackChunkName: "emoji-annotations/[request]" */
  158. `@kazvmoe-infra/unicode-emoji-json/annotations/${langCodeToCldrName(lang)}.json`
  159. )
  160. .then(k => k.default)
  161. }
  162. const injectAnnotations = (emoji, annotations) => {
  163. const availableLangs = Object.keys(annotations)
  164. return {
  165. ...emoji,
  166. annotations: availableLangs.reduce((acc, cur) => {
  167. acc[cur] = annotations[cur][emoji.replacement]
  168. return acc
  169. }, {})
  170. }
  171. }
  172. const injectRegionalIndicators = groups => {
  173. groups.symbols.push(...REGIONAL_INDICATORS)
  174. return groups
  175. }
  176. const instance = {
  177. state: defaultState,
  178. mutations: {
  179. setInstanceOption (state, { name, value }) {
  180. if (typeof value !== 'undefined') {
  181. state[name] = value
  182. }
  183. },
  184. setKnownDomains (state, domains) {
  185. state.knownDomains = domains
  186. },
  187. setUnicodeEmojiAnnotations (state, { lang, annotations }) {
  188. state.unicodeEmojiAnnotations[lang] = annotations
  189. }
  190. },
  191. getters: {
  192. instanceDefaultConfig (state) {
  193. return instanceDefaultProperties
  194. .map(key => [key, state[key]])
  195. .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})
  196. },
  197. groupedCustomEmojis (state) {
  198. const packsOf = emoji => {
  199. const packs = emoji.tags
  200. .filter(k => k.startsWith('pack:'))
  201. .map(k => {
  202. const packName = k.slice(5) // remove 'pack:' prefix
  203. return {
  204. id: `custom-${packName}`,
  205. text: packName
  206. }
  207. })
  208. if (!packs.length) {
  209. return [{
  210. id: 'unpacked'
  211. }]
  212. } else {
  213. return packs
  214. }
  215. }
  216. return state.customEmoji
  217. .reduce((res, emoji) => {
  218. packsOf(emoji).forEach(({ id: packId, text: packName }) => {
  219. if (!res[packId]) {
  220. res[packId] = ({
  221. id: packId,
  222. text: packName,
  223. image: emoji.imageUrl,
  224. emojis: []
  225. })
  226. }
  227. res[packId].emojis.push(emoji)
  228. })
  229. return res
  230. }, {})
  231. },
  232. standardEmojiList (state) {
  233. return SORTED_EMOJI_GROUP_IDS
  234. .map(groupId => (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations)))
  235. .reduce((a, b) => a.concat(b), [])
  236. },
  237. standardEmojiGroupList (state) {
  238. return SORTED_EMOJI_GROUP_IDS.map(groupId => ({
  239. id: groupId,
  240. emojis: (state.emoji[groupId] || []).map(k => injectAnnotations(k, state.unicodeEmojiAnnotations))
  241. }))
  242. },
  243. instanceDomain (state) {
  244. return new URL(state.server).hostname
  245. },
  246. remoteInteractionLink (state) {
  247. const server = state.server.endsWith('/') ? state.server.slice(0, -1) : state.server
  248. const link = server + REMOTE_INTERACTION_URL
  249. return ({ statusId, nickname }) => {
  250. if (statusId) {
  251. return `${link}?status_id=${statusId}`
  252. } else {
  253. return `${link}?nickname=${nickname}`
  254. }
  255. }
  256. }
  257. },
  258. actions: {
  259. setInstanceOption ({ commit, dispatch }, { name, value }) {
  260. commit('setInstanceOption', { name, value })
  261. switch (name) {
  262. case 'name':
  263. dispatch('setPageTitle')
  264. break
  265. case 'shoutAvailable':
  266. if (value) {
  267. dispatch('initializeSocket')
  268. }
  269. break
  270. }
  271. },
  272. async getStaticEmoji ({ commit }) {
  273. try {
  274. const values = (await import(/* webpackChunkName: 'emoji' */ '../../static/emoji.json')).default
  275. const emoji = Object.keys(values).reduce((res, groupId) => {
  276. res[groupId] = values[groupId].map(e => ({
  277. displayText: e.slug,
  278. imageUrl: false,
  279. replacement: e.emoji
  280. }))
  281. return res
  282. }, {})
  283. commit('setInstanceOption', { name: 'emoji', value: injectRegionalIndicators(emoji) })
  284. } catch (e) {
  285. console.warn("Can't load static emoji")
  286. console.warn(e)
  287. }
  288. },
  289. loadUnicodeEmojiData ({ commit, state }, language) {
  290. const langList = ensureFinalFallback(language)
  291. return Promise.all(
  292. langList
  293. .map(async lang => {
  294. if (!state.unicodeEmojiAnnotations[lang]) {
  295. try {
  296. const annotations = await loadAnnotations(lang)
  297. commit('setUnicodeEmojiAnnotations', { lang, annotations })
  298. } catch (e) {
  299. console.warn(`Error loading unicode emoji annotations for ${lang}: `, e)
  300. // ignore
  301. }
  302. }
  303. }))
  304. },
  305. async getCustomEmoji ({ commit, state }) {
  306. try {
  307. const res = await window.fetch('/api/pleroma/emoji.json')
  308. if (res.ok) {
  309. const result = await res.json()
  310. const values = Array.isArray(result) ? Object.assign({}, ...result) : result
  311. const caseInsensitiveStrCmp = (a, b) => {
  312. const la = a.toLowerCase()
  313. const lb = b.toLowerCase()
  314. return la > lb ? 1 : (la < lb ? -1 : 0)
  315. }
  316. const noPackLast = (a, b) => {
  317. const aNull = a === ''
  318. const bNull = b === ''
  319. if (aNull === bNull) {
  320. return 0
  321. } else if (aNull && !bNull) {
  322. return 1
  323. } else {
  324. return -1
  325. }
  326. }
  327. const byPackThenByName = (a, b) => {
  328. const packOf = emoji => (emoji.tags.filter(k => k.startsWith('pack:'))[0] || '').slice(5)
  329. const packOfA = packOf(a)
  330. const packOfB = packOf(b)
  331. return noPackLast(packOfA, packOfB) || caseInsensitiveStrCmp(packOfA, packOfB) || caseInsensitiveStrCmp(a.displayText, b.displayText)
  332. }
  333. const emoji = Object.entries(values).map(([key, value]) => {
  334. const imageUrl = value.image_url
  335. return {
  336. displayText: key,
  337. imageUrl: imageUrl ? state.server + imageUrl : value,
  338. tags: imageUrl ? value.tags.sort((a, b) => a > b ? 1 : 0) : ['utf'],
  339. replacement: `:${key}: `
  340. }
  341. // Technically could use tags but those are kinda useless right now,
  342. // should have been "pack" field, that would be more useful
  343. }).sort(byPackThenByName)
  344. commit('setInstanceOption', { name: 'customEmoji', value: emoji })
  345. } else {
  346. throw (res)
  347. }
  348. } catch (e) {
  349. console.warn("Can't load custom emojis")
  350. console.warn(e)
  351. }
  352. },
  353. fetchEmoji ({ dispatch, state }) {
  354. if (!state.customEmojiFetched) {
  355. state.customEmojiFetched = true
  356. dispatch('getCustomEmoji')
  357. }
  358. if (!state.emojiFetched) {
  359. state.emojiFetched = true
  360. dispatch('getStaticEmoji')
  361. }
  362. },
  363. async getKnownDomains ({ commit, rootState }) {
  364. try {
  365. const result = await apiService.fetchKnownDomains({
  366. credentials: rootState.users.currentUser.credentials
  367. })
  368. commit('setKnownDomains', result)
  369. } catch (e) {
  370. console.warn("Can't load known domains")
  371. console.warn(e)
  372. }
  373. }
  374. }
  375. }
  376. export default instance