diff --git a/.github/workflows/calibreapp-image-actions.yml b/.github/workflows/calibreapp-image-actions.yml
index d336cad2328ab..1315f1fe5915b 100644
--- a/.github/workflows/calibreapp-image-actions.yml
+++ b/.github/workflows/calibreapp-image-actions.yml
@@ -20,7 +20,7 @@ jobs:
compressOnly: true
- name: Create New Pull Request If Needed
if: steps.calibre.outputs.markdown != ''
- uses: peter-evans/create-pull-request@v6
+ uses: peter-evans/create-pull-request@v7
with:
title: Compressed Images Nightly
branch-suffix: timestamp
diff --git a/_scripts/ProcessLocalesPlugin.js b/_scripts/ProcessLocalesPlugin.js
index e3b2dd7a88cf9..51ff870c698f3 100644
--- a/_scripts/ProcessLocalesPlugin.js
+++ b/_scripts/ProcessLocalesPlugin.js
@@ -40,17 +40,17 @@ class ProcessLocalesPlugin {
/** @type {(updatedLocales: [string, string][]) => void|null} */
this.notifyLocaleChange = null
- if (this.hotReload) {
- this.hotReloadScript = readFileSync(`${__dirname}/_hotReloadLocalesScript.js`, 'utf-8')
- }
-
this.loadLocales()
}
/** @param {import('webpack').Compiler} compiler */
apply(compiler) {
const { CachedSource, RawSource } = compiler.webpack.sources;
- const { Compilation } = compiler.webpack
+ const { Compilation, DefinePlugin } = compiler.webpack
+
+ new DefinePlugin({
+ 'process.env.HOT_RELOAD_LOCALES': this.hotReload
+ }).apply(compiler)
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
const IS_DEV_SERVER = !!compiler.watching
@@ -136,19 +136,6 @@ class ProcessLocalesPlugin {
compilation.fileDependencies.addAll(this.filePaths)
}
})
-
- compiler.hooks.emit.tap(PLUGIN_NAME, (compilation) => {
- if (this.hotReload) {
- // Find generated JavaScript output file (e.g. renderer.js or web.js)
- // and inject the code snippet that listens for locale updates and replaces vue-i18n's locales
-
- /** @type {string} */
- const filename = [...[...compilation.chunks][0].files]
- .find(file => file.endsWith('.js'))
-
- compilation.assets[filename]._source._children.push(`\n${this.hotReloadScript}`)
- }
- })
}
loadLocales() {
diff --git a/_scripts/_hotReloadLocalesScript.js b/_scripts/_hotReloadLocalesScript.js
deleted file mode 100644
index 8cbfb6ef8812d..0000000000000
--- a/_scripts/_hotReloadLocalesScript.js
+++ /dev/null
@@ -1,18 +0,0 @@
-const websocket = new WebSocket('ws://localhost:9080/ws')
-
-websocket.onmessage = (event) => {
- const message = JSON.parse(event.data)
-
- if (message.type === 'freetube-locale-update') {
- const i18n = document.getElementById('app').__vue__.$i18n
-
- for (const [locale, data] of message.data) {
- // Only update locale data if it was already loaded
- if (i18n.availableLocales.includes(locale)) {
- const localeData = JSON.parse(data)
-
- i18n.setLocaleMessage(locale, localeData)
- }
- }
- }
-}
diff --git a/package.json b/package.json
index cf93e6ce8a487..a4731a535b0e7 100644
--- a/package.json
+++ b/package.json
@@ -63,12 +63,12 @@
"autolinker": "^4.0.0",
"electron-context-menu": "^4.0.4",
"lodash.debounce": "^4.0.8",
- "marked": "^14.1.0",
+ "marked": "^14.1.2",
"path-browserify": "^1.0.1",
"portal-vue": "^2.1.7",
"process": "^0.11.10",
- "shaka-player": "^4.10.12",
- "swiper": "^11.1.12",
+ "shaka-player": "^4.11.1",
+ "swiper": "^11.1.14",
"vue": "^2.7.16",
"vue-i18n": "^8.28.2",
"vue-observe-visibility": "^1.0.0",
@@ -87,18 +87,18 @@
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
- "electron": "^32.0.1",
- "electron-builder": "^24.13.3",
+ "electron": "^32.1.0",
+ "electron-builder": "^25.0.5",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
- "eslint-plugin-import": "^2.29.1",
+ "eslint-plugin-import": "^2.30.0",
"eslint-plugin-jsonc": "^2.16.0",
"eslint-plugin-n": "^17.10.2",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-unicorn": "^55.0.0",
- "eslint-plugin-vue": "^9.27.0",
+ "eslint-plugin-vue": "^9.28.0",
"eslint-plugin-vuejs-accessibility": "^2.4.1",
"eslint-plugin-yml": "^1.14.0",
"html-webpack-plugin": "^5.6.0",
@@ -107,14 +107,14 @@
"lefthook": "^1.7.15",
"mini-css-extract-plugin": "^2.9.1",
"npm-run-all2": "^6.2.2",
- "postcss": "^8.4.44",
+ "postcss": "^8.4.47",
"postcss-scss": "^4.0.9",
"prettier": "^2.8.8",
"rimraf": "^6.0.1",
- "sass": "^1.77.8",
+ "sass": "^1.78.0",
"sass-loader": "^16.0.1",
"stylelint": "^16.9.0",
- "stylelint-config-sass-guidelines": "^12.0.0",
+ "stylelint-config-sass-guidelines": "^12.1.0",
"stylelint-config-standard": "^36.0.1",
"stylelint-high-performance-animation": "^1.10.0",
"stylelint-use-logical-spec": "^5.0.1",
@@ -124,7 +124,7 @@
"vue-loader": "^15.10.0",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
- "webpack-dev-server": "^5.0.4",
+ "webpack-dev-server": "^5.1.0",
"yaml-eslint-parser": "^1.2.3"
}
}
diff --git a/src/constants.js b/src/constants.js
index 490c8d67771a5..a45a71598d527 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -51,6 +51,7 @@ const DBActions = {
},
HISTORY: {
+ OVERWRITE: 'db-action-history-overwrite',
UPDATE_WATCH_PROGRESS: 'db-action-history-update-watch-progress',
UPDATE_PLAYLIST: 'db-action-history-update-playlist',
},
@@ -78,6 +79,7 @@ const SyncEvents = {
},
HISTORY: {
+ OVERWRITE: 'sync-history-overwrite',
UPDATE_WATCH_PROGRESS: 'sync-history-update-watch-progress',
UPDATE_PLAYLIST: 'sync-history-update-playlist',
},
diff --git a/src/datastores/handlers/base.js b/src/datastores/handlers/base.js
index 4a7db5cbb8c3d..b3ec944b319ed 100644
--- a/src/datastores/handlers/base.js
+++ b/src/datastores/handlers/base.js
@@ -56,6 +56,12 @@ class History {
return db.history.updateAsync({ videoId: record.videoId }, record, { upsert: true })
}
+ static async overwrite(records) {
+ await db.history.removeAsync({}, { multi: true })
+
+ await db.history.insertAsync(records)
+ }
+
static updateWatchProgress(videoId, watchProgress) {
return db.history.updateAsync({ videoId }, { $set: { watchProgress } }, { upsert: true })
}
diff --git a/src/datastores/handlers/electron.js b/src/datastores/handlers/electron.js
index cc0b473a3b990..41d4872e45d8e 100644
--- a/src/datastores/handlers/electron.js
+++ b/src/datastores/handlers/electron.js
@@ -32,6 +32,13 @@ class History {
)
}
+ static overwrite(records) {
+ return ipcRenderer.invoke(
+ IpcChannels.DB_HISTORY,
+ { action: DBActions.HISTORY.OVERWRITE, data: records }
+ )
+ }
+
static updateWatchProgress(videoId, watchProgress) {
return ipcRenderer.invoke(
IpcChannels.DB_HISTORY,
diff --git a/src/datastores/handlers/web.js b/src/datastores/handlers/web.js
index 93ffa3d68c8ff..0fa321bcb4beb 100644
--- a/src/datastores/handlers/web.js
+++ b/src/datastores/handlers/web.js
@@ -29,6 +29,10 @@ class History {
return baseHandlers.history.upsert(record)
}
+ static overwrite(records) {
+ return baseHandlers.history.overwrite(records)
+ }
+
static updateWatchProgress(videoId, watchProgress) {
return baseHandlers.history.updateWatchProgress(videoId, watchProgress)
}
diff --git a/src/main/index.js b/src/main/index.js
index 4cd8cd680a5c0..0848a677c2f1d 100644
--- a/src/main/index.js
+++ b/src/main/index.js
@@ -1075,6 +1075,15 @@ function runApp() {
)
return null
+ case DBActions.HISTORY.OVERWRITE:
+ await baseHandlers.history.overwrite(data)
+ syncOtherWindows(
+ IpcChannels.SYNC_HISTORY,
+ event,
+ { event: SyncEvents.HISTORY.OVERWRITE, data }
+ )
+ return null
+
case DBActions.HISTORY.UPDATE_WATCH_PROGRESS:
await baseHandlers.history.updateWatchProgress(data.videoId, data.watchProgress)
syncOtherWindows(
diff --git a/src/renderer/App.js b/src/renderer/App.js
index e2ecc75e4140b..03e46519504db 100644
--- a/src/renderer/App.js
+++ b/src/renderer/App.js
@@ -453,6 +453,17 @@ export default defineComponent({
break
}
+ case 'post': {
+ const { postId, query } = result
+
+ openInternalPath({
+ path: `/post/${postId}`,
+ query,
+ doCreateNewWindow
+ })
+ break
+ }
+
case 'channel': {
const { channelId, subPath, url } = result
diff --git a/src/renderer/App.vue b/src/renderer/App.vue
index 93d14723725e7..a0235a5854898 100644
--- a/src/renderer/App.vue
+++ b/src/renderer/App.vue
@@ -23,6 +23,7 @@
>
diff --git a/src/renderer/components/data-settings/data-settings.js b/src/renderer/components/data-settings/data-settings.js
index c528fcabd283e..7c950732ebb5e 100644
--- a/src/renderer/components/data-settings/data-settings.js
+++ b/src/renderer/components/data-settings/data-settings.js
@@ -50,6 +50,9 @@ export default defineComponent({
allPlaylists: function () {
return this.$store.getters.getAllPlaylists
},
+ historyCacheById: function () {
+ return this.$store.getters.getHistoryCacheById
+ },
historyCacheSorted: function () {
return this.$store.getters.getHistoryCacheSorted
},
@@ -616,7 +619,7 @@ export default defineComponent({
})
},
- importFreeTubeHistory(textDecode) {
+ async importFreeTubeHistory(textDecode) {
textDecode.pop()
const requiredKeys = [
@@ -630,7 +633,6 @@ export default defineComponent({
'title',
'type',
'videoId',
- 'viewCount',
'watchProgress',
]
@@ -638,12 +640,17 @@ export default defineComponent({
// `_id` absent if marked as watched manually
'_id',
'lastViewedPlaylistId',
+ 'lastViewedPlaylistItemId',
+ 'lastViewedPlaylistType',
+ 'viewCount',
]
const ignoredKeys = [
'paid',
]
+ const historyItems = new Map(Object.entries(this.historyCacheById))
+
textDecode.forEach((history) => {
const historyData = JSON.parse(history)
// We would technically already be done by the time the data is parsed,
@@ -667,14 +674,16 @@ export default defineComponent({
showToast(this.$t('Settings.Data Settings.History object has insufficient data, skipping item'))
console.error('Missing Keys: ', missingKeys, historyData)
} else {
- this.updateHistory(historyObject)
+ historyItems.set(historyObject.videoId, historyObject)
}
})
+ await this.overwriteHistory(historyItems)
+
showToast(this.$t('Settings.Data Settings.All watched history has been successfully imported'))
},
- importYouTubeHistory(historyData) {
+ async importYouTubeHistory(historyData) {
const filterPredicate = item =>
item.products.includes('YouTube') &&
item.titleUrl != null && // removed video doesnt contain url...
@@ -722,6 +731,8 @@ export default defineComponent({
'activityControls',
].concat(Object.keys(keyMapping))
+ const historyItems = new Map(Object.entries(this.historyCacheById))
+
filteredHistoryData.forEach(element => {
const historyObject = {}
@@ -750,10 +761,12 @@ export default defineComponent({
historyObject.watchProgress = 1
historyObject.isLive = false
- this.updateHistory(historyObject)
+ historyItems.set(historyObject.videoId, historyObject)
}
})
+ await this.overwriteHistory(historyItems)
+
showToast(this.$t('Settings.Data Settings.All watched history has been successfully imported'))
},
@@ -1069,10 +1082,10 @@ export default defineComponent({
...mapActions([
'updateProfile',
'updateShowProgressBar',
- 'updateHistory',
'addPlaylist',
'addVideo',
'updatePlaylist',
+ 'overwriteHistory'
]),
...mapMutations([
diff --git a/src/renderer/components/ft-community-post/ft-community-post.js b/src/renderer/components/ft-community-post/ft-community-post.js
index 51fa39bb1afed..cbf30691f14ff 100644
--- a/src/renderer/components/ft-community-post/ft-community-post.js
+++ b/src/renderer/components/ft-community-post/ft-community-post.js
@@ -7,7 +7,7 @@ import autolinker from 'autolinker'
import { A11y, Navigation, Pagination } from 'swiper/modules'
-import { createWebURL, deepCopy, toLocalePublicationString } from '../../helpers/utils'
+import { createWebURL, deepCopy, formatNumber, toLocalePublicationString } from '../../helpers/utils'
import { youtubeImageUrlToInvidious } from '../../helpers/api/invidious'
export default defineComponent({
@@ -29,7 +29,11 @@ export default defineComponent({
hideForbiddenTitles: {
type: Boolean,
default: true
- }
+ },
+ singlePost: {
+ type: Boolean,
+ default: false
+ },
},
data: function () {
return {
@@ -37,9 +41,11 @@ export default defineComponent({
postId: '',
authorThumbnails: null,
publishedText: '',
- voteCount: '',
+ voteCount: 0,
+ formattedVoteCount: '',
postContent: '',
- commentCount: '',
+ commentCount: null,
+ formattedCommentCount: '',
author: '',
authorId: '',
}
@@ -56,6 +62,16 @@ export default defineComponent({
hideVideo() {
return this.forbiddenTitles.some((text) => this.data.postContent.content.title?.toLowerCase().includes(text.toLowerCase()))
+ },
+
+ backendPreference: function () {
+ return this.$store.getters.getBackendPreference
+ },
+ backendFallback: function () {
+ return this.$store.getters.getBackendFallback
+ },
+ isInvidiousAllowed: function() {
+ return this.backendPreference === 'invidious' || this.backendFallback
}
},
created: function () {
@@ -127,7 +143,9 @@ export default defineComponent({
isRSS: this.data.isRSS
})
this.voteCount = this.data.voteCount
+ this.formattedVoteCount = formatNumber(this.voteCount)
this.commentCount = this.data.commentCount
+ this.formattedCommentCount = formatNumber(this.commentCount)
this.type = (this.data.postContent !== null && this.data.postContent !== undefined) ? this.data.postContent.type : 'text'
this.author = this.data.author
this.authorId = this.data.authorId
diff --git a/src/renderer/components/ft-community-post/ft-community-post.scss b/src/renderer/components/ft-community-post/ft-community-post.scss
index bb50bb5b4fb43..6f7b7ebce9e6a 100644
--- a/src/renderer/components/ft-community-post/ft-community-post.scss
+++ b/src/renderer/components/ft-community-post/ft-community-post.scss
@@ -59,6 +59,12 @@
white-space: pre-wrap;
}
+.commentsLink {
+ color: var(--primary-text-color);
+ text-decoration: none;
+ font-weight: bold;
+}
+
.bottomSection {
color: var(--tertiary-text-color);
display: block;
diff --git a/src/renderer/components/ft-community-post/ft-community-post.vue b/src/renderer/components/ft-community-post/ft-community-post.vue
index f65e379e47824..b7f864efde2da 100644
--- a/src/renderer/components/ft-community-post/ft-community-post.vue
+++ b/src/renderer/components/ft-community-post/ft-community-post.vue
@@ -115,11 +115,42 @@
- {{ voteCount }}
-