logo

pleroma-fe

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

after_store.js (14974B)


  1. import { createApp } from 'vue'
  2. import { createRouter, createWebHistory } from 'vue-router'
  3. import vClickOutside from 'click-outside-vue3'
  4. import VueVirtualScroller from 'vue-virtual-scroller'
  5. import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
  6. import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
  7. import App from '../App.vue'
  8. import routes from './routes'
  9. import VBodyScrollLock from 'src/directives/body_scroll_lock'
  10. import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
  11. import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
  12. import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
  13. import { applyConfig } from '../services/style_setter/style_setter.js'
  14. import FaviconService from '../services/favicon_service/favicon_service.js'
  15. import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
  16. let staticInitialResults = null
  17. const parsedInitialResults = () => {
  18. if (!document.getElementById('initial-results')) {
  19. return null
  20. }
  21. if (!staticInitialResults) {
  22. staticInitialResults = JSON.parse(document.getElementById('initial-results').textContent)
  23. }
  24. return staticInitialResults
  25. }
  26. const decodeUTF8Base64 = (data) => {
  27. const rawData = atob(data)
  28. const array = Uint8Array.from([...rawData].map((char) => char.charCodeAt(0)))
  29. const text = new TextDecoder().decode(array)
  30. return text
  31. }
  32. const preloadFetch = async (request) => {
  33. const data = parsedInitialResults()
  34. if (!data || !data[request]) {
  35. return window.fetch(request)
  36. }
  37. const decoded = decodeUTF8Base64(data[request])
  38. const requestData = JSON.parse(decoded)
  39. return {
  40. ok: true,
  41. json: () => requestData,
  42. text: () => requestData
  43. }
  44. }
  45. const getInstanceConfig = async ({ store }) => {
  46. try {
  47. const res = await preloadFetch('/api/v1/instance')
  48. if (res.ok) {
  49. const data = await res.json()
  50. const textlimit = data.max_toot_chars
  51. const vapidPublicKey = data.pleroma.vapid_public_key
  52. store.dispatch('setInstanceOption', { name: 'textlimit', value: textlimit })
  53. store.dispatch('setInstanceOption', { name: 'accountApprovalRequired', value: data.approval_required })
  54. store.dispatch('setInstanceOption', { name: 'birthdayRequired', value: !!data.pleroma.metadata.birthday_required })
  55. store.dispatch('setInstanceOption', { name: 'birthdayMinAge', value: data.pleroma.metadata.birthday_min_age || 0 })
  56. if (vapidPublicKey) {
  57. store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
  58. }
  59. } else {
  60. throw (res)
  61. }
  62. } catch (error) {
  63. console.error('Could not load instance config, potentially fatal')
  64. console.error(error)
  65. }
  66. }
  67. const getBackendProvidedConfig = async ({ store }) => {
  68. try {
  69. const res = await window.fetch('/api/pleroma/frontend_configurations')
  70. if (res.ok) {
  71. const data = await res.json()
  72. return data.pleroma_fe
  73. } else {
  74. throw (res)
  75. }
  76. } catch (error) {
  77. console.error('Could not load backend-provided frontend config, potentially fatal')
  78. console.error(error)
  79. }
  80. }
  81. const getStaticConfig = async () => {
  82. try {
  83. const res = await window.fetch('/static/config.json')
  84. if (res.ok) {
  85. return res.json()
  86. } else {
  87. throw (res)
  88. }
  89. } catch (error) {
  90. console.warn('Failed to load static/config.json, continuing without it.')
  91. console.warn(error)
  92. return {}
  93. }
  94. }
  95. const setSettings = async ({ apiConfig, staticConfig, store }) => {
  96. const overrides = window.___pleromafe_dev_overrides || {}
  97. const env = window.___pleromafe_mode.NODE_ENV
  98. // This takes static config and overrides properties that are present in apiConfig
  99. let config = {}
  100. if (overrides.staticConfigPreference && env === 'development') {
  101. console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG')
  102. config = Object.assign({}, apiConfig, staticConfig)
  103. } else {
  104. config = Object.assign({}, staticConfig, apiConfig)
  105. }
  106. const copyInstanceOption = (name) => {
  107. store.dispatch('setInstanceOption', { name, value: config[name] })
  108. }
  109. copyInstanceOption('theme')
  110. copyInstanceOption('style')
  111. copyInstanceOption('palette')
  112. copyInstanceOption('nsfwCensorImage')
  113. copyInstanceOption('background')
  114. copyInstanceOption('hidePostStats')
  115. copyInstanceOption('hideBotIndication')
  116. copyInstanceOption('hideUserStats')
  117. copyInstanceOption('hideFilteredStatuses')
  118. copyInstanceOption('logo')
  119. store.dispatch('setInstanceOption', {
  120. name: 'logoMask',
  121. value: typeof config.logoMask === 'undefined'
  122. ? true
  123. : config.logoMask
  124. })
  125. store.dispatch('setInstanceOption', {
  126. name: 'logoMargin',
  127. value: typeof config.logoMargin === 'undefined'
  128. ? 0
  129. : config.logoMargin
  130. })
  131. copyInstanceOption('logoLeft')
  132. store.commit('authFlow/setInitialStrategy', config.loginMethod)
  133. copyInstanceOption('redirectRootNoLogin')
  134. copyInstanceOption('redirectRootLogin')
  135. copyInstanceOption('showInstanceSpecificPanel')
  136. copyInstanceOption('minimalScopesMode')
  137. copyInstanceOption('hideMutedPosts')
  138. copyInstanceOption('collapseMessageWithSubject')
  139. copyInstanceOption('scopeCopy')
  140. copyInstanceOption('subjectLineBehavior')
  141. copyInstanceOption('postContentType')
  142. copyInstanceOption('alwaysShowSubjectInput')
  143. copyInstanceOption('showFeaturesPanel')
  144. copyInstanceOption('hideSitename')
  145. copyInstanceOption('sidebarRight')
  146. }
  147. const getTOS = async ({ store }) => {
  148. try {
  149. const res = await window.fetch('/static/terms-of-service.html')
  150. if (res.ok) {
  151. const html = await res.text()
  152. store.dispatch('setInstanceOption', { name: 'tos', value: html })
  153. } else {
  154. throw (res)
  155. }
  156. } catch (e) {
  157. console.warn("Can't load TOS")
  158. console.warn(e)
  159. }
  160. }
  161. const getInstancePanel = async ({ store }) => {
  162. try {
  163. const res = await preloadFetch('/instance/panel.html')
  164. if (res.ok) {
  165. const html = await res.text()
  166. store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
  167. } else {
  168. throw (res)
  169. }
  170. } catch (e) {
  171. console.warn("Can't load instance panel")
  172. console.warn(e)
  173. }
  174. }
  175. const getStickers = async ({ store }) => {
  176. try {
  177. const res = await window.fetch('/static/stickers.json')
  178. if (res.ok) {
  179. const values = await res.json()
  180. const stickers = (await Promise.all(
  181. Object.entries(values).map(async ([name, path]) => {
  182. const resPack = await window.fetch(path + 'pack.json')
  183. let meta = {}
  184. if (resPack.ok) {
  185. meta = await resPack.json()
  186. }
  187. return {
  188. pack: name,
  189. path,
  190. meta
  191. }
  192. })
  193. )).sort((a, b) => {
  194. return a.meta.title.localeCompare(b.meta.title)
  195. })
  196. store.dispatch('setInstanceOption', { name: 'stickers', value: stickers })
  197. } else {
  198. throw (res)
  199. }
  200. } catch (e) {
  201. console.warn("Can't load stickers")
  202. console.warn(e)
  203. }
  204. }
  205. const getAppSecret = async ({ store }) => {
  206. const { state, commit } = store
  207. const { oauth, instance } = state
  208. return getOrCreateApp({ ...oauth, instance: instance.server, commit })
  209. .then((app) => getClientToken({ ...app, instance: instance.server }))
  210. .then((token) => {
  211. commit('setAppToken', token.access_token)
  212. commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
  213. })
  214. }
  215. const resolveStaffAccounts = ({ store, accounts }) => {
  216. const nicknames = accounts.map(uri => uri.split('/').pop())
  217. store.dispatch('setInstanceOption', { name: 'staffAccounts', value: nicknames })
  218. }
  219. const getNodeInfo = async ({ store }) => {
  220. try {
  221. const res = await preloadFetch('/nodeinfo/2.1.json')
  222. if (res.ok) {
  223. const data = await res.json()
  224. const metadata = data.metadata
  225. const features = metadata.features
  226. store.dispatch('setInstanceOption', { name: 'name', value: metadata.nodeName })
  227. store.dispatch('setInstanceOption', { name: 'registrationOpen', value: data.openRegistrations })
  228. store.dispatch('setInstanceOption', { name: 'mediaProxyAvailable', value: features.includes('media_proxy') })
  229. store.dispatch('setInstanceOption', { name: 'safeDM', value: features.includes('safe_dm_mentions') })
  230. store.dispatch('setInstanceOption', { name: 'shoutAvailable', value: features.includes('chat') })
  231. store.dispatch('setInstanceOption', { name: 'pleromaChatMessagesAvailable', value: features.includes('pleroma_chat_messages') })
  232. store.dispatch('setInstanceOption', { name: 'pleromaCustomEmojiReactionsAvailable', value: features.includes('pleroma_custom_emoji_reactions') })
  233. store.dispatch('setInstanceOption', { name: 'pleromaBookmarkFoldersAvailable', value: features.includes('pleroma:bookmark_folders') })
  234. store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
  235. store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
  236. store.dispatch('setInstanceOption', { name: 'editingAvailable', value: features.includes('editing') })
  237. store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
  238. store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
  239. store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
  240. store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
  241. const uploadLimits = metadata.uploadLimits
  242. store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
  243. store.dispatch('setInstanceOption', { name: 'avatarlimit', value: parseInt(uploadLimits.avatar) })
  244. store.dispatch('setInstanceOption', { name: 'backgroundlimit', value: parseInt(uploadLimits.background) })
  245. store.dispatch('setInstanceOption', { name: 'bannerlimit', value: parseInt(uploadLimits.banner) })
  246. store.dispatch('setInstanceOption', { name: 'fieldsLimits', value: metadata.fieldsLimits })
  247. store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
  248. store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
  249. const suggestions = metadata.suggestions
  250. store.dispatch('setInstanceOption', { name: 'suggestionsEnabled', value: suggestions.enabled })
  251. store.dispatch('setInstanceOption', { name: 'suggestionsWeb', value: suggestions.web })
  252. const software = data.software
  253. store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
  254. store.dispatch('setInstanceOption', { name: 'backendRepository', value: software.repository })
  255. store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
  256. const priv = metadata.private
  257. store.dispatch('setInstanceOption', { name: 'private', value: priv })
  258. const frontendVersion = window.___pleromafe_commit_hash
  259. store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
  260. const federation = metadata.federation
  261. store.dispatch('setInstanceOption', {
  262. name: 'tagPolicyAvailable',
  263. value: typeof federation.mrf_policies === 'undefined'
  264. ? false
  265. : metadata.federation.mrf_policies.includes('TagPolicy')
  266. })
  267. store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
  268. store.dispatch('setInstanceOption', {
  269. name: 'federating',
  270. value: typeof federation.enabled === 'undefined'
  271. ? true
  272. : federation.enabled
  273. })
  274. const accountActivationRequired = metadata.accountActivationRequired
  275. store.dispatch('setInstanceOption', { name: 'accountActivationRequired', value: accountActivationRequired })
  276. const accounts = metadata.staffAccounts
  277. resolveStaffAccounts({ store, accounts })
  278. } else {
  279. throw (res)
  280. }
  281. } catch (e) {
  282. console.warn('Could not load nodeinfo')
  283. console.warn(e)
  284. }
  285. }
  286. const setConfig = async ({ store }) => {
  287. // apiConfig, staticConfig
  288. const configInfos = await Promise.all([getBackendProvidedConfig({ store }), getStaticConfig()])
  289. const apiConfig = configInfos[0]
  290. const staticConfig = configInfos[1]
  291. await setSettings({ store, apiConfig, staticConfig }).then(getAppSecret({ store }))
  292. }
  293. const checkOAuthToken = async ({ store }) => {
  294. if (store.getters.getUserToken()) {
  295. return store.dispatch('loginUser', store.getters.getUserToken())
  296. }
  297. return Promise.resolve()
  298. }
  299. const afterStoreSetup = async ({ store, i18n }) => {
  300. store.dispatch('setLayoutWidth', windowWidth())
  301. store.dispatch('setLayoutHeight', windowHeight())
  302. FaviconService.initFaviconService()
  303. initServiceWorker(store)
  304. window.addEventListener('focus', () => updateFocus())
  305. const overrides = window.___pleromafe_dev_overrides || {}
  306. const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
  307. store.dispatch('setInstanceOption', { name: 'server', value: server })
  308. await setConfig({ store })
  309. try {
  310. await store.dispatch('applyTheme').catch((e) => { console.error('Error setting theme', e) })
  311. } catch (e) {
  312. window.splashError(e)
  313. return Promise.reject(e)
  314. }
  315. applyConfig(store.state.config, i18n.global)
  316. // Now we can try getting the server settings and logging in
  317. // Most of these are preloaded into the index.html so blocking is minimized
  318. await Promise.all([
  319. checkOAuthToken({ store }),
  320. getInstancePanel({ store }),
  321. getNodeInfo({ store }),
  322. getInstanceConfig({ store })
  323. ]).catch(e => Promise.reject(e))
  324. await store.dispatch('loadDrafts')
  325. // Start fetching things that don't need to block the UI
  326. store.dispatch('fetchMutes')
  327. store.dispatch('startFetchingAnnouncements')
  328. getTOS({ store })
  329. getStickers({ store })
  330. const router = createRouter({
  331. history: createWebHistory(),
  332. routes: routes(store),
  333. scrollBehavior: (to, _from, savedPosition) => {
  334. if (to.matched.some(m => m.meta.dontScroll)) {
  335. return false
  336. }
  337. return savedPosition || { left: 0, top: 0 }
  338. }
  339. })
  340. const app = createApp(App)
  341. app.use(router)
  342. app.use(store)
  343. app.use(i18n)
  344. // Little thing to get out of invalid theme state
  345. window.resetThemes = () => {
  346. store.dispatch('resetThemeV3')
  347. store.dispatch('resetThemeV3Palette')
  348. store.dispatch('resetThemeV2')
  349. }
  350. app.use(vClickOutside)
  351. app.use(VBodyScrollLock)
  352. app.use(VueVirtualScroller)
  353. app.component('FAIcon', FontAwesomeIcon)
  354. app.component('FALayers', FontAwesomeLayers)
  355. // remove after vue 3.3
  356. app.config.unwrapInjectedRef = true
  357. app.mount('#app')
  358. return app
  359. }
  360. export default afterStoreSetup