Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9abd05d05a | |||
| 59b688dafb |
@@ -99,6 +99,29 @@ jobs:
|
||||
APK=$(ls artifacts/postiz-mobile/dist/*.apk | sort | tail -1)
|
||||
echo "path=$APK" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
run: |
|
||||
PREV_TAG=$(git tag --sort=-version:refname | grep -v "^${{ github.ref_name }}$" | head -1)
|
||||
echo "Previous tag: $PREV_TAG"
|
||||
|
||||
FEATS=$(git log "${PREV_TAG}..HEAD" --pretty=format:"%s" --no-merges \
|
||||
| grep -E "^feat(\([^)]+\))?: " \
|
||||
| sed -E 's/^feat(\([^)]+\))?: //' \
|
||||
| sed 's/^/- /')
|
||||
|
||||
FIXES=$(git log "${PREV_TAG}..HEAD" --pretty=format:"%s" --no-merges \
|
||||
| grep -E "^fix(\([^)]+\))?: " \
|
||||
| sed -E 's/^fix(\([^)]+\))?: //' \
|
||||
| sed 's/^/- /')
|
||||
|
||||
{
|
||||
echo "changelog<<CEOF"
|
||||
[ -n "$FEATS" ] && printf "### What's New\n%s\n\n" "$FEATS"
|
||||
[ -n "$FIXES" ] && printf "### Bug Fixes\n%s\n\n" "$FIXES"
|
||||
echo "CEOF"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
@@ -106,8 +129,7 @@ jobs:
|
||||
body: |
|
||||
## Postiz Mobile ${{ github.ref_name }}
|
||||
|
||||
Signed APK for Android — direct install (sideload).
|
||||
|
||||
${{ steps.changelog.outputs.changelog }}
|
||||
### Installation
|
||||
1. Enable "Unknown sources" on the device
|
||||
2. Transfer the APK to the device and open it to install
|
||||
|
||||
@@ -56,22 +56,6 @@ type MediaItem =
|
||||
| { type: "local"; uri: string }
|
||||
| { type: "uploaded"; id: string; path: string; workspaceId: string };
|
||||
|
||||
// Maps a type string to a display label, used for grouping within a workspace
|
||||
function networkLabel(intg: PostizIntegration): string {
|
||||
const t = (intg.type ?? intg.internalType ?? "").toLowerCase();
|
||||
if (t.includes("twitter") || t.includes("x-") || t === "x") return "X / Twitter";
|
||||
if (t.includes("instagram")) return "Instagram";
|
||||
if (t.includes("linkedin")) return "LinkedIn";
|
||||
if (t.includes("facebook")) return "Facebook";
|
||||
if (t.includes("tiktok")) return "TikTok";
|
||||
if (t.includes("youtube")) return "YouTube";
|
||||
if (t.includes("pinterest")) return "Pinterest";
|
||||
if (t.includes("mastodon")) return "Mastodon";
|
||||
if (t.includes("bluesky") || t.includes("bsky")) return "Bluesky";
|
||||
if (t.includes("threads")) return "Threads";
|
||||
if (t.includes("reddit")) return "Reddit";
|
||||
return "Other";
|
||||
}
|
||||
|
||||
function resolveMediaUrl(path: string, baseUrl: string): string {
|
||||
if (path.startsWith("http://") || path.startsWith("https://")) return path;
|
||||
@@ -156,7 +140,7 @@ export default function ComposeScreen() {
|
||||
staleTime: 60000,
|
||||
});
|
||||
|
||||
// Group: workspace → network label → integrations
|
||||
// Group: workspace → flat list of integrations
|
||||
const grouped = useMemo(() => {
|
||||
if (!allIntegrations) return [];
|
||||
const byWorkspace = new Map<string, IntegrationWithWorkspace[]>();
|
||||
@@ -166,21 +150,20 @@ export default function ComposeScreen() {
|
||||
}
|
||||
return workspaces
|
||||
.filter((ws) => byWorkspace.has(ws.id))
|
||||
.map((ws) => {
|
||||
const intgs = byWorkspace.get(ws.id)!;
|
||||
const byNetwork = new Map<string, IntegrationWithWorkspace[]>();
|
||||
for (const intg of intgs) {
|
||||
const key = networkLabel(intg);
|
||||
if (!byNetwork.has(key)) byNetwork.set(key, []);
|
||||
byNetwork.get(key)!.push(intg);
|
||||
}
|
||||
return {
|
||||
workspace: ws,
|
||||
networks: Array.from(byNetwork.entries()).map(([label, channels]) => ({ label, channels })),
|
||||
};
|
||||
});
|
||||
.map((ws) => ({ workspace: ws, channels: byWorkspace.get(ws.id)! }));
|
||||
}, [allIntegrations, workspaces]);
|
||||
|
||||
const toggleWorkspace = (wsId: string) => {
|
||||
const wsChannels = grouped.find((g) => g.workspace.id === wsId)?.channels ?? [];
|
||||
const allIds = wsChannels.map((c) => c.id);
|
||||
const allSelected = allIds.every((id) => selectedChannels.includes(id));
|
||||
if (allSelected) {
|
||||
setSelectedChannels((prev) => prev.filter((id) => !allIds.includes(id)));
|
||||
} else {
|
||||
setSelectedChannels((prev) => [...new Set([...prev, ...allIds])]);
|
||||
}
|
||||
};
|
||||
|
||||
const effectiveCharLimit = useMemo(() => {
|
||||
if (selectedChannels.length === 0 || !allIntegrations) return 3000;
|
||||
const selected = allIntegrations.filter((i) => selectedChannels.includes(i.id));
|
||||
@@ -552,50 +535,51 @@ export default function ComposeScreen() {
|
||||
</Text>
|
||||
) : (
|
||||
<View style={styles.channelGroups}>
|
||||
{grouped.map(({ workspace, networks }, wsIdx) => (
|
||||
<View
|
||||
key={workspace.id}
|
||||
style={[
|
||||
styles.workspaceSection,
|
||||
{
|
||||
backgroundColor: colors.card,
|
||||
borderColor: colors.border,
|
||||
},
|
||||
wsIdx > 0 && { marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
{/* Workspace header */}
|
||||
<View style={[styles.workspaceHeader, { borderBottomColor: colors.border }]}>
|
||||
<Feather name="briefcase" size={12} color={colors.primary} />
|
||||
<Text style={[styles.workspaceName, { color: colors.primary }]}>
|
||||
{workspace.name}
|
||||
</Text>
|
||||
</View>
|
||||
{grouped.map(({ workspace, channels }, wsIdx) => {
|
||||
const allIds = channels.map((c) => c.id);
|
||||
const selectedCount = allIds.filter((id) => selectedChannels.includes(id)).length;
|
||||
const allSelected = selectedCount === allIds.length;
|
||||
const someSelected = selectedCount > 0 && !allSelected;
|
||||
return (
|
||||
<View
|
||||
key={workspace.id}
|
||||
style={[
|
||||
styles.workspaceSection,
|
||||
{ backgroundColor: colors.card, borderColor: colors.border },
|
||||
wsIdx > 0 && { marginTop: 8 },
|
||||
]}
|
||||
>
|
||||
{/* Workspace header — tap to select/deselect all */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
onPress={() => toggleWorkspace(workspace.id)}
|
||||
style={[styles.workspaceHeader, { borderBottomColor: colors.border }]}
|
||||
>
|
||||
<Feather name="briefcase" size={12} color={colors.primary} />
|
||||
<Text style={[styles.workspaceName, { color: colors.primary, flex: 1 }]}>
|
||||
{workspace.name}
|
||||
</Text>
|
||||
<Feather
|
||||
name={allSelected ? "check-square" : someSelected ? "minus-square" : "square"}
|
||||
size={14}
|
||||
color={allSelected || someSelected ? colors.primary : colors.mutedForeground}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Network groups */}
|
||||
<View style={styles.networkGroups}>
|
||||
{networks.map(({ label, channels }) => (
|
||||
<View key={label} style={styles.networkGroup}>
|
||||
{networks.length > 1 && (
|
||||
<Text style={[styles.networkLabel, { color: colors.mutedForeground }]}>
|
||||
{label}
|
||||
</Text>
|
||||
)}
|
||||
<View style={styles.chipRow}>
|
||||
{channels.map((intg) => (
|
||||
<ChannelChip
|
||||
key={intg.id}
|
||||
integration={intg}
|
||||
selected={selectedChannels.includes(intg.id)}
|
||||
onToggle={() => toggleChannel(intg.id)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
{/* Flat channel chips */}
|
||||
<View style={styles.chipRow}>
|
||||
{channels.map((intg) => (
|
||||
<ChannelChip
|
||||
key={intg.id}
|
||||
integration={intg}
|
||||
selected={selectedChannels.includes(intg.id)}
|
||||
onToggle={() => toggleChannel(intg.id)}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -777,10 +761,7 @@ const styles = StyleSheet.create({
|
||||
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
workspaceName: { fontSize: 11, fontFamily: "Inter_600SemiBold", letterSpacing: 0.5 },
|
||||
networkGroups: { padding: 10, gap: 10 },
|
||||
networkGroup: { gap: 4 },
|
||||
networkLabel: { fontSize: 10, fontFamily: "Inter_500Medium", letterSpacing: 0.4, marginLeft: 2 },
|
||||
chipRow: { flexDirection: "row", flexWrap: "wrap", gap: 6 },
|
||||
chipRow: { flexDirection: "row", flexWrap: "wrap", gap: 6, padding: 10 },
|
||||
scheduleRow: {
|
||||
flexDirection: "row", alignItems: "center", justifyContent: "space-between",
|
||||
paddingHorizontal: 16, paddingVertical: 12, borderRadius: 12, borderWidth: 1,
|
||||
|
||||
Reference in New Issue
Block a user