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 (12011B)


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